Skip to content

Commit 42fc326

Browse files
committed
test: daily challenge card tests
1 parent 3f2d3e9 commit 42fc326

1 file changed

Lines changed: 213 additions & 0 deletions

File tree

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:freecodecamp/app/app.locator.dart';
4+
import 'package:freecodecamp/app/app.router.dart';
5+
import 'package:freecodecamp/models/learn/daily_challenge_model.dart';
6+
import 'package:freecodecamp/ui/views/learn/widgets/daily_challenge_card.dart';
7+
import 'package:mockito/mockito.dart';
8+
import 'package:timezone/data/latest.dart' as tzdata;
9+
10+
import '../helpers/test_helpers.dart';
11+
import '../helpers/test_helpers.mocks.dart';
12+
13+
void main() {
14+
// Mock service variables
15+
late MockNavigationService mockNavigationService;
16+
17+
// Test data
18+
final testChallenge = DailyChallenge(
19+
id: 'test-challenge-id',
20+
challengeNumber: 1,
21+
date: DateTime(2025, 7, 26),
22+
title: 'Test Daily Challenge',
23+
description: 'A test challenge description',
24+
javascript: DailyChallengeLanguageData(
25+
tests: [],
26+
challengeFiles: [],
27+
),
28+
python: DailyChallengeLanguageData(
29+
tests: [],
30+
challengeFiles: [],
31+
),
32+
);
33+
34+
setUp(() {
35+
TestWidgetsFlutterBinding.ensureInitialized();
36+
tzdata.initializeTimeZones();
37+
38+
// Register services
39+
registerServices();
40+
mockNavigationService = getAndRegisterNavigationService();
41+
});
42+
43+
tearDown(() {
44+
locator.reset();
45+
});
46+
47+
Widget createTestWidget({
48+
DailyChallenge? challenge,
49+
bool isCompleted = false,
50+
}) {
51+
return MaterialApp(
52+
home: Scaffold(
53+
body: DailyChallengeCard(
54+
dailyChallenge: challenge,
55+
isCompleted: isCompleted,
56+
),
57+
),
58+
onGenerateRoute: (settings) {
59+
// Handle navigation routes used by the widget
60+
if (settings.name == '/challenge-template-view') {
61+
return MaterialPageRoute(
62+
builder: (context) => Scaffold(
63+
body: Center(child: Text('Challenge Template View')),
64+
),
65+
);
66+
}
67+
return null;
68+
},
69+
);
70+
}
71+
72+
testWidgets('should return empty widget when no challenge is provided',
73+
(WidgetTester tester) async {
74+
await tester.pumpWidget(createTestWidget());
75+
76+
expect(find.byType(DailyChallengeCard), findsOneWidget);
77+
expect(find.text('Today\'s challenge'), findsNothing);
78+
expect(find.text('Start the challenge'), findsNothing);
79+
});
80+
81+
testWidgets(
82+
'should show challenge details when challenge is provided and not completed',
83+
(WidgetTester tester) async {
84+
await tester.pumpWidget(createTestWidget(
85+
challenge: testChallenge,
86+
isCompleted: false,
87+
));
88+
89+
expect(find.text('Today\'s challenge'), findsOneWidget);
90+
expect(find.text('Test Daily Challenge'), findsOneWidget);
91+
expect(find.text('Do you have the skills to complete this challenge?'),
92+
findsOneWidget);
93+
expect(find.text('Start the challenge'), findsOneWidget);
94+
expect(find.byIcon(Icons.arrow_forward_ios), findsOneWidget);
95+
96+
// Test navigation to the challenge
97+
await tester.tap(find.text('Start the challenge'));
98+
await tester.pumpAndSettle();
99+
expect(find.text('Challenge Template View'), findsOneWidget);
100+
});
101+
102+
testWidgets(
103+
'should show completed state with timer and allow navigation to past challenges',
104+
(WidgetTester tester) async {
105+
await tester.pumpWidget(createTestWidget(
106+
challenge: testChallenge,
107+
isCompleted: true,
108+
));
109+
110+
expect(find.text('Today\'s challenge completed!'), findsOneWidget);
111+
expect(find.textContaining('Next challenge in:'), findsOneWidget);
112+
expect(find.text('View past challenges'), findsOneWidget);
113+
expect(find.byIcon(Icons.history), findsOneWidget);
114+
115+
final timerRegex = RegExp(r'^Next challenge in: \d{2}:\d{2}:\d{2}$');
116+
final timerFinder = find.byWidgetPredicate((widget) {
117+
if (widget is Text) {
118+
return timerRegex.hasMatch(widget.data ?? '');
119+
}
120+
return false;
121+
});
122+
expect(timerFinder, findsOneWidget);
123+
124+
// Test navigation to past challenges
125+
await tester.tap(find.text('View past challenges'));
126+
await tester.pumpAndSettle();
127+
verify(mockNavigationService.navigateTo(Routes.dailyChallengeView))
128+
.called(1);
129+
});
130+
131+
testWidgets(
132+
'should have proper accessibility semantics for not completed state',
133+
(WidgetTester tester) async {
134+
await tester.pumpWidget(createTestWidget(
135+
challenge: testChallenge,
136+
isCompleted: false,
137+
));
138+
139+
final cardSemanticsFinder = find.byElementPredicate((element) {
140+
return element.widget is Semantics &&
141+
(element.widget as Semantics).properties.label ==
142+
'Daily challenge card';
143+
});
144+
145+
final buttonSemanticsFinder = find.byElementPredicate((element) {
146+
return element.widget is Semantics &&
147+
(element.widget as Semantics).properties.label == 'Go to challenge';
148+
});
149+
150+
expect(cardSemanticsFinder, findsOneWidget);
151+
expect(buttonSemanticsFinder, findsOneWidget);
152+
});
153+
154+
testWidgets('should have proper accessibility semantics for completed state',
155+
(WidgetTester tester) async {
156+
await tester.pumpWidget(createTestWidget(
157+
challenge: testChallenge,
158+
isCompleted: true,
159+
));
160+
161+
final cardSemanticsFinder = find.byElementPredicate((element) {
162+
return element.widget is Semantics &&
163+
(element.widget as Semantics).properties.label ==
164+
'Daily challenge completed. View past challenges.';
165+
});
166+
167+
expect(cardSemanticsFinder, findsOneWidget);
168+
});
169+
170+
testWidgets('should have live region semantics for timer in completed state',
171+
(WidgetTester tester) async {
172+
await tester.pumpWidget(createTestWidget(
173+
challenge: testChallenge,
174+
isCompleted: true,
175+
));
176+
177+
final semanticsWidgets =
178+
tester.widgetList<Semantics>(find.byType(Semantics));
179+
bool hasLiveRegion = false;
180+
181+
for (final semantics in semanticsWidgets) {
182+
if (semantics.properties.liveRegion == true) {
183+
hasLiveRegion = true;
184+
break;
185+
}
186+
}
187+
188+
expect(hasLiveRegion, isTrue,
189+
reason: 'Expected to find a Semantics widget with liveRegion: true');
190+
});
191+
192+
testWidgets('should display countdown timer in completed state',
193+
(WidgetTester tester) async {
194+
await tester.pumpWidget(createTestWidget(
195+
challenge: testChallenge,
196+
isCompleted: true,
197+
));
198+
199+
final timerRegex = RegExp(r'^Next challenge in: \d{2}:\d{2}:\d{2}$');
200+
final timerFinder = find.byWidgetPredicate((widget) {
201+
if (widget is Text) {
202+
return timerRegex.hasMatch(widget.data ?? '');
203+
}
204+
return false;
205+
});
206+
207+
expect(timerFinder, findsOneWidget);
208+
209+
// Check that timer text contains expected format
210+
final timerWidget = tester.widget<Text>(timerFinder);
211+
expect(timerWidget.data, matches(timerRegex));
212+
});
213+
}

0 commit comments

Comments
 (0)