266 lines
10 KiB
Python
266 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
from copienator import atomic_write_json, read_json
|
|
from copienator_gui.app import CopienatorApp
|
|
from copienator_gui.refaire import (
|
|
ALL_COPIES,
|
|
SECTION,
|
|
available_copies,
|
|
resolve_layout,
|
|
validate_selection,
|
|
)
|
|
from copienator_gui.workflow import build_command, build_workflow
|
|
|
|
|
|
class SelectionTests(unittest.TestCase):
|
|
def test_selection_validates_and_deduplicates_labels(self):
|
|
copies = {"Copie01": Path("/tmp/Copie01.pdf")}
|
|
self.assertEqual(
|
|
validate_selection(
|
|
[["Copie01", ["Ex 2", "Ex 1", "Ex 2"]]], copies, ["Ex 1", "Ex 2"]
|
|
),
|
|
[["Copie01", ["Ex 1", "Ex 2"]]],
|
|
)
|
|
for invalid in (
|
|
[],
|
|
[["missing", []]],
|
|
[["Copie01", ["unknown"]]],
|
|
[["../Copie01", []]],
|
|
):
|
|
with self.subTest(invalid=invalid), self.assertRaises(ValueError):
|
|
validate_selection(invalid, copies, ["Ex 1"])
|
|
|
|
def test_automatic_layout_groups_shared_labels_and_honors_explicit_choice(self):
|
|
selection = [["Copie01", ["Ex 1"]], ["Copie02", ["Ex 1"]]]
|
|
self.assertEqual(resolve_layout(selection, ["Ex 1", "Ex 2"]), "grouped")
|
|
self.assertEqual(resolve_layout(selection, ["Ex 1"], "copies"), "copies")
|
|
self.assertEqual(resolve_layout([["Copie01", ["Ex 1"]]], ["Ex 1"]), "copies")
|
|
self.assertEqual(
|
|
resolve_layout([["Copie01", ["Ex 1"]], ["Copie02", []]], ["Ex 1"]),
|
|
"grouped",
|
|
)
|
|
|
|
def test_redo_steps_are_in_both_profiles_and_never_autostart(self):
|
|
for personal in (False, True):
|
|
steps = [
|
|
step for step in build_workflow(personal) if step.section == SECTION
|
|
]
|
|
self.assertEqual(len(steps), 9)
|
|
self.assertEqual(steps[0].id, "refaire_selection")
|
|
self.assertTrue(all(not step.auto_start_first_visit for step in steps))
|
|
merge = steps[-1]
|
|
for mode in ("BGnot", "Bnot", "Anot"):
|
|
command = build_command(
|
|
Path.cwd(),
|
|
merge,
|
|
merge.variants[0],
|
|
{"target": "/tmp/Exam", "annotation_dir": mode},
|
|
"/tmp/Exam",
|
|
)
|
|
self.assertEqual(
|
|
command[4:],
|
|
[
|
|
"read-grouped",
|
|
"/tmp/Exam",
|
|
"--refaire",
|
|
"--annotation-dir",
|
|
mode,
|
|
],
|
|
)
|
|
|
|
|
|
@unittest.skipUnless(
|
|
os.environ.get("DISPLAY"), "Tk tests require a display (use xvfb-run)"
|
|
)
|
|
class RefaireGuiTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.root = Path(self.temp.name)
|
|
(self.root / "Copies").mkdir()
|
|
(self.root / "Anot").mkdir()
|
|
(self.root / "Par label").mkdir()
|
|
(self.root / "BRnot").mkdir()
|
|
for name in ("Copie01", "Copie02"):
|
|
(self.root / "Copies" / f"{name}.pdf").touch()
|
|
(self.root / "labels").write_text("Ex 1\nEx 2\n", encoding="utf-8")
|
|
atomic_write_json(self.root / "correction.json", {})
|
|
self.app = CopienatorApp(Path.cwd(), False, self.root)
|
|
self.app.update()
|
|
|
|
def tearDown(self):
|
|
for callback in self.app.tk.splitlist(self.app.tk.call("after", "info")):
|
|
self.app.after_cancel(callback)
|
|
self.app.destroy()
|
|
self.temp.cleanup()
|
|
|
|
def select(self, ident):
|
|
self.app.tree.selection_set(ident)
|
|
self.app.tree.see(ident)
|
|
self.app.update()
|
|
|
|
def save_selection(self):
|
|
self.select("refaire_selection")
|
|
panel = self.app.refaire_panel
|
|
panel.label_var.set("Ex 1")
|
|
panel.add()
|
|
panel.label_var.set("Ex 2")
|
|
panel.add()
|
|
panel.copy_var.set("Copie02")
|
|
panel.label_var.set("Toute la copie")
|
|
panel.add()
|
|
self.app._run_current_step()
|
|
self.app.update()
|
|
|
|
def test_collapsed_branch_is_separate_and_stays_open_when_refreshed(self):
|
|
section = self.app.tree.parent("refaire_selection")
|
|
self.assertFalse(self.app.tree.item(section, "open"))
|
|
self.assertNotIn("refaire_selection", self.app._progression_ids("giving_names"))
|
|
self.assertNotIn("clean", self.app._progression_ids("refaire_merge"))
|
|
self.select("refaire_selection")
|
|
self.app._populate_tree()
|
|
self.assertTrue(self.app.tree.item(section, "open"))
|
|
|
|
def test_dropdown_selection_saves_json_and_drives_all_commands(self):
|
|
self.save_selection()
|
|
self.assertEqual(
|
|
read_json(self.root / "refaire.json"),
|
|
[["Copie01", ["Ex 1", "Ex 2"]], ["Copie02", []]],
|
|
)
|
|
self.assertEqual(self.app.current_step.id, "refaire_review")
|
|
commands = self.app._refaire_commands()
|
|
self.assertEqual(
|
|
[command[5] for command in commands],
|
|
[str(path) for path in available_copies(self.root).values()],
|
|
)
|
|
self.select("refaire_annotate")
|
|
self.assertEqual(
|
|
self.app._make_command()[4:],
|
|
["annotate-grouped", str(self.root), "--refaire", "--overwrite"],
|
|
)
|
|
self.select("refaire_merge")
|
|
self.assertEqual(
|
|
self.app._make_command()[4:],
|
|
["read-grouped", str(self.root), "--refaire", "--annotation-dir", "Anot"],
|
|
)
|
|
self.assertEqual(self.app.state_store.step("annotation").get("status"), None)
|
|
|
|
def test_one_label_for_all_copies_and_layout_override(self):
|
|
for name in ("Copie01", "Copie02"):
|
|
directory = self.root / "Copies" / name
|
|
directory.mkdir()
|
|
(directory / "Ex 1.pdf").touch()
|
|
self.select("refaire_selection")
|
|
panel = self.app.refaire_panel
|
|
panel.copy_var.set(ALL_COPIES)
|
|
panel.label_var.set("Ex 1")
|
|
panel.add()
|
|
self.assertEqual(panel.entries, {"Copie01": ["Ex 1"], "Copie02": ["Ex 1"]})
|
|
panel.layout_var.set("Par copie")
|
|
self.app._run_current_step()
|
|
self.app.update()
|
|
self.select("refaire_annotate")
|
|
self.assertEqual(self.app._make_command()[4], "annotate-checks")
|
|
|
|
def test_bulk_add_skips_absent_answers_and_preserves_whole_copy_selection(self):
|
|
directory = self.root / "Copies/Copie01"
|
|
directory.mkdir()
|
|
(directory / "Ex 1_new.pdf").touch()
|
|
self.select("refaire_selection")
|
|
panel = self.app.refaire_panel
|
|
panel.copy_var.set("Copie01")
|
|
panel.add()
|
|
panel.copy_var.set(ALL_COPIES)
|
|
panel.label_var.set("Ex 1")
|
|
panel.add()
|
|
self.assertEqual(panel.entries, {"Copie01": []})
|
|
self.assertIn("1 sans réponse", panel.message_var.get())
|
|
|
|
def test_correction_folder_buttons_open_expected_folders(self):
|
|
for name in ("Sol", "Persp"):
|
|
(self.root / name).mkdir()
|
|
self.select("refaire_selection")
|
|
buttons = self.app.form.winfo_children()[0].winfo_children()
|
|
with patch("copienator_gui.app.open_path") as opened:
|
|
for button in buttons:
|
|
button.invoke()
|
|
self.assertEqual(
|
|
[call.args[0] for call in opened.call_args_list],
|
|
[self.root / "Sol", self.root / "Persp"],
|
|
)
|
|
|
|
def test_small_window_keeps_selection_accessible_by_scrolling(self):
|
|
self.select("refaire_selection")
|
|
self.app.geometry("900x640")
|
|
self.app.update()
|
|
self.app.form_canvas.yview_moveto(1)
|
|
self.app.update()
|
|
self.assertAlmostEqual(self.app.form_canvas.yview()[1], 1.0)
|
|
self.assertLess(
|
|
self.app.run_button.winfo_rooty() + self.app.run_button.winfo_height(),
|
|
self.app.winfo_rooty() + self.app.winfo_height(),
|
|
)
|
|
|
|
def test_queue_runs_copies_in_order_and_stops_on_failure(self):
|
|
self.save_selection()
|
|
self.select("refaire_split")
|
|
with patch.object(self.app.runner, "start") as start:
|
|
self.app._run_current_step()
|
|
self.assertEqual(start.call_count, 1)
|
|
self.assertEqual(len(self.app.pending_refaire_commands), 1)
|
|
self.app._finish_process(0, False)
|
|
self.assertEqual(start.call_count, 2)
|
|
self.assertEqual(self.app.active_step_id, "refaire_split")
|
|
self.app._finish_process(1, False)
|
|
self.assertEqual(
|
|
self.app.state_store.step("refaire_split")["status"], "failed"
|
|
)
|
|
self.assertIsNone(self.app.active_step_id)
|
|
self.assertFalse(self.app.pending_refaire_commands)
|
|
|
|
def test_interruption_does_not_launch_next_copy(self):
|
|
self.save_selection()
|
|
self.select("refaire_split")
|
|
with patch.object(self.app.runner, "start") as start:
|
|
self.app._run_current_step()
|
|
self.app._finish_process(130, True)
|
|
self.assertEqual(start.call_count, 1)
|
|
self.assertFalse(self.app.pending_refaire_commands)
|
|
self.assertEqual(
|
|
self.app.state_store.step("refaire_split")["status"], "interrupted"
|
|
)
|
|
|
|
def test_unsaved_or_external_changes_block_commands(self):
|
|
self.save_selection()
|
|
atomic_write_json(self.root / "refaire.json", [["Copie02", []]])
|
|
with self.assertRaisesRegex(ValueError, "sélection a changé"):
|
|
self.app._refaire_commands()
|
|
|
|
def test_selection_reload_and_finalization_invalidation(self):
|
|
self.save_selection()
|
|
self.app.state_store.update_step("giving_names", status="success")
|
|
self.app.state_store.update_step(
|
|
"annotation", status="success", last_run_variant="simple"
|
|
)
|
|
self.select("refaire_merge")
|
|
with patch.object(self.app.runner, "start"):
|
|
self.app._run_current_step()
|
|
self.app._finish_process(0, False)
|
|
self.app.update()
|
|
self.assertEqual(self.app.state_store.step("giving_names")["status"], "stale")
|
|
self.assertEqual(self.app.state_store.step("annotation")["status"], "success")
|
|
self.assertEqual(self.app.current_step.id, "refaire_merge")
|
|
self.select("refaire_selection")
|
|
self.assertEqual(
|
|
self.app.refaire_panel.entries, {"Copie01": ["Ex 1", "Ex 2"], "Copie02": []}
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|