|  | // 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 'dart:math'; | 
|  |  | 
|  | import 'package:collection/collection.dart'; | 
|  | import 'package:flutter/foundation.dart'; | 
|  | import 'package:flutter_test/flutter_test.dart'; | 
|  | import 'package:vitool/vitool.dart'; | 
|  | import 'package:path/path.dart' as path; | 
|  |  | 
|  | const String kPackagePath = '..'; | 
|  |  | 
|  | void main() { | 
|  |  | 
|  | test('parsePixels', () { | 
|  | expect(parsePixels('23px'), 23); | 
|  | expect(parsePixels('9px'), 9); | 
|  | expect(() { parsePixels('9pt'); }, throwsArgumentError); | 
|  | }); | 
|  |  | 
|  | test('parsePoints', () { | 
|  | expect(parsePoints('1.0, 2.0'), | 
|  | const <Point<double>>[Point<double>(1.0, 2.0)], | 
|  | ); | 
|  | expect(parsePoints('12.0, 34.0 5.0, 6.6'), | 
|  | const <Point<double>>[ | 
|  | Point<double>(12.0, 34.0), | 
|  | Point<double>(5.0, 6.6), | 
|  | ], | 
|  | ); | 
|  | expect(parsePoints('12.0 34.0 5.0 6.6'), | 
|  | const <Point<double>>[ | 
|  | Point<double>(12.0, 34.0), | 
|  | Point<double>(5.0, 6.6), | 
|  | ], | 
|  | ); | 
|  | }); | 
|  |  | 
|  | group('parseSvg', () { | 
|  | test('empty SVGs', () { | 
|  | interpretSvg(testAsset('empty_svg_1_48x48.svg')); | 
|  | interpretSvg(testAsset('empty_svg_2_100x50.svg')); | 
|  | }); | 
|  |  | 
|  | test('illegal SVGs', () { | 
|  | expect( | 
|  | () { interpretSvg(testAsset('illegal_svg_multiple_roots.svg')); }, | 
|  | throwsA(anything), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('SVG size', () { | 
|  | expect( | 
|  | interpretSvg(testAsset('empty_svg_1_48x48.svg')).size, | 
|  | const Point<double>(48.0, 48.0), | 
|  | ); | 
|  |  | 
|  | expect( | 
|  | interpretSvg(testAsset('empty_svg_2_100x50.svg')).size, | 
|  | const Point<double>(100.0, 50.0), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('horizontal bar', () { | 
|  | final FrameData frameData = interpretSvg(testAsset('horizontal_bar.svg')); | 
|  | expect(frameData.paths, <SvgPath>[ | 
|  | const SvgPath('path_1', <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 19.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 19.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 29.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(0.0, 29.0)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | ]), | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | test('leading space path command', () { | 
|  | interpretSvg(testAsset('leading_space_path_command.svg')); | 
|  | }); | 
|  |  | 
|  | test('SVG illegal path', () { | 
|  | expect( | 
|  | () { interpretSvg(testAsset('illegal_path.svg')); }, | 
|  | throwsA(anything), | 
|  | ); | 
|  | }); | 
|  |  | 
|  |  | 
|  | test('SVG group', () { | 
|  | final FrameData frameData = interpretSvg(testAsset('bars_group.svg')); | 
|  | expect(frameData.paths, const <SvgPath>[ | 
|  | SvgPath('path_1', <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 19.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 19.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 29.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(0.0, 29.0)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | ]), | 
|  | SvgPath('path_2', <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 34.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 34.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 44.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(0.0, 44.0)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | ]), | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | test('SVG group translate', () { | 
|  | final FrameData frameData = interpretSvg(testAsset('bar_group_translate.svg')); | 
|  | expect(frameData.paths, const <SvgPath>[ | 
|  | SvgPath('path_1', <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 34.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 34.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 44.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(0.0, 44.0)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | ]), | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | test('SVG group scale', () { | 
|  | final FrameData frameData = interpretSvg(testAsset('bar_group_scale.svg')); | 
|  | expect(frameData.paths, const <SvgPath>[ | 
|  | SvgPath( | 
|  | 'path_1', <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 9.5)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(24.0, 9.5)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(24.0, 14.5)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(0.0, 14.5)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | ]), | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | test('SVG group rotate scale', () { | 
|  | final FrameData frameData = interpretSvg(testAsset('bar_group_rotate_scale.svg')); | 
|  | expect(frameData.paths, const <PathMatcher>[ | 
|  | PathMatcher( | 
|  | SvgPath( | 
|  | 'path_1', <SvgPathCommand>[ | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(29.0, 0.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(29.0, 48.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(19.0, 48.0)]), | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(19.0, 0.0)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | ]), | 
|  | margin: precisionErrorTolerance, | 
|  | ), | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | test('SVG illegal transform', () { | 
|  | expect( | 
|  | () { interpretSvg(testAsset('illegal_transform.svg')); }, | 
|  | throwsA(anything), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('SVG group opacity', () { | 
|  | final FrameData frameData = interpretSvg(testAsset('bar_group_opacity.svg')); | 
|  | expect(frameData.paths, const <SvgPath>[ | 
|  | SvgPath( | 
|  | 'path_1', | 
|  | <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 19.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 19.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 29.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(0.0, 29.0)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | ], | 
|  | opacity: 0.5, | 
|  | ), | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | test('horizontal bar relative', () { | 
|  | // This asset uses the relative 'l' command instead of 'L'. | 
|  | final FrameData frameData = interpretSvg(testAsset('horizontal_bar_relative.svg')); | 
|  | expect(frameData.paths, const <SvgPath>[ | 
|  | SvgPath( | 
|  | 'path_1', <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 19.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 19.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(48.0, 29.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(0.0, 29.0)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | ]), | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | test('close in middle of path', () { | 
|  | // This asset uses the relative 'l' command instead of 'L'. | 
|  | final FrameData frameData = interpretSvg(testAsset('close_path_in_middle.svg')); | 
|  | expect(frameData.paths, const <SvgPath>[ | 
|  | SvgPath( | 
|  | 'path_1', <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(50.0, 50.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(60.0, 50.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(60.0, 60.0)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(50.0, 40.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(40.0, 40.0)]), | 
|  | SvgPathCommand('Z', <Point<double>>[]), | 
|  | ]), | 
|  | ]); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | group('create PathAnimation', () { | 
|  | test('single path', () { | 
|  | const List<FrameData> frameData = <FrameData>[ | 
|  | FrameData( | 
|  | Point<double>(10.0, 10.0), | 
|  | <SvgPath>[ | 
|  | SvgPath( | 
|  | 'path_1', | 
|  | <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 0.0)]), | 
|  | SvgPathCommand('L', <Point<double>>[Point<double>(10.0, 10.0)]), | 
|  | ], | 
|  | ), | 
|  | ], | 
|  | ), | 
|  | ]; | 
|  | expect(PathAnimation.fromFrameData(frameData, 0), | 
|  | const PathAnimationMatcher(PathAnimation( | 
|  | <PathCommandAnimation>[ | 
|  | PathCommandAnimation('M', <List<Point<double>>>[ | 
|  | <Point<double>>[Point<double>(0.0, 0.0)], | 
|  | ]), | 
|  | PathCommandAnimation('L', <List<Point<double>>>[ | 
|  | <Point<double>>[Point<double>(10.0, 10.0)], | 
|  | ]), | 
|  | ], | 
|  | opacities: <double>[1.0], | 
|  | )), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('multiple paths', () { | 
|  | const List<FrameData> frameData = <FrameData>[ | 
|  | FrameData( | 
|  | Point<double>(10.0, 10.0), | 
|  | <SvgPath>[ | 
|  | SvgPath( | 
|  | 'path_1', | 
|  | <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 0.0)]), | 
|  | ], | 
|  | ), | 
|  | SvgPath( | 
|  | 'path_2', | 
|  | <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(5.0, 6.0)]), | 
|  | ], | 
|  | ), | 
|  | ], | 
|  | ), | 
|  | ]; | 
|  | expect(PathAnimation.fromFrameData(frameData, 0), | 
|  | const PathAnimationMatcher(PathAnimation( | 
|  | <PathCommandAnimation>[ | 
|  | PathCommandAnimation('M', <List<Point<double>>>[ | 
|  | <Point<double>>[Point<double>(0.0, 0.0)], | 
|  | ]), | 
|  | ], | 
|  | opacities: <double>[1.0], | 
|  | )), | 
|  | ); | 
|  |  | 
|  | expect(PathAnimation.fromFrameData(frameData, 1), | 
|  | const PathAnimationMatcher(PathAnimation( | 
|  | <PathCommandAnimation>[ | 
|  | PathCommandAnimation('M', <List<Point<double>>>[ | 
|  | <Point<double>>[Point<double>(5.0, 6.0)], | 
|  | ]) | 
|  | ], | 
|  | opacities: <double>[1.0], | 
|  | )), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('multiple frames', () { | 
|  | const List<FrameData> frameData = <FrameData>[ | 
|  | FrameData( | 
|  | Point<double>(10.0, 10.0), | 
|  | <SvgPath>[ | 
|  | SvgPath( | 
|  | 'path_1', | 
|  | <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 0.0)]), | 
|  | ], | 
|  | opacity: 0.5, | 
|  | ), | 
|  | ], | 
|  | ), | 
|  | FrameData( | 
|  | Point<double>(10.0, 10.0), | 
|  | <SvgPath>[ | 
|  | SvgPath( | 
|  | 'path_1', | 
|  | <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(10.0, 10.0)]), | 
|  | ], | 
|  | ), | 
|  | ], | 
|  | ), | 
|  | ]; | 
|  | expect(PathAnimation.fromFrameData(frameData, 0), | 
|  | const PathAnimationMatcher(PathAnimation( | 
|  | <PathCommandAnimation>[ | 
|  | PathCommandAnimation('M', <List<Point<double>>>[ | 
|  | <Point<double>>[ | 
|  | Point<double>(0.0, 0.0), | 
|  | Point<double>(10.0, 10.0), | 
|  | ], | 
|  | ]), | 
|  | ], | 
|  | opacities: <double>[0.5, 1.0], | 
|  | )), | 
|  | ); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | group('create Animation', () { | 
|  | test('multiple paths', () { | 
|  | const List<FrameData> frameData = <FrameData>[ | 
|  | FrameData( | 
|  | Point<double>(10.0, 10.0), | 
|  | <SvgPath>[ | 
|  | SvgPath( | 
|  | 'path_1', | 
|  | <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(0.0, 0.0)]), | 
|  | ], | 
|  | ), | 
|  | SvgPath( | 
|  | 'path_1', | 
|  | <SvgPathCommand>[ | 
|  | SvgPathCommand('M', <Point<double>>[Point<double>(5.0, 6.0)]), | 
|  | ], | 
|  | ), | 
|  | ], | 
|  | ), | 
|  | ]; | 
|  | final Animation animation = Animation.fromFrameData(frameData); | 
|  | expect(animation.paths[0], | 
|  | const PathAnimationMatcher(PathAnimation( | 
|  | <PathCommandAnimation>[ | 
|  | PathCommandAnimation('M', <List<Point<double>>>[ | 
|  | <Point<double>>[Point<double>(0.0, 0.0)], | 
|  | ]), | 
|  | ], | 
|  | opacities: <double>[1.0], | 
|  | )), | 
|  | ); | 
|  |  | 
|  | expect(animation.paths[1], | 
|  | const PathAnimationMatcher(PathAnimation( | 
|  | <PathCommandAnimation>[ | 
|  | PathCommandAnimation('M', <List<Point<double>>>[ | 
|  | <Point<double>>[Point<double>(5.0, 6.0)], | 
|  | ]), | 
|  | ], | 
|  | opacities: <double>[1.0], | 
|  | )), | 
|  | ); | 
|  |  | 
|  | expect(animation.size, const Point<double>(10.0, 10.0)); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | group('toDart', () { | 
|  | test('_PathMoveTo', () { | 
|  | const PathCommandAnimation command = PathCommandAnimation( | 
|  | 'M', | 
|  | <List<Point<double>>>[ | 
|  | <Point<double>>[ | 
|  | Point<double>(1.0, 2.0), | 
|  | Point<double>(3.0, 4.0), | 
|  | ], | 
|  | ], | 
|  | ); | 
|  |  | 
|  | expect(command.toDart(), | 
|  | '        const _PathMoveTo(\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(1.0, 2.0),\n' | 
|  | '            const Offset(3.0, 4.0),\n' | 
|  | '          ],\n' | 
|  | '        ),\n', | 
|  |  | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('_PathLineTo', () { | 
|  | const PathCommandAnimation command = PathCommandAnimation( | 
|  | 'L', | 
|  | <List<Point<double>>>[ | 
|  | <Point<double>>[ | 
|  | Point<double>(1.0, 2.0), | 
|  | Point<double>(3.0, 4.0), | 
|  | ], | 
|  | ], | 
|  | ); | 
|  |  | 
|  | expect(command.toDart(), | 
|  | '        const _PathLineTo(\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(1.0, 2.0),\n' | 
|  | '            const Offset(3.0, 4.0),\n' | 
|  | '          ],\n' | 
|  | '        ),\n', | 
|  |  | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('_PathCubicTo', () { | 
|  | const PathCommandAnimation command = PathCommandAnimation( | 
|  | 'C', | 
|  | <List<Point<double>>>[ | 
|  | <Point<double>>[ | 
|  | Point<double>(16.0, 24.0), | 
|  | Point<double>(16.0, 10.0), | 
|  | ], | 
|  | <Point<double>>[ | 
|  | Point<double>(16.0, 25.0), | 
|  | Point<double>(16.0, 11.0), | 
|  | ], | 
|  | <Point<double>>[ | 
|  | Point<double>(40.0, 40.0), | 
|  | Point<double>(40.0, 40.0), | 
|  | ], | 
|  | ], | 
|  | ); | 
|  |  | 
|  | expect(command.toDart(), | 
|  | '        const _PathCubicTo(\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(16.0, 24.0),\n' | 
|  | '            const Offset(16.0, 10.0),\n' | 
|  | '          ],\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(16.0, 25.0),\n' | 
|  | '            const Offset(16.0, 11.0),\n' | 
|  | '          ],\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(40.0, 40.0),\n' | 
|  | '            const Offset(40.0, 40.0),\n' | 
|  | '          ],\n' | 
|  | '        ),\n', | 
|  |  | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('_PathClose', () { | 
|  | const PathCommandAnimation command = PathCommandAnimation( | 
|  | 'Z', | 
|  | <List<Point<double>>>[], | 
|  | ); | 
|  |  | 
|  | expect(command.toDart(), | 
|  | '        const _PathClose(\n' | 
|  | '        ),\n', | 
|  |  | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('Unsupported path command', () { | 
|  | const PathCommandAnimation command = PathCommandAnimation( | 
|  | 'h', | 
|  | <List<Point<double>>>[], | 
|  | ); | 
|  |  | 
|  | expect( | 
|  | () { command.toDart(); }, | 
|  | throwsA(anything), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('_PathFrames', () { | 
|  | const PathAnimation pathAnimation = PathAnimation( | 
|  | <PathCommandAnimation>[ | 
|  | PathCommandAnimation('M', <List<Point<double>>>[ | 
|  | <Point<double>>[ | 
|  | Point<double>(0.0, 0.0), | 
|  | Point<double>(10.0, 10.0), | 
|  | ], | 
|  | ]), | 
|  | PathCommandAnimation('L', <List<Point<double>>>[ | 
|  | <Point<double>>[ | 
|  | Point<double>(48.0, 10.0), | 
|  | Point<double>(0.0, 0.0), | 
|  | ], | 
|  | ]), | 
|  | ], | 
|  | opacities: <double>[0.5, 1.0], | 
|  | ); | 
|  |  | 
|  | expect(pathAnimation.toDart(), | 
|  | '    const _PathFrames(\n' | 
|  | '      opacities: const <double>[\n' | 
|  | '        0.5,\n' | 
|  | '        1.0,\n' | 
|  | '      ],\n' | 
|  | '      commands: const <_PathCommand>[\n' | 
|  | '        const _PathMoveTo(\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(0.0, 0.0),\n' | 
|  | '            const Offset(10.0, 10.0),\n' | 
|  | '          ],\n' | 
|  | '        ),\n' | 
|  | '        const _PathLineTo(\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(48.0, 10.0),\n' | 
|  | '            const Offset(0.0, 0.0),\n' | 
|  | '          ],\n' | 
|  | '        ),\n' | 
|  | '      ],\n' | 
|  | '    ),\n', | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('Animation', () { | 
|  | const Animation animation = Animation( | 
|  | Point<double>(48.0, 48.0), | 
|  | <PathAnimation>[ | 
|  | PathAnimation( | 
|  | <PathCommandAnimation>[ | 
|  | PathCommandAnimation('M', <List<Point<double>>>[ | 
|  | <Point<double>>[ | 
|  | Point<double>(0.0, 0.0), | 
|  | Point<double>(10.0, 10.0), | 
|  | ], | 
|  | ]), | 
|  | PathCommandAnimation('L', <List<Point<double>>>[ | 
|  | <Point<double>>[ | 
|  | Point<double>(48.0, 10.0), | 
|  | Point<double>(0.0, 0.0), | 
|  | ], | 
|  | ]), | 
|  | ], | 
|  | opacities: <double>[0.5, 1.0], | 
|  | ), | 
|  |  | 
|  | PathAnimation( | 
|  | <PathCommandAnimation>[ | 
|  | PathCommandAnimation('M', <List<Point<double>>>[ | 
|  | <Point<double>>[ | 
|  | Point<double>(0.0, 0.0), | 
|  | Point<double>(10.0, 10.0), | 
|  | ], | 
|  | ]), | 
|  | ], | 
|  | opacities: <double>[0.5, 1.0], | 
|  | ), | 
|  | ]); | 
|  |  | 
|  | expect(animation.toDart('_AnimatedIconData', r'_$data1'), | 
|  | 'const _AnimatedIconData _\$data1 = const _AnimatedIconData(\n' | 
|  | '  const Size(48.0, 48.0),\n' | 
|  | '  const <_PathFrames>[\n' | 
|  | '    const _PathFrames(\n' | 
|  | '      opacities: const <double>[\n' | 
|  | '        0.5,\n' | 
|  | '        1.0,\n' | 
|  | '      ],\n' | 
|  | '      commands: const <_PathCommand>[\n' | 
|  | '        const _PathMoveTo(\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(0.0, 0.0),\n' | 
|  | '            const Offset(10.0, 10.0),\n' | 
|  | '          ],\n' | 
|  | '        ),\n' | 
|  | '        const _PathLineTo(\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(48.0, 10.0),\n' | 
|  | '            const Offset(0.0, 0.0),\n' | 
|  | '          ],\n' | 
|  | '        ),\n' | 
|  | '      ],\n' | 
|  | '    ),\n' | 
|  | '    const _PathFrames(\n' | 
|  | '      opacities: const <double>[\n' | 
|  | '        0.5,\n' | 
|  | '        1.0,\n' | 
|  | '      ],\n' | 
|  | '      commands: const <_PathCommand>[\n' | 
|  | '        const _PathMoveTo(\n' | 
|  | '          const <Offset>[\n' | 
|  | '            const Offset(0.0, 0.0),\n' | 
|  | '            const Offset(10.0, 10.0),\n' | 
|  | '          ],\n' | 
|  | '        ),\n' | 
|  | '      ],\n' | 
|  | '    ),\n' | 
|  | '  ],\n' | 
|  | ');', | 
|  | ); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | // Matches all path commands' points within an error margin. | 
|  | class PathMatcher extends Matcher { | 
|  | const PathMatcher(this.actual, {this.margin = 0.0}); | 
|  |  | 
|  | final SvgPath actual; | 
|  | final double margin; | 
|  |  | 
|  | @override | 
|  | Description describe(Description description) => description.add('$actual (±$margin)'); | 
|  |  | 
|  | @override | 
|  | bool matches(dynamic item, Map<dynamic, dynamic> matchState) { | 
|  | if (item == null || actual == null) | 
|  | return item == actual; | 
|  |  | 
|  | if (item.runtimeType != actual.runtimeType) | 
|  | return false; | 
|  |  | 
|  | final SvgPath other = item as SvgPath; | 
|  | if (other.id != actual.id || other.opacity != actual.opacity) | 
|  | return false; | 
|  |  | 
|  | if (other.commands.length != actual.commands.length) | 
|  | return false; | 
|  |  | 
|  | for (int i = 0; i < other.commands.length; i += 1) { | 
|  | if (!commandsMatch(actual.commands[i], other.commands[i])) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool commandsMatch(SvgPathCommand actual, SvgPathCommand other) { | 
|  | if (other.points.length != actual.points.length) | 
|  | return false; | 
|  |  | 
|  | for (int i = 0; i < other.points.length; i += 1) { | 
|  | if ((other.points[i].x - actual.points[i].x).abs() > margin) | 
|  | return false; | 
|  | if ((other.points[i].y - actual.points[i].y).abs() > margin) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | class PathAnimationMatcher extends Matcher { | 
|  | const PathAnimationMatcher(this.expected); | 
|  |  | 
|  | final PathAnimation expected; | 
|  |  | 
|  | @override | 
|  | Description describe(Description description) => description.add('$expected'); | 
|  |  | 
|  | @override | 
|  | bool matches(dynamic item, Map<dynamic, dynamic> matchState) { | 
|  | if (item == null || expected == null) | 
|  | return item == expected; | 
|  |  | 
|  | if (item.runtimeType != expected.runtimeType) | 
|  | return false; | 
|  |  | 
|  | final PathAnimation other = item as PathAnimation; | 
|  |  | 
|  | if (!const ListEquality<double>().equals(other.opacities, expected.opacities)) | 
|  | return false; | 
|  |  | 
|  | if (other.commands.length != expected.commands.length) | 
|  | return false; | 
|  |  | 
|  | for (int i = 0; i < other.commands.length; i += 1) { | 
|  | if (!commandsMatch(expected.commands[i], other.commands[i])) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool commandsMatch(PathCommandAnimation expected, PathCommandAnimation other) { | 
|  | if (other.points.length != expected.points.length) | 
|  | return false; | 
|  |  | 
|  | for (int i = 0; i < other.points.length; i += 1) | 
|  | if (!const ListEquality<Point<double>>().equals(other.points[i], expected.points[i])) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | String testAsset(String name) { | 
|  | return path.join(kPackagePath, 'test_assets', name); | 
|  | } |