ink_paint_test.dart 15.1 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 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
  testWidgets('The Ink widget renders a Container by default', (WidgetTester tester) async {
    await tester.pumpWidget(
      Material(
        child: Ink(),
      ),
    );
    expect(tester.getSize(find.byType(Container)).height, 600.0);
    expect(tester.getSize(find.byType(Container)).width, 800.0);

    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();
    expect(tester.getSize(find.byType(Container)).height, height);
    expect(tester.getSize(find.byType(Container)).width, width);
  });

Ian Hickson's avatar
Ian Hickson committed
38
  testWidgets('The InkWell widget renders an ink splash', (WidgetTester tester) async {
39 40
    const Color highlightColor = Color(0xAAFF0000);
    const Color splashColor = Color(0xAA0000FF);
41
    final BorderRadius borderRadius = BorderRadius.circular(6.0);
42 43

    await tester.pumpWidget(
44
      Directionality(
45
        textDirection: TextDirection.ltr,
46 47
        child: Material(
          child: Center(
48
            child: SizedBox(
49 50
              width: 200.0,
              height: 60.0,
51
              child: InkWell(
52 53 54 55 56
                borderRadius: borderRadius,
                highlightColor: highlightColor,
                splashColor: splashColor,
                onTap: () { },
              ),
57 58 59 60 61 62
            ),
          ),
        ),
      ),
    );

63
    final Offset center = tester.getCenter(find.byType(InkWell));
64 65
    final TestGesture gesture = await tester.startGesture(center);
    await tester.pump(); // start gesture
66
    await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
67

68
    final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox;
69 70 71
    expect(
      box,
      paints
72 73 74
        ..translate(x: 0.0, y: 0.0)
        ..save()
        ..translate(x: 300.0, y: 270.0)
75
        ..clipRRect(rrect: RRect.fromLTRBR(0.0, 0.0, 200.0, 60.0, const Radius.circular(6.0)))
76 77
        ..circle(x: 100.0, y: 30.0, radius: 21.0, color: splashColor)
        ..restore()
78
        ..rrect(
79
          rrect: RRect.fromLTRBR(300.0, 270.0, 500.0, 330.0, const Radius.circular(6.0)),
80
          color: highlightColor,
81
        ),
82 83 84 85
    );

    await gesture.up();
  });
86

Ian Hickson's avatar
Ian Hickson committed
87
  testWidgets('The InkWell widget renders an ink ripple', (WidgetTester tester) async {
88 89
    const Color highlightColor = Color(0xAAFF0000);
    const Color splashColor = Color(0xB40000FF);
90
    final BorderRadius borderRadius = BorderRadius.circular(6.0);
91 92

    await tester.pumpWidget(
93
      Directionality(
94
        textDirection: TextDirection.ltr,
95 96
        child: Material(
          child: Center(
97
            child: SizedBox(
98 99
              width: 100.0,
              height: 100.0,
100
              child: InkWell(
101 102 103 104 105 106 107
                borderRadius: borderRadius,
                highlightColor: highlightColor,
                splashColor: splashColor,
                onTap: () { },
                radius: 100.0,
                splashFactory: InkRipple.splashFactory,
              ),
108 109 110 111 112 113 114 115 116 117 118 119
            ),
          ),
        ),
      ),
    );

    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

120
    final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox;
121 122 123 124

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

125 126 127 128 129
    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) {
130
          if (method != #drawCircle)
131
            return false;
132 133 134
          final Offset center = arguments[0] as Offset;
          final double radius = arguments[1] as double;
          final Paint paint = arguments[2] as Paint;
135
          if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == expectedAlpha)
136 137 138 139
            return true;
          throw '''
            Expected: center == $expectedCenter, radius == $expectedRadius, alpha == $expectedAlpha
            Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
140
        }
141 142 143
      );
    }

Ian Hickson's avatar
Ian Hickson committed
144
    // Initially the ripple's center is where the tap occurred;
145 146
    // ripplePattern always add a translation of tapDownOffset.
    expect(box, ripplePattern(Offset.zero, 30.0, 0));
147 148

    // The ripple fades in for 75ms. During that time its alpha is eased from
149
    // 0 to the splashColor's alpha value and its center moves towards the
150 151
    // center of the ink well.
    await tester.pump(const Duration(milliseconds: 50));
152
    expect(box, ripplePattern(const Offset(17.0, 17.0), 56.0, 120));
153 154 155 156

    // 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));
157
    expect(box, ripplePattern(const Offset(29.0, 29.0), 73.0, 180));
158 159 160 161

    // 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.
162 163
    // The fade-out begins at 225ms = 50ms + 25ms + 150ms.
    await tester.pump(const Duration(milliseconds: 150));
164
    expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 180));
165 166 167

    // After another 150ms the fade-out is complete.
    await tester.pump(const Duration(milliseconds: 150));
168
    expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 0));
Ian Hickson's avatar
Ian Hickson committed
169 170 171 172
  });

  testWidgets('Does the Ink widget render anything', (WidgetTester tester) async {
    await tester.pumpWidget(
173
      Directionality(
174
        textDirection: TextDirection.ltr,
175 176 177
        child: Material(
          child: Center(
            child: Ink(
178 179 180
              color: Colors.blue,
              width: 200.0,
              height: 200.0,
181
              child: InkWell(
182 183 184
                splashColor: Colors.green,
                onTap: () { },
              ),
Ian Hickson's avatar
Ian Hickson committed
185 186 187 188 189
            ),
          ),
        ),
      ),
    );
190

Ian Hickson's avatar
Ian Hickson committed
191 192 193 194 195
    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

196
    final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox;
Ian Hickson's avatar
Ian Hickson committed
197 198 199
    expect(
      box,
      paints
Dan Field's avatar
Dan Field committed
200
        ..rect(rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), color: Color(Colors.blue.value))
201
        ..circle(color: Color(Colors.green.value)),
Ian Hickson's avatar
Ian Hickson committed
202 203 204
    );

    await tester.pumpWidget(
205
      Directionality(
206
        textDirection: TextDirection.ltr,
207 208 209
        child: Material(
          child: Center(
            child: Ink(
210 211 212
              color: Colors.red,
              width: 200.0,
              height: 200.0,
213
              child: InkWell(
214 215 216
                splashColor: Colors.green,
                onTap: () { },
              ),
Ian Hickson's avatar
Ian Hickson committed
217 218 219 220 221 222 223 224 225 226 227
            ),
          ),
        ),
      ),
    );

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

    expect(
      box,
      paints
Dan Field's avatar
Dan Field committed
228
        ..rect(rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), color: Color(Colors.red.value))
229
        ..circle(color: Color(Colors.green.value)),
Ian Hickson's avatar
Ian Hickson committed
230 231 232
    );

    await tester.pumpWidget(
233
      Directionality(
234
        textDirection: TextDirection.ltr,
235 236 237
        child: Material(
          child: Center(
            child: InkWell( // this is at a different depth in the tree so it's now a new InkWell
238 239 240
              splashColor: Colors.green,
              onTap: () { },
            ),
Ian Hickson's avatar
Ian Hickson committed
241 242 243 244 245 246 247 248 249 250 251
          ),
        ),
      ),
    );

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

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

    await gesture.up();
252
  });
253

254
  testWidgets('The InkWell widget renders an SelectAction or ActivateAction-induced ink ripple', (WidgetTester tester) async {
255 256 257 258 259
    const Color highlightColor = Color(0xAAFF0000);
    const Color splashColor = Color(0xB40000FF);
    final BorderRadius borderRadius = BorderRadius.circular(6.0);

    final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
260
    Future<void> buildTest(Intent intent) async {
261
      return tester.pumpWidget(
262
        Shortcuts(
263 264
          shortcuts: <ShortcutActivator, Intent>{
            const SingleActivator(LogicalKeyboardKey.space): intent,
265 266 267 268 269
          },
          child: Directionality(
            textDirection: TextDirection.ltr,
            child: Material(
              child: Center(
270
                child: SizedBox(
271 272 273 274 275 276 277 278 279 280 281
                  width: 100.0,
                  height: 100.0,
                  child: InkWell(
                    borderRadius: borderRadius,
                    highlightColor: highlightColor,
                    splashColor: splashColor,
                    focusNode: focusNode,
                    onTap: () { },
                    radius: 100.0,
                    splashFactory: InkRipple.splashFactory,
                  ),
282 283 284 285 286
                ),
              ),
            ),
          ),
        ),
287 288
      );
    }
289

290
    await buildTest(const ActivateIntent());
291 292 293
    focusNode.requestFocus();
    await tester.pumpAndSettle();

294 295
    final Offset topLeft = tester.getTopLeft(find.byType(InkWell));
    final Offset inkWellCenter = tester.getCenter(find.byType(InkWell)) - topLeft;
296 297 298 299 300 301 302 303 304 305 306 307

    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;
          }
308 309 310
          final Offset center = arguments[0] as Offset;
          final double radius = arguments[1] as double;
          final Paint paint = arguments[2] as Paint;
311 312 313 314 315 316 317 318 319 320 321 322
          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}''';
        },
        );
    }

323
    await buildTest(const ActivateIntent());
324 325 326 327
    await tester.pumpAndSettle();
    await tester.sendKeyEvent(LogicalKeyboardKey.space);
    await tester.pump();

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

330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
    // 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));
  });

354 355 356
  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(
357
      Directionality(
358
        textDirection: TextDirection.ltr,
359 360
        child: Material(
          child: Center(
361
            child: SizedBox(
362 363
              width: 100.0,
              height: 100.0,
364
              child: InkWell(
365 366 367 368
                onTap: () { },
                radius: 100.0,
                splashFactory: InkRipple.splashFactory,
              ),
369 370 371 372 373 374 375 376 377 378 379 380 381
            ),
          ),
        ),
      ),
    );

    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
382
    await gesture.moveTo(Offset.zero);
383 384 385
    await gesture.up(); // generates a tap cancel
    await tester.pumpAndSettle();
  });
386 387

  testWidgets('Cancel an InkRipple that was disposed when its animation ended', (WidgetTester tester) async {
388 389
    const Color highlightColor = Color(0xAAFF0000);
    const Color splashColor = Color(0xB40000FF);
390 391 392

    // Regression test for https://github.com/flutter/flutter/issues/14391
    await tester.pumpWidget(
393
      Directionality(
394
        textDirection: TextDirection.ltr,
395 396
        child: Material(
          child: Center(
397
            child: SizedBox(
398 399
              width: 100.0,
              height: 100.0,
400
              child: InkWell(
401 402 403 404 405 406
                splashColor: splashColor,
                highlightColor: highlightColor,
                onTap: () { },
                radius: 100.0,
                splashFactory: InkRipple.splashFactory,
              ),
407 408 409 410 411 412 413 414 415 416 417 418 419 420
            ),
          ),
        ),
      ),
    );

    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
421
    await gesture.moveTo(Offset.zero);
422 423
    await gesture.up(); // generates a tap cancel

424
    final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox;
425 426 427
    expect(box, paints..everything((Symbol method, List<dynamic> arguments) {
      if (method != #drawCircle)
        return true;
428
      final Paint paint = arguments[2] as Paint;
429 430 431 432 433
      if (paint.color.alpha == 0)
        return true;
      throw 'Expected: paint.color.alpha == 0, found: ${paint.color.alpha}';
    }));
  });
434
}