test: add widget tests for ControlRoomPage

- Add 13 widget tests covering:
  - Sections nav panel with Stack/Data Management groups
  - Containers section with stacks filter panel
  - Proxy Hosts section navigation
  - Placeholder for unimplemented sections
  - Loading and empty states

- Add 5 unit tests for ControlRoomNav enum:
  - Unique IDs, labels, sections
  - Route path generation
  - NavItem conversion

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-01-06 12:12:17 +01:00
co-authored by Claude Opus 4.5
parent 422fac392c
commit 71eaccce67
@@ -0,0 +1,260 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tatlock_ui/features/control_room/presentation/pages/control_room_page.dart';
import 'package:tatlock_ui/features/control_room/router.dart';
import '../../../../harness/test_harness.dart';
void main() {
final harness = TestHarness();
setUp(() => harness.setUp());
tearDown(() => harness.tearDown());
// Set larger window size and suppress overflow errors
Future<void> setLargeWindowSize(WidgetTester tester) async {
tester.view.physicalSize = const Size(1600, 900);
tester.view.devicePixelRatio = 1.0;
addTearDown(() => tester.view.resetPhysicalSize());
// Suppress overflow errors - not what we're testing
final originalOnError = FlutterError.onError;
FlutterError.onError = (details) {
if (details.exceptionAsString().contains('overflowed')) {
return; // Ignore overflow errors
}
originalOnError?.call(details);
};
addTearDown(() => FlutterError.onError = originalOnError);
}
group('ControlRoomPage', () {
testWidgets('displays Sections nav panel header', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
expect(find.text('Sections'), findsOneWidget);
expect(find.byIcon(Icons.dns_outlined), findsWidgets);
});
testWidgets('displays Stack Management section items', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
expect(find.text('STACK MANAGEMENT'), findsOneWidget);
expect(find.text('Containers'), findsOneWidget);
expect(find.text('Proxy Hosts'), findsOneWidget);
});
testWidgets('displays Data Management section items', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
expect(find.text('DATA MANAGEMENT'), findsOneWidget);
expect(find.text('PostgreSQL'), findsOneWidget);
expect(find.text('Redis'), findsOneWidget);
expect(find.text('Qdrant'), findsOneWidget);
expect(find.text('Neo4j'), findsOneWidget);
});
testWidgets('displays Containers section by default', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
// Default nav is containers - should show stacks filter and containers
expect(find.text('Stacks'), findsOneWidget);
expect(find.text('All Containers'), findsOneWidget);
});
testWidgets('displays Stacks filter panel in Containers section',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
// Stacks filter panel elements
expect(find.text('Stacks'), findsOneWidget);
expect(find.byIcon(Icons.layers), findsOneWidget);
expect(find.text('All Containers'), findsOneWidget);
expect(find.byIcon(Icons.all_inbox), findsOneWidget);
});
testWidgets('displays containers list in main content', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
// Should show container names from fixtures
expect(find.text('nginx-proxy'), findsOneWidget);
expect(find.text('postgres-db'), findsOneWidget);
expect(find.text('redis-cache'), findsOneWidget);
});
testWidgets('displays Proxy Hosts section when nav selected',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(
harness.wrap(const ControlRoomPage(nav: ControlRoomNav.proxyHosts)),
);
await tester.pumpAndSettle();
// Should show domain names from fixtures
expect(find.text('home.schweitz.net'), findsOneWidget);
expect(find.text('api.schweitz.net'), findsOneWidget);
});
testWidgets('displays placeholder for unimplemented sections',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
await tester.pumpWidget(
harness.wrap(const ControlRoomPage(nav: ControlRoomNav.postgres)),
);
await tester.pumpAndSettle();
// Should show placeholder with "Coming soon"
expect(find.text('PostgreSQL'), findsWidgets);
expect(find.text('Data Management • Coming soon'), findsOneWidget);
});
testWidgets('displays search field for stacks', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
expect(find.text('Search stacks...'), findsOneWidget);
});
testWidgets('displays refresh button in stacks panel', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
expect(find.byIcon(Icons.refresh), findsWidgets);
});
testWidgets('displays nav icons for all sections', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
// Stack Management icons
expect(find.byIcon(Icons.dns), findsWidgets);
expect(find.byIcon(Icons.public), findsOneWidget);
// Data Management icons
expect(find.byIcon(Icons.table_chart), findsOneWidget);
expect(find.byIcon(Icons.memory), findsOneWidget);
expect(find.byIcon(Icons.scatter_plot), findsOneWidget);
expect(find.byIcon(Icons.hub), findsOneWidget);
});
testWidgets('displays empty stacks state when no stacks found',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers(); // Fixtures have no stack labels
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pumpAndSettle();
// Stacks are derived from container labels - fixtures have none
expect(find.text('No stacks found'), findsOneWidget);
});
testWidgets('displays loading state for containers initially',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenContainers();
harness.givenDomains();
await tester.pumpWidget(harness.wrap(const ControlRoomPage()));
await tester.pump();
expect(find.byType(CircularProgressIndicator), findsWidgets);
await tester.pumpAndSettle();
});
});
group('ControlRoomNav', () {
test('all nav items have unique IDs', () {
final ids = ControlRoomNav.values.map((n) => n.id).toSet();
expect(ids.length, ControlRoomNav.values.length);
});
test('all nav items have labels', () {
for (final nav in ControlRoomNav.values) {
expect(nav.label, isNotEmpty);
}
});
test('all nav items have sections', () {
for (final nav in ControlRoomNav.values) {
expect(nav.section, isNotEmpty);
}
});
test('path returns correct route', () {
expect(ControlRoomNav.containers.path, '/control-room/containers');
expect(ControlRoomNav.proxyHosts.path, '/control-room/proxy-hosts');
expect(ControlRoomNav.postgres.path, '/control-room/postgres');
});
test('toNavItem creates valid NavItem', () {
final navItem = ControlRoomNav.containers.toNavItem();
expect(navItem.id, 'containers');
expect(navItem.label, 'Containers');
expect(navItem.section, 'Stack Management');
});
});
}