diff --git a/test/features/control_room/presentation/pages/control_room_page_test.dart b/test/features/control_room/presentation/pages/control_room_page_test.dart new file mode 100644 index 0000000..5a93c05 --- /dev/null +++ b/test/features/control_room/presentation/pages/control_room_page_test.dart @@ -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 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'); + }); + }); +}