animated_icons_test.dart 8.62 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 10 11
// 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';

import '../widgets/semantics_tester.dart';

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

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

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

  @override
26
  void scale(double sx, [double? sy]) {
27
    capturedSx = sx;
28
    capturedSy = sy!;
29 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
  }

  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);
}
77 78 79 80 81 82

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

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

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

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

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

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

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

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

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

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

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

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

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

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) {
299
    final Paint actualPaint = item as Paint;
300
    return actualPaint.color == Color(expectedColor);
301 302
  }
}