120 lines
4.2 KiB
Python
120 lines
4.2 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
||
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||
|
|
|
||
|
|
"""L'écran de reprise doit OFFRIR le retour à une étape, pas le cacher.
|
||
|
|
|
||
|
|
Le mécanisme existait — Entrée sur une ligne du tableau — mais vivait dans une
|
||
|
|
ligne d'astuce sous quatre boutons nommés. Une capacité qu'il faut deviner
|
||
|
|
n'est pas offerte : elle a donc une touche, un bouton et un message, comme les
|
||
|
|
quatre autres actions.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import json
|
||
|
|
import unittest
|
||
|
|
|
||
|
|
from script.todo import todo_i18n
|
||
|
|
from script.todo.migration_form import run_resume_tui
|
||
|
|
from script.todo.todo_upgrade import TodoUpgrade
|
||
|
|
|
||
|
|
|
||
|
|
def context():
|
||
|
|
"""Le contexte d'une migration arrêtée avant les montées de version."""
|
||
|
|
progression = {
|
||
|
|
"migration_file": "./image_db/technolibre.zip",
|
||
|
|
"config_database_name": "technolibre_migration_01_neutralize",
|
||
|
|
"target_odoo_version": "18.0",
|
||
|
|
"date_create": "2026-08-12 05:04:22",
|
||
|
|
"state_0_install_odoo": True,
|
||
|
|
"state_1_restore_database": True,
|
||
|
|
"state_2_update_all": True,
|
||
|
|
"state_3_clean_database": True,
|
||
|
|
}
|
||
|
|
return TodoUpgrade.resume_context(TodoUpgrade, progression)
|
||
|
|
|
||
|
|
|
||
|
|
class TestBackIsOffered(unittest.TestCase):
|
||
|
|
def setUp(self):
|
||
|
|
self.addCleanup(
|
||
|
|
setattr, todo_i18n, "_current_lang", todo_i18n._current_lang
|
||
|
|
)
|
||
|
|
todo_i18n._current_lang = "en"
|
||
|
|
self.ctx = context()
|
||
|
|
|
||
|
|
def bindings(self, app):
|
||
|
|
out = {}
|
||
|
|
for item in app.BINDINGS:
|
||
|
|
if isinstance(item, tuple):
|
||
|
|
out[item[0]] = item[1]
|
||
|
|
else:
|
||
|
|
out[item.key] = item.action
|
||
|
|
return out
|
||
|
|
|
||
|
|
def test_b_is_a_binding_like_the_others(self):
|
||
|
|
app = run_resume_tui(self.ctx, run_app=False)
|
||
|
|
self.assertEqual(self.bindings(app).get("b"), "back_step")
|
||
|
|
|
||
|
|
def test_the_action_exists(self):
|
||
|
|
self.assertTrue(
|
||
|
|
hasattr(
|
||
|
|
run_resume_tui(self.ctx, run_app=False), "action_back_step"
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestBackWorks(unittest.IsolatedAsyncioTestCase):
|
||
|
|
"""Piloté dans un terminal simulé : la touche mène bien au choix."""
|
||
|
|
|
||
|
|
def setUp(self):
|
||
|
|
self.addCleanup(
|
||
|
|
setattr, todo_i18n, "_current_lang", todo_i18n._current_lang
|
||
|
|
)
|
||
|
|
todo_i18n._current_lang = "en"
|
||
|
|
self.ctx = context()
|
||
|
|
|
||
|
|
async def test_b_focuses_the_steps_and_enter_replays_from_there(self):
|
||
|
|
from textual.widgets import DataTable
|
||
|
|
|
||
|
|
app = run_resume_tui(self.ctx, run_app=False)
|
||
|
|
async with app.run_test() as pilot:
|
||
|
|
await pilot.press("b")
|
||
|
|
await pilot.pause()
|
||
|
|
table = app.query_one("#steps", DataTable)
|
||
|
|
self.assertTrue(table.has_focus)
|
||
|
|
# Le curseur part sur la première étape inachevée ; on remonte de
|
||
|
|
# deux pour viser « mettre à jour tous les modules ».
|
||
|
|
await pilot.press("up")
|
||
|
|
await pilot.press("up")
|
||
|
|
await pilot.pause()
|
||
|
|
expected = str(self.ctx["steps"][table.cursor_row]["step"])
|
||
|
|
await pilot.press("enter")
|
||
|
|
await pilot.pause()
|
||
|
|
self.assertEqual(app._result["answer"], expected)
|
||
|
|
|
||
|
|
async def test_there_is_a_button_too(self):
|
||
|
|
# C'est l'absence VISIBLE qui a été signalée : une touche seule ne se
|
||
|
|
# voit pas parmi quatre boutons. Les widgets ne se lisent qu'une fois
|
||
|
|
# l'écran monté, d'où le pilote.
|
||
|
|
from textual.widgets import Button
|
||
|
|
|
||
|
|
app = run_resume_tui(self.ctx, run_app=False)
|
||
|
|
async with app.run_test():
|
||
|
|
labels = [b.label.plain for b in app.query(Button)]
|
||
|
|
self.assertIn("Go back to a step", labels)
|
||
|
|
|
||
|
|
async def test_the_button_does_the_same(self):
|
||
|
|
from textual.widgets import Button, DataTable
|
||
|
|
|
||
|
|
app = run_resume_tui(self.ctx, run_app=False)
|
||
|
|
async with app.run_test() as pilot:
|
||
|
|
button = next(b for b in app.query(Button) if b.id == "a_back")
|
||
|
|
await pilot.click(button)
|
||
|
|
await pilot.pause()
|
||
|
|
self.assertTrue(app.query_one("#steps", DataTable).has_focus)
|
||
|
|
# Et surtout : le bouton ne répond PAS à la place de l'utilisateur.
|
||
|
|
self.assertIsNone(app._result["answer"])
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|