"""Tests for ask_user tool integration in the CLI."""

from __future__ import annotations

import asyncio
from pathlib import Path
from typing import TYPE_CHECKING

from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.widgets import Markdown, Static

from deepagents_code.tool_display import format_tool_display
from deepagents_code.widgets.ask_user import (
    _TRAILING_ANNOTATION_RE,
    AskUserMenu,
    AskUserTextArea,
    _QuestionWidget,
)

if TYPE_CHECKING:
    from deepagents_code._ask_user_types import AskUserWidgetResult, Question


class _AskUserTestApp(App[None]):
    CSS_PATH = Path(__file__).resolve().parents[2] / "deepagents_code" / "app.tcss"

    def __init__(self, questions: list[Question]) -> None:
        super().__init__()
        self._questions = questions

    def compose(self) -> ComposeResult:
        yield AskUserMenu(self._questions, id="ask-user-menu")


class TestAskUserToolDisplay:
    """Tests for ask_user formatting in tool_display."""

    def test_format_single_question(self) -> None:
        result = format_tool_display(
            "ask_user",
            {
                "questions": [
                    {"question": "What is your name?", "type": "text"},
                ]
            },
        )
        assert "ask_user" in result
        assert "1 question" in result

    def test_format_multiple_questions(self) -> None:
        result = format_tool_display(
            "ask_user",
            {
                "questions": [
                    {"question": "Name?", "type": "text"},
                    {
                        "question": "Color?",
                        "type": "multiple_choice",
                        "choices": [{"value": "red"}, {"value": "blue"}],
                    },
                ]
            },
        )
        assert "ask_user" in result
        assert "2 questions" in result

    def test_format_empty_questions(self) -> None:
        result = format_tool_display("ask_user", {"questions": []})
        assert "ask_user" in result
        assert "0 questions" in result

    def test_format_no_questions_key(self) -> None:
        result = format_tool_display("ask_user", {})
        assert "ask_user" in result


class TestTrailingAnnotationRegex:
    """Strips LLM-appended '(optional)'/'- required' annotations from question text."""

    def test_strips_dash_optional(self) -> None:
        assert _TRAILING_ANNOTATION_RE.sub("", "Your name? - optional") == "Your name?"

    def test_strips_parens_optional(self) -> None:
        assert _TRAILING_ANNOTATION_RE.sub("", "Your name? (optional)") == "Your name?"

    def test_strips_brackets_required(self) -> None:
        assert _TRAILING_ANNOTATION_RE.sub("", "Pick one [required]") == "Pick one"

    def test_strips_em_dash(self) -> None:
        text = "Your name? \u2014 optional"
        assert _TRAILING_ANNOTATION_RE.sub("", text) == "Your name?"

    def test_strips_en_dash(self) -> None:
        text = "Your name? \u2013 optional"
        assert _TRAILING_ANNOTATION_RE.sub("", text) == "Your name?"

    def test_strips_parens_required(self) -> None:
        assert _TRAILING_ANNOTATION_RE.sub("", "Pick one (required)") == "Pick one"

    def test_strips_dash_required(self) -> None:
        assert _TRAILING_ANNOTATION_RE.sub("", "Pick one - required") == "Pick one"

    def test_strips_with_trailing_whitespace(self) -> None:
        text = "Your name? (optional)   "
        assert _TRAILING_ANNOTATION_RE.sub("", text) == "Your name?"

    def test_strips_with_trailing_newline(self) -> None:
        text = "Your name? (optional)\n"
        assert _TRAILING_ANNOTATION_RE.sub("", text) == "Your name?"

    def test_strips_with_trailing_punctuation(self) -> None:
        text = "Your name? \u2014 Optional."
        assert _TRAILING_ANNOTATION_RE.sub("", text) == "Your name?"
        assert _TRAILING_ANNOTATION_RE.sub("", "Pick one (OPTIONAL!)") == "Pick one"

    def test_case_insensitive(self) -> None:
        assert _TRAILING_ANNOTATION_RE.sub("", "Your name? (Optional)") == "Your name?"

    def test_preserves_trailing_word_optional_without_delimiter(self) -> None:
        """Bare trailing 'optional' with no delimiter is not an annotation."""
        text = "Which field is optional"
        assert _TRAILING_ANNOTATION_RE.sub("", text) == text

    def test_leaves_question_without_annotation(self) -> None:
        text = "What is your name?"
        assert _TRAILING_ANNOTATION_RE.sub("", text) == text


class TestAskUserTextAreaBindings:
    """Ensures the ask-user text area matches chat input editing shortcuts."""

    def test_modified_backspace_deletes_word_left(self) -> None:
        """Modified Backspace aliases should delete the previous word."""
        word_delete_keys = {
            key.strip()
            for binding in AskUserTextArea.BINDINGS
            if isinstance(binding, Binding) and binding.action == "delete_word_left"
            for key in binding.key.split(",")
        }

        assert "ctrl+backspace" in word_delete_keys
        assert "alt+backspace" in word_delete_keys


class TestAskUserMenu:
    def test_find_menu_logs_when_hierarchy_is_missing(
        self,
        caplog,
    ) -> None:
        """`_find_menu` should warn when no AskUserMenu ancestor exists."""
        question_widget = _QuestionWidget({"question": "Name?", "type": "text"}, 0)
        with caplog.at_level("WARNING", logger="deepagents_code.widgets.ask_user"):
            assert question_widget._find_menu() is None
        assert "Failed to find AskUserMenu ancestor" in caplog.text

    async def test_text_input_receives_focus_on_mount(self) -> None:
        """The text area must have focus after mount so the user can type."""
        app = _AskUserTestApp([{"question": "What is your name?", "type": "text"}])

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            text_input = menu.query_one(".ask-user-text-input", AskUserTextArea)
            assert text_input.has_focus

    async def test_multiple_choice_question_widget_receives_focus_on_mount(
        self,
    ) -> None:
        """The _QuestionWidget must have focus so arrow/enter bindings work."""
        app = _AskUserTestApp(
            [
                {
                    "question": "Pick one",
                    "type": "multiple_choice",
                    "choices": [{"value": "red"}, {"value": "blue"}],
                }
            ]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            qw = menu._question_widgets[0]
            assert qw.has_focus

    async def test_multiple_choice_option_wraps_in_narrow_menu(self) -> None:
        """Long choice labels should wrap instead of being clipped to one row."""
        app = _AskUserTestApp(
            [
                {
                    "question": "Pick one",
                    "type": "multiple_choice",
                    "choices": [
                        {
                            "value": (
                                "this option label is intentionally long enough "
                                "to wrap in a narrow ask user menu"
                            )
                        }
                    ],
                }
            ]
        )

        async with app.run_test(size=(36, 24)) as pilot:
            await pilot.pause()
            choice = app.query_one(".ask-user-choice", Static)
            assert choice.size.height > 1

    async def test_text_question_submits_typed_answer(self) -> None:
        app = _AskUserTestApp([{"question": "What is your name?", "type": "text"}])

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            text_input = menu.query_one(".ask-user-text-input", AskUserTextArea)
            text_input.text = "Alice"
            text_input.focus()
            await pilot.pause()
            await pilot.press("enter")
            await pilot.pause()

            assert future.done()
            assert future.result() == {"type": "answered", "answers": ["Alice"]}

    async def test_text_input_soft_wraps_long_answers(self) -> None:
        """Soft-wrap is enabled so long answers wrap visually without newlines."""
        app = _AskUserTestApp([{"question": "Describe?", "type": "text"}])

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            text_input = menu.query_one(".ask-user-text-input", AskUserTextArea)
            assert text_input.soft_wrap is True

    async def test_enter_submits_without_inserting_newline(self) -> None:
        """Enter submits the answer instead of inserting a newline."""
        app = _AskUserTestApp([{"question": "Describe?", "type": "text"}])

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            text_input = menu.query_one(".ask-user-text-input", AskUserTextArea)
            text_input.text = "hi"
            text_input.focus()
            await pilot.pause()
            await pilot.press("enter")
            await pilot.pause()

            assert future.done()
            assert future.result() == {"type": "answered", "answers": ["hi"]}

    async def test_shift_enter_inserts_newline_for_multiline_answer(self) -> None:
        """Shift+Enter inserts a literal newline for multi-paragraph answers."""
        app = _AskUserTestApp([{"question": "Describe?", "type": "text"}])

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            text_input = menu.query_one(".ask-user-text-input", AskUserTextArea)
            text_input.focus()
            await pilot.pause()

            await pilot.press("a")
            await pilot.press("shift+enter")
            await pilot.press("b")
            await pilot.pause()
            assert text_input.text == "a\nb"

            await pilot.press("enter")
            await pilot.pause()

            assert future.done()
            assert future.result() == {"type": "answered", "answers": ["a\nb"]}

    async def test_escape_cancels_and_resolves_future(self) -> None:
        app = _AskUserTestApp([{"question": "Name?", "type": "text"}])

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            menu.focus()
            await pilot.pause()
            await pilot.press("escape")
            await pilot.pause()

            assert future.done()
            assert future.result() == {"type": "cancelled"}

    async def test_multiple_choice_submits_without_text_input(self) -> None:
        app = _AskUserTestApp(
            [
                {
                    "question": "Pick one",
                    "type": "multiple_choice",
                    "choices": [{"value": "red"}, {"value": "blue"}],
                }
            ]
        )

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            await pilot.press("enter")
            await pilot.pause()

            assert future.done()
            assert future.result() == {"type": "answered", "answers": ["red"]}

    async def test_multiple_choice_other_accepts_custom_text(self) -> None:
        app = _AskUserTestApp(
            [
                {
                    "question": "Pick one",
                    "type": "multiple_choice",
                    "choices": [{"value": "red"}, {"value": "blue"}],
                }
            ]
        )

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            await pilot.press("down")
            await pilot.press("down")
            await pilot.press("enter")
            await pilot.pause()

            other_input = menu.query_one(".ask-user-other-input", AskUserTextArea)
            other_input.text = "green"
            other_input.focus()
            await pilot.pause()
            await pilot.press("enter")
            await pilot.pause()

            assert future.done()
            assert future.result() == {"type": "answered", "answers": ["green"]}

    async def test_enter_advances_sequentially_through_mc_questions(self) -> None:
        """Enter on a MC question should advance to the next, not skip."""
        app = _AskUserTestApp(
            [
                {
                    "question": "Color?",
                    "type": "multiple_choice",
                    "choices": [{"value": "red"}, {"value": "blue"}],
                },
                {
                    "question": "Size?",
                    "type": "multiple_choice",
                    "choices": [{"value": "S"}, {"value": "M"}, {"value": "L"}],
                },
                {"question": "Name?", "type": "text"},
            ]
        )

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            # Q1 (MC) — first question should be active
            qw0 = menu._question_widgets[0]
            assert qw0.has_focus
            assert qw0.has_class("ask-user-question-active")

            # Press Enter to confirm Q1 default ("red") → should advance to Q2
            await pilot.press("enter")
            await pilot.pause()
            qw1 = menu._question_widgets[1]
            assert qw1.has_focus
            assert qw1.has_class("ask-user-question-active")
            assert qw0.has_class("ask-user-question-inactive")
            assert not future.done(), "Should not submit yet"

            # Navigate to "M" on Q2 and confirm
            await pilot.press("down")
            await pilot.press("enter")
            await pilot.pause()
            text_input = menu.query_one(".ask-user-text-input", AskUserTextArea)
            assert text_input.has_focus
            assert not future.done(), "Should not submit yet"

            # Type answer for Q3 and submit
            text_input.text = "Alice"
            await pilot.pause()
            await pilot.press("enter")
            await pilot.pause()

            assert future.done()
            assert future.result() == {
                "type": "answered",
                "answers": ["red", "M", "Alice"],
            }

    async def test_active_question_has_visual_indicator(self) -> None:
        """The active question should have the active CSS class."""
        app = _AskUserTestApp(
            [
                {"question": "Q1?", "type": "text"},
                {"question": "Q2?", "type": "text"},
            ]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            qw0 = menu._question_widgets[0]
            qw1 = menu._question_widgets[1]
            assert qw0.has_class("ask-user-question-active")
            assert qw1.has_class("ask-user-question-inactive")

    async def test_tab_advances_to_next_question(self) -> None:
        """Tab moves active indicator forward without confirming."""
        app = _AskUserTestApp(
            [
                {"question": "Q1?", "type": "text"},
                {"question": "Q2?", "type": "text"},
            ]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            qw0 = menu._question_widgets[0]
            qw1 = menu._question_widgets[1]
            assert qw0.has_class("ask-user-question-active")

            await pilot.press("tab")
            await pilot.pause()

            assert qw1.has_class("ask-user-question-active")
            assert qw0.has_class("ask-user-question-inactive")
            # Tab should NOT confirm the answer
            assert not menu._confirmed[0]

    async def test_tab_clamps_at_last_question(self) -> None:
        """Tab at the last question is a no-op."""
        app = _AskUserTestApp(
            [
                {"question": "Q1?", "type": "text"},
                {"question": "Q2?", "type": "text"},
            ]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)

            # Move to last question
            menu.action_next_question()
            await pilot.pause()
            assert menu._current_question == 1

            # Tab again — should stay at 1
            menu.action_next_question()
            await pilot.pause()
            assert menu._current_question == 1

    async def test_tab_noop_for_single_question(self) -> None:
        """Single question: tab does nothing."""
        app = _AskUserTestApp([{"question": "Q1?", "type": "text"}])

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            assert menu._current_question == 0

            menu.action_next_question()
            await pilot.pause()
            assert menu._current_question == 0

    async def test_previous_question_navigates_backward(self) -> None:
        """`action_previous_question` moves backward."""
        app = _AskUserTestApp(
            [
                {"question": "Q1?", "type": "text"},
                {"question": "Q2?", "type": "text"},
            ]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            qw0 = menu._question_widgets[0]
            qw1 = menu._question_widgets[1]

            # Move forward first
            menu.action_next_question()
            await pilot.pause()
            assert qw1.has_class("ask-user-question-active")

            # Move backward
            menu.action_previous_question()
            await pilot.pause()
            assert qw0.has_class("ask-user-question-active")
            assert qw1.has_class("ask-user-question-inactive")

    async def test_previous_question_clamps_at_first(self) -> None:
        """At first question: previous is a no-op."""
        app = _AskUserTestApp(
            [
                {"question": "Q1?", "type": "text"},
                {"question": "Q2?", "type": "text"},
            ]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            assert menu._current_question == 0

            menu.action_previous_question()
            await pilot.pause()
            assert menu._current_question == 0

    async def test_help_text_shows_tab_hint_for_multiple(self) -> None:
        """Footer mentions Tab for 2+ questions."""
        app = _AskUserTestApp(
            [
                {"question": "Q1?", "type": "text"},
                {"question": "Q2?", "type": "text"},
            ]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            help_text = menu.query_one(".ask-user-help").render()
            assert "Tab" in str(help_text)

    async def test_help_text_omits_tab_hint_for_single(self) -> None:
        """Footer omits Tab for 1 question."""
        app = _AskUserTestApp([{"question": "Q1?", "type": "text"}])

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            help_text = menu.query_one(".ask-user-help").render()
            assert "Tab" not in str(help_text)

    async def test_required_label_shown_for_required_question(self) -> None:
        """Required questions display a (required) indicator."""
        app = _AskUserTestApp([{"question": "Name?", "type": "text", "required": True}])

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            qw = menu._question_widgets[0]
            md = qw.query_one(Markdown)
            assert "required" in md.source

    async def test_required_label_hidden_for_optional_question(self) -> None:
        """Optional questions do not display a (required) indicator."""
        app = _AskUserTestApp(
            [{"question": "Nickname?", "type": "text", "required": False}]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            qw = menu._question_widgets[0]
            md = qw.query_one(Markdown)
            assert "required" not in md.source

    async def test_required_is_true_by_default(self) -> None:
        """Questions without explicit required field default to required."""
        app = _AskUserTestApp([{"question": "Name?", "type": "text"}])

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            qw = menu._question_widgets[0]
            assert qw._required is True
            md = qw.query_one(Markdown)
            assert "required" in md.source

    async def test_optional_question_submits_with_empty_answer(self) -> None:
        """Non-required questions can be submitted with empty answers."""
        app = _AskUserTestApp(
            [{"question": "Nickname?", "type": "text", "required": False}]
        )

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            # Press enter without typing anything
            await pilot.press("enter")
            await pilot.pause()

            assert future.done()
            assert future.result() == {"type": "answered", "answers": [""]}

    async def test_required_question_blocks_empty_submit(self) -> None:
        """Required questions block submission when answer is empty."""
        app = _AskUserTestApp([{"question": "Name?", "type": "text", "required": True}])

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            # Press enter without typing anything
            await pilot.press("enter")
            await pilot.pause()

            assert not future.done()

    async def test_up_from_other_input_selects_last_choice_directly(self) -> None:
        """Pressing up while Other input is focused jumps to last real choice."""
        app = _AskUserTestApp(
            [
                {
                    "question": "Pick one",
                    "type": "multiple_choice",
                    "choices": [{"value": "red"}, {"value": "blue"}],
                }
            ]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            qw = menu._question_widgets[0]

            # Navigate to Other and enter it
            await pilot.press("down")
            await pilot.press("down")
            await pilot.press("enter")
            await pilot.pause()
            other_input = menu.query_one(".ask-user-other-input", AskUserTextArea)
            assert other_input.has_focus

            # Single up press should select "blue" (last real choice)
            await pilot.press("up")
            await pilot.pause()
            assert qw._selected_choice == 1
            assert not qw._is_other_selected
            assert qw.has_focus

    async def test_down_in_wrapped_other_input_moves_cursor(self) -> None:
        """Down inside a wrapped Other answer should not leave the text input."""
        app = _AskUserTestApp(
            [
                {
                    "question": "Pick one",
                    "type": "multiple_choice",
                    "choices": [{"value": "red"}, {"value": "blue"}],
                }
            ]
        )

        async with app.run_test(size=(50, 24)) as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            qw = menu._question_widgets[0]

            await pilot.press("down")
            await pilot.press("down")
            await pilot.press("enter")
            await pilot.pause()
            other_input = menu.query_one(".ask-user-other-input", AskUserTextArea)
            other_input.text = " ".join(["wrapped"] * 20)
            other_input.move_cursor((0, 0))
            other_input.focus()
            await pilot.pause()

            await pilot.press("down")
            await pilot.pause()

            assert other_input.has_focus
            assert qw._is_other_selected
            assert other_input.cursor_location != (0, 0)

    async def test_return_to_mc_other_refocuses_input(self) -> None:
        """Tab away from Other input and Shift+Tab back refocuses it."""
        app = _AskUserTestApp(
            [
                {
                    "question": "Pick one",
                    "type": "multiple_choice",
                    "choices": [{"value": "red"}, {"value": "blue"}],
                },
                {"question": "Name?", "type": "text"},
            ]
        )

        async with app.run_test() as pilot:
            await pilot.pause()
            menu = app.query_one("#ask-user-menu", AskUserMenu)

            # Navigate to Other and enter it
            await pilot.press("down")
            await pilot.press("down")
            await pilot.press("enter")
            await pilot.pause()
            other_input = menu.query_one(".ask-user-other-input", AskUserTextArea)
            assert other_input.has_focus

            # Tab to next question
            menu.action_next_question()
            await pilot.pause()
            assert menu._current_question == 1

            # Go back — Other input should regain focus
            menu.action_previous_question()
            await pilot.pause()
            assert menu._current_question == 0
            assert other_input.has_focus

    async def test_cancel_after_submit_does_not_override_answer(self) -> None:
        """Cancel after submit should be ignored by the `_submitted` guard."""
        app = _AskUserTestApp([{"question": "Name?", "type": "text"}])

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            text_input = menu.query_one(".ask-user-text-input", AskUserTextArea)
            text_input.text = "Alice"
            await pilot.pause()
            await pilot.press("enter")
            await pilot.pause()

            menu.action_cancel()
            await pilot.pause()

            assert future.done()
            assert future.result() == {"type": "answered", "answers": ["Alice"]}

    async def test_submit_after_cancel_does_not_override_cancel(self) -> None:
        """Submit after cancel should be ignored by the `_submitted` guard."""
        app = _AskUserTestApp([{"question": "Name?", "type": "text"}])

        async with app.run_test() as pilot:
            menu = app.query_one("#ask-user-menu", AskUserMenu)
            future: asyncio.Future[AskUserWidgetResult] = (
                asyncio.get_running_loop().create_future()
            )
            menu.set_future(future)

            await pilot.pause()
            menu.action_cancel()
            await pilot.pause()

            menu._submit()
            await pilot.pause()

            assert future.done()
            assert future.result() == {"type": "cancelled"}
