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 16
import 'dart:ui' show Offset, lerpDouble;
import 'dart:ui' as ui show Canvas, Paint, Path;
17

18
import 'package:flutter/foundation.dart' show clampDouble;
19
import 'package:flutter/widgets.dart';
20
import 'package:flutter_test/flutter_test.dart';
21

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

// 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).
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
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() {
47 48
  group('Interpolate points', () {
    test('- single point', () {
49 50
      const List<Offset> points = <Offset>[
        Offset(25.0, 1.0),
51 52 53 54 55 56 57
      ];
      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', () {
58 59 60
      const List<Offset> points = <Offset>[
        Offset(25.0, 1.0),
        Offset(12.0, 12.0),
61 62 63 64 65 66 67
      ];
      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', () {
68 69 70 71
      const List<Offset> points = <Offset>[
        Offset(25.0, 1.0),
        Offset(12.0, 12.0),
        Offset(23.0, 9.0),
72 73 74 75 76 77 78 79 80 81
      ];
      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', () {
82
    const Size size = Size(48.0, 48.0);
83 84 85 86
    late MockPath mockPath;
    late MockCanvas mockCanvas;
    late List<MockPath> generatedPaths;
    late _UiPathFactory pathFactory;
87 88

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

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

110 111 112 113 114 115 116
      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'),
117 118 119 120
      ]);
    });

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

132 133 134 135 136 137 138
      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'),
139 140 141 142
      ]);
    });

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

154 155 156 157 158 159 160
      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'),
161 162 163 164
      ]);
    });

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

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

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

210 211 212 213 214 215 216
      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'),
217 218 219 220
      ]);
    });

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

232 233 234 235 236
      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'),
237 238 239 240
      ]);
    });

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

252 253 254 255 256
      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>[]),
257 258 259 260
      ]);
    });

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

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

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

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

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

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

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

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

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

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

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

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

350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
// 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>.*)"\)');
366
    final RegExpMatch? match = symbolMatch.firstMatch(memberSymbol.toString());
367
    assert(match != null);
368
    return match!.namedGroup('name')!;
369
  }
370

371
  final List<dynamic>? positionalArguments;
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 399
  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;
400 401
        for (final dynamic arg in call.positionalArguments!) {
          expect(arg, equals(expected[count].positionalArguments![countArg]),
402
              reason: 'Failed at call $count. Positional argument $countArg to ${call.memberName} '
403
                  'not as expected. Expected ${expected[count].positionalArguments![countArg]} '
404 405 406 407 408 409 410 411 412 413 414 415 416 417
                  'and received $arg');
          countArg++;
        }
      }
      count++;
    }
  }

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

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

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