// 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/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('Animates forward when built', (WidgetTester tester) async { final List<int> values = <int>[]; int endCount = 0; await tester.pumpWidget( TweenAnimationBuilder<int>( duration: const Duration(seconds: 1), tween: IntTween(begin: 10, end: 110), builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, onEnd: () { endCount++; }, ), ); expect(endCount, 0); expect(values, <int>[10]); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[10, 60]); await tester.pump(const Duration(milliseconds: 501)); expect(endCount, 1); expect(values, <int>[10, 60, 110]); await tester.pump(const Duration(milliseconds: 500)); expect(endCount, 1); expect(values, <int>[10, 60, 110]); }); testWidgets('No initial animation when begin=null', (WidgetTester tester) async { final List<int> values = <int>[]; int endCount = 0; await tester.pumpWidget( TweenAnimationBuilder<int>( duration: const Duration(seconds: 1), tween: IntTween(end: 100), builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, onEnd: () { endCount++; }, ), ); expect(endCount, 0); expect(values, <int>[100]); await tester.pump(const Duration(seconds: 2)); expect(endCount, 0); expect(values, <int>[100]); }); testWidgets('No initial animation when begin=end', (WidgetTester tester) async { final List<int> values = <int>[]; int endCount = 0; await tester.pumpWidget( TweenAnimationBuilder<int>( duration: const Duration(seconds: 1), tween: IntTween(begin: 100, end: 100), builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, onEnd: () { endCount++; }, ), ); expect(endCount, 0); expect(values, <int>[100]); await tester.pump(const Duration(seconds: 2)); expect(endCount, 0); expect(values, <int>[100]); }); testWidgets('Replace tween animates new tween', (WidgetTester tester) async { final List<int> values = <int>[]; Widget buildWidget({required IntTween tween}) { return TweenAnimationBuilder<int>( duration: const Duration(seconds: 1), tween: tween, builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, ); } await tester.pumpWidget(buildWidget(tween: IntTween(begin: 0, end: 100))); expect(values, <int>[0]); await tester.pump(const Duration(seconds: 2)); // finish first animation. expect(values, <int>[0, 100]); await tester.pumpWidget(buildWidget(tween: IntTween(begin: 100, end: 200))); expect(values, <int>[0, 100, 100]); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 100, 100, 150]); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 100, 100, 150, 200]); }); testWidgets('Curve is respected', (WidgetTester tester) async { final List<int> values = <int>[]; Widget buildWidget({required IntTween tween, required Curve curve}) { return TweenAnimationBuilder<int>( duration: const Duration(seconds: 1), tween: tween, curve: curve, builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, ); } await tester.pumpWidget(buildWidget(tween: IntTween(begin: 0, end: 100), curve: Curves.easeInExpo)); expect(values, <int>[0]); await tester.pump(const Duration(milliseconds: 500)); expect(values.last, lessThan(50)); expect(values.last, greaterThan(0)); await tester.pump(const Duration(seconds: 2)); // finish animation. values.clear(); // Update curve (and tween to re-trigger animation). await tester.pumpWidget(buildWidget(tween: IntTween(begin: 100, end: 200), curve: Curves.linear)); expect(values, <int>[100]); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[100, 150]); }); testWidgets('Duration is respected', (WidgetTester tester) async { final List<int> values = <int>[]; Widget buildWidget({required IntTween tween, required Duration duration}) { return TweenAnimationBuilder<int>( tween: tween, duration: duration, builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, ); } await tester.pumpWidget(buildWidget(tween: IntTween(begin: 0, end: 100), duration: const Duration(seconds: 1))); expect(values, <int>[0]); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 50]); await tester.pump(const Duration(seconds: 2)); // finish animation. values.clear(); // Update duration (and tween to re-trigger animation). await tester.pumpWidget(buildWidget(tween: IntTween(begin: 100, end: 200), duration: const Duration(seconds: 2))); expect(values, <int>[100]); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[100, 125]); }); testWidgets('Child is integrated into tree', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: TweenAnimationBuilder<int>( tween: IntTween(begin: 0, end: 100), duration: const Duration(seconds: 1), builder: (BuildContext context, int i, Widget? child) { return child!; }, child: const Text('Hello World'), ), ), ); expect(find.text('Hello World'), findsOneWidget); }); group('Change tween gapless while', () { testWidgets('running forward', (WidgetTester tester) async { final List<int> values = <int>[]; Widget buildWidget({required IntTween tween}) { return TweenAnimationBuilder<int>( tween: tween, duration: const Duration(seconds: 1), builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, ); } await tester.pumpWidget(buildWidget( tween: IntTween(begin: 0, end: 100), )); expect(values, <int>[0]); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 50]); // Change tween await tester.pumpWidget(buildWidget( tween: IntTween(begin: 200, end: 300), )); expect(values, <int>[0, 50, 50]); // gapless: animation continues where it left off. await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 50, 50, 175]); // 175 = halfway between 50 and new target 300. // Run animation to end await tester.pump(const Duration(seconds: 2)); expect(values, <int>[0, 50, 50, 175, 300]); values.clear(); }); testWidgets('running forward and then reverse with same tween instance', (WidgetTester tester) async { final List<int> values = <int>[]; Widget buildWidget({required IntTween tween}) { return TweenAnimationBuilder<int>( tween: tween, duration: const Duration(seconds: 1), builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, ); } final IntTween tween1 = IntTween(begin: 0, end: 100); final IntTween tween2 = IntTween(begin: 200, end: 300); await tester.pumpWidget(buildWidget( tween: tween1, )); await tester.pump(const Duration(milliseconds: 500)); await tester.pumpWidget(buildWidget( tween: tween2, )); await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(seconds: 2)); expect(values, <int>[0, 50, 50, 175, 300]); values.clear(); }); }); testWidgets('Changing tween while gapless tween change is in progress', (WidgetTester tester) async { final List<int> values = <int>[]; Widget buildWidget({required IntTween tween}) { return TweenAnimationBuilder<int>( tween: tween, duration: const Duration(seconds: 1), builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, ); } final IntTween tween1 = IntTween(begin: 0, end: 100); final IntTween tween2 = IntTween(begin: 200, end: 300); final IntTween tween3 = IntTween(begin: 400, end: 501); await tester.pumpWidget(buildWidget( tween: tween1, )); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 50]); values.clear(); // Change tween await tester.pumpWidget(buildWidget( tween: tween2, )); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[50, 175]); values.clear(); await tester.pumpWidget(buildWidget( tween: tween3, )); await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[175, 338, 501]); }); testWidgets('Changing curve while no animation is running does not trigger animation', (WidgetTester tester) async { final List<int> values = <int>[]; Widget buildWidget({required Curve curve}) { return TweenAnimationBuilder<int>( tween: IntTween(begin: 0, end: 100), curve: curve, duration: const Duration(seconds: 1), builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, ); } await tester.pumpWidget(buildWidget( curve: Curves.linear, )); await tester.pump(const Duration(seconds: 2)); expect(values, <int>[0, 100]); values.clear(); await tester.pumpWidget(buildWidget( curve: Curves.easeInExpo, )); expect(values, <int>[100]); await tester.pump(const Duration(seconds: 2)); expect(values, <int>[100]); }); testWidgets('Setting same tween and direction does not trigger animation', (WidgetTester tester) async { final List<int> values = <int>[]; Widget buildWidget({required IntTween tween}) { return TweenAnimationBuilder<int>( tween: tween, duration: const Duration(seconds: 1), builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, ); } await tester.pumpWidget(buildWidget( tween: IntTween(begin: 0, end: 100), )); await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 50, 100]); values.clear(); await tester.pumpWidget(buildWidget( tween: IntTween(begin: 0, end: 100), )); await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500)); expect(values, everyElement(100)); }); testWidgets('Setting same tween and direction while gapless animation is in progress works', (WidgetTester tester) async { final List<int> values = <int>[]; Widget buildWidget({required IntTween tween}) { return TweenAnimationBuilder<int>( tween: tween, duration: const Duration(seconds: 1), builder: (BuildContext context, int i, Widget? child) { values.add(i); return const Placeholder(); }, ); } await tester.pumpWidget(buildWidget( tween: IntTween(begin: 0, end: 100), )); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 50]); await tester.pumpWidget(buildWidget( tween: IntTween(begin: 200, end: 300), )); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 50, 50, 175]); await tester.pumpWidget(buildWidget( tween: IntTween(begin: 200, end: 300), )); expect(values, <int>[0, 50, 50, 175, 175]); await tester.pump(const Duration(milliseconds: 500)); expect(values, <int>[0, 50, 50, 175, 175, 300]); values.clear(); await tester.pump(const Duration(seconds: 2)); expect(values, everyElement(300)); }); testWidgets('Works with nullable tweens', (WidgetTester tester) async { final List<Size?> values = <Size?>[]; await tester.pumpWidget( TweenAnimationBuilder<Size?>( duration: const Duration(seconds: 1), tween: SizeTween(end: const Size(10,10)), builder: (BuildContext context, Size? s, Widget? child) { values.add(s); return const Placeholder(); }, ), ); expect(values, <Size>[const Size(10,10)]); await tester.pump(const Duration(seconds: 2)); expect(values, <Size>[const Size(10,10)]); }); }