Main coves client
1import 'package:coves_flutter/widgets/icons/animated_heart_icon.dart';
2import 'package:flutter/material.dart';
3import 'package:flutter_test/flutter_test.dart';
4
5void main() {
6 group('AnimatedHeartIcon', () {
7 testWidgets('should render with default size', (tester) async {
8 await tester.pumpWidget(
9 const MaterialApp(
10 home: Scaffold(body: AnimatedHeartIcon(isLiked: false)),
11 ),
12 );
13
14 // Widget should render
15 expect(find.byType(AnimatedHeartIcon), findsOneWidget);
16
17 // Find the SizedBox that defines the size
18 final sizedBox = tester.widget<SizedBox>(
19 find
20 .descendant(
21 of: find.byType(AnimatedHeartIcon),
22 matching: find.byType(SizedBox),
23 )
24 .first,
25 );
26
27 // Default size should be 18
28 expect(sizedBox.width, 18);
29 expect(sizedBox.height, 18);
30 });
31
32 testWidgets('should render with custom size', (tester) async {
33 await tester.pumpWidget(
34 const MaterialApp(
35 home: Scaffold(body: AnimatedHeartIcon(isLiked: false, size: 32)),
36 ),
37 );
38
39 // Find the SizedBox that defines the size
40 final sizedBox = tester.widget<SizedBox>(
41 find
42 .descendant(
43 of: find.byType(AnimatedHeartIcon),
44 matching: find.byType(SizedBox),
45 )
46 .first,
47 );
48
49 // Custom size should be 32
50 expect(sizedBox.width, 32);
51 expect(sizedBox.height, 32);
52 });
53
54 testWidgets('should use custom color when provided', (tester) async {
55 const customColor = Colors.blue;
56
57 await tester.pumpWidget(
58 const MaterialApp(
59 home: Scaffold(
60 body: AnimatedHeartIcon(isLiked: false, color: customColor),
61 ),
62 ),
63 );
64
65 // Widget should render with custom color
66 expect(find.byType(AnimatedHeartIcon), findsOneWidget);
67 // Note: We can't easily verify the color without accessing the
68 // CustomPainter, but we can verify the widget accepts the parameter
69 });
70
71 testWidgets('should use custom liked color when provided', (tester) async {
72 const customLikedColor = Colors.pink;
73
74 await tester.pumpWidget(
75 const MaterialApp(
76 home: Scaffold(
77 body: AnimatedHeartIcon(
78 isLiked: true,
79 likedColor: customLikedColor,
80 ),
81 ),
82 ),
83 );
84
85 // Widget should render with custom liked color
86 expect(find.byType(AnimatedHeartIcon), findsOneWidget);
87 });
88
89 testWidgets('should start animation when isLiked changes to true', (
90 tester,
91 ) async {
92 // Start with unliked state
93 await tester.pumpWidget(
94 const MaterialApp(
95 home: Scaffold(body: AnimatedHeartIcon(isLiked: false)),
96 ),
97 );
98
99 // Verify initial state
100 expect(find.byType(AnimatedHeartIcon), findsOneWidget);
101
102 // Change to liked state
103 await tester.pumpWidget(
104 const MaterialApp(
105 home: Scaffold(body: AnimatedHeartIcon(isLiked: true)),
106 ),
107 );
108
109 // Pump frames to allow animation to start
110 await tester.pump();
111 await tester.pump(const Duration(milliseconds: 100));
112
113 // Widget should still be present and animating
114 expect(find.byType(AnimatedHeartIcon), findsOneWidget);
115 });
116
117 testWidgets('should not animate when isLiked changes to false', (
118 tester,
119 ) async {
120 // Start with liked state
121 await tester.pumpWidget(
122 const MaterialApp(
123 home: Scaffold(body: AnimatedHeartIcon(isLiked: true)),
124 ),
125 );
126
127 await tester.pump();
128
129 // Change to unliked state
130 await tester.pumpWidget(
131 const MaterialApp(
132 home: Scaffold(body: AnimatedHeartIcon(isLiked: false)),
133 ),
134 );
135
136 await tester.pump();
137
138 // Widget should update without error
139 expect(find.byType(AnimatedHeartIcon), findsOneWidget);
140 });
141
142 testWidgets('should complete animation after duration', (tester) async {
143 // Start with unliked state
144 await tester.pumpWidget(
145 const MaterialApp(
146 home: Scaffold(body: AnimatedHeartIcon(isLiked: false)),
147 ),
148 );
149
150 // Change to liked state
151 await tester.pumpWidget(
152 const MaterialApp(
153 home: Scaffold(body: AnimatedHeartIcon(isLiked: true)),
154 ),
155 );
156
157 // Pump through the entire animation duration (800ms)
158 await tester.pump();
159 await tester.pump(const Duration(milliseconds: 800));
160 await tester.pumpAndSettle();
161
162 // Widget should still be present after animation completes
163 expect(find.byType(AnimatedHeartIcon), findsOneWidget);
164 });
165
166 testWidgets('should handle rapid state changes', (tester) async {
167 // Start with unliked state
168 await tester.pumpWidget(
169 const MaterialApp(
170 home: Scaffold(body: AnimatedHeartIcon(isLiked: false)),
171 ),
172 );
173
174 // Rapidly toggle states
175 await tester.pumpWidget(
176 const MaterialApp(
177 home: Scaffold(body: AnimatedHeartIcon(isLiked: true)),
178 ),
179 );
180 await tester.pump(const Duration(milliseconds: 50));
181
182 await tester.pumpWidget(
183 const MaterialApp(
184 home: Scaffold(body: AnimatedHeartIcon(isLiked: false)),
185 ),
186 );
187 await tester.pump(const Duration(milliseconds: 50));
188
189 await tester.pumpWidget(
190 const MaterialApp(
191 home: Scaffold(body: AnimatedHeartIcon(isLiked: true)),
192 ),
193 );
194 await tester.pump(const Duration(milliseconds: 50));
195
196 // Widget should handle rapid changes without error
197 expect(find.byType(AnimatedHeartIcon), findsOneWidget);
198 });
199
200 testWidgets('should use OverflowBox to allow animation overflow', (
201 tester,
202 ) async {
203 await tester.pumpWidget(
204 const MaterialApp(
205 home: Scaffold(body: AnimatedHeartIcon(isLiked: true)),
206 ),
207 );
208
209 // Find the OverflowBox
210 expect(find.byType(OverflowBox), findsOneWidget);
211
212 final overflowBox = tester.widget<OverflowBox>(find.byType(OverflowBox));
213
214 // OverflowBox should have larger max dimensions (2.5x the icon size)
215 // to accommodate the 1.3x scale and particle burst
216 expect(overflowBox.maxWidth, 18 * 2.5);
217 expect(overflowBox.maxHeight, 18 * 2.5);
218 });
219
220 testWidgets('should render CustomPaint for heart icon', (tester) async {
221 await tester.pumpWidget(
222 const MaterialApp(
223 home: Scaffold(body: AnimatedHeartIcon(isLiked: false)),
224 ),
225 );
226
227 // Find the CustomPaint widget (used for rendering the heart)
228 expect(find.byType(CustomPaint), findsAtLeastNWidgets(1));
229 });
230
231 testWidgets('should not animate on initial render when isLiked is true', (
232 tester,
233 ) async {
234 // Render with isLiked=true initially
235 await tester.pumpWidget(
236 const MaterialApp(
237 home: Scaffold(body: AnimatedHeartIcon(isLiked: true)),
238 ),
239 );
240
241 await tester.pump();
242
243 // Widget should render in liked state without animation
244 // (Animation only triggers on state change, not initial render)
245 expect(find.byType(AnimatedHeartIcon), findsOneWidget);
246 });
247
248 testWidgets('should dispose controller properly', (tester) async {
249 await tester.pumpWidget(
250 const MaterialApp(
251 home: Scaffold(body: AnimatedHeartIcon(isLiked: false)),
252 ),
253 );
254
255 // Remove the widget
256 await tester.pumpWidget(
257 const MaterialApp(home: Scaffold(body: SizedBox.shrink())),
258 );
259
260 // Should dispose without error
261 // (No assertions needed - test passes if no exception is thrown)
262 });
263
264 testWidgets('should rebuild when isLiked changes', (tester) async {
265 var buildCount = 0;
266
267 await tester.pumpWidget(
268 MaterialApp(
269 home: Scaffold(
270 body: Builder(
271 builder: (context) {
272 buildCount++;
273 return const AnimatedHeartIcon(isLiked: false);
274 },
275 ),
276 ),
277 ),
278 );
279
280 final initialBuildCount = buildCount;
281
282 // Change isLiked state
283 await tester.pumpWidget(
284 MaterialApp(
285 home: Scaffold(
286 body: Builder(
287 builder: (context) {
288 buildCount++;
289 return const AnimatedHeartIcon(isLiked: true);
290 },
291 ),
292 ),
293 ),
294 );
295
296 // Should rebuild
297 expect(buildCount, greaterThan(initialBuildCount));
298 });
299 });
300}