Miscs personal GUI improvement
This commit is contained in:
+97
-24
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -10,6 +12,7 @@ from typing import Any
|
|||||||
|
|
||||||
from copienator import CliError, EvaluationWorkspace, ExitCode, atomic_write_json
|
from copienator import CliError, EvaluationWorkspace, ExitCode, atomic_write_json
|
||||||
from copienator.copy_errors import copy_errors
|
from copienator.copy_errors import copy_errors
|
||||||
|
from copienator.filesystem import staged_files
|
||||||
from copienator.platform import (
|
from copienator.platform import (
|
||||||
WindowsLabelError,
|
WindowsLabelError,
|
||||||
add_platform_executable_paths,
|
add_platform_executable_paths,
|
||||||
@@ -52,6 +55,7 @@ STATUS_LABELS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_HTTPS_PROXY = "http://10.0.0.1:3128"
|
DEFAULT_HTTPS_PROXY = "http://10.0.0.1:3128"
|
||||||
|
PERSONAL_INTERRO_SOURCE = Path("/home/sebastien/Prépa/Staging/Interro")
|
||||||
ANNOTATION_DIRECTORIES = ("BGnot", "Bnot", "Anot")
|
ANNOTATION_DIRECTORIES = ("BGnot", "Bnot", "Anot")
|
||||||
ANNOTATION_VARIANT_DIRECTORIES = {
|
ANNOTATION_VARIANT_DIRECTORIES = {
|
||||||
"simple": "Anot",
|
"simple": "Anot",
|
||||||
@@ -60,6 +64,33 @@ ANNOTATION_VARIANT_DIRECTORIES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_personal_interro_files(
|
||||||
|
evaluation: Path, source_directory: Path = PERSONAL_INTERRO_SOURCE
|
||||||
|
) -> tuple[Path, Path, Path]:
|
||||||
|
"""Copy an Interro project's statement sources into the evaluation."""
|
||||||
|
match = re.fullmatch(r"Interro(\d+)", evaluation.name)
|
||||||
|
if match is None:
|
||||||
|
raise ValueError("Le dossier d’évaluation doit s’appeler Interro{id}, avec un identifiant numérique.")
|
||||||
|
|
||||||
|
project_name = evaluation.name
|
||||||
|
source_to_destination = (
|
||||||
|
(source_directory / f"{project_name}.pdf", "enonce.pdf"),
|
||||||
|
(source_directory / f"{project_name}.tex", "enonce.tex"),
|
||||||
|
(source_directory / f"{project_name}c.tex", "correction.tex"),
|
||||||
|
)
|
||||||
|
missing = [source.name for source, _destination in source_to_destination if not source.is_file()]
|
||||||
|
if missing:
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"Fichier(s) source introuvable(s) dans "
|
||||||
|
f"{source_directory} : {', '.join(missing)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
with staged_files(evaluation) as staging:
|
||||||
|
for source, destination_name in source_to_destination:
|
||||||
|
shutil.copy2(source, staging / destination_name)
|
||||||
|
return tuple(evaluation / name for _source, name in source_to_destination)
|
||||||
|
|
||||||
|
|
||||||
class Tooltip:
|
class Tooltip:
|
||||||
"""Small delayed tooltip for Tk and ttk widgets."""
|
"""Small delayed tooltip for Tk and ttk widgets."""
|
||||||
|
|
||||||
@@ -338,13 +369,23 @@ class CopienatorApp(tk.Tk):
|
|||||||
|
|
||||||
self.detail = ttk.Frame(main_pane, padding=(12, 6, 4, 4))
|
self.detail = ttk.Frame(main_pane, padding=(12, 6, 4, 4))
|
||||||
self.detail.columnconfigure(0, weight=1)
|
self.detail.columnconfigure(0, weight=1)
|
||||||
self.detail.rowconfigure(2, weight=1)
|
self.detail.rowconfigure(3, weight=1)
|
||||||
self.title_label = ttk.Label(self.detail, text="Sélectionnez une étape", font=("TkDefaultFont", 15, "bold"))
|
self.title_label = ttk.Label(self.detail, text="Sélectionnez une étape", font=("TkDefaultFont", 15, "bold"))
|
||||||
self.title_label.grid(row=0, column=0, sticky="w")
|
self.title_label.grid(row=0, column=0, sticky="w")
|
||||||
self.description_label = ttk.Label(self.detail, text="", wraplength=680, justify="left")
|
self.description_label = ttk.Label(self.detail, text="", wraplength=680, justify="left")
|
||||||
self.description_label.grid(row=1, column=0, sticky="ew", pady=(5, 8))
|
self.description_label.grid(row=1, column=0, sticky="ew", pady=(5, 8))
|
||||||
|
ttk.Style(self).configure("MissingPrerequisite.TLabel", foreground="#c62828")
|
||||||
|
self.missing_requirements_label = ttk.Label(
|
||||||
|
self.detail,
|
||||||
|
text="",
|
||||||
|
style="MissingPrerequisite.TLabel",
|
||||||
|
wraplength=680,
|
||||||
|
justify="left",
|
||||||
|
)
|
||||||
|
self.missing_requirements_label.grid(row=2, column=0, sticky="ew", pady=(0, 8))
|
||||||
|
self.missing_requirements_label.grid_remove()
|
||||||
form_container = ttk.Frame(self.detail)
|
form_container = ttk.Frame(self.detail)
|
||||||
form_container.grid(row=2, column=0, sticky="nsew")
|
form_container.grid(row=3, column=0, sticky="nsew")
|
||||||
form_container.columnconfigure(0, weight=1)
|
form_container.columnconfigure(0, weight=1)
|
||||||
form_container.rowconfigure(0, weight=1)
|
form_container.rowconfigure(0, weight=1)
|
||||||
self.form_canvas = tk.Canvas(form_container, highlightthickness=0, width=1, height=1)
|
self.form_canvas = tk.Canvas(form_container, highlightthickness=0, width=1, height=1)
|
||||||
@@ -359,7 +400,7 @@ class CopienatorApp(tk.Tk):
|
|||||||
self.form.columnconfigure(1, weight=1)
|
self.form.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
command_box = ttk.LabelFrame(self.detail, text="Commande", padding=6)
|
command_box = ttk.LabelFrame(self.detail, text="Commande", padding=6)
|
||||||
command_box.grid(row=3, column=0, sticky="ew", pady=(8, 5))
|
command_box.grid(row=4, column=0, sticky="ew", pady=(8, 5))
|
||||||
command_box.columnconfigure(0, weight=1)
|
command_box.columnconfigure(0, weight=1)
|
||||||
ttk.Label(command_box, textvariable=self.command_var, wraplength=690, justify="left").grid(
|
ttk.Label(command_box, textvariable=self.command_var, wraplength=690, justify="left").grid(
|
||||||
row=0, column=0, sticky="ew"
|
row=0, column=0, sticky="ew"
|
||||||
@@ -368,7 +409,7 @@ class CopienatorApp(tk.Tk):
|
|||||||
self.copy_command_button.grid(row=1, column=0, sticky="w", pady=(5, 0))
|
self.copy_command_button.grid(row=1, column=0, sticky="w", pady=(5, 0))
|
||||||
|
|
||||||
buttons = ttk.Frame(self.detail)
|
buttons = ttk.Frame(self.detail)
|
||||||
buttons.grid(row=4, column=0, sticky="ew", pady=(5, 0))
|
buttons.grid(row=5, column=0, sticky="ew", pady=(5, 0))
|
||||||
self.previous_button = ttk.Button(buttons, text="← Précédente", command=lambda: self._move_selection(-1))
|
self.previous_button = ttk.Button(buttons, text="← Précédente", command=lambda: self._move_selection(-1))
|
||||||
self.previous_button.pack(side="left")
|
self.previous_button.pack(side="left")
|
||||||
self.next_button = ttk.Button(buttons, text="Suivante →", command=lambda: self._move_selection(1))
|
self.next_button = ttk.Button(buttons, text="Suivante →", command=lambda: self._move_selection(1))
|
||||||
@@ -603,6 +644,40 @@ class CopienatorApp(tk.Tk):
|
|||||||
self._render_step()
|
self._render_step()
|
||||||
self.info_var.set("Fichiers revérifiés — " + ("manquants : " + ", ".join(missing) if missing else "tous les fichiers d’entrée sont présents."))
|
self.info_var.set("Fichiers revérifiés — " + ("manquants : " + ", ".join(missing) if missing else "tous les fichiers d’entrée sont présents."))
|
||||||
|
|
||||||
|
def _get_input_files(self) -> None:
|
||||||
|
evaluation = self.evaluation
|
||||||
|
if (
|
||||||
|
not self.show_personal_steps
|
||||||
|
or not evaluation
|
||||||
|
or not self.state_store.evaluation
|
||||||
|
or self.active_step_id
|
||||||
|
or self.runner.running
|
||||||
|
):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
copied = get_personal_interro_files(evaluation)
|
||||||
|
except (OSError, ValueError) as exc:
|
||||||
|
messagebox.showerror("Récupération impossible", str(exc))
|
||||||
|
return
|
||||||
|
|
||||||
|
missing = self._missing_requirements(self.step_by_id["inputs"])
|
||||||
|
if missing:
|
||||||
|
self.state_store.update_step("inputs", status="ready")
|
||||||
|
self._populate_tree()
|
||||||
|
self._render_step()
|
||||||
|
messagebox.showwarning(
|
||||||
|
"Prérequis encore manquant",
|
||||||
|
"Les trois fichiers ont été copiés, mais l’étape ne peut pas encore "
|
||||||
|
"être terminée :\n\n" + "\n".join(missing),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._mark_step("success", automatic=True, reason="Fichiers d’entrée récupérés")
|
||||||
|
self.info_var.set(
|
||||||
|
"Fichiers récupérés : " + ", ".join(path.name for path in copied)
|
||||||
|
)
|
||||||
|
self._move_selection_from("inputs", 1)
|
||||||
|
|
||||||
def _on_tree_select(self, _event: tk.Event[Any] | None = None) -> None:
|
def _on_tree_select(self, _event: tk.Event[Any] | None = None) -> None:
|
||||||
selection = self.tree.selection()
|
selection = self.tree.selection()
|
||||||
if not selection or selection[0].startswith("section:"):
|
if not selection or selection[0].startswith("section:"):
|
||||||
@@ -692,10 +767,15 @@ class CopienatorApp(tk.Tk):
|
|||||||
if step.id == "statement" and saved_variant == "personal":
|
if step.id == "statement" and saved_variant == "personal":
|
||||||
description = ("Lit les blocs SHEETINFO de enonce.tex et récupère les énoncés, solutions "
|
description = ("Lit les blocs SHEETINFO de enonce.tex et récupère les énoncés, solutions "
|
||||||
"et barèmes du service personnel (localhost:8080). Crée un groupe par exercice. "
|
"et barèmes du service personnel (localhost:8080). Crée un groupe par exercice. "
|
||||||
"Les actions Gemini ci-dessous restent facultatives.")
|
"Les deux étapes Gemini suivantes restent facultatives.")
|
||||||
if missing:
|
|
||||||
description += "\nPrérequis non détectés : " + ", ".join(missing)
|
|
||||||
self.description_label.configure(text=description)
|
self.description_label.configure(text=description)
|
||||||
|
if missing:
|
||||||
|
self.missing_requirements_label.configure(
|
||||||
|
text="Prérequis non détectés : " + ", ".join(missing)
|
||||||
|
)
|
||||||
|
self.missing_requirements_label.grid()
|
||||||
|
else:
|
||||||
|
self.missing_requirements_label.grid_remove()
|
||||||
|
|
||||||
row = self._render_context_controls(step, 0)
|
row = self._render_context_controls(step, 0)
|
||||||
if len(step.variants) > 1:
|
if len(step.variants) > 1:
|
||||||
@@ -765,14 +845,6 @@ class CopienatorApp(tk.Tk):
|
|||||||
attach_tooltip(extra_entry, step.extra_arguments_help)
|
attach_tooltip(extra_entry, step.extra_arguments_help)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
if self.show_personal_steps and step.id == "statement":
|
|
||||||
actions = ttk.LabelFrame(self.form, text="Après génération — facultatif", padding=7)
|
|
||||||
actions.grid(row=row, column=0, columnspan=2, sticky="ew", pady=(10, 8))
|
|
||||||
for ident, title in (("statement_groups", "Regrouper avec Gemini…"),
|
|
||||||
("statement_persp", "Remplacer Persp avec Gemini…")):
|
|
||||||
ttk.Button(actions, text=title, command=lambda target=ident: self._select_statement_action(target)).pack(
|
|
||||||
side="left", padx=(0, 6))
|
|
||||||
|
|
||||||
self.run_button.configure(text="Enregistrer la sélection" if step.id == "refaire_selection" else ("Marquer terminée" if step.is_manual else "Exécuter"))
|
self.run_button.configure(text="Enregistrer la sélection" if step.id == "refaire_selection" else ("Marquer terminée" if step.is_manual else "Exécuter"))
|
||||||
self.skip_button.configure(state="normal" if step.optional else "disabled")
|
self.skip_button.configure(state="normal" if step.optional else "disabled")
|
||||||
self._rendering = False
|
self._rendering = False
|
||||||
@@ -790,7 +862,16 @@ class CopienatorApp(tk.Tk):
|
|||||||
ttk.Button(self.form, text="Recharger — vérifier à nouveau", command=self._reload_inputs).grid(
|
ttk.Button(self.form, text="Recharger — vérifier à nouveau", command=self._reload_inputs).grid(
|
||||||
row=row, column=0, columnspan=2, sticky="w", pady=(0, 8)
|
row=row, column=0, columnspan=2, sticky="w", pady=(0, 8)
|
||||||
)
|
)
|
||||||
return row + 1
|
row += 1
|
||||||
|
if self.show_personal_steps:
|
||||||
|
self.get_input_files_button = ttk.Button(
|
||||||
|
self.form, text="Get the files", command=self._get_input_files
|
||||||
|
)
|
||||||
|
self.get_input_files_button.grid(
|
||||||
|
row=row, column=0, columnspan=2, sticky="w", pady=(0, 8)
|
||||||
|
)
|
||||||
|
row += 1
|
||||||
|
return row
|
||||||
if step.section == REFAIRE_SECTION:
|
if step.section == REFAIRE_SECTION:
|
||||||
evaluation = self.evaluation
|
evaluation = self.evaluation
|
||||||
if step.id in {"refaire_selection", "refaire_correct"}:
|
if step.id in {"refaire_selection", "refaire_correct"}:
|
||||||
@@ -891,14 +972,6 @@ class CopienatorApp(tk.Tk):
|
|||||||
row += 1
|
row += 1
|
||||||
return row
|
return row
|
||||||
|
|
||||||
def _select_statement_action(self, step_id: str) -> None:
|
|
||||||
self._save_current_form()
|
|
||||||
target = self.arg_vars.get("target")
|
|
||||||
if target:
|
|
||||||
self.state_store.update_step(step_id, values={"target": target.get()})
|
|
||||||
self.tree.selection_set(step_id)
|
|
||||||
self.tree.see(step_id)
|
|
||||||
|
|
||||||
def _refaire_restart_controls(self, row: int) -> int:
|
def _refaire_restart_controls(self, row: int) -> int:
|
||||||
controls = ttk.Frame(self.form)
|
controls = ttk.Frame(self.form)
|
||||||
controls.grid(row=row, column=0, columnspan=2, sticky="w", pady=(0, 5))
|
controls.grid(row=row, column=0, columnspan=2, sticky="w", pady=(0, 5))
|
||||||
|
|||||||
@@ -102,10 +102,9 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
"Prétraitement de l’énoncé",
|
"Prétraitement de l’énoncé",
|
||||||
"Analyser l’énoncé",
|
"Analyser l’énoncé",
|
||||||
"Détecte les questions, leurs groupes, le corrigé et les indications de barème.",
|
"Détecte les questions, leurs groupes, le corrigé et les indications de barème.",
|
||||||
(
|
((python("personal", "Énoncés et solutions personnels (SHEETINFO)", "statement-personal"),)
|
||||||
python("gemini", "Analyse avec Gemini", "statement"),
|
if show_personal_steps else ())
|
||||||
) + ((python("personal", "Énoncés et solutions personnels (SHEETINFO)", "statement-personal"),)
|
+ (python("gemini", "Analyse avec Gemini", "statement"),),
|
||||||
if show_personal_steps else ()),
|
|
||||||
arguments=(
|
arguments=(
|
||||||
arg_target("le dossier de l’évaluation"),
|
arg_target("le dossier de l’évaluation"),
|
||||||
ArgumentSpec(
|
ArgumentSpec(
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ class GuiConvenienceTests(unittest.TestCase):
|
|||||||
(self.evaluation / "enonce.pdf").unlink()
|
(self.evaluation / "enonce.pdf").unlink()
|
||||||
self.app._reload_inputs()
|
self.app._reload_inputs()
|
||||||
self.assertEqual(self.app.state_store.step("inputs")["status"], "ready")
|
self.assertEqual(self.app.state_store.step("inputs")["status"], "ready")
|
||||||
self.assertIn("enonce.pdf", self.app.description_label.cget("text"))
|
self.assertIn("enonce.pdf", self.app.missing_requirements_label.cget("text"))
|
||||||
|
style = ttk.Style(self.app)
|
||||||
|
self.assertEqual(
|
||||||
|
style.lookup("MissingPrerequisite.TLabel", "foreground"), "#c62828"
|
||||||
|
)
|
||||||
|
|
||||||
def test_compact_evaluation_input_and_open_folder_button(self):
|
def test_compact_evaluation_input_and_open_folder_button(self):
|
||||||
self.assertEqual(int(self.app.evaluation_entry.cget("width")), 42)
|
self.assertEqual(int(self.app.evaluation_entry.cget("width")), 42)
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from copienator_gui.app import (
|
|||||||
build_runner_environment,
|
build_runner_environment,
|
||||||
copy_pdf_paths,
|
copy_pdf_paths,
|
||||||
detected_annotation_directories,
|
detected_annotation_directories,
|
||||||
|
get_personal_interro_files,
|
||||||
has_manual_conflicts,
|
has_manual_conflicts,
|
||||||
plotting_shortcut_lines,
|
plotting_shortcut_lines,
|
||||||
process_status,
|
process_status,
|
||||||
@@ -1660,6 +1661,53 @@ class WorkflowTests(unittest.TestCase):
|
|||||||
self.assertNotIn("update_ods", standard_ids)
|
self.assertNotIn("update_ods", standard_ids)
|
||||||
self.assertIn("update_ods", personal_ids)
|
self.assertIn("update_ods", personal_ids)
|
||||||
|
|
||||||
|
def test_get_personal_interro_files_copies_the_three_expected_sources(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
source = root / "source"
|
||||||
|
evaluation = root / "Interro07"
|
||||||
|
source.mkdir()
|
||||||
|
evaluation.mkdir()
|
||||||
|
contents = {
|
||||||
|
"Interro07.pdf": b"pdf",
|
||||||
|
"Interro07.tex": b"statement",
|
||||||
|
"Interro07c.tex": b"correction",
|
||||||
|
}
|
||||||
|
for name, content in contents.items():
|
||||||
|
(source / name).write_bytes(content)
|
||||||
|
|
||||||
|
copied = get_personal_interro_files(evaluation, source)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
copied,
|
||||||
|
(
|
||||||
|
evaluation / "enonce.pdf",
|
||||||
|
evaluation / "enonce.tex",
|
||||||
|
evaluation / "correction.tex",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual((evaluation / "enonce.pdf").read_bytes(), b"pdf")
|
||||||
|
self.assertEqual((evaluation / "enonce.tex").read_bytes(), b"statement")
|
||||||
|
self.assertEqual((evaluation / "correction.tex").read_bytes(), b"correction")
|
||||||
|
|
||||||
|
def test_get_personal_interro_files_validates_name_and_all_sources_first(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
source = root / "source"
|
||||||
|
source.mkdir()
|
||||||
|
invalid = root / "DS07"
|
||||||
|
invalid.mkdir()
|
||||||
|
with self.assertRaisesRegex(ValueError, "Interro"):
|
||||||
|
get_personal_interro_files(invalid, source)
|
||||||
|
|
||||||
|
evaluation = root / "Interro07"
|
||||||
|
evaluation.mkdir()
|
||||||
|
(evaluation / "enonce.pdf").write_bytes(b"existing")
|
||||||
|
(source / "Interro07.pdf").write_bytes(b"new")
|
||||||
|
with self.assertRaisesRegex(FileNotFoundError, "Interro07.tex"):
|
||||||
|
get_personal_interro_files(evaluation, source)
|
||||||
|
self.assertEqual((evaluation / "enonce.pdf").read_bytes(), b"existing")
|
||||||
|
|
||||||
def test_first_visit_automation_is_declared_on_expected_steps(self) -> None:
|
def test_first_visit_automation_is_declared_on_expected_steps(self) -> None:
|
||||||
auto_start = {
|
auto_start = {
|
||||||
step.id for step in self.steps.values() if step.auto_start_first_visit
|
step.id for step in self.steps.values() if step.auto_start_first_visit
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import os
|
|||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from tkinter import ttk
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from copienator import CliError, EvaluationWorkspace, ExitCode
|
from copienator import CliError, EvaluationWorkspace, ExitCode
|
||||||
from copienator.commands import enonce_info as personal
|
from copienator.commands import enonce_info as personal
|
||||||
from copienator.commands import gemini_for_enonce as gemini
|
from copienator.commands import gemini_for_enonce as gemini
|
||||||
from copienator_gui.app import CopienatorApp
|
from copienator_gui.app import CopienatorApp, get_personal_interro_files
|
||||||
from copienator_gui.workflow import build_workflow
|
from copienator_gui.workflow import build_workflow
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ class PersonalStatementTests(unittest.TestCase):
|
|||||||
enabled = {step.id: step for step in build_workflow(True)}
|
enabled = {step.id: step for step in build_workflow(True)}
|
||||||
self.assertEqual([variant.id for variant in standard["statement"].variants], ["gemini"])
|
self.assertEqual([variant.id for variant in standard["statement"].variants], ["gemini"])
|
||||||
self.assertEqual([variant.program for variant in enabled["statement"].variants],
|
self.assertEqual([variant.program for variant in enabled["statement"].variants],
|
||||||
["statement", "statement-personal"])
|
["statement-personal", "statement"])
|
||||||
for ident in ("statement_groups", "statement_persp"):
|
for ident in ("statement_groups", "statement_persp"):
|
||||||
self.assertNotIn(ident, standard)
|
self.assertNotIn(ident, standard)
|
||||||
self.assertTrue(enabled[ident].optional)
|
self.assertTrue(enabled[ident].optional)
|
||||||
@@ -140,6 +141,48 @@ class SelectiveGeminiTests(unittest.TestCase):
|
|||||||
|
|
||||||
@unittest.skipUnless(os.environ.get("DISPLAY"), "Tk requires a display")
|
@unittest.skipUnless(os.environ.get("DISPLAY"), "Tk requires a display")
|
||||||
class PersonalStatementGuiTests(unittest.TestCase):
|
class PersonalStatementGuiTests(unittest.TestCase):
|
||||||
|
def test_get_file_button_completes_inputs_and_advances(self):
|
||||||
|
with tempfile.TemporaryDirectory() as temporary:
|
||||||
|
repository = Path(temporary)
|
||||||
|
evaluation = repository / "Interro12"
|
||||||
|
source = repository / "source"
|
||||||
|
evaluation.mkdir()
|
||||||
|
source.mkdir()
|
||||||
|
(repository / "names").touch()
|
||||||
|
for name, content in (
|
||||||
|
("Interro12.pdf", b"pdf"),
|
||||||
|
("Interro12.tex", b"statement"),
|
||||||
|
("Interro12c.tex", b"correction"),
|
||||||
|
):
|
||||||
|
(source / name).write_bytes(content)
|
||||||
|
app = CopienatorApp(repository, True, evaluation)
|
||||||
|
try:
|
||||||
|
app.update()
|
||||||
|
buttons = [
|
||||||
|
child
|
||||||
|
for child in app.form.winfo_children()
|
||||||
|
if isinstance(child, ttk.Button)
|
||||||
|
]
|
||||||
|
get_button = next(
|
||||||
|
button for button in buttons if button.cget("text") == "Get the files"
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"copienator_gui.app.get_personal_interro_files",
|
||||||
|
side_effect=lambda target: get_personal_interro_files(target, source),
|
||||||
|
):
|
||||||
|
get_button.invoke()
|
||||||
|
app.update()
|
||||||
|
|
||||||
|
self.assertEqual(app.state_store.step("inputs")["status"], "success")
|
||||||
|
self.assertEqual(app.current_step.id, "statement")
|
||||||
|
self.assertEqual((evaluation / "enonce.pdf").read_bytes(), b"pdf")
|
||||||
|
self.assertEqual((evaluation / "enonce.tex").read_bytes(), b"statement")
|
||||||
|
self.assertEqual((evaluation / "correction.tex").read_bytes(), b"correction")
|
||||||
|
finally:
|
||||||
|
for callback in app.tk.splitlist(app.tk.call("after", "info")):
|
||||||
|
app.after_cancel(callback)
|
||||||
|
app.destroy()
|
||||||
|
|
||||||
def test_personal_requirements_and_optional_button_command_previews(self):
|
def test_personal_requirements_and_optional_button_command_previews(self):
|
||||||
with tempfile.TemporaryDirectory() as temporary:
|
with tempfile.TemporaryDirectory() as temporary:
|
||||||
evaluation = Path(temporary)
|
evaluation = Path(temporary)
|
||||||
@@ -149,12 +192,19 @@ class PersonalStatementGuiTests(unittest.TestCase):
|
|||||||
app.update()
|
app.update()
|
||||||
app.tree.selection_set("statement")
|
app.tree.selection_set("statement")
|
||||||
app.update()
|
app.update()
|
||||||
app._select_variant(1)
|
self.assertEqual(app.variant_var.get(), "personal")
|
||||||
self.assertIn("statement-personal", app.command_var.get())
|
self.assertIn("statement-personal", app.command_var.get())
|
||||||
self.assertEqual(app._missing_requirements(app.current_step), [])
|
self.assertEqual(app._missing_requirements(app.current_step), [])
|
||||||
self.assertIn("SHEETINFO", app.description_label.cget("text"))
|
self.assertIn("SHEETINFO", app.description_label.cget("text"))
|
||||||
|
self.assertFalse(
|
||||||
|
any(
|
||||||
|
isinstance(child, ttk.LabelFrame)
|
||||||
|
and child.cget("text") == "Après génération — facultatif"
|
||||||
|
for child in app.form.winfo_children()
|
||||||
|
)
|
||||||
|
)
|
||||||
for ident, flag in (("statement_groups", "--groups-only"), ("statement_persp", "--persp-only")):
|
for ident, flag in (("statement_groups", "--groups-only"), ("statement_persp", "--persp-only")):
|
||||||
app._select_statement_action(ident)
|
app.tree.selection_set(ident)
|
||||||
app.update()
|
app.update()
|
||||||
self.assertEqual(app.current_step.id, ident)
|
self.assertEqual(app.current_step.id, ident)
|
||||||
self.assertIn(flag, app.command_var.get())
|
self.assertIn(flag, app.command_var.get())
|
||||||
|
|||||||
Reference in New Issue
Block a user