Files
Copies/tests/test_gui_refaire.py
2026-09-06 19:20:51 +02:00

309 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_restart_actions_warn_reset_and_preserve_or_clear_selection(self):
self.save_selection()
self.app.state_store.update_step("refaire_merge", status="success")
self.app.state_store.update_step("annotation", status="success")
selection = read_json(self.root / "refaire.json")
with patch(
"copienator_gui.app.messagebox.askyesno", return_value=False
) as confirm:
self.app._start_refaire_pass(False)
self.assertIn("importé", confirm.call_args.args[1])
self.assertIn("Mettre à jour", confirm.call_args.args[1])
self.assertEqual(read_json(self.root / "refaire.json"), selection)
with patch("copienator_gui.app.messagebox.askyesno", return_value=True):
self.app._start_refaire_pass(True)
self.app.update()
self.assertEqual(self.app.current_step.id, "refaire_selection")
self.assertEqual(self.app.refaire_panel.values()["selection"], selection)
self.assertIsNone(self.app.state_store.step("refaire_merge").get("status"))
self.assertEqual(self.app.state_store.step("annotation")["status"], "success")
first = self.app.state_store.workspace.refaire_session_id
with patch(
"copienator_gui.app.messagebox.askyesno", return_value=True
) as confirm:
self.app._start_refaire_pass(False)
self.app.update()
self.assertIn("nest pas marquée comme fusionnée", confirm.call_args.args[1])
self.assertEqual(self.app.refaire_panel.entries, {})
self.assertEqual(read_json(self.root / "refaire.json"), [])
self.assertNotEqual(self.app.state_store.workspace.refaire_session_id, first)
self.assertEqual(self.app.refaire_panel.source_var.get(), "Anot")
def test_restart_is_blocked_while_a_command_is_active(self):
self.save_selection()
self.app.active_step_id = "refaire_correct"
with (
patch("copienator_gui.app.messagebox.showwarning") as warning,
patch("copienator_gui.app.messagebox.askyesno") as confirm,
):
self.app._start_refaire_pass(False)
warning.assert_called_once()
confirm.assert_not_called()
self.assertIsNone(self.app.state_store.workspace.refaire_session_id)
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()