test: add widget tests for FrontHallPage

- Add 14 widget tests covering:
  - Welcome message and Quick Links panel display
  - System Stats and Environment sections
  - Loading and error states
  - API endpoint verification
  - Default links fallback behavior
  - Category headers display

- Fix fixtures:
  - Add SystemStats fixture for dashboard content
  - Remove duplicate quickLinks definition
  - Update quickLinks endpoint to /dashboard/quick-links

🤖 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-05 22:14:27 +01:00
co-authored by Claude Opus 4.5
parent d6fd9aea60
commit 837ccb6709
3 changed files with 308 additions and 38 deletions
@@ -0,0 +1,232 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tatlock_ui/features/front_hall/presentation/pages/front_hall_page.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(1400, 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('FrontHallPage', () {
testWidgets('displays welcome message', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.text('Welcome to Tatlock'), findsOneWidget);
expect(find.text('Your homelab dashboard is ready.'), findsOneWidget);
});
testWidgets('displays Quick Links panel header', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.text('Quick Links'), findsOneWidget);
expect(find.byIcon(Icons.link), findsOneWidget);
});
testWidgets('displays quick links from API', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Should display quick link names from fixtures
expect(find.text('Portainer'), findsOneWidget);
expect(find.text('Gitea'), findsOneWidget);
expect(find.text('Jellyfin'), findsOneWidget);
});
testWidgets('displays System Stats section header', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.text('System Stats'), findsOneWidget);
expect(find.byIcon(Icons.monitor_heart), findsWidgets);
});
testWidgets('displays Environment section header', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.text('Environment'), findsOneWidget);
expect(find.byIcon(Icons.eco), findsOneWidget);
});
testWidgets('displays Settings button', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.text('Settings'), findsOneWidget);
expect(find.byIcon(Icons.settings), findsOneWidget);
});
testWidgets('displays refresh button in Quick Links panel', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.byIcon(Icons.refresh), findsWidgets);
});
testWidgets('displays loading state for system stats initially',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pump();
// Should show loading indicator before data loads
expect(find.byType(CircularProgressIndicator), findsWidgets);
await tester.pumpAndSettle();
});
testWidgets('displays system stats gauges after loading', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Should display gauge labels
expect(find.text('CPU'), findsOneWidget);
expect(find.text('RAM'), findsOneWidget);
});
testWidgets('displays error state when system stats fails', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenApiError(
method: 'GET',
path: '/tools/system/stats',
statusCode: 500,
message: 'Internal server error',
);
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Should show system stats error message or error indicator
expect(
find.text('Failed to load system stats').evaluate().isNotEmpty ||
find.byIcon(Icons.error_outline).evaluate().isNotEmpty,
isTrue,
reason: 'Should display system stats error',
);
});
testWidgets('calls quick links API on mount', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
harness.verifyApiCalled('GET', '/dashboard/quick-links');
});
testWidgets('calls system stats API on mount', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
harness.verifyApiCalled('GET', '/tools/system/stats');
});
testWidgets('uses default links when API returns empty list',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks([]); // Empty list triggers fallback
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Should display default links (from getDefaultQuickLinks)
expect(find.text('Jellyfin'), findsOneWidget);
expect(find.text('Open WebUI'), findsOneWidget);
});
testWidgets('displays category headers when multiple categories present',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks(); // Fixtures have Infrastructure, Development, Home
harness.givenSystemStats();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Should display category headers (uppercase)
// At least INFRASTRUCTURE should be visible since Portainer is first
expect(find.text('INFRASTRUCTURE'), findsOneWidget);
});
});
}