animated_switcher_test.dart 15.1 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 7 8
import 'package:flutter_test/flutter_test.dart';

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    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(
136
      AnimatedSwitcher(
137
        duration: const Duration(milliseconds: 100),
138
        child: Container(color: const Color(0x00000000)),
139 140 141
      ),
    );

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    StatefulTestState.generation = 0;

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

    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(
277
      AnimatedSwitcher(
278
        duration: const Duration(milliseconds: 100),
279
        child: StatefulTest(key: statefulTwo),
280 281 282 283 284 285 286 287 288 289
      ),
    );

    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(
290
      AnimatedSwitcher(
291
        duration: const Duration(milliseconds: 100),
292
        child: StatefulTest(key: statefulThree),
293 294 295 296 297 298
      ),
    );

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

  testWidgets('AnimatedSwitcher updates widgets without animating if they are isomorphic.', (WidgetTester tester) async {
309
    Future<void> pumpChild(Widget child) async {
310
      return tester.pumpWidget(
311
        Directionality(
312
          textDirection: TextDirection.rtl,
313
          child: AnimatedSwitcher(
314
            duration: const Duration(milliseconds: 100),
315
            child: child,
316 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);
  });

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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