Skip to content

Extract shared HeadlessTerminaFixture for TUI page tests #929

@Aaronontheweb

Description

@Aaronontheweb

Summary

Three TUI test fixtures duplicate near-identical 28-32-line CreateHeadlessApp helpers that wire up Termina's VirtualTerminal + VirtualInputSource against a ServiceCollection. Extracting a shared generic fixture would let new TUI page tests onboard with a single call instead of copying the boilerplate.

Affected files

  • src/Netclaw.Cli.Tests/Tui/InitWizardPageTests.cs:410-441 (CreateHeadlessApp, 32 lines)
  • src/Netclaw.Cli.Tests/Mcp/McpToolPermissionsPageTests.cs:305-336 (CreateHeadlessApp, 32 lines)
  • src/Netclaw.Cli.Tests/Tui/ApprovalsManagerPageTests.cs:137-164 (CreateHeadlessApp, 28 lines)

The variation between them is purely:

  • The route string ("/init", "/mcp-tools", "/approvals")
  • The page and view-model types
  • The view-model factory's dependencies (some need ProviderDescriptorRegistry, some need DaemonApi, etc.)

Everything else — VirtualTerminal(120, 40), AddSingleton<IAnsiTerminal>, AddTerminaVirtualInput, AddTermina(route, builder => builder.RegisterRoute<TPage, TViewModel>(...)), BuildServiceProvider, GetRequiredService<TerminaApplication> — is identical.

Suggested shape

A generic builder in a shared test-utility location, e.g.:

public static class HeadlessTerminaFixture
{
    public sealed record Result<TViewModel>(
        VirtualTerminal Terminal,
        TerminaApplication App,
        VirtualInputSource Input,
        TViewModel ViewModel);

    public static Result<TViewModel> Build<TPage, TViewModel>(
        string route,
        Func<IServiceProvider, TPage> pageFactory,
        Func<IServiceProvider, TViewModel> viewModelFactory,
        Action<IServiceCollection>? configureServices = null)
        where TPage : class
        where TViewModel : class
    { /* shared body */ }
}

Each existing call site collapses to ~5 lines.

Suggested home

Two candidates:

  1. Netclaw.Tests.Utilities — already houses DisposableTempDir and other cross-test helpers. Most discoverable for future page tests outside the CLI tree.
  2. src/Netclaw.Cli.Tests/Tui/HeadlessTerminaFixture.cs — keeps it scoped to CLI tests since that's where every current consumer lives.

I'd lean toward option 1 because the next consumer almost certainly will not be in Netclaw.Cli.Tests (e.g., if other projects gain Termina-based UIs) and Netclaw.Tests.Utilities is already designed for this.

Acceptance

  • A shared HeadlessTerminaFixture (or equivalent name) callable from any test project.
  • The three existing call sites migrated to use it.
  • Existing test behavior unchanged: same render assertions, same input sequences, same termination semantics (Ctrl+Q drains the input queue).
  • dotnet slopwatch analyze clean.

Out of scope

  • Changes to the production pages or Termina library itself.
  • Adding new test scenarios — this is a pure refactor.

Discovered during

Audit of PR #927 (netclaw approvals CLI). The /simplify reuse review flagged the 3× duplication after the third copy was added. Deferred to keep the PR's diff focused on the new feature rather than touching unrelated test files.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions