animated_switcher_test.dart 15.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/widgets.dart';
6
import 'package:flutter_test/flutter_test.dart';
7
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
8 9

void main() {
10
  testWidgetsWithLeakTracking('AnimatedSwitcher fades in a new child.', (WidgetTester tester) async {
11 12 13
    final UniqueKey containerOne = UniqueKey();
    final UniqueKey containerTwo = UniqueKey();
    final UniqueKey containerThree = UniqueKey();
14
    await tester.pumpWidget(
15
      AnimatedSwitcher(
16
        duration: const Duration(milliseconds: 100),
17
        child: Container(key: containerOne, color: const Color(0x00000000)),
18 19 20
      ),
    );

21
    expect(find.byType(FadeTransition), findsOneWidget);
22 23 24 25
    FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, equals(1.0));

    await tester.pumpWidget(
26
      AnimatedSwitcher(
27
        duration: const Duration(milliseconds: 100),
28
        child: Container(key: containerTwo, color: const Color(0xff000000)),
29 30 31 32
      ),
    );

    await tester.pump(const Duration(milliseconds: 50));
33
    expect(find.byType(FadeTransition), findsNWidgets(2));
34 35 36 37
    transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, equals(0.5));

    await tester.pumpWidget(
38
      AnimatedSwitcher(
39
        duration: const Duration(milliseconds: 100),
40
        child: Container(key: containerThree, color: const Color(0xffff0000)),
41 42 43 44 45
      ),
    );

    await tester.pump(const Duration(milliseconds: 10));
    transition = tester.widget(find.byType(FadeTransition).at(0));
46
    expect(transition.opacity.value, moreOrLessEquals(0.4, epsilon: 0.01));
47
    transition = tester.widget(find.byType(FadeTransition).at(1));
48
    expect(transition.opacity.value, moreOrLessEquals(0.4, epsilon: 0.01));
49
    transition = tester.widget(find.byType(FadeTransition).at(2));
50
    expect(transition.opacity.value, moreOrLessEquals(0.1, epsilon: 0.01));
51 52 53
    await tester.pumpAndSettle();
  });

54
  testWidgetsWithLeakTracking('AnimatedSwitcher can handle back-to-back changes.', (WidgetTester tester) async {
55 56 57 58 59 60
    final UniqueKey container1 = UniqueKey();
    final UniqueKey container2 = UniqueKey();
    final UniqueKey container3 = UniqueKey();
    await tester.pumpWidget(
      AnimatedSwitcher(
        duration: const Duration(milliseconds: 100),
61
        child: Container(key: container1),
62 63 64 65 66 67 68 69 70
      ),
    );
    expect(find.byKey(container1), findsOneWidget);
    expect(find.byKey(container2), findsNothing);
    expect(find.byKey(container3), findsNothing);

    await tester.pumpWidget(
      AnimatedSwitcher(
        duration: const Duration(milliseconds: 100),
71
        child: Container(key: container2),
72 73 74 75 76 77 78 79 80
      ),
    );
    expect(find.byKey(container1), findsOneWidget);
    expect(find.byKey(container2), findsOneWidget);
    expect(find.byKey(container3), findsNothing);

    await tester.pumpWidget(
      AnimatedSwitcher(
        duration: const Duration(milliseconds: 100),
81
        child: Container(key: container3),
82 83 84 85 86 87 88
      ),
    );
    expect(find.byKey(container1), findsOneWidget);
    expect(find.byKey(container2), findsNothing);
    expect(find.byKey(container3), findsOneWidget);
  });

89
  testWidgetsWithLeakTracking("AnimatedSwitcher doesn't transition in a new child of the same type.", (WidgetTester tester) async {
90
    await tester.pumpWidget(
91
      AnimatedSwitcher(
92
        duration: const Duration(milliseconds: 100),
93
        child: Container(color: const Color(0x00000000)),
94 95
      ),
    );
96

97
    expect(find.byType(FadeTransition), findsOneWidget);
98 99 100 101
    FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, equals(1.0));

    await tester.pumpWidget(
102
      AnimatedSwitcher(
103
        duration: const Duration(milliseconds: 100),
104
        child: Container(color: const Color(0xff000000)),
105 106 107 108
      ),
    );

    await tester.pump(const Duration(milliseconds: 50));
109 110
    expect(find.byType(FadeTransition), findsOneWidget);
    transition = tester.firstWidget(find.byType(FadeTransition));
111 112 113 114
    expect(transition.opacity.value, equals(1.0));
    await tester.pumpAndSettle();
  });

115
  testWidgetsWithLeakTracking('AnimatedSwitcher handles null children.', (WidgetTester tester) async {
116
    await tester.pumpWidget(
117
      const AnimatedSwitcher(
118
        duration: Duration(milliseconds: 100),
119 120 121 122 123 124
      ),
    );

    expect(find.byType(FadeTransition), findsNothing);

    await tester.pumpWidget(
125
      AnimatedSwitcher(
126
        duration: const Duration(milliseconds: 100),
127
        child: Container(color: const Color(0xff000000)),
128 129
      ),
    );
130 131 132 133 134 135 136

    await tester.pump(const Duration(milliseconds: 50));
    FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, equals(0.5));
    await tester.pumpAndSettle();

    await tester.pumpWidget(
137
      AnimatedSwitcher(
138
        duration: const Duration(milliseconds: 100),
139
        child: Container(color: const Color(0x00000000)),
140 141 142
      ),
    );

143
    expect(find.byType(FadeTransition), findsOneWidget);
144 145 146 147
    transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, equals(1.0));

    await tester.pumpWidget(
148
      const AnimatedSwitcher(
149
        duration: Duration(milliseconds: 100),
150 151 152
      ),
    );

153 154 155
    await tester.pump(const Duration(milliseconds: 50));
    transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, equals(0.5));
156 157

    await tester.pumpWidget(
158
      const AnimatedSwitcher(
159
        duration: Duration(milliseconds: 100),
160 161 162 163 164 165 166
      ),
    );

    await tester.pump(const Duration(milliseconds: 50));
    transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, equals(0.0));

167 168 169
    await tester.pumpAndSettle();
  });

170
  testWidgetsWithLeakTracking("AnimatedSwitcher doesn't start any animations after dispose.", (WidgetTester tester) async {
171
    await tester.pumpWidget(AnimatedSwitcher(
172
      duration: const Duration(milliseconds: 100),
173
      child: Container(color: const Color(0xff000000)),
174 175 176 177
    ));
    await tester.pump(const Duration(milliseconds: 50));

    // Change the widget tree in the middle of the animation.
178
    await tester.pumpWidget(Container(color: const Color(0xffff0000)));
179
    expect(await tester.pumpAndSettle(), equals(1));
180
  });
181

182
  testWidgetsWithLeakTracking('AnimatedSwitcher uses custom layout.', (WidgetTester tester) async {
183
    Widget newLayoutBuilder(Widget? currentChild, List<Widget> previousChildren) {
184
      return Column(
185 186 187 188
        children: <Widget>[
          ...previousChildren,
          if (currentChild != null) currentChild,
        ],
189 190 191 192
      );
    }

    await tester.pumpWidget(
193
      AnimatedSwitcher(
194 195
        duration: const Duration(milliseconds: 100),
        layoutBuilder: newLayoutBuilder,
196
        child: Container(color: const Color(0x00000000)),
197 198 199 200 201 202
      ),
    );

    expect(find.byType(Column), findsOneWidget);
  });

203
  testWidgetsWithLeakTracking('AnimatedSwitcher uses custom transitions.', (WidgetTester tester) async {
204 205 206 207 208 209 210
    late List<Widget> foundChildren;
    Widget newLayoutBuilder(Widget? currentChild, List<Widget> previousChildren) {
      foundChildren = <Widget>[
        if (currentChild != null) currentChild,
        ...previousChildren,
      ];
      return Column(children: foundChildren);
211 212 213
    }

    Widget newTransitionBuilder(Widget child, Animation<double> animation) {
214
      return SizeTransition(
215 216 217 218 219 220
        sizeFactor: animation,
        child: child,
      );
    }

    await tester.pumpWidget(
221
      Directionality(
222
        textDirection: TextDirection.rtl,
223
        child: AnimatedSwitcher(
224 225 226
          duration: const Duration(milliseconds: 100),
          layoutBuilder: newLayoutBuilder,
          transitionBuilder: newTransitionBuilder,
227
          child: Container(color: const Color(0x00000000)),
228 229 230 231 232
        ),
      ),
    );

    expect(find.byType(Column), findsOneWidget);
233
    for (final Widget child in foundChildren) {
Dan Field's avatar
Dan Field committed
234
      expect(child, isA<KeyedSubtree>());
235 236 237
    }

    await tester.pumpWidget(
238
      Directionality(
239
        textDirection: TextDirection.rtl,
240
        child: AnimatedSwitcher(
241 242 243 244 245 246 247 248
          duration: const Duration(milliseconds: 100),
          layoutBuilder: newLayoutBuilder,
          transitionBuilder: newTransitionBuilder,
        ),
      ),
    );
    await tester.pump(const Duration(milliseconds: 50));

249
    for (final Widget child in foundChildren) {
Dan Field's avatar
Dan Field committed
250
      expect(child, isA<KeyedSubtree>());
251
      expect(
252
        find.descendant(of: find.byWidget(child), matching: find.byType(SizeTransition)),
253 254 255 256
        findsOneWidget,
      );
    }
  });
257

258
  testWidgetsWithLeakTracking("AnimatedSwitcher doesn't reset state of the children in transitions.", (WidgetTester tester) async {
259 260 261
    final UniqueKey statefulOne = UniqueKey();
    final UniqueKey statefulTwo = UniqueKey();
    final UniqueKey statefulThree = UniqueKey();
262 263 264 265

    StatefulTestState.generation = 0;

    await tester.pumpWidget(
266
      AnimatedSwitcher(
267
        duration: const Duration(milliseconds: 100),
268
        child: StatefulTest(key: statefulOne),
269 270 271 272 273 274 275 276 277
      ),
    );

    expect(find.byType(FadeTransition), findsOneWidget);
    FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, equals(1.0));
    expect(StatefulTestState.generation, equals(1));

    await tester.pumpWidget(
278
      AnimatedSwitcher(
279
        duration: const Duration(milliseconds: 100),
280
        child: StatefulTest(key: statefulTwo),
281 282 283 284 285 286 287 288 289 290
      ),
    );

    await tester.pump(const Duration(milliseconds: 50));
    expect(find.byType(FadeTransition), findsNWidgets(2));
    transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, equals(0.5));
    expect(StatefulTestState.generation, equals(2));

    await tester.pumpWidget(
291
      AnimatedSwitcher(
292
        duration: const Duration(milliseconds: 100),
293
        child: StatefulTest(key: statefulThree),
294 295 296 297 298 299
      ),
    );

    await tester.pump(const Duration(milliseconds: 10));
    expect(StatefulTestState.generation, equals(3));
    transition = tester.widget(find.byType(FadeTransition).at(0));
300
    expect(transition.opacity.value, moreOrLessEquals(0.4, epsilon: 0.01));
301
    transition = tester.widget(find.byType(FadeTransition).at(1));
302
    expect(transition.opacity.value, moreOrLessEquals(0.4, epsilon: 0.01));
303
    transition = tester.widget(find.byType(FadeTransition).at(2));
304
    expect(transition.opacity.value, moreOrLessEquals(0.1, epsilon: 0.01));
305 306 307 308
    await tester.pumpAndSettle();
    expect(StatefulTestState.generation, equals(3));
  });

309
  testWidgetsWithLeakTracking('AnimatedSwitcher updates widgets without animating if they are isomorphic.', (WidgetTester tester) async {
310
    Future<void> pumpChild(Widget child) async {
311
      return tester.pumpWidget(
312
        Directionality(
313
          textDirection: TextDirection.rtl,
314
          child: AnimatedSwitcher(
315
            duration: const Duration(milliseconds: 100),
316
            child: child,
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
          ),
        ),
      );
    }

    await pumpChild(const Text('1'));
    await tester.pump(const Duration(milliseconds: 10));
    FadeTransition transition = tester.widget(find.byType(FadeTransition).first);
    expect(transition.opacity.value, equals(1.0));
    expect(find.text('1'), findsOneWidget);
    expect(find.text('2'), findsNothing);
    await pumpChild(const Text('2'));
    transition = tester.widget(find.byType(FadeTransition).first);
    await tester.pump(const Duration(milliseconds: 20));
    expect(transition.opacity.value, equals(1.0));
    expect(find.text('1'), findsNothing);
    expect(find.text('2'), findsOneWidget);
  });

336
  testWidgetsWithLeakTracking('AnimatedSwitcher updates previous child transitions if the transitionBuilder changes.', (WidgetTester tester) async {
337 338 339
    final UniqueKey containerOne = UniqueKey();
    final UniqueKey containerTwo = UniqueKey();
    final UniqueKey containerThree = UniqueKey();
340

341 342 343 344 345 346 347
    late List<Widget> foundChildren;
    Widget newLayoutBuilder(Widget? currentChild, List<Widget> previousChildren) {
      foundChildren = <Widget>[
        if (currentChild != null) currentChild,
        ...previousChildren,
      ];
      return Column(children: foundChildren);
348 349 350 351
    }

    // Insert three unique children so that we have some previous children.
    await tester.pumpWidget(
352
      AnimatedSwitcher(
353 354
        duration: const Duration(milliseconds: 100),
        layoutBuilder: newLayoutBuilder,
355
        child: Container(key: containerOne, color: const Color(0xFFFF0000)),
356 357 358 359 360 361
      ),
    );

    await tester.pump(const Duration(milliseconds: 10));

    await tester.pumpWidget(
362
      AnimatedSwitcher(
363 364
        duration: const Duration(milliseconds: 100),
        layoutBuilder: newLayoutBuilder,
365
        child: Container(key: containerTwo, color: const Color(0xFF00FF00)),
366 367 368 369 370 371
      ),
    );

    await tester.pump(const Duration(milliseconds: 10));

    await tester.pumpWidget(
372
      AnimatedSwitcher(
373 374
        duration: const Duration(milliseconds: 100),
        layoutBuilder: newLayoutBuilder,
375
        child: Container(key: containerThree, color: const Color(0xFF0000FF)),
376 377 378 379 380 381
      ),
    );

    await tester.pump(const Duration(milliseconds: 10));

    expect(foundChildren.length, equals(3));
382
    for (final Widget child in foundChildren) {
Dan Field's avatar
Dan Field committed
383
      expect(child, isA<KeyedSubtree>());
384 385 386 387 388 389 390
      expect(
        find.descendant(of: find.byWidget(child), matching: find.byType(FadeTransition)),
        findsOneWidget,
      );
    }

    Widget newTransitionBuilder(Widget child, Animation<double> animation) {
391
      return ScaleTransition(
392 393 394 395 396 397 398 399
        scale: animation,
        child: child,
      );
    }

    // Now set a new transition builder and make sure all the previous
    // transitions are replaced.
    await tester.pumpWidget(
400
      AnimatedSwitcher(
401 402 403
        duration: const Duration(milliseconds: 100),
        layoutBuilder: newLayoutBuilder,
        transitionBuilder: newTransitionBuilder,
404
        child: Container(color: const Color(0x00000000)),
405 406 407 408 409 410
      ),
    );

    await tester.pump(const Duration(milliseconds: 10));

    expect(foundChildren.length, equals(3));
411
    for (final Widget child in foundChildren) {
Dan Field's avatar
Dan Field committed
412
      expect(child, isA<KeyedSubtree>());
413 414 415 416 417 418
      expect(
        find.descendant(of: find.byWidget(child), matching: find.byType(ScaleTransition)),
        findsOneWidget,
      );
    }
  });
419

420
  testWidgetsWithLeakTracking('AnimatedSwitcher does not duplicate animations if the same child is entered twice.', (WidgetTester tester) async {
421
    Future<void> pumpChild(Widget child) async {
422
      return tester.pumpWidget(
423 424 425 426 427 428
        Directionality(
          textDirection: TextDirection.ltr,
          child: AnimatedSwitcher(
            duration: const Duration(milliseconds: 1000),
            child: child,
          ),
429 430 431
        ),
      );
    }
432 433 434
    await pumpChild(const Text('1', key: Key('1')));
    await pumpChild(const Text('2', key: Key('2')));
    await pumpChild(const Text('1', key: Key('1')));
435
    await tester.pump(const Duration(milliseconds: 1000));
436
    expect(find.text('1'), findsOneWidget);
437
  });
438 439 440
}

class StatefulTest extends StatefulWidget {
441
  const StatefulTest({super.key});
442 443

  @override
444
  StatefulTestState createState() => StatefulTestState();
445 446 447 448 449 450 451 452 453 454 455 456 457
}

class StatefulTestState extends State<StatefulTest> {
  StatefulTestState();
  static int generation = 0;

  @override
  void initState() {
    super.initState();
    generation++;
  }

  @override
458
  Widget build(BuildContext context) => Container();
459
}