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

import 'package:flutter/material.dart';
6
import 'package:flutter/services.dart';
7 8 9 10 11
import 'package:flutter_test/flutter_test.dart';

import '../rendering/mock_canvas.dart';

void main() {
12
  testWidgets('The Ink widget renders a SizedBox by default', (WidgetTester tester) async {
13 14 15 16 17
    await tester.pumpWidget(
      Material(
        child: Ink(),
      ),
    );
18 19 20 21 22 23 24
    Finder sizedBox = find.descendant(
      of: find.byType(Ink),
      matching: find.byType(SizedBox),
    );
    expect(sizedBox, findsOneWidget);
    expect(tester.getSize(sizedBox).height, 600.0);
    expect(tester.getSize(sizedBox).width, 800.0);
25 26 27 28 29 30 31 32 33 34 35 36 37 38

    const double height = 150.0;
    const double width = 200.0;
    await tester.pumpWidget(
      Material(
        child: Center( // used to constrain to child's size
          child: Ink(
            height: height,
            width: width,
          ),
        ),
      ),
    );
    await tester.pumpAndSettle();
39 40 41 42 43 44 45
    sizedBox = find.descendant(
      of: find.byType(Ink),
      matching: find.byType(SizedBox),
    );
    expect(sizedBox, findsNWidgets(2));
    expect(tester.getSize(sizedBox.at(0)).height, height);
    expect(tester.getSize(sizedBox.at(0)).width, width);
46 47
  });

Ian Hickson's avatar
Ian Hickson committed
48
  testWidgets('The InkWell widget renders an ink splash', (WidgetTester tester) async {
49 50
    const Color highlightColor = Color(0xAAFF0000);
    const Color splashColor = Color(0xAA0000FF);
51
    const BorderRadius borderRadius = BorderRadius.all(Radius.circular(6.0));
52 53

    await tester.pumpWidget(
54
      Directionality(
55
        textDirection: TextDirection.ltr,
56 57
        child: Material(
          child: Center(
58
            child: SizedBox(
59 60
              width: 200.0,
              height: 60.0,
61
              child: InkWell(
62 63 64 65 66
                borderRadius: borderRadius,
                highlightColor: highlightColor,
                splashColor: splashColor,
                onTap: () { },
              ),
67 68 69 70 71 72
            ),
          ),
        ),
      ),
    );

73
    final Offset center = tester.getCenter(find.byType(InkWell));
74 75
    final TestGesture gesture = await tester.startGesture(center);
    await tester.pump(); // start gesture
76
    await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
77

78
    final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox;
79 80 81
    expect(
      box,
      paints
82 83 84
        ..translate(x: 0.0, y: 0.0)
        ..save()
        ..translate(x: 300.0, y: 270.0)
85
        ..clipRRect(rrect: RRect.fromLTRBR(0.0, 0.0, 200.0, 60.0, const Radius.circular(6.0)))
86 87
        ..circle(x: 100.0, y: 30.0, radius: 21.0, color: splashColor)
        ..restore()
88
        ..rrect(
89
          rrect: RRect.fromLTRBR(300.0, 270.0, 500.0, 330.0, const Radius.circular(6.0)),
90
          color: highlightColor,
91
        ),
92 93 94 95
    );

    await gesture.up();
  });
96

Ian Hickson's avatar
Ian Hickson committed
97
  testWidgets('The InkWell widget renders an ink ripple', (WidgetTester tester) async {
98 99
    const Color highlightColor = Color(0xAAFF0000);
    const Color splashColor = Color(0xB40000FF);
100
    const BorderRadius borderRadius = BorderRadius.all(Radius.circular(6.0));
101 102

    await tester.pumpWidget(
103
      Directionality(
104
        textDirection: TextDirection.ltr,
105 106
        child: Material(
          child: Center(
107
            child: SizedBox(
108 109
              width: 100.0,
              height: 100.0,
110
              child: InkWell(
111 112 113 114 115 116 117
                borderRadius: borderRadius,
                highlightColor: highlightColor,
                splashColor: splashColor,
                onTap: () { },
                radius: 100.0,
                splashFactory: InkRipple.splashFactory,
              ),
118 119 120 121 122 123 124 125 126 127 128 129
            ),
          ),
        ),
      ),
    );

    final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell));
    final Offset inkWellCenter = tester.getCenter(find.byType(InkWell));
    //final TestGesture gesture = await tester.startGesture(tapDownOffset);
    await tester.tapAt(tapDownOffset);
    await tester.pump(); // start gesture

130
    final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox;
131 132 133 134

    bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0;
    bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0;

135 136 137 138 139
    PaintPattern ripplePattern(Offset expectedCenter, double expectedRadius, int expectedAlpha) {
      return paints
        ..translate(x: 0.0, y: 0.0)
        ..translate(x: tapDownOffset.dx, y: tapDownOffset.dy)
        ..something((Symbol method, List<dynamic> arguments) {
140
          if (method != #drawCircle) {
141
            return false;
142
          }
143 144 145
          final Offset center = arguments[0] as Offset;
          final double radius = arguments[1] as double;
          final Paint paint = arguments[2] as Paint;
146
          if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == expectedAlpha) {
147
            return true;
148
          }
149 150 151
          throw '''
            Expected: center == $expectedCenter, radius == $expectedRadius, alpha == $expectedAlpha
            Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
152
        }
153 154 155
      );
    }

Ian Hickson's avatar
Ian Hickson committed
156
    // Initially the ripple's center is where the tap occurred;
157 158
    // ripplePattern always add a translation of tapDownOffset.
    expect(box, ripplePattern(Offset.zero, 30.0, 0));
159 160

    // The ripple fades in for 75ms. During that time its alpha is eased from
161
    // 0 to the splashColor's alpha value and its center moves towards the
162 163
    // center of the ink well.
    await tester.pump(const Duration(milliseconds: 50));
164
    expect(box, ripplePattern(const Offset(17.0, 17.0), 56.0, 120));
165 166 167 168

    // At 75ms the ripple has fade in: it's alpha matches the splashColor's
    // alpha and its center has moved closer to the ink well's center.
    await tester.pump(const Duration(milliseconds: 25));
169
    expect(box, ripplePattern(const Offset(29.0, 29.0), 73.0, 180));
170 171 172 173

    // At this point the splash radius has expanded to its limit: 5 past the
    // ink well's radius parameter. The splash center has moved to its final
    // location at the inkwell's center and the fade-out is about to start.
174 175
    // The fade-out begins at 225ms = 50ms + 25ms + 150ms.
    await tester.pump(const Duration(milliseconds: 150));
176
    expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 180));
177 178 179

    // After another 150ms the fade-out is complete.
    await tester.pump(const Duration(milliseconds: 150));
180
    expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 0));
Ian Hickson's avatar
Ian Hickson committed
181 182 183 184
  });

  testWidgets('Does the Ink widget render anything', (WidgetTester tester) async {
    await tester.pumpWidget(
185
      Directionality(
186
        textDirection: TextDirection.ltr,
187 188 189
        child: Material(
          child: Center(
            child: Ink(
190 191 192
              color: Colors.blue,
              width: 200.0,
              height: 200.0,
193
              child: InkWell(
194 195 196
                splashColor: Colors.green,
                onTap: () { },
              ),
Ian Hickson's avatar
Ian Hickson committed
197 198 199 200 201
            ),
          ),
        ),
      ),
    );
202

Ian Hickson's avatar
Ian Hickson committed
203 204 205 206 207
    final Offset center = tester.getCenter(find.byType(InkWell));
    final TestGesture gesture = await tester.startGesture(center);
    await tester.pump(); // start gesture
    await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way

208
    final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox;
Ian Hickson's avatar
Ian Hickson committed
209 210 211
    expect(
      box,
      paints
Dan Field's avatar
Dan Field committed
212
        ..rect(rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), color: Color(Colors.blue.value))
213
        ..circle(color: Color(Colors.green.value)),
Ian Hickson's avatar
Ian Hickson committed
214 215 216
    );

    await tester.pumpWidget(
217
      Directionality(
218
        textDirection: TextDirection.ltr,
219 220 221
        child: Material(
          child: Center(
            child: Ink(
222 223 224
              color: Colors.red,
              width: 200.0,
              height: 200.0,
225
              child: InkWell(
226 227 228
                splashColor: Colors.green,
                onTap: () { },
              ),
Ian Hickson's avatar
Ian Hickson committed
229 230 231 232 233 234 235 236 237 238 239
            ),
          ),
        ),
      ),
    );

    expect(Material.of(tester.element(find.byType(InkWell))), same(box));

    expect(
      box,
      paints
Dan Field's avatar
Dan Field committed
240
        ..rect(rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), color: Color(Colors.red.value))
241
        ..circle(color: Color(Colors.green.value)),
Ian Hickson's avatar
Ian Hickson committed
242 243 244
    );

    await tester.pumpWidget(
245
      Directionality(
246
        textDirection: TextDirection.ltr,
247 248 249
        child: Material(
          child: Center(
            child: InkWell( // this is at a different depth in the tree so it's now a new InkWell
250 251 252
              splashColor: Colors.green,
              onTap: () { },
            ),
Ian Hickson's avatar
Ian Hickson committed
253 254 255 256 257 258 259 260 261 262 263
          ),
        ),
      ),
    );

    expect(Material.of(tester.element(find.byType(InkWell))), same(box));

    expect(box, isNot(paints..rect()));
    expect(box, isNot(paints..circle()));

    await gesture.up();
264
  });
265

266
  testWidgets('The InkWell widget renders an SelectAction or ActivateAction-induced ink ripple', (WidgetTester tester) async {
267 268
    const Color highlightColor = Color(0xAAFF0000);
    const Color splashColor = Color(0xB40000FF);
269
    const BorderRadius borderRadius = BorderRadius.all(Radius.circular(6.0));
270 271

    final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
272
    Future<void> buildTest(Intent intent) async {
273
      return tester.pumpWidget(
274
        Shortcuts(
275 276
          shortcuts: <ShortcutActivator, Intent>{
            const SingleActivator(LogicalKeyboardKey.space): intent,
277 278 279 280 281
          },
          child: Directionality(
            textDirection: TextDirection.ltr,
            child: Material(
              child: Center(
282
                child: SizedBox(
283 284 285 286 287 288 289 290 291 292 293
                  width: 100.0,
                  height: 100.0,
                  child: InkWell(
                    borderRadius: borderRadius,
                    highlightColor: highlightColor,
                    splashColor: splashColor,
                    focusNode: focusNode,
                    onTap: () { },
                    radius: 100.0,
                    splashFactory: InkRipple.splashFactory,
                  ),
294 295 296 297 298
                ),
              ),
            ),
          ),
        ),
299 300
      );
    }
301

302
    await buildTest(const ActivateIntent());
303 304 305
    focusNode.requestFocus();
    await tester.pumpAndSettle();

306 307
    final Offset topLeft = tester.getTopLeft(find.byType(InkWell));
    final Offset inkWellCenter = tester.getCenter(find.byType(InkWell)) - topLeft;
308 309 310 311 312 313 314 315 316 317 318 319

    bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0;
    bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0;

    PaintPattern ripplePattern(double expectedRadius, int expectedAlpha) {
      return paints
        ..translate(x: 0.0, y: 0.0)
        ..translate(x: topLeft.dx, y: topLeft.dy)
        ..something((Symbol method, List<dynamic> arguments) {
          if (method != #drawCircle) {
            return false;
          }
320 321 322
          final Offset center = arguments[0] as Offset;
          final double radius = arguments[1] as double;
          final Paint paint = arguments[2] as Paint;
323 324 325 326 327 328 329 330 331 332 333 334
          if (offsetsAreClose(center, inkWellCenter) &&
              radiiAreClose(radius, expectedRadius) &&
              paint.color.alpha == expectedAlpha) {
            return true;
          }
          throw '''
            Expected: center == $inkWellCenter, radius == $expectedRadius, alpha == $expectedAlpha
            Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
        },
        );
    }

335
    await buildTest(const ActivateIntent());
336 337 338 339
    await tester.pumpAndSettle();
    await tester.sendKeyEvent(LogicalKeyboardKey.space);
    await tester.pump();

340
    final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox;
341

342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
    // ripplePattern always add a translation of topLeft.
    expect(box, ripplePattern(30.0, 0));

    // The ripple fades in for 75ms. During that time its alpha is eased from
    // 0 to the splashColor's alpha value.
    await tester.pump(const Duration(milliseconds: 50));
    expect(box, ripplePattern(56.0, 120));

    // At 75ms the ripple has faded in: it's alpha matches the splashColor's
    // alpha.
    await tester.pump(const Duration(milliseconds: 25));
    expect(box, ripplePattern(73.0, 180));

    // At this point the splash radius has expanded to its limit: 5 past the
    // ink well's radius parameter. The fade-out is about to start.
    // The fade-out begins at 225ms = 50ms + 25ms + 150ms.
    await tester.pump(const Duration(milliseconds: 150));
    expect(box, ripplePattern(105.0, 180));

    // After another 150ms the fade-out is complete.
    await tester.pump(const Duration(milliseconds: 150));
    expect(box, ripplePattern(105.0, 0));
  });

366 367 368
  testWidgets('Cancel an InkRipple that was disposed when its animation ended', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/14391
    await tester.pumpWidget(
369
      Directionality(
370
        textDirection: TextDirection.ltr,
371 372
        child: Material(
          child: Center(
373
            child: SizedBox(
374 375
              width: 100.0,
              height: 100.0,
376
              child: InkWell(
377 378 379 380
                onTap: () { },
                radius: 100.0,
                splashFactory: InkRipple.splashFactory,
              ),
381 382 383 384 385 386 387 388 389 390 391 392 393
            ),
          ),
        ),
      ),
    );

    final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell));
    await tester.tapAt(tapDownOffset);
    await tester.pump(); // start splash
    await tester.pump(const Duration(milliseconds: 375)); // _kFadeOutDuration, in_ripple.dart

    final TestGesture gesture = await tester.startGesture(tapDownOffset);
    await tester.pump(); // start gesture
394
    await gesture.moveTo(Offset.zero);
395 396 397
    await gesture.up(); // generates a tap cancel
    await tester.pumpAndSettle();
  });
398 399

  testWidgets('Cancel an InkRipple that was disposed when its animation ended', (WidgetTester tester) async {
400 401
    const Color highlightColor = Color(0xAAFF0000);
    const Color splashColor = Color(0xB40000FF);
402 403 404

    // Regression test for https://github.com/flutter/flutter/issues/14391
    await tester.pumpWidget(
405
      Directionality(
406
        textDirection: TextDirection.ltr,
407 408
        child: Material(
          child: Center(
409
            child: SizedBox(
410 411
              width: 100.0,
              height: 100.0,
412
              child: InkWell(
413 414 415 416 417 418
                splashColor: splashColor,
                highlightColor: highlightColor,
                onTap: () { },
                radius: 100.0,
                splashFactory: InkRipple.splashFactory,
              ),
419 420 421 422 423 424 425 426 427 428 429 430 431 432
            ),
          ),
        ),
      ),
    );

    final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell));
    await tester.tapAt(tapDownOffset);
    await tester.pump(); // start splash
    // No delay here so _fadeInController.value=1.0 (InkRipple.dart)

    // Generate a tap cancel; Will cancel the ink splash before it started
    final TestGesture gesture = await tester.startGesture(tapDownOffset);
    await tester.pump(); // start gesture
433
    await gesture.moveTo(Offset.zero);
434 435
    await gesture.up(); // generates a tap cancel

436
    final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox;
437
    expect(box, paints..everything((Symbol method, List<dynamic> arguments) {
438
      if (method != #drawCircle) {
439
        return true;
440
      }
441
      final Paint paint = arguments[2] as Paint;
442
      if (paint.color.alpha == 0) {
443
        return true;
444
      }
445 446 447
      throw 'Expected: paint.color.alpha == 0, found: ${paint.color.alpha}';
    }));
  });
448
}