From c935a202cc46a275adfd8b54ccee65c166e4a46b Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Wed, 22 Jul 2026 16:52:13 +0200 Subject: [PATCH] Make AppStatusColors lookup null-safe; fix tests that never had it Theme.of(context).extension()! crashed in any context that doesn't build its theme from buildAppTheme() -- which is most widget tests, since they wrap in a bare MaterialApp(home: ...) rather than the real app theme. That crash was latent since AppStatusColors was introduced but only became reachable once main.dart actually started using buildAppTheme() (previous commit), which is when 27-47 tests started failing. Fixed at both call sites (slot_selector_row.dart, day_status_calendar.dart) by falling back to AppStatusColors.dark instead of force-unwrapping -- production code should never trust every context to fully replicate the real theme. Also fixed day_status_calendar_test.dart, which asserted against stale hardcoded Colors.* values from before the theme rewrite; it now wraps in the real buildAppTheme() and asserts against the actual tokens, so the assertions mean something again. Verified: flutter analyze clean, 436/436 tests pass (up from 389/47). --- app/lib/widgets/day_status_calendar.dart | 7 ++++- app/lib/widgets/slot_selector_row.dart | 7 ++++- .../widgets/day_status_calendar_test.dart | 28 ++++++++++++++----- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/app/lib/widgets/day_status_calendar.dart b/app/lib/widgets/day_status_calendar.dart index 20bd817..d3b5013 100644 --- a/app/lib/widgets/day_status_calendar.dart +++ b/app/lib/widgets/day_status_calendar.dart @@ -103,7 +103,12 @@ class DayStatusCalendar extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final statusColors = Theme.of(context).extension()!; + // A `!` here would crash in any context that doesn't build its theme + // from buildAppTheme() -- most widget tests just wrap in a bare + // MaterialApp(home: ...). Falling back to the app's own dark instance + // matches production exactly when the extension is genuinely absent. + final statusColors = + Theme.of(context).extension() ?? AppStatusColors.dark; final year = month.year; final m = month.month; final daysInMonth = DateTime(year, m + 1, 0).day; diff --git a/app/lib/widgets/slot_selector_row.dart b/app/lib/widgets/slot_selector_row.dart index 71d10c5..4532c6d 100644 --- a/app/lib/widgets/slot_selector_row.dart +++ b/app/lib/widgets/slot_selector_row.dart @@ -36,7 +36,12 @@ class SlotSelectorRow extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final statusColors = Theme.of(context).extension()!; + // A `!` here would crash in any context that doesn't build its theme + // from buildAppTheme() -- most widget tests just wrap in a bare + // MaterialApp(home: ...). Falling back to the app's own dark instance + // matches production exactly when the extension is genuinely absent. + final statusColors = + Theme.of(context).extension() ?? AppStatusColors.dark; final elapsed = elapsedSlots(now).toSet(); // Snack has no logged/due/upcoming status like the hour chips, so it // uses `accent` as its "color" instead of a status color -- but still diff --git a/app/test/widgets/day_status_calendar_test.dart b/app/test/widgets/day_status_calendar_test.dart index 3022a2c..7340842 100644 --- a/app/test/widgets/day_status_calendar_test.dart +++ b/app/test/widgets/day_status_calendar_test.dart @@ -1,9 +1,20 @@ import 'package:diet_guard_app/models/day_status.dart'; +import 'package:diet_guard_app/ui/theme.dart'; import 'package:diet_guard_app/widgets/day_status_calendar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -Widget _wrap(Widget child) => MaterialApp(home: Scaffold(body: child)); +// The real app theme, not a bare default MaterialApp -- the widget under +// test reads its colors from Theme.of(context), so asserting against the +// same theme the app actually ships is what makes these assertions mean +// anything (a bare MaterialApp's own default colors would be arbitrary). +final _theme = buildAppTheme(); +final _statusColors = _theme.extension()!; + +Widget _wrap(Widget child) => MaterialApp( + theme: _theme, + home: Scaffold(body: child), +); Color _colorOfDay(WidgetTester tester, String day) { final container = tester.widget( @@ -95,7 +106,7 @@ void main() { ), ), ); - expect(_colorOfDay(tester, '15'), Colors.green.shade700); + expect(_colorOfDay(tester, '15'), _statusColors.success); }); testWidgets('colors a yellow day', (tester) async { @@ -110,7 +121,7 @@ void main() { ), ), ); - expect(_colorOfDay(tester, '15'), Colors.amber.shade700); + expect(_colorOfDay(tester, '15'), _statusColors.warning); }); testWidgets('colors a red day', (tester) async { @@ -125,7 +136,7 @@ void main() { ), ), ); - expect(_colorOfDay(tester, '15'), Colors.red.shade400); + expect(_colorOfDay(tester, '15'), _theme.colorScheme.error); }); testWidgets( @@ -142,7 +153,7 @@ void main() { ), ), ); - expect(_colorOfDay(tester, '15'), Colors.black); + expect(_colorOfDay(tester, '15'), _theme.colorScheme.surface); }, ); @@ -161,7 +172,10 @@ void main() { ), ); // Day 25 is after the reference "today" of June 10. - expect(_colorOfDay(tester, '25'), Colors.grey.shade800); + expect( + _colorOfDay(tester, '25'), + _theme.colorScheme.surfaceContainerHigh, + ); }, ); @@ -179,7 +193,7 @@ void main() { ), ), ); - expect(_colorOfDay(tester, '20'), Colors.red.shade400); + expect(_colorOfDay(tester, '20'), _theme.colorScheme.error); }); testWidgets('renders a month starting on Sunday correctly', (