band-aider-pub/tests/band_aider/test_startup_wizard.py
2026-10-17 11:57:37 -07:00

252 lines
7.4 KiB
Python

# SPDX-FileCopyrightText: 2026 Art Sea LLC
#
# SPDX-License-Identifier: AGPL-3.0-only
from pathlib import Path
from band_aider.ui.startup_wizard import StartupWizard
def test_startup_wizard_uses_vertical_navigation_and_horizontal_toggles() -> None:
wizard = StartupWizard(
initial_repo_root=None,
initial_task=None,
initial_resume=False,
initial_overflow_strategy="miser",
initial_language="python",
)
wizard._supported_languages = ["python", "ruby"]
wizard._move_step(1)
assert wizard._step == 1
wizard._adjust_current_option(1)
assert wizard._mode_resume is True
wizard._move_step(1)
wizard._adjust_current_option(1)
assert wizard._overflow_strategy == "jacker"
wizard._move_step(1)
wizard._adjust_current_option(1)
assert wizard._language == "ruby"
def test_startup_wizard_marks_selected_item_with_star() -> None:
wizard = StartupWizard(
initial_repo_root=Path("/tmp/repo"),
initial_task="fix it",
initial_resume=False,
initial_overflow_strategy="miser",
initial_language="python",
)
wizard._step = 2
lines = wizard._prompt_lines(120)
assert "* Step 3/5 Escalation: MISER" in lines
assert " Step 2/5 Mode: New Task" in lines
assert all("Up/Down toggles" not in line for line in lines)
def test_startup_wizard_footer_changes_with_selected_field() -> None:
wizard = StartupWizard(
initial_repo_root=None,
initial_task=None,
initial_resume=False,
initial_overflow_strategy="miser",
initial_language="python",
)
wizard._step = 0
assert wizard._footer_text() == (
"[Up/Down] Move [Type] Edit Path [Backspace] Delete "
"[Enter] Continue/Start [Esc] Quit"
)
wizard._step = 2
assert wizard._footer_text() == (
"[Up/Down] Move [Left/Right] Toggle Escalation "
"[Enter] Continue/Start [Esc] Quit"
)
def test_startup_wizard_final_enter_revalidates_repo_root() -> None:
wizard = StartupWizard(
initial_repo_root=None,
initial_task="fix it",
initial_resume=False,
initial_overflow_strategy="miser",
initial_language="python",
)
wizard._step = 4
assert wizard._on_enter() is False
assert wizard._step == 0
assert wizard._message == "Repository root is required."
def test_startup_wizard_uses_authoritative_resume_validation(tmp_path) -> None:
"""Verify resume cannot start when the project adapter reports no checkpoint."""
wizard = StartupWizard(
initial_repo_root=tmp_path,
initial_task=None,
initial_resume=True,
initial_overflow_strategy="miser",
initial_language="python",
resume_available=lambda root: False,
)
wizard._step = 4
assert wizard._on_enter() is False
assert wizard._step == 1
assert "No saved Band-Aider state" in wizard._message
def test_startup_wizard_accepts_authoritative_resume_checkpoint(tmp_path) -> None:
"""Verify resume starts when the project adapter reports a checkpoint."""
checked: list[Path] = []
wizard = StartupWizard(
initial_repo_root=tmp_path,
initial_task=None,
initial_resume=True,
initial_overflow_strategy="miser",
initial_language="python",
resume_available=lambda root: checked.append(root) is None,
)
wizard._step = 4
assert wizard._on_enter() is True
assert checked == [tmp_path]
def test_startup_wizard_final_resume_starts_without_task() -> None:
"""A validated resume reaches the terminal step without requiring task text."""
root = Path("/testbed")
wizard = StartupWizard(
initial_repo_root=root,
initial_task=None,
initial_resume=True,
resume_available=lambda candidate: candidate == root,
)
wizard._step = 4
assert wizard._on_enter() is True
def test_startup_wizard_advances_a_complete_new_task_without_terminal_io() -> None:
"""The five owned steps accept a complete stationary new-task configuration."""
wizard = StartupWizard(
initial_repo_root=Path("/testbed"),
initial_task="Repair the widget contract.",
initial_resume=False,
initial_overflow_strategy="miser",
initial_language="python",
)
assert wizard._on_enter() is False
assert wizard._step == 1
assert wizard._on_enter() is False
assert wizard._step == 2
assert wizard._on_enter() is False
assert wizard._step == 3
assert wizard._on_enter() is False
assert wizard._step == 4
assert wizard._on_enter() is True
def test_startup_wizard_keeps_resume_unavailable_when_its_authority_fails() -> None:
"""An unavailable resume authority fails closed and redirects to mode selection."""
def unavailable(_root: Path) -> bool:
"""Represent a stationary checkpoint authority failure."""
raise RuntimeError("checkpoint authority unavailable")
wizard = StartupWizard(
initial_repo_root=Path("/testbed"),
initial_task=None,
initial_resume=True,
resume_available=unavailable,
)
wizard._step = 4
assert wizard._on_enter() is False
assert wizard._step == 1
assert "No saved Band-Aider state" in wizard._message
def test_startup_wizard_edits_only_the_active_new_task_field() -> None:
"""Text entry and backspace are owned by the selected editable field only."""
wizard = StartupWizard(
initial_repo_root=Path("/testbed"),
initial_task="repair",
initial_resume=False,
)
wizard._step = 4
wizard._handle_text("!")
wizard._handle_backspace()
assert wizard._task == "repair"
wizard._mode_resume = True
wizard._handle_text("x")
wizard._handle_backspace()
assert wizard._task == "repair"
def test_startup_wizard_edits_the_repository_field_and_cycles_unknown_options() -> None:
"""Repository text is editable at step one and option cycling has safe fallbacks."""
wizard = StartupWizard(
initial_repo_root=None,
initial_task=None,
initial_resume=False,
)
wizard._handle_text("/")
wizard._handle_text("x")
wizard._handle_backspace()
assert wizard._repo_root_text == "/"
assert StartupWizard._cycle_value("unknown", ("miser", "jacker"), 1) == "jacker"
assert StartupWizard._cycle_value("fixed", (), -1) == "fixed"
def test_startup_wizard_requires_nonblank_new_task_at_the_terminal_step() -> None:
"""A configured project cannot begin a new run without task evidence."""
wizard = StartupWizard(
initial_repo_root=Path("/testbed"),
initial_task=" ",
initial_resume=False,
)
wizard._step = 4
assert wizard._on_enter() is False
assert wizard._step == 4
assert wizard._message == "Task is required for New Task mode."
def test_startup_wizard_renders_resume_specific_prompt_and_footer_copy() -> None:
"""Resume presentation omits task editing while retaining its no-task explanation."""
wizard = StartupWizard(
initial_repo_root=Path("/testbed"),
initial_task=None,
initial_resume=True,
initial_overflow_strategy="jacker",
initial_language="python",
)
wizard._step = 4
prompt = "\n".join(wizard._prompt_lines(28))
assert "Step 5/5 Ready: [resume has no task input]" in prompt
assert "[Type] Edit Task" not in wizard._footer_text()
wizard._step = 3
assert "[Left/Right] Change Language" in wizard._footer_text()