Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions tests/ui/test_olm_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from korvid.ui.widgets.resource_table import ResourceTable
from tests.app_factory import build_test_app

from .waits import until
from .waits import screen_ready, until

_PODS_META = ResourceMeta("Pod", "pods", "", "v1", True, ("po",))
PKG_META = ResourceMeta("PackageManifest", "packagemanifests", PACKAGES_GROUP, "v1", True)
Expand Down Expand Up @@ -356,9 +356,7 @@ async def test_install_key_walks_wizard_confirm_and_creates_subscription(
table = app.query_one(ResourceTable)
await until(pilot, lambda: table.row_count == 1, label="package listed")
await pilot.press("I")
await until(
pilot, lambda: isinstance(app.screen, OperatorInstallPrompt), label="wizard open"
)
await until(pilot, lambda: screen_ready(app, OperatorInstallPrompt), label="wizard open")
await pilot.press("enter") # accept defaults: operators/stable/Automatic
await until(pilot, lambda: isinstance(app.screen, ConfirmScreen), label="confirm open")
body = app.screen.query_one(".confirm-operation").render()
Expand Down Expand Up @@ -595,9 +593,7 @@ async def test_install_fetches_manifest_by_canonical_view_kind(tmp_path: Path) -
table = app.query_one(ResourceTable)
await until(pilot, lambda: table.row_count == 1, label="package listed")
await pilot.press("I")
await until(
pilot, lambda: isinstance(app.screen, OperatorInstallPrompt), label="wizard open"
)
await until(pilot, lambda: screen_ready(app, OperatorInstallPrompt), label="wizard open")
assert fetch_log == [qualified]


Expand Down Expand Up @@ -675,9 +671,7 @@ async def test_install_wizard_prefills_default_namespace_with_zero_config(
table = app.query_one(ResourceTable)
await until(pilot, lambda: table.row_count == 1, label="package listed")
await pilot.press("I")
await until(
pilot, lambda: isinstance(app.screen, OperatorInstallPrompt), label="wizard open"
)
await until(pilot, lambda: screen_ready(app, OperatorInstallPrompt), label="wizard open")
ns_input = app.screen.query_one("#install-namespace")
assert getattr(ns_input, "value", "") == "default"

Expand Down Expand Up @@ -729,9 +723,7 @@ async def record_check(
table = app.query_one(ResourceTable)
await until(pilot, lambda: table.row_count == 1, label="package listed")
await pilot.press("I")
await until(
pilot, lambda: isinstance(app.screen, OperatorInstallPrompt), label="wizard open"
)
await until(pilot, lambda: screen_ready(app, OperatorInstallPrompt), label="wizard open")
await pilot.press("enter")
await until(pilot, lambda: bool(checks), label="permission pre-check ran")
assert checks[0] == ("create", "subscriptions", "")
Expand Down Expand Up @@ -761,7 +753,7 @@ async def test_the_controller_routes_the_install_key_to_the_package_wizard(
await app._olm.install_selected()
await until(
pilot,
lambda: isinstance(app.screen, OperatorInstallPrompt),
lambda: screen_ready(app, OperatorInstallPrompt),
label="install wizard",
)
assert rec.calls == []
Expand Down
52 changes: 51 additions & 1 deletion tests/ui/test_waits.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
from __future__ import annotations

import pytest
from textual.app import App, ComposeResult
from textual.screen import ModalScreen
from textual.widgets import Select

from .waits import WaitTimeout, until
from .waits import WaitTimeout, screen_ready, until


class _FakePilot:
Expand Down Expand Up @@ -50,3 +53,50 @@ async def test_until_timeout_is_not_an_assertion_error() -> None:
with pytest.raises(WaitTimeout, match=r"condition not met within 0\.05s") as caught:
await until(pilot, lambda: False, timeout=0.05)
assert not isinstance(caught.value, AssertionError)


class _Node:
def __init__(self, *, mounted: bool = True, children: tuple[_Node, ...] = ()) -> None:
self.is_mounted = mounted
self.children = children

def query(self, selector: str) -> tuple[_Node, ...]:
assert selector == "*"
return self.children


class _Prompt(_Node):
pass


class _App:
def __init__(self, screen: _Node) -> None:
self.screen = screen


@pytest.mark.parametrize(
("screen", "ready"),
[
pytest.param(_Node(), False, id="another screen"),
pytest.param(_Prompt(mounted=False), False, id="screen still mounting"),
pytest.param(
_Prompt(children=(_Node(), _Node(mounted=False))), False, id="a widget still mounting"
),
pytest.param(_Prompt(children=(_Node(), _Node())), True, id="every widget mounted"),
],
)
def test_screen_ready_waits_for_the_screen_and_every_widget(screen: _Node, ready: bool) -> None:
assert screen_ready(_App(screen), _Prompt) is ready


async def test_screen_ready_holds_only_once_a_select_has_mounted() -> None:
class Prompt(ModalScreen[None]):
def compose(self) -> ComposeResult:
yield Select([("stable", "stable"), ("fast", "fast")], value="stable")

app = App[None]()
async with app.run_test() as pilot:
app.push_screen(Prompt())
await until(pilot, lambda: screen_ready(app, Prompt), label="prompt ready")

assert app.screen.query_one(Select).is_mounted
6 changes: 2 additions & 4 deletions tests/ui/test_write_confirm_characterization.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
from .test_resize_flow import ResizeRecorder
from .test_resize_flow import make_app as make_resize_app
from .test_write_ops import Recorder, _edit_fixtures, _to_view, make_app
from .waits import until
from .waits import screen_ready, until


async def _row_listed(pilot, kind: str) -> None: # type: ignore[no-untyped-def] # Pilot's app type isn't exposed
Expand Down Expand Up @@ -266,9 +266,7 @@ async def test_install_cancelled_when_catalog_entry_changes_during_approval(
table = app.query_one(ResourceTable)
await until(pilot, lambda: table.row_count == 1, label="package listed")
await pilot.press("I")
await until(
pilot, lambda: isinstance(app.screen, OperatorInstallPrompt), label="wizard open"
)
await until(pilot, lambda: screen_ready(app, OperatorInstallPrompt), label="wizard open")
await pilot.press("enter") # accept defaults
await until(pilot, lambda: isinstance(app.screen, ConfirmScreen), label="confirm open")
# Same name, different incarnation lands while the dialog is open.
Expand Down
14 changes: 14 additions & 0 deletions tests/ui/waits.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,17 @@ async def until(
if cond():
return
raise WaitTimeout(f"{label} not met within {timeout}s")


def screen_ready(app: Any, screen_type: type) -> bool:
"""Whether `app` shows a `screen_type` whose every widget has finished mounting.

A screen is current as soon as it is pushed, before its widgets run their
Mount handlers. A test that leaves `run_test` at that point can tear a
widget down mid-handler: Textual's `Select` looks up its overlay in
`_on_mount`, and fails if the overlay is already gone (#504).
"""
if not isinstance(app.screen, screen_type):
return False
screen: Any = app.screen
return bool(screen.is_mounted) and all(widget.is_mounted for widget in screen.query("*"))
Loading