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

import 'dart:math' as math show pi;

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

10
import '../flutter_test_alternative.dart' show Fake;
11 12
import '../widgets/semantics_tester.dart';

13
class MockCanvas extends Fake implements Canvas {
14 15
  late Path capturedPath;
  late Paint capturedPaint;
16 17 18 19 20 21 22

  @override
  void drawPath(Path path, Paint paint) {
    capturedPath = path;
    capturedPaint = paint;
  }

23 24
  late double capturedSx;
  late double capturedSy;
25 26

  @override
27
  void scale(double sx, [double? sy]) {
28
    capturedSx = sx;
29
    capturedSy = sy!;
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
  }

  final List<RecordedCanvasCall> invocations = <RecordedCanvasCall>[];

  @override
  void rotate(double radians) {
    invocations.add(RecordedRotate(radians));
  }

  @override
  void translate(double dx, double dy) {
    invocations.add(RecordedTranslate(dx, dy));
  }
}

@immutable
abstract class RecordedCanvasCall {
  const RecordedCanvasCall();
}

class RecordedRotate extends RecordedCanvasCall {
  const RecordedRotate(this.radians);

  final double radians;

  @override
  bool operator ==(Object other) {
    return other is RecordedRotate && other.radians == radians;
  }

  @override
  int get hashCode => radians.hashCode;
}

class RecordedTranslate extends RecordedCanvasCall {
  const RecordedTranslate(this.dx, this.dy);

  final double dx;
  final double dy;

  @override
  bool operator ==(Object other) {
    return other is RecordedTranslate && other.dx == dx && other.dy == dy;
  }

  @override
  int get hashCode => hashValues(dx, dy);
}
78 79 80 81 82 83

void main() {
  testWidgets('IconTheme color', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
84 85 86
        child: IconTheme(
          data: IconThemeData(
            color: Color(0xFF666666),
87
          ),
88 89
          child: AnimatedIcon(
            progress: AlwaysStoppedAnimation<double>(0.0),
90
            icon: AnimatedIcons.arrow_menu,
91
          ),
92 93 94 95
        ),
      ),
    );
    final CustomPaint customPaint = tester.widget(find.byType(CustomPaint));
96
    final MockCanvas canvas = MockCanvas();
97
    customPaint.painter!.paint(canvas, const Size(48.0, 48.0));
98
    expect(canvas.capturedPaint, hasColor(0xFF666666));
99 100 101 102 103 104
  });

  testWidgets('IconTheme opacity', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
105 106 107
        child: IconTheme(
          data: IconThemeData(
            color: Color(0xFF666666),
108
            opacity: 0.5,
109
          ),
110 111
          child: AnimatedIcon(
            progress: AlwaysStoppedAnimation<double>(0.0),
112
            icon: AnimatedIcons.arrow_menu,
113
          ),
114 115 116 117
        ),
      ),
    );
    final CustomPaint customPaint = tester.widget(find.byType(CustomPaint));
118
    final MockCanvas canvas = MockCanvas();
119
    customPaint.painter!.paint(canvas, const Size(48.0, 48.0));
120
    expect(canvas.capturedPaint, hasColor(0x80666666));
121 122 123 124 125 126
  });

  testWidgets('color overrides IconTheme color', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
127 128 129
        child: IconTheme(
          data: IconThemeData(
            color: Color(0xFF666666),
130
          ),
131 132
          child: AnimatedIcon(
            progress: AlwaysStoppedAnimation<double>(0.0),
133
            icon: AnimatedIcons.arrow_menu,
134
            color: Color(0xFF0000FF),
135
          ),
136 137 138 139
        ),
      ),
    );
    final CustomPaint customPaint = tester.widget(find.byType(CustomPaint));
140
    final MockCanvas canvas = MockCanvas();
141
    customPaint.painter!.paint(canvas, const Size(48.0, 48.0));
142
    expect(canvas.capturedPaint, hasColor(0xFF0000FF));
143 144 145 146 147 148
  });

  testWidgets('IconTheme size', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
149 150 151
        child: IconTheme(
          data: IconThemeData(
            color: Color(0xFF666666),
152 153
            size: 12.0,
          ),
154 155
          child: AnimatedIcon(
            progress: AlwaysStoppedAnimation<double>(0.0),
156
            icon: AnimatedIcons.arrow_menu,
157
          ),
158 159 160 161
        ),
      ),
    );
    final CustomPaint customPaint = tester.widget(find.byType(CustomPaint));
162
    final MockCanvas canvas = MockCanvas();
163
    customPaint.painter!.paint(canvas, const Size(12.0, 12.0));
164
    // arrow_menu default size is 48x48 so we expect it to be scaled by 0.25.
165 166
    expect(canvas.capturedSx, 0.25);
    expect(canvas.capturedSy, 0.25);
167 168 169 170 171 172
  });

  testWidgets('size overridesIconTheme size', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
173 174 175
        child: IconTheme(
          data: IconThemeData(
            color: Color(0xFF666666),
176 177
            size: 12.0,
          ),
178 179
          child: AnimatedIcon(
            progress: AlwaysStoppedAnimation<double>(0.0),
180 181
            icon: AnimatedIcons.arrow_menu,
            size: 96.0,
182
          ),
183 184 185 186
        ),
      ),
    );
    final CustomPaint customPaint = tester.widget(find.byType(CustomPaint));
187
    final MockCanvas canvas = MockCanvas();
188
    customPaint.painter!.paint(canvas, const Size(12.0, 12.0));
189
    // arrow_menu default size is 48x48 so we expect it to be scaled by 2.
190 191
    expect(canvas.capturedSx, 2);
    expect(canvas.capturedSy, 2);
192 193 194
  });

  testWidgets('Semantic label', (WidgetTester tester) async {
195
    final SemanticsTester semantics = SemanticsTester(tester);
196 197 198 199

    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
200 201
        child: AnimatedIcon(
          progress: AlwaysStoppedAnimation<double>(0.0),
202 203 204 205 206 207 208 209
          icon: AnimatedIcons.arrow_menu,
          size: 96.0,
          semanticLabel: 'a label',
        ),
      ),
    );

    expect(semantics, includesNodeWith(label: 'a label'));
210 211

    semantics.dispose();
212 213 214 215 216 217
  });

  testWidgets('Inherited text direction rtl', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.rtl,
218 219 220
        child: IconTheme(
          data: IconThemeData(
            color: Color(0xFF666666),
221
          ),
222 223
          child: AnimatedIcon(
            progress: AlwaysStoppedAnimation<double>(0.0),
224
            icon: AnimatedIcons.arrow_menu,
225
          ),
226 227 228 229
        ),
      ),
    );
    final CustomPaint customPaint = tester.widget(find.byType(CustomPaint));
230
    final MockCanvas canvas = MockCanvas();
231
    customPaint.painter!.paint(canvas, const Size(48.0, 48.0));
232 233 234
    expect(canvas.invocations, const <RecordedCanvasCall>[
      RecordedRotate(math.pi),
      RecordedTranslate(-48, -48),
235 236 237 238 239 240 241
    ]);
  });

  testWidgets('Inherited text direction ltr', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
242 243 244
        child: IconTheme(
          data: IconThemeData(
            color: Color(0xFF666666),
245
          ),
246 247
          child: AnimatedIcon(
            progress: AlwaysStoppedAnimation<double>(0.0),
248
            icon: AnimatedIcons.arrow_menu,
249
          ),
250 251 252 253
        ),
      ),
    );
    final CustomPaint customPaint = tester.widget(find.byType(CustomPaint));
254
    final MockCanvas canvas = MockCanvas();
255
    customPaint.painter!.paint(canvas, const Size(48.0, 48.0));
256
    expect(canvas.invocations, isEmpty);
257 258 259 260 261 262
  });

  testWidgets('Inherited text direction overridden', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
263 264 265
        child: IconTheme(
          data: IconThemeData(
            color: Color(0xFF666666),
266
          ),
267 268
          child: AnimatedIcon(
            progress: AlwaysStoppedAnimation<double>(0.0),
269 270
            icon: AnimatedIcons.arrow_menu,
            textDirection: TextDirection.rtl,
271
          ),
272 273 274 275
        ),
      ),
    );
    final CustomPaint customPaint = tester.widget(find.byType(CustomPaint));
276
    final MockCanvas canvas = MockCanvas();
277
    customPaint.painter!.paint(canvas, const Size(48.0, 48.0));
278 279 280
    expect(canvas.invocations, const <RecordedCanvasCall>[
      RecordedRotate(math.pi),
      RecordedTranslate(-48, -48),
281 282 283 284
    ]);
  });
}

285
PaintColorMatcher hasColor(int color) {
286
  return PaintColorMatcher(color);
287 288 289 290 291 292 293 294 295 296 297 298 299
}

class PaintColorMatcher extends Matcher {
  const PaintColorMatcher(this.expectedColor);

  final int expectedColor;

  @override
  Description describe(Description description) =>
    description.add('color was not $expectedColor');

  @override
  bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
300
    final Paint actualPaint = item as Paint;
301
    return actualPaint.color == Color(expectedColor);
302 303
  }
}