Files
tatlock-ui/test/features/front_hall/environment_test.dart
T
Jeroen SchweitzerandClaude Opus 4.5 d200cad8be
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 3m11s
feat: environment widgets layout redesign with always-visible widgets
- Desktop: 4-in-a-row layout (30/20/20/30 distribution)
- Tablet: 2x2 grid layout
- Mobile: Stacked vertically
- All widgets show "No data available" state instead of being hidden
- Sun position calculates from clock, defaults to 6am/6pm
- Environment refresh changed from 5min to 1 hour

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 11:21:48 +01:00

222 lines
7.6 KiB
Dart

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 'package:tatlock_ui/shared/widgets/air_quality_widget.dart';
import 'package:tatlock_ui/shared/widgets/forecast_widget.dart';
import 'package:tatlock_ui/shared/widgets/sun_position_widget.dart';
import 'package:tatlock_ui/shared/widgets/weather_widget.dart';
import '../../harness/screen_sizes.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;
}
originalOnError?.call(details);
};
addTearDown(() => FlutterError.onError = originalOnError);
}
group('Environment Widgets', () {
testWidgets('displays Sun Position widget', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.byType(SunPositionWidget), findsOneWidget);
expect(find.text('Sun Position'), findsOneWidget);
});
testWidgets('displays Weather widget', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.byType(WeatherWidget), findsOneWidget);
});
testWidgets('displays Forecast widget', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.byType(ForecastWidget), findsOneWidget);
expect(find.text('Forecast'), findsOneWidget);
});
testWidgets('displays Air Quality widget when data available',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment(); // Has air quality data
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.byType(AirQualityWidget), findsOneWidget);
expect(find.text('Air Quality'), findsOneWidget);
});
testWidgets('shows Air Quality widget with no data state when data unavailable',
(tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironmentNoAirQuality(); // No air quality data
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Air Quality widget should still be present, showing "no data" state
expect(find.byType(AirQualityWidget), findsOneWidget);
expect(find.text('Air Quality'), findsOneWidget);
});
testWidgets('calls environment API on mount', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
harness.verifyApiCalled('GET', '/tools/environment');
});
testWidgets('displays error state when environment fails', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenApiError(
method: 'GET',
path: '/tools/environment',
statusCode: 500,
message: 'Internal server error',
);
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Should show environment error message or error indicator
expect(
find.text('Failed to load environment data').evaluate().isNotEmpty ||
find.byIcon(Icons.error_outline).evaluate().isNotEmpty,
isTrue,
reason: 'Should display environment error',
);
});
testWidgets('displays sunrise and sunset times', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Should display sunrise/sunset labels
expect(find.text('Sunrise'), findsOneWidget);
expect(find.text('Sunset'), findsOneWidget);
});
testWidgets('displays daylight duration', (tester) async {
await setLargeWindowSize(tester);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Should display daylight label
expect(find.text('Daylight'), findsOneWidget);
});
});
group('Environment Widgets Responsive', () {
testWidgets('renders environment widgets at desktop size', (tester) async {
configureScreenSize(tester, ScreenSizes.desktopLarge);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.byType(SunPositionWidget), findsOneWidget);
expect(find.byType(WeatherWidget), findsOneWidget);
expect(find.byType(ForecastWidget), findsOneWidget);
});
testWidgets('renders environment widgets at tablet size', (tester) async {
configureScreenSize(tester, ScreenSizes.tabletLandscape);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
expect(find.byType(SunPositionWidget), findsOneWidget);
expect(find.byType(WeatherWidget), findsOneWidget);
expect(find.byType(ForecastWidget), findsOneWidget);
});
testWidgets('renders environment widgets at mobile size', (tester) async {
configureScreenSize(tester, ScreenSizes.mobile);
harness.givenAuthenticatedUser();
harness.givenQuickLinks();
harness.givenSystemStats();
harness.givenEnvironment();
await tester.pumpWidget(harness.wrap(const FrontHallPage()));
await tester.pumpAndSettle();
// Page should render without crashing at mobile size
// Widgets may be below the fold due to scrolling
expect(find.byType(FrontHallPage), findsOneWidget);
});
});
}