Skip to content
Closed
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
18 changes: 18 additions & 0 deletions tests/evals/operation_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,25 @@ def _apply_intervention(self) -> None:
f"{self._journey.id}: dialog_intervention replacement did not apply"
)

async def _await_dialog_composed(self, pilot: Any) -> None:
"""Wait until a current approval dialog has its widgets, or closes.

Textual makes a pushed screen current before it composes the screen's
widgets, and a loaded runner can poll in between (#478): reading the
title then would find none. A dialog that closes first is left to
`handle`, which already treats it as closed before a decision.
"""
screen = self._app.screen
if not isinstance(screen, ConfirmScreen):
return
await until(
pilot,
lambda: self._app.screen is not screen or bool(screen.query(".confirm-title")),
label="approval dialog composed",
)

async def handle(self, pilot: Any) -> None:
await self._await_dialog_composed(pilot)
screen = self._app.screen
if not isinstance(screen, ConfirmScreen):
if self._remaining <= 0 or not self._dialog_seen:
Expand Down
21 changes: 21 additions & 0 deletions tests/evals/test_operation_journeys.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from __future__ import annotations

import asyncio
import json
import os
from collections.abc import AsyncIterator, Callable
Expand All @@ -31,6 +32,7 @@
from korvid.evals.scripted import ScriptedProvider
from korvid.tools.executor import RecordedExecution, ToolOutcome
from korvid.tools.structured import dump_yaml
from korvid.ui.widgets.confirm_screen import ConfirmScreen

from . import operation_app
from .operation_app import (
Expand Down Expand Up @@ -1331,6 +1333,25 @@ def polls_that_miss_every_dialog(
assert run.audit == ()


async def test_a_dialog_still_composing_is_read_once_it_has_its_widgets(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Textual makes a pushed screen current before it composes the screen's
widgets, and a loaded runner can poll in between (#478). The driver must
wait for the dialog's title instead of reading a dialog that has none."""
original = ConfirmScreen._compose

async def slow_compose(self: ConfirmScreen) -> None:
await asyncio.sleep(0.3)
await original(self)

monkeypatch.setattr(ConfirmScreen, "_compose", slow_compose)
run = await run_scripted_journey("scale-deployment-up", tmp_path)
observed = [entry for entry in run.journal if entry["event"] == "dialog_observed"]
assert [entry["result"] for entry in observed] == ["matched"]
assert run.grade.hard_failures == ()


def test_the_replacement_journey_declares_its_intervention_in_the_fixture() -> None:
"""B1: the mid-dialog replacement is fixture data, not test code, so
`tests.evals.operation_campaign` runs the identical journey."""
Expand Down
Loading