animated_icons_private_test.dart.tmpl 16.2 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 6
// ignore_for_file: public_member_api_docs

7
// This is the test for the private implementation of animated icons.
8
// To make the private API accessible from the test we do not import the
9 10 11 12 13 14
// material material_animated_icons library, but instead, this test file is an
// implementation of that library, using some of the parts of the real
// material_animated_icons, this give the test access to the private APIs.
library material_animated_icons;

import 'dart:math' as math show pi;
15
import 'dart:ui' show lerpDouble, Offset;
16 17
import 'dart:ui' as ui show Paint, Path, Canvas;

18
import 'package:flutter/widgets.dart';
19
import 'package:flutter_test/flutter_test.dart';
20

21 22
part 'src/material/animated_icons/animated_icons.dart';
part 'src/material/animated_icons/animated_icons_data.dart';
23 24 25 26

// We have to import all the generated files in the material library to avoid
// analysis errors (as the generated constants are all referenced in the
// animated_icons library).
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
part 'src/material/animated_icons/data/add_event.g.dart';
part 'src/material/animated_icons/data/arrow_menu.g.dart';
part 'src/material/animated_icons/data/close_menu.g.dart';
part 'src/material/animated_icons/data/ellipsis_search.g.dart';
part 'src/material/animated_icons/data/event_add.g.dart';
part 'src/material/animated_icons/data/home_menu.g.dart';
part 'src/material/animated_icons/data/list_view.g.dart';
part 'src/material/animated_icons/data/menu_arrow.g.dart';
part 'src/material/animated_icons/data/menu_close.g.dart';
part 'src/material/animated_icons/data/menu_home.g.dart';
part 'src/material/animated_icons/data/pause_play.g.dart';
part 'src/material/animated_icons/data/play_pause.g.dart';
part 'src/material/animated_icons/data/search_ellipsis.g.dart';
part 'src/material/animated_icons/data/view_list.g.dart';

class MockCanvas extends Mock implements Canvas {}
class MockPath extends Mock implements Path {}

void main() {
46 47
  group('Interpolate points', () {
    test('- single point', () {
48 49
      const List<Offset> points = <Offset>[
        Offset(25.0, 1.0),
50 51 52 53 54 55 56
      ];
      expect(_interpolate(points, 0.0, Offset.lerp), const Offset(25.0, 1.0));
      expect(_interpolate(points, 0.5, Offset.lerp), const Offset(25.0, 1.0));
      expect(_interpolate(points, 1.0, Offset.lerp), const Offset(25.0, 1.0));
    });

    test('- two points', () {
57 58 59
      const List<Offset> points = <Offset>[
        Offset(25.0, 1.0),
        Offset(12.0, 12.0),
60 61 62 63 64 65 66
      ];
      expect(_interpolate(points, 0.0, Offset.lerp), const Offset(25.0, 1.0));
      expect(_interpolate(points, 0.5, Offset.lerp), const Offset(18.5, 6.5));
      expect(_interpolate(points, 1.0, Offset.lerp), const Offset(12.0, 12.0));
    });

    test('- three points', () {
67 68 69 70
      const List<Offset> points = <Offset>[
        Offset(25.0, 1.0),
        Offset(12.0, 12.0),
        Offset(23.0, 9.0),
71 72 73 74 75 76 77 78 79 80
      ];
      expect(_interpolate(points, 0.0, Offset.lerp), const Offset(25.0, 1.0));
      expect(_interpolate(points, 0.25, Offset.lerp), const Offset(18.5, 6.5));
      expect(_interpolate(points, 0.5, Offset.lerp), const Offset(12.0, 12.0));
      expect(_interpolate(points, 0.75, Offset.lerp), const Offset(17.5, 10.5));
      expect(_interpolate(points, 1.0, Offset.lerp), const Offset(23.0, 9.0));
    });
  });

  group('_AnimatedIconPainter', () {
81
    const Size size = Size(48.0, 48.0);
82 83 84 85
    late MockPath mockPath;
    late MockCanvas mockCanvas;
    late List<MockPath> generatedPaths;
    late _UiPathFactory pathFactory;
86 87

    setUp(() {
88 89 90 91 92 93 94
      generatedPaths = <MockPath>[];
      mockCanvas = MockCanvas();
      mockPath = MockPath();
      pathFactory = () {
        generatedPaths.add(mockPath);
        return mockPath;
      };
95 96 97
    });

    test('progress 0', () {
98
      final _AnimatedIconPainter painter = _AnimatedIconPainter(
99
        paths: _movingBar.paths,
100 101 102 103
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
104
        uiPathFactory: pathFactory,
105
      );
106
      painter.paint(mockCanvas, size);
107 108
      expect(generatedPaths.length, 1);

109 110 111 112 113 114 115
      generatedPaths[0].verifyCallsInOrder(<MockCall>[
        MockCall('moveTo', <dynamic>[0.0, 0.0]),
        MockCall('lineTo', <dynamic>[48.0, 0.0]),
        MockCall('lineTo', <dynamic>[48.0, 10.0]),
        MockCall('lineTo', <dynamic>[0.0, 10.0]),
        MockCall('lineTo', <dynamic>[0.0, 0.0]),
        MockCall('close'),
116 117 118 119
      ]);
    });

    test('progress 1', () {
120
      final _AnimatedIconPainter painter = _AnimatedIconPainter(
121
        paths: _movingBar.paths,
122 123 124 125
        progress: const AlwaysStoppedAnimation<double>(1.0),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
126
        uiPathFactory: pathFactory,
127
      );
128
      painter.paint(mockCanvas, size);
129 130
      expect(generatedPaths.length, 1);

131 132 133 134 135 136 137
      generatedPaths[0].verifyCallsInOrder(<MockCall>[
        MockCall('moveTo', <dynamic>[0.0, 38.0]),
        MockCall('lineTo', <dynamic>[48.0, 38.0]),
        MockCall('lineTo', <dynamic>[48.0, 48.0]),
        MockCall('lineTo', <dynamic>[0.0, 48.0]),
        MockCall('lineTo', <dynamic>[0.0, 38.0]),
        MockCall('close'),
138 139 140 141
      ]);
    });

    test('clamped progress', () {
142
      final _AnimatedIconPainter painter = _AnimatedIconPainter(
143
        paths: _movingBar.paths,
144 145 146 147
        progress: const AlwaysStoppedAnimation<double>(1.5),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
148
        uiPathFactory: pathFactory,
149
      );
150
      painter.paint(mockCanvas, size);
151 152
      expect(generatedPaths.length, 1);

153 154 155 156 157 158 159
      generatedPaths[0].verifyCallsInOrder(<MockCall>[
        MockCall('moveTo', <dynamic>[0.0, 38.0]),
        MockCall('lineTo', <dynamic>[48.0, 38.0]),
        MockCall('lineTo', <dynamic>[48.0, 48.0]),
        MockCall('lineTo', <dynamic>[0.0, 48.0]),
        MockCall('lineTo', <dynamic>[0.0, 38.0]),
        MockCall('close'),
160 161 162 163
      ]);
    });

    test('scale', () {
164
      final _AnimatedIconPainter painter = _AnimatedIconPainter(
165
        paths: _movingBar.paths,
166 167 168 169
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFF00FF00),
        scale: 0.5,
        shouldMirror: false,
170
        uiPathFactory: pathFactory,
171
      );
172
      painter.paint(mockCanvas, size);
173 174 175 176
      mockCanvas.verifyCallsInOrder(<MockCall>[
        MockCall('scale', <dynamic>[0.5, 0.5]),
        MockCall.any('drawPath'),
      ]);
177 178 179
    });

    test('mirror', () {
180
      final _AnimatedIconPainter painter = _AnimatedIconPainter(
181
        paths: _movingBar.paths,
182 183 184 185
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: true,
186
        uiPathFactory: pathFactory,
187
      );
188
      painter.paint(mockCanvas, size);
189 190 191 192 193
      mockCanvas.verifyCallsInOrder(<MockCall>[
        MockCall('scale', <dynamic>[1.0, 1.0]),
        MockCall('rotate', <dynamic>[math.pi]),
        MockCall('translate', <dynamic>[-48.0, -48.0]),
        MockCall.any('drawPath'),
194 195 196 197
      ]);
    });

    test('interpolated frame', () {
198
      final _AnimatedIconPainter painter = _AnimatedIconPainter(
199
        paths: _movingBar.paths,
200 201 202 203
        progress: const AlwaysStoppedAnimation<double>(0.5),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
204
        uiPathFactory: pathFactory,
205
      );
206
      painter.paint(mockCanvas, size);
207 208
      expect(generatedPaths.length, 1);

209 210 211 212 213 214 215
      generatedPaths[0].verifyCallsInOrder(<MockCall>[
        MockCall('moveTo', <dynamic>[0.0, 19.0]),
        MockCall('lineTo', <dynamic>[48.0, 19.0]),
        MockCall('lineTo', <dynamic>[48.0, 29.0]),
        MockCall('lineTo', <dynamic>[0.0, 29.0]),
        MockCall('lineTo', <dynamic>[0.0, 19.0]),
        MockCall('close'),
216 217 218 219
      ]);
    });

    test('curved frame', () {
220
      final _AnimatedIconPainter painter = _AnimatedIconPainter(
221
        paths: _bow.paths,
222 223 224 225
        progress: const AlwaysStoppedAnimation<double>(1.0),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
226
        uiPathFactory: pathFactory,
227
      );
228
      painter.paint(mockCanvas, size);
229 230
      expect(generatedPaths.length, 1);

231 232 233 234 235
      generatedPaths[0].verifyCallsInOrder(<MockCall>[
        MockCall('moveTo', <dynamic>[0.0, 24.0]),
        MockCall('cubicTo', <dynamic>[16.0, 48.0, 32.0, 48.0, 48.0, 24.0]),
        MockCall('lineTo', <dynamic>[0.0, 24.0]),
        MockCall('close'),
236 237 238 239
      ]);
    });

    test('interpolated curved frame', () {
240
      final _AnimatedIconPainter painter = _AnimatedIconPainter(
241
        paths: _bow.paths,
242 243 244 245
        progress: const AlwaysStoppedAnimation<double>(0.25),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
246
        uiPathFactory: pathFactory,
247
      );
248
      painter.paint(mockCanvas, size);
249 250
      expect(generatedPaths.length, 1);

251 252 253 254 255
      generatedPaths[0].verifyCallsInOrder(<MockCall>[
        MockCall('moveTo', <dynamic>[0.0, 24.0]),
        MockCall('cubicTo', <dynamic>[16.0, 17.0, 32.0, 17.0, 48.0, 24.0]),
        MockCall('lineTo', <dynamic>[0.0, 24.0]),
        MockCall('close', <dynamic>[]),
256 257 258 259
      ]);
    });

    test('should not repaint same values', () {
260
      final _AnimatedIconPainter painter1 = _AnimatedIconPainter(
261
        paths: _bow.paths,
262 263 264 265
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
266
        uiPathFactory: pathFactory,
267 268
      );

269
      final _AnimatedIconPainter painter2 = _AnimatedIconPainter(
270
        paths: _bow.paths,
271 272 273 274
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
275
        uiPathFactory: pathFactory,
276 277 278 279 280 281
      );

      expect(painter1.shouldRepaint(painter2), false);
    });

    test('should repaint on progress change', () {
282
      final _AnimatedIconPainter painter1 = _AnimatedIconPainter(
283
        paths: _bow.paths,
284 285 286 287
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
288
        uiPathFactory: pathFactory,
289 290
      );

291
      final _AnimatedIconPainter painter2 = _AnimatedIconPainter(
292
        paths: _bow.paths,
293 294 295 296
        progress: const AlwaysStoppedAnimation<double>(0.1),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
297
        uiPathFactory: pathFactory,
298 299 300 301 302 303
      );

      expect(painter1.shouldRepaint(painter2), true);
    });

    test('should repaint on color change', () {
304
      final _AnimatedIconPainter painter1 = _AnimatedIconPainter(
305
        paths: _bow.paths,
306 307 308 309
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFF00FF00),
        scale: 1.0,
        shouldMirror: false,
310
        uiPathFactory: pathFactory,
311 312
      );

313
      final _AnimatedIconPainter painter2 = _AnimatedIconPainter(
314
        paths: _bow.paths,
315 316 317 318
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFFFF0000),
        scale: 1.0,
        shouldMirror: false,
319
        uiPathFactory: pathFactory,
320 321 322 323 324 325
      );

      expect(painter1.shouldRepaint(painter2), true);
    });

    test('should repaint on paths change', () {
326
      final _AnimatedIconPainter painter1 = _AnimatedIconPainter(
327
        paths: _bow.paths,
328 329 330 331
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFF0000FF),
        scale: 1.0,
        shouldMirror: false,
332
        uiPathFactory: pathFactory,
333 334
      );

335
      final _AnimatedIconPainter painter2 = _AnimatedIconPainter(
336
        paths: const <_PathFrames>[],
337 338 339 340
        progress: const AlwaysStoppedAnimation<double>(0.0),
        color: const Color(0xFF0000FF),
        scale: 1.0,
        shouldMirror: false,
341
        uiPathFactory: pathFactory,
342 343 344 345 346 347 348
      );

      expect(painter1.shouldRepaint(painter2), true);
    });
  });
}

349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
// Contains the data from an invocation used for collection of calls and for
// expectations in Mock class.
class MockCall {
  // Creates a mock call with optional positional arguments.
  MockCall(String memberName, [this.positionalArguments, this.acceptAny = false])
      : memberSymbol = Symbol(memberName);
  MockCall.fromSymbol(this.memberSymbol, [this.positionalArguments, this.acceptAny = false]);
  // Creates a mock call expectation that doesn't care about what the arguments were.
  MockCall.any(String memberName)
      : memberSymbol = Symbol(memberName),
        acceptAny = true,
        positionalArguments = null;

  final Symbol memberSymbol;
  String get memberName {
    final RegExp symbolMatch = RegExp(r'Symbol\("(?<name>.*)"\)');
365
    final RegExpMatch? match = symbolMatch.firstMatch(memberSymbol.toString());
366
    assert(match != null);
367
    return match!.namedGroup('name')!;
368
  }
369

370
  final List<dynamic>? positionalArguments;
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
  final bool acceptAny;

  @override
  String toString() {
    return '$memberName(${positionalArguments?.join(', ') ?? ''})';
  }
}

// A very simplified version of a Mock class.
//
// Only verifies positional arguments, and only can verify calls in order.
class Mock {
  final List<MockCall> _calls = <MockCall>[];

  // Verify that the given calls happened in the order given.
  void verifyCallsInOrder(List<MockCall> expected) {
    int count = 0;
    expect(expected.length, equals(_calls.length),
        reason: 'Incorrect number of calls received. '
            'Expected ${expected.length} and received ${_calls.length}.\n'
            '  Calls Received: $_calls\n'
            '  Calls Expected: $expected');
    for (final MockCall call in _calls) {
      expect(call.memberSymbol, equals(expected[count].memberSymbol),
          reason: 'Unexpected call to ${call.memberName}, expected a call to '
              '${expected[count].memberName} instead.');
      if (call.positionalArguments != null && !expected[count].acceptAny) {
        int countArg = 0;
399 400
        for (final dynamic arg in call.positionalArguments!) {
          expect(arg, equals(expected[count].positionalArguments![countArg]),
401
              reason: 'Failed at call $count. Positional argument $countArg to ${call.memberName} '
402
                  'not as expected. Expected ${expected[count].positionalArguments![countArg]} '
403 404 405 406 407 408 409 410 411 412 413 414 415 416
                  'and received $arg');
          countArg++;
        }
      }
      count++;
    }
  }

  @override
  void noSuchMethod(Invocation invocation) {
    _calls.add(MockCall.fromSymbol(invocation.memberName, invocation.positionalArguments));
  }
}

417
const _AnimatedIconData _movingBar = _AnimatedIconData(
418
  Size(48.0, 48.0),
419
  <_PathFrames>[
420
    _PathFrames(
421 422
      opacities: <double>[1.0, 0.2],
      commands: <_PathCommand>[
423
        _PathMoveTo(
424
          <Offset>[
425
            Offset.zero,
426
            Offset(0.0, 38.0),
427 428
          ],
        ),
429
        _PathLineTo(
430
          <Offset>[
431 432
            Offset(48.0, 0.0),
            Offset(48.0, 38.0),
433 434
          ],
        ),
435
        _PathLineTo(
436
          <Offset>[
437 438
            Offset(48.0, 10.0),
            Offset(48.0, 48.0),
439 440
          ],
        ),
441
        _PathLineTo(
442
          <Offset>[
443 444
            Offset(0.0, 10.0),
            Offset(0.0, 48.0),
445 446
          ],
        ),
447
        _PathLineTo(
448
          <Offset>[
449
            Offset.zero,
450
            Offset(0.0, 38.0),
451 452
          ],
        ),
453
        _PathClose(),
454 455 456 457 458
      ],
    ),
  ],
);

459
const _AnimatedIconData _bow = _AnimatedIconData(
460
  Size(48.0, 48.0),
461
  <_PathFrames>[
462
    _PathFrames(
463 464
      opacities: <double>[1.0, 1.0],
      commands: <_PathCommand>[
465
        _PathMoveTo(
466
          <Offset>[
467 468 469
            Offset(0.0, 24.0),
            Offset(0.0, 24.0),
            Offset(0.0, 24.0),
470 471
          ],
        ),
472
        _PathCubicTo(
473
          <Offset>[
474 475 476
            Offset(16.0, 24.0),
            Offset(16.0, 10.0),
            Offset(16.0, 48.0),
477
          ],
478
          <Offset>[
479 480 481
            Offset(32.0, 24.0),
            Offset(32.0, 10.0),
            Offset(32.0, 48.0),
482
          ],
483
          <Offset>[
484 485 486
            Offset(48.0, 24.0),
            Offset(48.0, 24.0),
            Offset(48.0, 24.0),
487 488
          ],
        ),
489
        _PathLineTo(
490
          <Offset>[
491 492 493
            Offset(0.0, 24.0),
            Offset(0.0, 24.0),
            Offset(0.0, 24.0),
494 495
          ],
        ),
496
        _PathClose(),
497 498 499 500
      ],
    ),
  ],
);