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:
Netclaw.Tests.Utilities — already houses DisposableTempDir and other cross-test helpers. Most discoverable for future page tests outside the CLI tree.
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.
Summary
Three TUI test fixtures duplicate near-identical 28-32-line
CreateHeadlessApphelpers that wire up Termina'sVirtualTerminal+VirtualInputSourceagainst aServiceCollection. 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:
"/init","/mcp-tools","/approvals")ProviderDescriptorRegistry, some needDaemonApi, 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.:
Each existing call site collapses to ~5 lines.
Suggested home
Two candidates:
Netclaw.Tests.Utilities— already housesDisposableTempDirand other cross-test helpers. Most discoverable for future page tests outside the CLI tree.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) andNetclaw.Tests.Utilitiesis already designed for this.Acceptance
HeadlessTerminaFixture(or equivalent name) callable from any test project.dotnet slopwatch analyzeclean.Out of scope
Discovered during
Audit of PR #927 (
netclaw approvalsCLI). 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.