// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; class TestPaintingContext implements PaintingContext { final List<Invocation> invocations = <Invocation>[]; @override void noSuchMethod(Invocation invocation) { invocations.add(invocation); } } void main() { group('AnimatedSize', () { testWidgets('animates forwards then backwards with stable-sized children', (WidgetTester tester) async { await tester.pumpWidget( Center( child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: const SizedBox( width: 100.0, height: 100.0, ), ), ), ); RenderBox box = tester.renderObject(find.byType(AnimatedSize)); expect(box.size.width, equals(100.0)); expect(box.size.height, equals(100.0)); await tester.pumpWidget( Center( child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: const SizedBox( width: 200.0, height: 200.0, ), ), ), ); await tester.pump(const Duration(milliseconds: 100)); box = tester.renderObject(find.byType(AnimatedSize)); expect(box.size.width, equals(150.0)); expect(box.size.height, equals(150.0)); TestPaintingContext context = TestPaintingContext(); box.paint(context, Offset.zero); expect(context.invocations.first.memberName, equals(#pushClipRect)); await tester.pump(const Duration(milliseconds: 100)); box = tester.renderObject(find.byType(AnimatedSize)); expect(box.size.width, equals(200.0)); expect(box.size.height, equals(200.0)); await tester.pumpWidget( Center( child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: const SizedBox( width: 100.0, height: 100.0, ), ), ), ); await tester.pump(const Duration(milliseconds: 100)); box = tester.renderObject(find.byType(AnimatedSize)); expect(box.size.width, equals(150.0)); expect(box.size.height, equals(150.0)); context = TestPaintingContext(); box.paint(context, Offset.zero); expect(context.invocations.first.memberName, equals(#paintChild)); await tester.pump(const Duration(milliseconds: 100)); box = tester.renderObject(find.byType(AnimatedSize)); expect(box.size.width, equals(100.0)); expect(box.size.height, equals(100.0)); }); testWidgets('clamps animated size to constraints', (WidgetTester tester) async { await tester.pumpWidget( Center( child: SizedBox ( width: 100.0, height: 100.0, child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: const SizedBox( width: 100.0, height: 100.0, ), ), ), ), ); RenderBox box = tester.renderObject(find.byType(AnimatedSize)); expect(box.size.width, equals(100.0)); expect(box.size.height, equals(100.0)); // Attempt to animate beyond the outer SizedBox. await tester.pumpWidget( Center( child: SizedBox ( width: 100.0, height: 100.0, child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: const SizedBox( width: 200.0, height: 200.0, ), ), ), ), ); // Verify that animated size is the same as the outer SizedBox. await tester.pump(const Duration(milliseconds: 100)); box = tester.renderObject(find.byType(AnimatedSize)); expect(box.size.width, equals(100.0)); expect(box.size.height, equals(100.0)); }); testWidgets('tracks unstable child, then resumes animation when child stabilizes', (WidgetTester tester) async { Future<void> pumpMillis(int millis) async { await tester.pump(Duration(milliseconds: millis)); } void verify({ double size, RenderAnimatedSizeState state }) { assert(size != null || state != null); final RenderAnimatedSize box = tester.renderObject(find.byType(AnimatedSize)); if (size != null) { expect(box.size.width, size); expect(box.size.height, size); } if (state != null) { expect(box.state, state); } } await tester.pumpWidget( Center( child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: AnimatedContainer( duration: const Duration(milliseconds: 100), width: 100.0, height: 100.0, ), ), ), ); verify(size: 100.0, state: RenderAnimatedSizeState.stable); // Animate child size from 100 to 200 slowly (100ms). await tester.pumpWidget( Center( child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: AnimatedContainer( duration: const Duration(milliseconds: 100), width: 200.0, height: 200.0, ), ), ), ); // Make sure animation proceeds at child's pace, with AnimatedSize // tightly tracking the child's size. verify(state: RenderAnimatedSizeState.stable); await pumpMillis(1); // register change verify(state: RenderAnimatedSizeState.changed); await pumpMillis(49); verify(size: 150.0, state: RenderAnimatedSizeState.unstable); await pumpMillis(50); verify(size: 200.0, state: RenderAnimatedSizeState.unstable); // Stabilize size await pumpMillis(50); verify(size: 200.0, state: RenderAnimatedSizeState.stable); // Quickly (in 1ms) change size back to 100 await tester.pumpWidget( Center( child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: AnimatedContainer( duration: const Duration(milliseconds: 1), width: 100.0, height: 100.0, ), ), ), ); verify(size: 200.0, state: RenderAnimatedSizeState.stable); await pumpMillis(1); // register change verify(state: RenderAnimatedSizeState.changed); await pumpMillis(100); verify(size: 150.0, state: RenderAnimatedSizeState.stable); await pumpMillis(100); verify(size: 100.0, state: RenderAnimatedSizeState.stable); }); testWidgets('resyncs its animation controller', (WidgetTester tester) async { await tester.pumpWidget( const Center( child: AnimatedSize( duration: Duration(milliseconds: 200), vsync: TestVSync(), child: SizedBox( width: 100.0, height: 100.0, ), ), ), ); await tester.pumpWidget( Center( child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: const SizedBox( width: 200.0, height: 100.0, ), ), ), ); await tester.pump(const Duration(milliseconds: 100)); final RenderBox box = tester.renderObject(find.byType(AnimatedSize)); expect(box.size.width, equals(150.0)); }); testWidgets('does not run animation unnecessarily', (WidgetTester tester) async { await tester.pumpWidget( Center( child: AnimatedSize( duration: const Duration(milliseconds: 200), vsync: tester, child: const SizedBox( width: 100.0, height: 100.0, ), ), ), ); for (int i = 0; i < 20; i++) { final RenderAnimatedSize box = tester.renderObject(find.byType(AnimatedSize)); expect(box.size.width, 100.0); expect(box.size.height, 100.0); expect(box.state, RenderAnimatedSizeState.stable); expect(box.isAnimating, false); await tester.pump(const Duration(milliseconds: 10)); } }); }); }