1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/rendering.dart';
6import 'package:flutter/widgets.dart';
7import 'package:flutter_test/flutter_test.dart';
8
9class TestPaintingContext implements PaintingContext {
10 final List<Invocation> invocations = <Invocation>[];
11
12 @override
13 void noSuchMethod(Invocation invocation) {
14 invocations.add(invocation);
15 }
16}
17
18void main() {
19 group('AnimatedSize', () {
20 testWidgets('animates forwards then backwards with stable-sized children', (WidgetTester tester) async {
21 await tester.pumpWidget(
22 const Center(
23 child: AnimatedSize(
24 duration: Duration(milliseconds: 200),
25 child: SizedBox(
26 width: 100.0,
27 height: 100.0,
28 ),
29 ),
30 ),
31 );
32
33 RenderBox box = tester.renderObject(find.byType(AnimatedSize));
34 expect(box.size.width, equals(100.0));
35 expect(box.size.height, equals(100.0));
36
37 await tester.pumpWidget(
38 const Center(
39 child: AnimatedSize(
40 duration: Duration(milliseconds: 200),
41 child: SizedBox(
42 width: 200.0,
43 height: 200.0,
44 ),
45 ),
46 ),
47 );
48
49 await tester.pump(const Duration(milliseconds: 100));
50 box = tester.renderObject(find.byType(AnimatedSize));
51 expect(box.size.width, equals(150.0));
52 expect(box.size.height, equals(150.0));
53
54 TestPaintingContext context = TestPaintingContext();
55 box.paint(context, Offset.zero);
56 expect(context.invocations.first.memberName, equals(#pushClipRect));
57
58 await tester.pump(const Duration(milliseconds: 100));
59 box = tester.renderObject(find.byType(AnimatedSize));
60 expect(box.size.width, equals(200.0));
61 expect(box.size.height, equals(200.0));
62
63 await tester.pumpWidget(
64 const Center(
65 child: AnimatedSize(
66 duration: Duration(milliseconds: 200),
67 child: SizedBox(
68 width: 100.0,
69 height: 100.0,
70 ),
71 ),
72 ),
73 );
74
75 await tester.pump(const Duration(milliseconds: 100));
76 box = tester.renderObject(find.byType(AnimatedSize));
77 expect(box.size.width, equals(150.0));
78 expect(box.size.height, equals(150.0));
79
80 context = TestPaintingContext();
81 box.paint(context, Offset.zero);
82 expect(context.invocations.first.memberName, equals(#paintChild));
83
84 await tester.pump(const Duration(milliseconds: 100));
85 box = tester.renderObject(find.byType(AnimatedSize));
86 expect(box.size.width, equals(100.0));
87 expect(box.size.height, equals(100.0));
88 });
89
90 testWidgets('calls onEnd when animation is completed', (WidgetTester tester) async {
91 int callCount = 0;
92 void handleEnd() {
93 callCount++;
94 }
95
96 await tester.pumpWidget(
97 Center(
98 child: AnimatedSize(
99 onEnd: handleEnd,
100 duration: const Duration(milliseconds: 200),
101 child: const SizedBox(
102 width: 100.0,
103 height: 100.0,
104 ),
105 ),
106 ),
107 );
108
109 expect(callCount, equals(0));
110
111 await tester.pumpWidget(
112 Center(
113 child: AnimatedSize(
114 onEnd: handleEnd,
115 duration: const Duration(milliseconds: 200),
116 child: const SizedBox(
117 width: 200.0,
118 height: 200.0,
119 ),
120 ),
121 ),
122 );
123
124 expect(callCount, equals(0));
125 await tester.pumpAndSettle();
126 expect(callCount, equals(1));
127
128 await tester.pumpWidget(
129 Center(
130 child: AnimatedSize(
131 onEnd: handleEnd,
132 duration: const Duration(milliseconds: 200),
133 child: const SizedBox(
134 width: 100.0,
135 height: 100.0,
136 ),
137 ),
138 ),
139 );
140
141 await tester.pumpAndSettle();
142 expect(callCount, equals(2));
143 });
144
145 testWidgets('clamps animated size to constraints', (WidgetTester tester) async {
146 await tester.pumpWidget(
147 const Center(
148 child: SizedBox (
149 width: 100.0,
150 height: 100.0,
151 child: AnimatedSize(
152 duration: Duration(milliseconds: 200),
153 child: SizedBox(
154 width: 100.0,
155 height: 100.0,
156 ),
157 ),
158 ),
159 ),
160 );
161
162 RenderBox box = tester.renderObject(find.byType(AnimatedSize));
163 expect(box.size.width, equals(100.0));
164 expect(box.size.height, equals(100.0));
165
166 // Attempt to animate beyond the outer SizedBox.
167 await tester.pumpWidget(
168 const Center(
169 child: SizedBox (
170 width: 100.0,
171 height: 100.0,
172 child: AnimatedSize(
173 duration: Duration(milliseconds: 200),
174 child: SizedBox(
175 width: 200.0,
176 height: 200.0,
177 ),
178 ),
179 ),
180 ),
181 );
182
183 // Verify that animated size is the same as the outer SizedBox.
184 await tester.pump(const Duration(milliseconds: 100));
185 box = tester.renderObject(find.byType(AnimatedSize));
186 expect(box.size.width, equals(100.0));
187 expect(box.size.height, equals(100.0));
188 });
189
190 testWidgets('tracks unstable child, then resumes animation when child stabilizes', (WidgetTester tester) async {
191 Future<void> pumpMillis(int millis) async {
192 await tester.pump(Duration(milliseconds: millis));
193 }
194
195 void verify({ double? size, RenderAnimatedSizeState? state }) {
196 assert(size != null || state != null);
197 final RenderAnimatedSize box = tester.renderObject(find.byType(AnimatedSize));
198 if (size != null) {
199 expect(box.size.width, size);
200 expect(box.size.height, size);
201 }
202 if (state != null) {
203 expect(box.state, state);
204 }
205 }
206
207 await tester.pumpWidget(
208 Center(
209 child: AnimatedSize(
210 duration: const Duration(milliseconds: 200),
211 child: AnimatedContainer(
212 duration: const Duration(milliseconds: 100),
213 width: 100.0,
214 height: 100.0,
215 ),
216 ),
217 ),
218 );
219
220 verify(size: 100.0, state: RenderAnimatedSizeState.stable);
221
222 // Animate child size from 100 to 200 slowly (100ms).
223 await tester.pumpWidget(
224 Center(
225 child: AnimatedSize(
226 duration: const Duration(milliseconds: 200),
227 child: AnimatedContainer(
228 duration: const Duration(milliseconds: 100),
229 width: 200.0,
230 height: 200.0,
231 ),
232 ),
233 ),
234 );
235
236 // Make sure animation proceeds at child's pace, with AnimatedSize
237 // tightly tracking the child's size.
238 verify(state: RenderAnimatedSizeState.stable);
239 await pumpMillis(1); // register change
240 verify(state: RenderAnimatedSizeState.changed);
241 await pumpMillis(49);
242 verify(size: 150.0, state: RenderAnimatedSizeState.unstable);
243 await pumpMillis(50);
244 verify(size: 200.0, state: RenderAnimatedSizeState.unstable);
245
246 // Stabilize size
247 await pumpMillis(50);
248 verify(size: 200.0, state: RenderAnimatedSizeState.stable);
249
250 // Quickly (in 1ms) change size back to 100
251 await tester.pumpWidget(
252 Center(
253 child: AnimatedSize(
254 duration: const Duration(milliseconds: 200),
255 child: AnimatedContainer(
256 duration: const Duration(milliseconds: 1),
257 width: 100.0,
258 height: 100.0,
259 ),
260 ),
261 ),
262 );
263
264 verify(size: 200.0, state: RenderAnimatedSizeState.stable);
265 await pumpMillis(1); // register change
266 verify(state: RenderAnimatedSizeState.changed);
267 await pumpMillis(100);
268 verify(size: 150.0, state: RenderAnimatedSizeState.stable);
269 await pumpMillis(100);
270 verify(size: 100.0, state: RenderAnimatedSizeState.stable);
271 });
272
273 testWidgets('resyncs its animation controller', (WidgetTester tester) async {
274 await tester.pumpWidget(
275 const Center(
276 child: AnimatedSize(
277 duration: Duration(milliseconds: 200),
278 child: SizedBox(
279 width: 100.0,
280 height: 100.0,
281 ),
282 ),
283 ),
284 );
285
286 await tester.pumpWidget(
287 const Center(
288 child: AnimatedSize(
289 duration: Duration(milliseconds: 200),
290 child: SizedBox(
291 width: 200.0,
292 height: 100.0,
293 ),
294 ),
295 ),
296 );
297
298 await tester.pump(const Duration(milliseconds: 100));
299
300 final RenderBox box = tester.renderObject(find.byType(AnimatedSize));
301 expect(box.size.width, equals(150.0));
302 });
303
304 testWidgets('does not run animation unnecessarily', (WidgetTester tester) async {
305 await tester.pumpWidget(
306 const Center(
307 child: AnimatedSize(
308 duration: Duration(milliseconds: 200),
309 child: SizedBox(
310 width: 100.0,
311 height: 100.0,
312 ),
313 ),
314 ),
315 );
316
317 for (int i = 0; i < 20; i++) {
318 final RenderAnimatedSize box = tester.renderObject(find.byType(AnimatedSize));
319 expect(box.size.width, 100.0);
320 expect(box.size.height, 100.0);
321 expect(box.state, RenderAnimatedSizeState.stable);
322 expect(box.isAnimating, false);
323 await tester.pump(const Duration(milliseconds: 10));
324 }
325 });
326
327 testWidgets('can set and update clipBehavior', (WidgetTester tester) async {
328 await tester.pumpWidget(
329 const Center(
330 child: AnimatedSize(
331 duration: Duration(milliseconds: 200),
332 child: SizedBox(
333 width: 100.0,
334 height: 100.0,
335 ),
336 ),
337 ),
338 );
339
340 // By default, clipBehavior should be Clip.hardEdge
341 final RenderAnimatedSize renderObject = tester.renderObject(find.byType(AnimatedSize));
342 expect(renderObject.clipBehavior, equals(Clip.hardEdge));
343
344 for (final Clip clip in Clip.values) {
345 await tester.pumpWidget(
346 Center(
347 child: AnimatedSize(
348 duration: const Duration(milliseconds: 200),
349 clipBehavior: clip,
350 child: const SizedBox(
351 width: 100.0,
352 height: 100.0,
353 ),
354 ),
355 ),
356 );
357 expect(renderObject.clipBehavior, clip);
358 }
359 });
360
361 testWidgets('works wrapped in IntrinsicHeight and Wrap', (WidgetTester tester) async {
362 Future<void> pumpWidget(Size size, [Duration? duration]) async {
363 return tester.pumpWidget(
364 Center(
365 child: IntrinsicHeight(
366 child: Wrap(
367 textDirection: TextDirection.ltr,
368 children: <Widget>[
369 AnimatedSize(
370 duration: const Duration(milliseconds: 200),
371 curve: Curves.easeInOutBack,
372 child: SizedBox(
373 width: size.width,
374 height: size.height,
375 ),
376 ),
377 ],
378 ),
379 ),
380 ),
381 duration: duration,
382 );
383 }
384
385 await pumpWidget(const Size(100, 100));
386 expect(tester.renderObject<RenderBox>(find.byType(IntrinsicHeight)).size, const Size(100, 100));
387
388 await pumpWidget(const Size(150, 200));
389 expect(tester.renderObject<RenderBox>(find.byType(IntrinsicHeight)).size, const Size(100, 100));
390
391 // Each pump triggers verification of dry layout.
392 for (int total = 0; total < 200; total += 10) {
393 await tester.pump(const Duration(milliseconds: 10));
394 }
395 expect(tester.renderObject<RenderBox>(find.byType(IntrinsicHeight)).size, const Size(150, 200));
396
397 // Change every pump
398 await pumpWidget(const Size(100, 100));
399 expect(tester.renderObject<RenderBox>(find.byType(IntrinsicHeight)).size, const Size(150, 200));
400
401 await pumpWidget(const Size(111, 111), const Duration(milliseconds: 10));
402 expect(tester.renderObject<RenderBox>(find.byType(IntrinsicHeight)).size, const Size(111, 111));
403
404 await pumpWidget(const Size(222, 222), const Duration(milliseconds: 10));
405 expect(tester.renderObject<RenderBox>(find.byType(IntrinsicHeight)).size, const Size(222, 222));
406 });
407
408 testWidgets('re-attach with interrupted animation', (WidgetTester tester) async {
409 const Key key1 = ValueKey<String>('key1');
410 const Key key2 = ValueKey<String>('key2');
411 late StateSetter setState;
412 Size childSize = const Size.square(100);
413 final Widget animatedSize = Center(
414 key: GlobalKey(debugLabel: 'animated size'),
415 // This SizedBox creates a relayout boundary so _cleanRelayoutBoundary
416 // does not mark the descendant render objects below the relayout boundary
417 // dirty.
418 child: SizedBox.fromSize(
419 size: const Size.square(200),
420 child: Center(
421 child: AnimatedSize(
422 duration: const Duration(seconds: 1),
423 child: StatefulBuilder(
424 builder: (BuildContext context, StateSetter stateSetter) {
425 setState = stateSetter;
426 return SizedBox.fromSize(size: childSize);
427 },
428 ),
429 ),
430 ),
431 ),
432 );
433
434 await tester.pumpWidget(
435 Directionality(
436 textDirection: TextDirection.ltr,
437 child: Row(
438 children: <Widget>[
439 SizedBox(
440 key: key1,
441 height: 200,
442 child: animatedSize,
443 ),
444 const SizedBox(
445 key: key2,
446 height: 200,
447 ),
448 ],
449 ),
450 )
451 );
452
453 setState(() {
454 childSize = const Size.square(150);
455 });
456 // Kick off the resizing animation.
457 await tester.pump();
458
459 // Immediately reparent the AnimatedSize subtree to a different parent
460 // with the same incoming constraints.
461 await tester.pumpWidget(
462 Directionality(
463 textDirection: TextDirection.ltr,
464 child: Row(
465 children: <Widget>[
466 const SizedBox(
467 key: key1,
468 height: 200,
469 ),
470 SizedBox(
471 key: key2,
472 height: 200,
473 child: animatedSize,
474 ),
475 ],
476 ),
477 ),
478 );
479
480 expect(
481 tester.renderObject<RenderBox>(find.byType(AnimatedSize)).size,
482 const Size.square(100),
483 );
484 await tester.pumpAndSettle();
485 // The animatedSize widget animates to the right size.
486 expect(
487 tester.renderObject<RenderBox>(find.byType(AnimatedSize)).size,
488 const Size.square(150),
489 );
490 });
491
492 testWidgets('disposes animation and controller', (WidgetTester tester) async {
493 await tester.pumpWidget(
494 const Center(
495 child: AnimatedSize(
496 duration: Duration(milliseconds: 200),
497 child: SizedBox(
498 width: 100.0,
499 height: 100.0,
500 ),
501 ),
502 ),
503 );
504
505 final RenderAnimatedSize box = tester.renderObject(find.byType(AnimatedSize));
506
507 await tester.pumpWidget(
508 const Center(),
509 );
510
511 expect(box.debugAnimation, isNotNull);
512 expect(box.debugAnimation!.isDisposed, isTrue);
513 expect(box.debugController, isNotNull);
514 expect(
515 () => box.debugController!.dispose(),
516 throwsA(isA<AssertionError>().having(
517 (AssertionError error) => error.message,
518 'message',
519 equalsIgnoringHashCodes(
520 'AnimationController.dispose() called more than once.\n'
521 'A given AnimationController cannot be disposed more than once.\n'
522 'The following AnimationController object was disposed multiple times:\n'
523 ' AnimationController#00000(⏮ 0.000; paused; DISPOSED)',
524 ),
525 )),
526 );
527 });
528 });
529}
530

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com