From 2ccba35637e26d4ca2267cdbbc797eae121c11d1 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Thu, 29 Aug 2024 17:58:47 -0400 Subject: [PATCH] update textual (#66) * update textual * add a few more pauses * fix help-text from reviewer comment * make ruff happy --- bmds_ui/desktop/app.py | 1 + bmds_ui/desktop/components/database_form.py | 10 +- .../components/transforms/polyk/OutputTabs.js | 2 +- pyproject.toml | 2 +- tests/desktop/test_app.py | 92 ++++++++----------- 5 files changed, 49 insertions(+), 58 deletions(-) diff --git a/bmds_ui/desktop/app.py b/bmds_ui/desktop/app.py index bc702014..703bc37b 100644 --- a/bmds_ui/desktop/app.py +++ b/bmds_ui/desktop/app.py @@ -9,6 +9,7 @@ class BmdsDesktopTui(App): + ENABLE_COMMAND_PALETTE = False CSS_PATH = "components/style.tcss" BINDINGS: ClassVar = [ diff --git a/bmds_ui/desktop/components/database_form.py b/bmds_ui/desktop/components/database_form.py index c7194e18..9f0e1895 100644 --- a/bmds_ui/desktop/components/database_form.py +++ b/bmds_ui/desktop/components/database_form.py @@ -12,6 +12,7 @@ from textual.validation import Function from textual.widget import Widget from textual.widgets import Button, Input, Label, Markdown +from textual.worker import Worker, WorkerState from ..actions import create_django_db from ..config import Config, Database, db_suffixes @@ -150,6 +151,7 @@ class DatabaseFormModel(ModalScreen): """ def __init__(self, *args, db: Database | None, **kw): + kw.setdefault("name", "db_form") self.db: Database | None = db super().__init__(*args, **kw) @@ -228,7 +230,7 @@ async def on_db_create(self) -> None: config = Config.get() self._create_django_db(config, db) - @work(exclusive=True, thread=True) + @work(exclusive=True, thread=True, group="modify-db") def _create_django_db(self, config, db): # sleeps are required for loading indicator to show/hide properly self.app.call_from_thread(self.set_loading, True) @@ -236,7 +238,11 @@ def _create_django_db(self, config, db): Config.sync() create_django_db(db) self.app.call_from_thread(self.set_loading, False) - self.app.call_from_thread(self.dismiss, True) + + def on_worker_state_changed(self, event: Worker.StateChanged) -> None: + """Called when the worker state changes.""" + if event.worker.group == "modify-db" and event.state == WorkerState.SUCCESS: + self.dismiss(True) @on(Button.Pressed, "#db-update") async def on_db_update(self) -> None: diff --git a/frontend/src/components/transforms/polyk/OutputTabs.js b/frontend/src/components/transforms/polyk/OutputTabs.js index f187e41c..2418a33a 100644 --- a/frontend/src/components/transforms/polyk/OutputTabs.js +++ b/frontend/src/components/transforms/polyk/OutputTabs.js @@ -180,7 +180,7 @@ class OutputTabs extends Component { textToCopy={copyText} onCopy={e => { alert( - 'Data copied to your clipboard! You can paste into Excel, or paste into an a BMDS analysis creating a new dataset and pressing the "Load dataset from Excel" button.' + 'Data copied to your clipboard! For your Multistage/Multitumor analysis, return to the Data tab, select the "Load dataset from Excel" button, and paste the clipboard contents to create a new dataset. Or, paste the clipboard contents into Excel for further analysis.' ); }} className="btn btn-link my-1" diff --git a/pyproject.toml b/pyproject.toml index 12179534..07992189 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ dependencies = [ # desktop "packaging~=24.0", - "textual==0.72.0", + "textual==0.78.0", "whitenoise~=6.7.0", ] diff --git a/tests/desktop/test_app.py b/tests/desktop/test_app.py index 90667ce7..6d7a7d64 100644 --- a/tests/desktop/test_app.py +++ b/tests/desktop/test_app.py @@ -4,7 +4,7 @@ from pathlib import Path import pytest -from textual.widgets import TabbedContent +from textual.widgets import Button, TabbedContent from textual.widgets._tabbed_content import ContentTab from bmds_ui.desktop import components @@ -125,18 +125,15 @@ async def test_db_form(self, rollback_get_app): db_items = len(list(app.query(".db-edit"))) # open db modal creation - await pilot.click("#create-db") - await pilot.pause() + await assert_change_screen(pilot, app, app.query_one("#create-db"), "main", "db_form") # cancel db modal creation - assert isinstance(app.screen, components.database_form.DatabaseFormModel) - await pilot.click("#db-edit-cancel") - await pilot.pause() - assert app.screen.name == "main" + await assert_change_screen( + pilot, app, app.query_one("#db-edit-cancel"), "db_form", "main" + ) # open db modal creation again - await pilot.click("#create-db") - await pilot.pause() + await assert_change_screen(pilot, app, app.query_one("#create-db"), "main", "db_form") # fill out form w/ valid data app.query_one("#name").value = f"Test {secrets.token_urlsafe(8)}" @@ -146,72 +143,59 @@ async def test_db_form(self, rollback_get_app): with tempfile.TemporaryDirectory() as temp_dir: resolved_temp_dir = str(Path(temp_dir).resolve()) - # create a new db + # CREATE app.query_one("#path").value = resolved_temp_dir - await _wait_until_form_closes(pilot, app, "#db-create") - assert isinstance(app.screen, components.main.Main) + await assert_change_screen( + pilot, app, app.query_one("#db-create"), "db_form", "main" + ) # make sure a new one appears on the list page newly_created = list(app.query(".db-edit")) first = newly_created[0] assert len(newly_created) == db_items + 1 - # start the application! - start_db = list(app.query(".db-start")) - start_db[0].focus() - await pilot.press("enter") - await pilot.pause() - - # stop the application! - stop_db = list(app.query(".db-stop")) - stop_db[0].focus() - await pilot.press("enter") - - # edit the newly created db - first.focus() - await pilot.press("enter") - assert isinstance(app.screen, components.database_form.DatabaseFormModel) + # START/STOP APPLICATION + await click_first_button(pilot, app, app.query(".db-start")) + await click_first_button(pilot, app, app.query(".db-stop")) + # CANCEL UPDATE + await assert_change_screen(pilot, app, first, "main", "db_form") assert app.query_one("#name").value.startswith("Test ") assert app.query_one("#filename").value == "test-db.db" assert app.query_one("#path").value == resolved_temp_dir assert app.query_one("#description").value == "test description" + await assert_change_screen( + pilot, app, app.query_one("#db-edit-cancel"), "db_form", "main" + ) - # update it and save (no changes) - await _wait_until_form_closes(pilot, app, "#db-edit-cancel") - assert isinstance(app.screen, components.main.Main) - - # edit the newly created db - first.focus() - await pilot.press("enter") - assert isinstance(app.screen, components.database_form.DatabaseFormModel) - + # UPDATE + await assert_change_screen(pilot, app, first, "main", "db_form") app.query_one("#description").value = "test description #2" - await _wait_until_form_closes(pilot, app, "#db-update") - await pilot.pause() - assert isinstance(app.screen, components.main.Main) + await assert_change_screen( + pilot, app, app.query_one("#db-update"), "db_form", "main" + ) # requery; update caused screen layout and removed first attribute newly_created = list(app.query(".db-edit")) first = newly_created[0] # delete it - first.focus() - await pilot.press("enter") - assert isinstance(app.screen, components.database_form.DatabaseFormModel) + await assert_change_screen(pilot, app, first, "main", "db_form") + await assert_change_screen( + pilot, app, app.query_one("#db-delete"), "db_form", "main" + ) + assert len(list(app.query(".db-edit"))) == db_items - await pilot.click("#db-delete") - await pilot.pause() - assert len(list(app.query(".db-edit"))) == db_items +async def click_first_button(pilot, app, query): + next(iter(query)).focus() + await pilot.press("enter") + await pilot.pause() -async def _wait_until_form_closes(pilot, app, btn: str): - assert isinstance(app.screen, components.database_form.DatabaseFormModel) - await pilot.click(btn) - # wait for db creation to finish or max of 60 seconds - max = 1 - while max < 60 and isinstance(app.screen, components.database_form.DatabaseFormModel): - await pilot.pause(delay=1) - max += 1 - assert app.screen.name == "main" +async def assert_change_screen(pilot, app, btn: Button, current_screen: str, new_screen: str): + assert app.screen.name == current_screen + btn.focus() + await pilot.press("enter") + await pilot.pause() + assert app.screen.name == new_screen