diff --git a/script/todo/auto_ask.py b/script/todo/auto_ask.py index eefee6e..d373ce7 100644 --- a/script/todo/auto_ask.py +++ b/script/todo/auto_ask.py @@ -23,6 +23,18 @@ derrière lui un `input()` toujours bloqué, qui volerait la frappe suivante. import os import sys +sys.path.append( + os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..")) +) + +try: + from script.todo.todo_i18n import t +except Exception: # pragma: no cover - repli si i18n indisponible + + def t(key: str) -> str: + return key + + ENV_ENABLED = "ERPLIBRE_AUTO_EXECUTE" ENV_DELAY = "ERPLIBRE_AUTO_DELAY" DEFAULT_DELAY = 5 @@ -90,7 +102,13 @@ def ask(prompt, default="", seconds=None): # lisible tout de suite et rend justement une ligne vide ; sans # ceci, le défaut ne servirait jamais là. return answer or default - print(f" ⏱ → {default or '(default)'}") + # « Entrée », parce que c'est ce que l'invite a proposé. Elle annonce + # « Entrée = effacer, k = garder » et ne mentionne nulle part « d » : + # afficher la valeur brute nommait donc une réponse qui n'était pas au + # menu, et l'on se demandait ce que l'outil venait de décider. La + # valeur reste, entre parenthèses, pour qui veut la précision. + detail = f" ({default})" if default else "" + print(f" ⏱ → {t('Enter')}{detail}") return default diff --git a/test/test_prompt_defaults.py b/test/test_prompt_defaults.py index f9f6f71..76427a3 100644 --- a/test/test_prompt_defaults.py +++ b/test/test_prompt_defaults.py @@ -163,6 +163,67 @@ class TestEnterMeansTheDefault(EnvCase): self.assertEqual(os.environ.get(auto_ask.ENV_ENABLED), "1") +class TestTheCountdownSpeaksThePromptsLanguage(EnvCase): + """« ⏱ → d » nommait une réponse qui n'était PAS au menu. + + L'invite annonce « Entrée = effacer, k = garder » : elle ne mentionne + « d » nulle part. Afficher la valeur brute obligeait donc à deviner ce + que l'outil venait de décider — alors que la décision était exactement + celle qu'Entrée promettait. + """ + + def setUp(self): + super().setUp() + from script.todo import todo_i18n + + self.addCleanup( + setattr, todo_i18n, "_current_lang", todo_i18n._current_lang + ) + todo_i18n._current_lang = "en" + + def tick(self, default): + """Forcer le chemin du DÉLAI, celui qui affiche la ligne.""" + import select + + original = select.select + select.select = lambda r, w, x, t: ([], [], []) + self.addCleanup(setattr, select, "select", original) + auto_ask.export(True, 0.01) + out = io.StringIO() + with contextlib.redirect_stdout(out): + valeur = auto_ask.ask("q : ", default=default) + return valeur, out.getvalue() + + def test_it_names_what_the_prompt_offered(self): + _valeur, texte = self.tick("d") + self.assertIn("Enter", texte) + + def test_the_raw_value_stays_for_precision(self): + # La nommer reste utile : deux invites voisines peuvent avoir des + # défauts différents, et l'on relit ces lignes après coup. + _valeur, texte = self.tick("d") + self.assertIn("(d)", texte) + + def test_an_empty_default_shows_no_parentheses(self): + # « Entrée () » n'aurait aucun sens : il n'y a rien à préciser. + _valeur, texte = self.tick("") + self.assertIn("Enter", texte) + self.assertNotIn("(", texte) + + def test_the_answer_itself_is_unchanged(self): + # L'affichage seul change : ce que la migration reçoit reste la + # valeur, sinon toutes les invites changeraient de comportement. + valeur, _texte = self.tick("d") + self.assertEqual(valeur, "d") + + def test_it_follows_the_language_of_the_migration(self): + from script.todo import todo_i18n + + todo_i18n._current_lang = "fr" + _valeur, texte = self.tick("d") + self.assertIn("Entrée", texte) + + class TestNoPromptOfTheMigrationCanHang(unittest.TestCase): """Un `input()` nu ne sait rien du mode auto : il attend, pour toujours.