at main 8.7 kB view raw
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}