Skip to content

Commit c961958

Browse files
richardthe3rdclaude
andcommitted
test: add coverage for theme functions, card accent bars, and loading/empty states
- Extract _buildTheme/_buildTextTheme to public lib/app_theme.dart so they are unit-testable without Firebase or GoRouter infrastructure - test/app_theme_test.dart: covers buildAppTheme (light + dark branches) and buildAppTextTheme font-size contracts - test/drink_card_test.dart: covers all _accentColor branches (cider/perry/mead/ wine/international-beer/low-no/apple-juice/default) - test/drinks_screen_style_filter_test.dart: covers barrel mascot in loading state and empty state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c814edd commit c961958

5 files changed

Lines changed: 372 additions & 115 deletions

File tree

lib/app_theme.dart

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:google_fonts/google_fonts.dart';
3+
4+
const Color appSeedColor = Color(0xFF2B3170); // CBF 2026: poster navy blue
5+
6+
TextTheme buildAppTextTheme(ColorScheme colorScheme) {
7+
final base = GoogleFonts.nunitoSansTextTheme();
8+
return base.copyWith(
9+
displayLarge: GoogleFonts.playfairDisplay(
10+
fontSize: 57,
11+
fontWeight: FontWeight.w700,
12+
color: colorScheme.onSurface,
13+
),
14+
displayMedium: GoogleFonts.playfairDisplay(
15+
fontSize: 45,
16+
fontWeight: FontWeight.w700,
17+
color: colorScheme.onSurface,
18+
),
19+
displaySmall: GoogleFonts.playfairDisplay(
20+
fontSize: 36,
21+
fontWeight: FontWeight.w600,
22+
color: colorScheme.onSurface,
23+
),
24+
headlineLarge: GoogleFonts.playfairDisplay(
25+
fontSize: 32,
26+
fontWeight: FontWeight.w600,
27+
color: colorScheme.onSurface,
28+
),
29+
headlineMedium: GoogleFonts.playfairDisplay(
30+
fontSize: 28,
31+
fontWeight: FontWeight.w600,
32+
color: colorScheme.onSurface,
33+
),
34+
headlineSmall: GoogleFonts.playfairDisplay(
35+
fontSize: 24,
36+
fontWeight: FontWeight.w600,
37+
color: colorScheme.onSurface,
38+
),
39+
titleLarge: GoogleFonts.playfairDisplay(
40+
fontSize: 22,
41+
fontWeight: FontWeight.w600,
42+
color: colorScheme.onSurface,
43+
),
44+
titleMedium: GoogleFonts.nunitoSans(
45+
fontSize: 16,
46+
fontWeight: FontWeight.w700,
47+
color: colorScheme.onSurface,
48+
),
49+
titleSmall: GoogleFonts.nunitoSans(
50+
fontSize: 14,
51+
fontWeight: FontWeight.w600,
52+
color: colorScheme.onSurface,
53+
),
54+
bodyLarge: GoogleFonts.nunitoSans(
55+
fontSize: 16,
56+
color: colorScheme.onSurface,
57+
),
58+
bodyMedium: GoogleFonts.nunitoSans(
59+
fontSize: 14,
60+
color: colorScheme.onSurface,
61+
),
62+
bodySmall: GoogleFonts.nunitoSans(
63+
fontSize: 12,
64+
color: colorScheme.onSurfaceVariant,
65+
),
66+
labelLarge: GoogleFonts.nunitoSans(
67+
fontSize: 14,
68+
fontWeight: FontWeight.w600,
69+
color: colorScheme.onSurface,
70+
),
71+
labelMedium: GoogleFonts.nunitoSans(
72+
fontSize: 12,
73+
fontWeight: FontWeight.w600,
74+
color: colorScheme.onSurface,
75+
),
76+
labelSmall: GoogleFonts.nunitoSans(
77+
fontSize: 11,
78+
fontWeight: FontWeight.w500,
79+
color: colorScheme.onSurfaceVariant,
80+
),
81+
);
82+
}
83+
84+
ThemeData buildAppTheme(Brightness brightness) {
85+
final colorScheme = ColorScheme.fromSeed(
86+
seedColor: appSeedColor,
87+
brightness: brightness,
88+
primary: brightness == Brightness.light ? appSeedColor : const Color(0xFF8FA3E8),
89+
onPrimary: Colors.white,
90+
);
91+
final textTheme = buildAppTextTheme(colorScheme);
92+
return ThemeData(
93+
colorScheme: colorScheme,
94+
textTheme: textTheme,
95+
useMaterial3: true,
96+
appBarTheme: AppBarTheme(
97+
backgroundColor: brightness == Brightness.light ? appSeedColor : colorScheme.surface,
98+
foregroundColor: brightness == Brightness.light ? Colors.white : colorScheme.onSurface,
99+
elevation: 0,
100+
centerTitle: false,
101+
titleTextStyle: GoogleFonts.playfairDisplay(
102+
fontSize: 20,
103+
fontWeight: FontWeight.w700,
104+
color: brightness == Brightness.light ? Colors.white : colorScheme.onSurface,
105+
),
106+
),
107+
navigationBarTheme: NavigationBarThemeData(
108+
indicatorColor: brightness == Brightness.light
109+
? appSeedColor.withValues(alpha: 0.15)
110+
: colorScheme.primaryContainer,
111+
),
112+
);
113+
}

lib/main.dart

Lines changed: 3 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
44
import 'package:firebase_core/firebase_core.dart';
55
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
66
import 'package:go_router/go_router.dart';
7-
import 'package:google_fonts/google_fonts.dart';
87
import 'package:provider/provider.dart';
8+
import 'app_theme.dart';
99
import 'providers/providers.dart';
1010
import 'router.dart';
1111
import 'services/services.dart';
@@ -47,116 +47,6 @@ void main() async {
4747
runApp(const BeerFestivalApp());
4848
}
4949

50-
const _seedColor = Color(0xFF2B3170); // CBF 2026: poster navy blue
51-
52-
TextTheme _buildTextTheme(ColorScheme colorScheme) {
53-
final base = GoogleFonts.nunitoSansTextTheme();
54-
return base.copyWith(
55-
displayLarge: GoogleFonts.playfairDisplay(
56-
fontSize: 57,
57-
fontWeight: FontWeight.w700,
58-
color: colorScheme.onSurface,
59-
),
60-
displayMedium: GoogleFonts.playfairDisplay(
61-
fontSize: 45,
62-
fontWeight: FontWeight.w700,
63-
color: colorScheme.onSurface,
64-
),
65-
displaySmall: GoogleFonts.playfairDisplay(
66-
fontSize: 36,
67-
fontWeight: FontWeight.w600,
68-
color: colorScheme.onSurface,
69-
),
70-
headlineLarge: GoogleFonts.playfairDisplay(
71-
fontSize: 32,
72-
fontWeight: FontWeight.w600,
73-
color: colorScheme.onSurface,
74-
),
75-
headlineMedium: GoogleFonts.playfairDisplay(
76-
fontSize: 28,
77-
fontWeight: FontWeight.w600,
78-
color: colorScheme.onSurface,
79-
),
80-
headlineSmall: GoogleFonts.playfairDisplay(
81-
fontSize: 24,
82-
fontWeight: FontWeight.w600,
83-
color: colorScheme.onSurface,
84-
),
85-
titleLarge: GoogleFonts.playfairDisplay(
86-
fontSize: 22,
87-
fontWeight: FontWeight.w600,
88-
color: colorScheme.onSurface,
89-
),
90-
titleMedium: GoogleFonts.nunitoSans(
91-
fontSize: 16,
92-
fontWeight: FontWeight.w700,
93-
color: colorScheme.onSurface,
94-
),
95-
titleSmall: GoogleFonts.nunitoSans(
96-
fontSize: 14,
97-
fontWeight: FontWeight.w600,
98-
color: colorScheme.onSurface,
99-
),
100-
bodyLarge: GoogleFonts.nunitoSans(
101-
fontSize: 16,
102-
color: colorScheme.onSurface,
103-
),
104-
bodyMedium: GoogleFonts.nunitoSans(
105-
fontSize: 14,
106-
color: colorScheme.onSurface,
107-
),
108-
bodySmall: GoogleFonts.nunitoSans(
109-
fontSize: 12,
110-
color: colorScheme.onSurfaceVariant,
111-
),
112-
labelLarge: GoogleFonts.nunitoSans(
113-
fontSize: 14,
114-
fontWeight: FontWeight.w600,
115-
color: colorScheme.onSurface,
116-
),
117-
labelMedium: GoogleFonts.nunitoSans(
118-
fontSize: 12,
119-
fontWeight: FontWeight.w600,
120-
color: colorScheme.onSurface,
121-
),
122-
labelSmall: GoogleFonts.nunitoSans(
123-
fontSize: 11,
124-
fontWeight: FontWeight.w500,
125-
color: colorScheme.onSurfaceVariant,
126-
),
127-
);
128-
}
129-
130-
ThemeData _buildTheme(Brightness brightness) {
131-
final colorScheme = ColorScheme.fromSeed(
132-
seedColor: _seedColor,
133-
brightness: brightness,
134-
primary: brightness == Brightness.light ? _seedColor : const Color(0xFF8FA3E8),
135-
onPrimary: Colors.white,
136-
);
137-
final textTheme = _buildTextTheme(colorScheme);
138-
return ThemeData(
139-
colorScheme: colorScheme,
140-
textTheme: textTheme,
141-
useMaterial3: true,
142-
appBarTheme: AppBarTheme(
143-
backgroundColor: brightness == Brightness.light ? _seedColor : colorScheme.surface,
144-
foregroundColor: brightness == Brightness.light ? Colors.white : colorScheme.onSurface,
145-
elevation: 0,
146-
centerTitle: false,
147-
titleTextStyle: GoogleFonts.playfairDisplay(
148-
fontSize: 20,
149-
fontWeight: FontWeight.w700,
150-
color: brightness == Brightness.light ? Colors.white : colorScheme.onSurface,
151-
),
152-
),
153-
navigationBarTheme: NavigationBarThemeData(
154-
indicatorColor: brightness == Brightness.light
155-
? _seedColor.withValues(alpha: 0.15)
156-
: colorScheme.primaryContainer,
157-
),
158-
);
159-
}
16050

16151
class BeerFestivalApp extends StatelessWidget {
16252
const BeerFestivalApp({super.key});
@@ -171,8 +61,8 @@ class BeerFestivalApp extends StatelessWidget {
17161
return MaterialApp.router(
17262
title: 'Cambridge Beer Festival',
17363
debugShowCheckedModeBanner: false,
174-
theme: _buildTheme(Brightness.light),
175-
darkTheme: _buildTheme(Brightness.dark),
64+
theme: buildAppTheme(Brightness.light),
65+
darkTheme: buildAppTheme(Brightness.dark),
17666
themeMode: themeMode,
17767
routerConfig: appRouter,
17868
);

test/app_theme_test.dart

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:google_fonts/google_fonts.dart';
4+
import 'package:cambridge_beer_festival/app_theme.dart';
5+
6+
void main() {
7+
setUpAll(() {
8+
GoogleFonts.config.allowRuntimeFetching = false;
9+
});
10+
11+
group('buildAppTheme', () {
12+
testWidgets('light theme uses navy seed colour as AppBar background',
13+
(WidgetTester tester) async {
14+
final theme = buildAppTheme(Brightness.light);
15+
expect(theme.appBarTheme.backgroundColor, equals(appSeedColor));
16+
});
17+
18+
testWidgets('light theme uses white as AppBar foreground',
19+
(WidgetTester tester) async {
20+
final theme = buildAppTheme(Brightness.light);
21+
expect(theme.appBarTheme.foregroundColor, equals(Colors.white));
22+
});
23+
24+
testWidgets('light theme primary colour equals seed colour',
25+
(WidgetTester tester) async {
26+
final theme = buildAppTheme(Brightness.light);
27+
expect(theme.colorScheme.primary, equals(appSeedColor));
28+
});
29+
30+
testWidgets('dark theme AppBar background is surface (not navy)',
31+
(WidgetTester tester) async {
32+
final lightTheme = buildAppTheme(Brightness.light);
33+
final darkTheme = buildAppTheme(Brightness.dark);
34+
expect(darkTheme.appBarTheme.backgroundColor, isNot(equals(appSeedColor)));
35+
expect(darkTheme.appBarTheme.backgroundColor, equals(darkTheme.colorScheme.surface));
36+
expect(darkTheme.appBarTheme.backgroundColor,
37+
isNot(equals(lightTheme.appBarTheme.backgroundColor)));
38+
});
39+
40+
testWidgets('dark theme primary colour is lighter blue (not navy)',
41+
(WidgetTester tester) async {
42+
final darkTheme = buildAppTheme(Brightness.dark);
43+
expect(darkTheme.colorScheme.primary, isNot(equals(appSeedColor)));
44+
});
45+
46+
testWidgets('AppBar has zero elevation', (WidgetTester tester) async {
47+
expect(buildAppTheme(Brightness.light).appBarTheme.elevation, equals(0));
48+
expect(buildAppTheme(Brightness.dark).appBarTheme.elevation, equals(0));
49+
});
50+
51+
testWidgets('AppBar title is not centered', (WidgetTester tester) async {
52+
expect(buildAppTheme(Brightness.light).appBarTheme.centerTitle, isFalse);
53+
expect(buildAppTheme(Brightness.dark).appBarTheme.centerTitle, isFalse);
54+
});
55+
56+
testWidgets('uses Material 3', (WidgetTester tester) async {
57+
expect(buildAppTheme(Brightness.light).useMaterial3, isTrue);
58+
expect(buildAppTheme(Brightness.dark).useMaterial3, isTrue);
59+
});
60+
});
61+
62+
group('buildAppTextTheme', () {
63+
testWidgets('returns a TextTheme with display styles set',
64+
(WidgetTester tester) async {
65+
final colorScheme = ColorScheme.fromSeed(
66+
seedColor: appSeedColor,
67+
brightness: Brightness.light,
68+
);
69+
final textTheme = buildAppTextTheme(colorScheme);
70+
expect(textTheme.displayLarge, isNotNull);
71+
expect(textTheme.displayLarge!.fontSize, equals(57));
72+
});
73+
74+
testWidgets('titleLarge has correct font size', (WidgetTester tester) async {
75+
final colorScheme = ColorScheme.fromSeed(
76+
seedColor: appSeedColor,
77+
brightness: Brightness.light,
78+
);
79+
final textTheme = buildAppTextTheme(colorScheme);
80+
expect(textTheme.titleLarge!.fontSize, equals(22));
81+
});
82+
83+
testWidgets('bodyMedium has correct font size', (WidgetTester tester) async {
84+
final colorScheme = ColorScheme.fromSeed(
85+
seedColor: appSeedColor,
86+
brightness: Brightness.light,
87+
);
88+
final textTheme = buildAppTextTheme(colorScheme);
89+
expect(textTheme.bodyMedium!.fontSize, equals(14));
90+
});
91+
});
92+
93+
group('appSeedColor', () {
94+
test('is the CBF 2026 navy', () {
95+
expect(appSeedColor, equals(const Color(0xFF2B3170)));
96+
});
97+
});
98+
}

0 commit comments

Comments
 (0)