Skip to content

Commit b707fc3

Browse files
committed
Add unit tests
1 parent aba1122 commit b707fc3

File tree

4 files changed

+319
-0
lines changed

4 files changed

+319
-0
lines changed

lib/features/roadster/widget/app_bar/image_carousel.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class ImageCarousel extends StatelessWidget {
5757
@override
5858
Widget build(BuildContext context) {
5959
return Transform.translate(
60+
key: const Key('parallax_transform'),
6061
offset: Offset(0, scrollOffset * 0.5),
6162
child: SizedBox(
6263
height: 300,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'dart:io';
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:flutter_bloc_app_template/app/localization.dart';
5+
import 'package:flutter_bloc_app_template/features/roadster/roadster_screen.dart';
6+
import 'package:flutter_bloc_app_template/features/roadster/widget/app_bar/roadster_app_bar.dart';
7+
import 'package:flutter_bloc_app_template/features/roadster/widget/background/animated_gradient_background.dart';
8+
import 'package:flutter_bloc_app_template/features/roadster/widget/background/animated_stars_field.dart';
9+
import 'package:flutter_bloc_app_template/features/roadster/widget/buttons/track_live_button.dart';
10+
import 'package:flutter_bloc_app_template/features/roadster/widget/roadster_content.dart';
11+
import 'package:flutter_bloc_app_template/models/roadster/roadster_resource.dart';
12+
import 'package:flutter_test/flutter_test.dart';
13+
import 'package:network_image_mock/network_image_mock.dart';
14+
15+
import '../../repository/roadster_repository_test.dart';
16+
17+
void main() {
18+
late RoadsterResource mockRoadster;
19+
late List<String> mockImages;
20+
21+
setUpAll(() => HttpOverrides.global = null);
22+
23+
setUp(() {
24+
mockRoadster = mockRoadsterResource;
25+
mockImages = ['image1.jpg', 'image2.jpg'];
26+
});
27+
28+
testWidgets('renders main sections and background', (tester) async {
29+
await mockNetworkImagesFor(
30+
() => tester.pumpWidget(
31+
MaterialApp(
32+
supportedLocales: appSupportedLocales,
33+
localizationsDelegates: appLocalizationsDelegates,
34+
home: RoadsterDetailScreen(
35+
roadster: mockRoadster,
36+
images: mockImages,
37+
),
38+
),
39+
),
40+
);
41+
// Animated background
42+
expect(find.byType(AnimatedGradientBackground), findsOneWidget);
43+
expect(find.byType(AnimatedStarsField), findsOneWidget);
44+
45+
// Roadster content
46+
expect(find.byType(RoadsterAppBar), findsOneWidget);
47+
expect(find.byType(RoadsterContent), findsOneWidget);
48+
49+
// Floating button
50+
expect(find.byType(TrackLiveButton), findsOneWidget);
51+
});
52+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc_app_template/app/localization.dart';
3+
import 'package:flutter_bloc_app_template/features/roadster/widget/app_bar/image_carousel.dart';
4+
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:network_image_mock/network_image_mock.dart';
6+
7+
void main() {
8+
group('ImageCarousel', () {
9+
late PageController pageController;
10+
late List<String> mockImages;
11+
var pageChangedCallCount = 0;
12+
13+
setUp(() {
14+
pageController = PageController(viewportFraction: 0.85);
15+
mockImages = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
16+
pageChangedCallCount = 0;
17+
});
18+
19+
tearDown(() {
20+
pageController.dispose();
21+
});
22+
23+
testWidgets('should render page view with images',
24+
(WidgetTester tester) async {
25+
await mockNetworkImagesFor(
26+
() => tester.pumpWidget(
27+
MaterialApp(
28+
supportedLocales: appSupportedLocales,
29+
localizationsDelegates: appLocalizationsDelegates,
30+
home: ImageCarousel(
31+
images: mockImages,
32+
pageController: pageController,
33+
scrollOffset: 0.0,
34+
onPageChanged: (index) => pageChangedCallCount++,
35+
),
36+
),
37+
),
38+
);
39+
40+
expect(find.byType(PageView), findsOneWidget);
41+
expect(find.byType(Card), findsAtLeastNWidgets(1));
42+
});
43+
44+
testWidgets('should apply parallax offset', (WidgetTester tester) async {
45+
const scrollOffset = 100.0;
46+
47+
await mockNetworkImagesFor(
48+
() => tester.pumpWidget(
49+
MaterialApp(
50+
supportedLocales: appSupportedLocales,
51+
localizationsDelegates: appLocalizationsDelegates,
52+
home: ImageCarousel(
53+
images: mockImages,
54+
pageController: pageController,
55+
scrollOffset: scrollOffset,
56+
onPageChanged: (index) => pageChangedCallCount++,
57+
),
58+
),
59+
),
60+
);
61+
62+
final transform =
63+
tester.widget<Transform>(find.byKey(const Key('parallax_transform')));
64+
65+
expect(
66+
transform.transform.getTranslation().y, equals(scrollOffset * 0.5));
67+
});
68+
69+
testWidgets('should call onPageChanged when swiping',
70+
(WidgetTester tester) async {
71+
pageChangedCallCount = 0;
72+
pageController = PageController(initialPage: 0);
73+
74+
await mockNetworkImagesFor(() async {
75+
await tester.pumpWidget(
76+
MaterialApp(
77+
supportedLocales: appSupportedLocales,
78+
localizationsDelegates: appLocalizationsDelegates,
79+
home: ImageCarousel(
80+
images: mockImages,
81+
pageController: pageController,
82+
scrollOffset: 0.0,
83+
onPageChanged: (index) => pageChangedCallCount++,
84+
),
85+
),
86+
);
87+
88+
// Wait for layout to stabilize
89+
await tester.pumpAndSettle();
90+
91+
// Drag by the width of the page to trigger page change
92+
await tester.drag(find.byType(PageView), const Offset(-400, 0));
93+
await tester.pumpAndSettle();
94+
95+
expect(pageChangedCallCount, greaterThan(0));
96+
});
97+
});
98+
});
99+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc_app_template/app/localization.dart';
3+
import 'package:flutter_bloc_app_template/features/roadster/widget/app_bar/image_carousel.dart';
4+
import 'package:flutter_bloc_app_template/features/roadster/widget/app_bar/image_indicators.dart';
5+
import 'package:flutter_bloc_app_template/features/roadster/widget/app_bar/roadster_app_bar.dart';
6+
import 'package:flutter_bloc_app_template/models/roadster/roadster_resource.dart';
7+
import 'package:flutter_test/flutter_test.dart' hide TestVSync;
8+
import 'package:network_image_mock/network_image_mock.dart';
9+
10+
import '../../helpers/test_helpers.dart';
11+
12+
void main() {
13+
group('RoadsterAppBar', () {
14+
late RoadsterResource mockRoadster;
15+
late List<String> mockImages;
16+
late AnimationController fadeController;
17+
18+
setUp(() {
19+
mockRoadster = createMockRoadster();
20+
mockImages = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
21+
fadeController = AnimationController(
22+
duration: const Duration(milliseconds: 1500),
23+
vsync: const TestVSync(),
24+
);
25+
});
26+
27+
tearDown(() {
28+
fadeController.dispose();
29+
});
30+
31+
testWidgets('should render sliver app bar', (WidgetTester tester) async {
32+
await mockNetworkImagesFor(
33+
() => tester.pumpWidget(
34+
MaterialApp(
35+
supportedLocales: appSupportedLocales,
36+
localizationsDelegates: appLocalizationsDelegates,
37+
home: Scaffold(
38+
body: CustomScrollView(
39+
slivers: [
40+
RoadsterAppBar(
41+
roadster: mockRoadster,
42+
images: mockImages,
43+
scrollOffset: 0.0,
44+
fadeController: fadeController,
45+
),
46+
],
47+
),
48+
),
49+
),
50+
),
51+
);
52+
53+
expect(find.byType(SliverAppBar), findsOneWidget);
54+
});
55+
56+
testWidgets('should show title when scrolled', (WidgetTester tester) async {
57+
await mockNetworkImagesFor(
58+
() => tester.pumpWidget(
59+
MaterialApp(
60+
supportedLocales: appSupportedLocales,
61+
localizationsDelegates: appLocalizationsDelegates,
62+
home: Scaffold(
63+
body: CustomScrollView(
64+
slivers: [
65+
RoadsterAppBar(
66+
roadster: mockRoadster,
67+
images: mockImages,
68+
scrollOffset: 250.0, // Scrolled past threshold
69+
fadeController: fadeController,
70+
),
71+
],
72+
),
73+
),
74+
),
75+
),
76+
);
77+
78+
expect(find.text('Tesla Roadster'), findsAtLeast(1));
79+
});
80+
81+
testWidgets('should hide title when not scrolled',
82+
(WidgetTester tester) async {
83+
await mockNetworkImagesFor(
84+
() => tester.pumpWidget(
85+
MaterialApp(
86+
supportedLocales: appSupportedLocales,
87+
localizationsDelegates: appLocalizationsDelegates,
88+
home: Scaffold(
89+
body: CustomScrollView(
90+
slivers: [
91+
RoadsterAppBar(
92+
roadster: mockRoadster,
93+
images: mockImages,
94+
scrollOffset: 0.0, // Not scrolled
95+
fadeController: fadeController,
96+
),
97+
],
98+
),
99+
),
100+
),
101+
),
102+
);
103+
// Title should be transparent
104+
final animatedOpacity = tester.widget<AnimatedOpacity>(
105+
find.byType(AnimatedOpacity),
106+
);
107+
expect(animatedOpacity.opacity, equals(0.0));
108+
});
109+
110+
testWidgets('should start auto scroll for multiple images',
111+
(WidgetTester tester) async {
112+
await mockNetworkImagesFor(
113+
() => tester.pumpWidget(
114+
MaterialApp(
115+
supportedLocales: appSupportedLocales,
116+
localizationsDelegates: appLocalizationsDelegates,
117+
home: Scaffold(
118+
body: CustomScrollView(
119+
slivers: [
120+
RoadsterAppBar(
121+
roadster: mockRoadster,
122+
images: mockImages,
123+
scrollOffset: 0.0,
124+
fadeController: fadeController,
125+
),
126+
],
127+
),
128+
),
129+
),
130+
),
131+
);
132+
133+
expect(find.byType(ImageCarousel), findsOneWidget);
134+
expect(find.byType(ImageIndicators), findsOneWidget);
135+
});
136+
137+
testWidgets('should dispose controllers properly',
138+
(WidgetTester tester) async {
139+
await mockNetworkImagesFor(
140+
() => tester.pumpWidget(
141+
MaterialApp(
142+
supportedLocales: appSupportedLocales,
143+
localizationsDelegates: appLocalizationsDelegates,
144+
home: Scaffold(
145+
body: CustomScrollView(
146+
slivers: [
147+
RoadsterAppBar(
148+
roadster: mockRoadster,
149+
images: mockImages,
150+
scrollOffset: 0.0,
151+
fadeController: fadeController,
152+
),
153+
],
154+
),
155+
),
156+
),
157+
),
158+
);
159+
160+
// Remove the widget to trigger dispose
161+
await tester.pumpWidget(const MaterialApp(home: Scaffold()));
162+
163+
// Verify no errors occurred during disposal
164+
expect(tester.takeException(), isNull);
165+
});
166+
});
167+
}

0 commit comments

Comments
 (0)