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

5 6 7
// reduced-test-set:
//   This file is run as part of a reduced test set in CI on Mac and Windows
//   machines.
8
@Tags(<String>['reduced-test-set'])
9

Ian Hickson's avatar
Ian Hickson committed
10
import 'package:flutter/material.dart';
11
import 'package:flutter/rendering.dart';
12
import 'package:flutter_test/flutter_test.dart';
13 14

import '../rendering/mock_canvas.dart';
15
import 'test_border.dart' show TestBorder;
Ian Hickson's avatar
Ian Hickson committed
16 17 18 19 20 21 22

final List<String> log = <String>[];

class PathClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    log.add('getClip');
23
    return Path()
Dan Field's avatar
Dan Field committed
24
      ..addRect(const Rect.fromLTWH(50.0, 50.0, 100.0, 100.0));
Ian Hickson's avatar
Ian Hickson committed
25 26
  }
  @override
27
  bool shouldReclip(PathClipper oldClipper) => false;
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
}

class ValueClipper<T> extends CustomClipper<T> {
  ValueClipper(this.message, this.value);

  final String message;
  final T value;

  @override
  T getClip(Size size) {
    log.add(message);
    return value;
  }

  @override
43
  bool shouldReclip(ValueClipper<T> oldClipper) {
44 45
    return oldClipper.message != message || oldClipper.value != value;
  }
Ian Hickson's avatar
Ian Hickson committed
46 47
}

48
class NotifyClipper<T> extends CustomClipper<T> {
49
  NotifyClipper({required this.clip}) : super(reclip: clip);
50 51 52 53 54 55 56 57 58 59

  final ValueNotifier<T> clip;

  @override
  T getClip(Size size) => clip.value;

  @override
  bool shouldReclip(NotifyClipper<T> oldClipper) => clip != oldClipper.clip;
}

Ian Hickson's avatar
Ian Hickson committed
60
void main() {
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
  testWidgets('ClipRect with a FittedBox child sized to zero works with semantics', (WidgetTester tester) async {
    await tester.pumpWidget(Directionality(
        textDirection: TextDirection.ltr,
        child: ClipRect(
          child: FittedBox(
            child: SizedBox.fromSize(
              size: Size.zero,
              child: Semantics(
                image: true,
                label: 'Image',
              ),
            ),
          ),
        ),
      ),
    );
    expect(find.byType(FittedBox), findsOneWidget);
  });

80
  testWidgets('ClipRect updates clipBehavior in updateRenderObject', (WidgetTester tester) async {
81
    await tester.pumpWidget(const ClipRect());
82 83 84

    final RenderClipRect renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;

85
    expect(renderClip.clipBehavior, equals(Clip.hardEdge));
86

87
    await tester.pumpWidget(const ClipRect(clipBehavior: Clip.antiAlias));
88

89
    expect(renderClip.clipBehavior, equals(Clip.antiAlias));
90 91 92 93

    await tester.pumpWidget(const ClipRect(clipBehavior: Clip.none));

    expect(renderClip.clipBehavior, equals(Clip.none));
94 95
  });

96 97 98 99 100 101
  test('ClipRRect constructs with the right default values', () {
    const ClipRRect clipRRect = ClipRRect();
    expect(clipRRect.clipBehavior, equals(Clip.antiAlias));
    expect(clipRRect.borderRadius, equals(BorderRadius.zero));
  });

102
  testWidgets('ClipRRect updates clipBehavior in updateRenderObject', (WidgetTester tester) async {
103
    await tester.pumpWidget(const ClipRRect());
104 105 106 107 108

    final RenderClipRRect renderClip = tester.allRenderObjects.whereType<RenderClipRRect>().first;

    expect(renderClip.clipBehavior, equals(Clip.antiAlias));

109
    await tester.pumpWidget(const ClipRRect(clipBehavior: Clip.hardEdge));
110 111

    expect(renderClip.clipBehavior, equals(Clip.hardEdge));
112 113 114 115

    await tester.pumpWidget(const ClipRRect(clipBehavior: Clip.none));

    expect(renderClip.clipBehavior, equals(Clip.none));
116 117 118
  });

  testWidgets('ClipOval updates clipBehavior in updateRenderObject', (WidgetTester tester) async {
119
    await tester.pumpWidget(const ClipOval());
120 121 122 123 124

    final RenderClipOval renderClip = tester.allRenderObjects.whereType<RenderClipOval>().first;

    expect(renderClip.clipBehavior, equals(Clip.antiAlias));

125
    await tester.pumpWidget(const ClipOval(clipBehavior: Clip.hardEdge));
126 127

    expect(renderClip.clipBehavior, equals(Clip.hardEdge));
128 129 130 131

    await tester.pumpWidget(const ClipOval(clipBehavior: Clip.none));

    expect(renderClip.clipBehavior, equals(Clip.none));
132 133 134
  });

  testWidgets('ClipPath updates clipBehavior in updateRenderObject', (WidgetTester tester) async {
135
    await tester.pumpWidget(const ClipPath());
136 137 138 139 140

    final RenderClipPath renderClip = tester.allRenderObjects.whereType<RenderClipPath>().first;

    expect(renderClip.clipBehavior, equals(Clip.antiAlias));

141
    await tester.pumpWidget(const ClipPath(clipBehavior: Clip.hardEdge));
142 143

    expect(renderClip.clipBehavior, equals(Clip.hardEdge));
144 145 146 147

    await tester.pumpWidget(const ClipPath(clipBehavior: Clip.none));

    expect(renderClip.clipBehavior, equals(Clip.none));
148 149
  });

150 151
  testWidgets('ClipPath', (WidgetTester tester) async {
    await tester.pumpWidget(
152 153 154
      ClipPath(
        clipper: PathClipper(),
        child: GestureDetector(
Ian Hickson's avatar
Ian Hickson committed
155 156
          behavior: HitTestBehavior.opaque,
          onTap: () { log.add('tap'); },
157
        ),
158
      ),
Ian Hickson's avatar
Ian Hickson committed
159
    );
160
    expect(log, equals(<String>['getClip']));
Ian Hickson's avatar
Ian Hickson committed
161

162
    await tester.tapAt(const Offset(10.0, 10.0));
163
    expect(log, equals(<String>['getClip']));
Ian Hickson's avatar
Ian Hickson committed
164 165
    log.clear();

166
    await tester.tapAt(const Offset(100.0, 100.0));
167
    expect(log, equals(<String>['tap']));
Ian Hickson's avatar
Ian Hickson committed
168 169 170
    log.clear();
  });

171 172
  testWidgets('ClipOval', (WidgetTester tester) async {
    await tester.pumpWidget(
173 174
      ClipOval(
        child: GestureDetector(
Ian Hickson's avatar
Ian Hickson committed
175 176
          behavior: HitTestBehavior.opaque,
          onTap: () { log.add('tap'); },
177
        ),
178
      ),
Ian Hickson's avatar
Ian Hickson committed
179
    );
180
    expect(log, equals(<String>[]));
Ian Hickson's avatar
Ian Hickson committed
181

182
    await tester.tapAt(const Offset(10.0, 10.0));
183
    expect(log, equals(<String>[]));
Ian Hickson's avatar
Ian Hickson committed
184 185
    log.clear();

186
    await tester.tapAt(const Offset(400.0, 300.0));
187
    expect(log, equals(<String>['tap']));
Ian Hickson's avatar
Ian Hickson committed
188 189
    log.clear();
  });
190

191 192
  testWidgets('Transparent ClipOval hit test', (WidgetTester tester) async {
    await tester.pumpWidget(
193
      Opacity(
194
        opacity: 0.0,
195 196
        child: ClipOval(
          child: GestureDetector(
197 198
            behavior: HitTestBehavior.opaque,
            onTap: () { log.add('tap'); },
199 200
          ),
        ),
201
      ),
202 203 204
    );
    expect(log, equals(<String>[]));

205
    await tester.tapAt(const Offset(10.0, 10.0));
206 207 208
    expect(log, equals(<String>[]));
    log.clear();

209
    await tester.tapAt(const Offset(400.0, 300.0));
210 211 212 213
    expect(log, equals(<String>['tap']));
    log.clear();
  });

214 215
  testWidgets('ClipRect', (WidgetTester tester) async {
    await tester.pumpWidget(
216
      Align(
217
        alignment: Alignment.topLeft,
218
        child: SizedBox(
219 220
          width: 100.0,
          height: 100.0,
221
          child: ClipRect(
Dan Field's avatar
Dan Field committed
222
            clipper: ValueClipper<Rect>('a', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
223
            child: GestureDetector(
224 225
              behavior: HitTestBehavior.opaque,
              onTap: () { log.add('tap'); },
226 227 228
            ),
          ),
        ),
229
      ),
230 231 232
    );
    expect(log, equals(<String>['a']));

233
    await tester.tapAt(const Offset(10.0, 10.0));
234 235
    expect(log, equals(<String>['a', 'tap']));

236
    await tester.tapAt(const Offset(100.0, 100.0));
237 238 239
    expect(log, equals(<String>['a', 'tap']));

    await tester.pumpWidget(
240
      Align(
241
        alignment: Alignment.topLeft,
242
        child: SizedBox(
243 244
          width: 100.0,
          height: 100.0,
245
          child: ClipRect(
Dan Field's avatar
Dan Field committed
246
            clipper: ValueClipper<Rect>('a', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
247
            child: GestureDetector(
248 249
              behavior: HitTestBehavior.opaque,
              onTap: () { log.add('tap'); },
250 251 252
            ),
          ),
        ),
253
      ),
254 255 256 257
    );
    expect(log, equals(<String>['a', 'tap']));

    await tester.pumpWidget(
258
      Align(
259
        alignment: Alignment.topLeft,
260
        child: SizedBox(
261 262
          width: 200.0,
          height: 200.0,
263
          child: ClipRect(
Dan Field's avatar
Dan Field committed
264
            clipper: ValueClipper<Rect>('a', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
265
            child: GestureDetector(
266 267
              behavior: HitTestBehavior.opaque,
              onTap: () { log.add('tap'); },
268 269 270
            ),
          ),
        ),
271
      ),
272 273 274 275
    );
    expect(log, equals(<String>['a', 'tap', 'a']));

    await tester.pumpWidget(
276
      Align(
277
        alignment: Alignment.topLeft,
278
        child: SizedBox(
279 280
          width: 200.0,
          height: 200.0,
281
          child: ClipRect(
Dan Field's avatar
Dan Field committed
282
            clipper: ValueClipper<Rect>('a', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
283
            child: GestureDetector(
284 285
              behavior: HitTestBehavior.opaque,
              onTap: () { log.add('tap'); },
286 287 288
            ),
          ),
        ),
289
      ),
290 291 292 293
    );
    expect(log, equals(<String>['a', 'tap', 'a']));

    await tester.pumpWidget(
294
      Align(
295
        alignment: Alignment.topLeft,
296
        child: SizedBox(
297 298
          width: 200.0,
          height: 200.0,
299
          child: ClipRect(
Dan Field's avatar
Dan Field committed
300
            clipper: ValueClipper<Rect>('b', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
301
            child: GestureDetector(
302 303
              behavior: HitTestBehavior.opaque,
              onTap: () { log.add('tap'); },
304 305 306
            ),
          ),
        ),
307
      ),
308 309 310 311
    );
    expect(log, equals(<String>['a', 'tap', 'a', 'b']));

    await tester.pumpWidget(
312
      Align(
313
        alignment: Alignment.topLeft,
314
        child: SizedBox(
315 316
          width: 200.0,
          height: 200.0,
317
          child: ClipRect(
Dan Field's avatar
Dan Field committed
318
            clipper: ValueClipper<Rect>('c', const Rect.fromLTWH(25.0, 25.0, 10.0, 10.0)),
319
            child: GestureDetector(
320 321
              behavior: HitTestBehavior.opaque,
              onTap: () { log.add('tap'); },
322 323 324
            ),
          ),
        ),
325
      ),
326 327 328
    );
    expect(log, equals(<String>['a', 'tap', 'a', 'b', 'c']));

329
    await tester.tapAt(const Offset(30.0, 30.0));
330 331
    expect(log, equals(<String>['a', 'tap', 'a', 'b', 'c', 'tap']));

332
    await tester.tapAt(const Offset(100.0, 100.0));
333
    expect(log, equals(<String>['a', 'tap', 'a', 'b', 'c', 'tap']));
334
    log.clear();
335 336
  });

337 338 339
  testWidgets('debugPaintSizeEnabled', (WidgetTester tester) async {
    await tester.pumpWidget(
      const ClipRect(
340
        child: Placeholder(),
341 342 343 344
      ),
    );
    expect(tester.renderObject(find.byType(ClipRect)).paint, paints
      ..save()
Dan Field's avatar
Dan Field committed
345
      ..clipRect(rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0))
346 347 348
      ..save()
      ..path() // Placeholder
      ..restore()
349
      ..restore(),
350 351
    );
    debugPaintSizeEnabled = true;
352
    expect(tester.renderObject(find.byType(ClipRect)).debugPaint, paints
Dan Field's avatar
Dan Field committed
353
      ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0))
354
      ..paragraph(),
355 356 357
    );
    debugPaintSizeEnabled = false;
  });
358 359 360

  testWidgets('ClipRect painting', (WidgetTester tester) async {
    await tester.pumpWidget(
361 362 363
      Center(
        child: RepaintBoundary(
          child: Container(
364
            color: Colors.white,
365
            child: Padding(
366
              padding: const EdgeInsets.all(100.0),
367
              child: SizedBox(
368 369
                height: 100.0,
                width: 100.0,
370
                child: Transform.rotate(
371
                  angle: 1.0, // radians
372 373
                  child: ClipRect(
                    child: Container(
374
                      color: Colors.red,
375
                      child: Container(
376
                        color: Colors.white,
377 378 379
                        child: RepaintBoundary(
                          child: Center(
                            child: Container(
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
                              color: Colors.black,
                              height: 10.0,
                              width: 10.0,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
    await expectLater(
      find.byType(RepaintBoundary).first,
398
      matchesGoldenFile('clip.ClipRect.png'),
399
    );
400
  });
401

402 403
  testWidgets('ClipRect save, overlay, and antialiasing', (WidgetTester tester) async {
    await tester.pumpWidget(
404 405
      RepaintBoundary(
        child: Stack(
406 407
          textDirection: TextDirection.ltr,
          children: <Widget>[
408
            Positioned(
409 410 411 412
              top: 0.0,
              left: 0.0,
              width: 100.0,
              height: 100.0,
413 414
              child: ClipRect(
                child: Container(
415 416 417 418
                  color: Colors.blue,
                ),
              ),
            ),
419
            Positioned(
420 421 422 423
              top: 50.0,
              left: 50.0,
              width: 100.0,
              height: 100.0,
424
              child: Transform.rotate(
425
                angle: 1.0,
426
                child: Container(
427 428 429 430 431 432 433 434 435 436
                  color: Colors.red,
                ),
              ),
            ),
          ],
        ),
      ),
    );
    await expectLater(
      find.byType(RepaintBoundary).first,
437
      matchesGoldenFile('clip.ClipRectOverlay.png'),
438
    );
439
  });
440

441 442
  testWidgets('ClipRRect painting', (WidgetTester tester) async {
    await tester.pumpWidget(
443 444 445
      Center(
        child: RepaintBoundary(
          child: Container(
446
            color: Colors.white,
447
            child: Padding(
448
              padding: const EdgeInsets.all(100.0),
449
              child: SizedBox(
450 451
                height: 100.0,
                width: 100.0,
452
                child: Transform.rotate(
453
                  angle: 1.0, // radians
454
                  child: ClipRRect(
455
                    borderRadius: const BorderRadius.only(
456 457 458 459
                      topLeft: Radius.elliptical(10.0, 20.0),
                      topRight: Radius.elliptical(5.0, 30.0),
                      bottomLeft: Radius.elliptical(2.5, 12.0),
                      bottomRight: Radius.elliptical(15.0, 6.0),
460
                    ),
461
                    child: Container(
462
                      color: Colors.red,
463
                      child: Container(
464
                        color: Colors.white,
465 466 467
                        child: RepaintBoundary(
                          child: Center(
                            child: Container(
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
                              color: Colors.black,
                              height: 10.0,
                              width: 10.0,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
    await expectLater(
      find.byType(RepaintBoundary).first,
486
      matchesGoldenFile('clip.ClipRRect.png'),
487
    );
488
  });
489 490 491

  testWidgets('ClipOval painting', (WidgetTester tester) async {
    await tester.pumpWidget(
492 493 494
      Center(
        child: RepaintBoundary(
          child: Container(
495
            color: Colors.white,
496
            child: Padding(
497
              padding: const EdgeInsets.all(100.0),
498
              child: SizedBox(
499 500
                height: 100.0,
                width: 100.0,
501
                child: Transform.rotate(
502
                  angle: 1.0, // radians
503 504
                  child: ClipOval(
                    child: Container(
505
                      color: Colors.red,
506
                      child: Container(
507
                        color: Colors.white,
508 509 510
                        child: RepaintBoundary(
                          child: Center(
                            child: Container(
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
                              color: Colors.black,
                              height: 10.0,
                              width: 10.0,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
    await expectLater(
      find.byType(RepaintBoundary).first,
529
      matchesGoldenFile('clip.ClipOval.png'),
530
    );
531
  });
532 533 534

  testWidgets('ClipPath painting', (WidgetTester tester) async {
    await tester.pumpWidget(
535 536 537
      Center(
        child: RepaintBoundary(
          child: Container(
538
            color: Colors.white,
539
            child: Padding(
540
              padding: const EdgeInsets.all(100.0),
541
              child: SizedBox(
542 543
                height: 100.0,
                width: 100.0,
544
                child: Transform.rotate(
545
                  angle: 1.0, // radians
546
                  child: ClipPath(
547
                    clipper: const ShapeBorderClipper(
548
                      shape: BeveledRectangleBorder(
549
                        borderRadius: BorderRadius.all(Radius.circular(20.0)),
550 551
                      ),
                    ),
552
                    child: Container(
553
                      color: Colors.red,
554
                      child: Container(
555
                        color: Colors.white,
556 557 558
                        child: RepaintBoundary(
                          child: Center(
                            child: Container(
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
                              color: Colors.black,
                              height: 10.0,
                              width: 10.0,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
    await expectLater(
      find.byType(RepaintBoundary).first,
577
      matchesGoldenFile('clip.ClipPath.png'),
578
    );
579
  });
580

581
  Center genPhysicalModel(Clip clipBehavior) {
582 583 584
    return Center(
      child: RepaintBoundary(
        child: Container(
585
          color: Colors.white,
586
          child: Padding(
587
            padding: const EdgeInsets.all(100.0),
588
            child: SizedBox(
589 590
              height: 100.0,
              width: 100.0,
591
              child: Transform.rotate(
592
                angle: 1.0, // radians
593
                child: PhysicalModel(
594
                  borderRadius: const BorderRadius.all(Radius.circular(20.0)),
595 596
                  color: Colors.red,
                  clipBehavior: clipBehavior,
597
                  child: Container(
598
                    color: Colors.white,
599 600 601
                    child: RepaintBoundary(
                      child: Center(
                        child: Container(
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
                          color: Colors.black,
                          height: 10.0,
                          width: 10.0,
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  testWidgets('PhysicalModel painting with Clip.antiAlias', (WidgetTester tester) async {
    await tester.pumpWidget(genPhysicalModel(Clip.antiAlias));
    await expectLater(
      find.byType(RepaintBoundary).first,
622
      matchesGoldenFile('clip.PhysicalModel.antiAlias.png'),
623
    );
624
  });
625 626 627 628 629

  testWidgets('PhysicalModel painting with Clip.hardEdge', (WidgetTester tester) async {
    await tester.pumpWidget(genPhysicalModel(Clip.hardEdge));
    await expectLater(
      find.byType(RepaintBoundary).first,
630
      matchesGoldenFile('clip.PhysicalModel.hardEdge.png'),
631
    );
632
  });
633 634 635 636 637 638 639

  // There will be bleeding edges on the rect edges, but there shouldn't be any bleeding edges on the
  // round corners.
  testWidgets('PhysicalModel painting with Clip.antiAliasWithSaveLayer', (WidgetTester tester) async {
    await tester.pumpWidget(genPhysicalModel(Clip.antiAliasWithSaveLayer));
    await expectLater(
      find.byType(RepaintBoundary).first,
640
      matchesGoldenFile('clip.PhysicalModel.antiAliasWithSaveLayer.png'),
641
    );
642
  });
643 644

  testWidgets('Default PhysicalModel painting', (WidgetTester tester) async {
645
    await tester.pumpWidget(
646 647 648
      Center(
        child: RepaintBoundary(
          child: Container(
649
            color: Colors.white,
650
            child: Padding(
651
              padding: const EdgeInsets.all(100.0),
652
              child: SizedBox(
653 654
                height: 100.0,
                width: 100.0,
655
                child: Transform.rotate(
656
                  angle: 1.0, // radians
657
                  child: PhysicalModel(
658
                    borderRadius: const BorderRadius.all(Radius.circular(20.0)),
659
                    color: Colors.red,
660
                    child: Container(
661
                      color: Colors.white,
662 663 664
                      child: RepaintBoundary(
                        child: Center(
                          child: Container(
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
                            color: Colors.black,
                            height: 10.0,
                            width: 10.0,
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
    await expectLater(
      find.byType(RepaintBoundary).first,
682
      matchesGoldenFile('clip.PhysicalModel.default.png'),
683
    );
684
  });
685 686

  Center genPhysicalShape(Clip clipBehavior) {
687 688 689
    return Center(
      child: RepaintBoundary(
        child: Container(
690
          color: Colors.white,
691
          child: Padding(
692
            padding: const EdgeInsets.all(100.0),
693
            child: SizedBox(
694 695
              height: 100.0,
              width: 100.0,
696
              child: Transform.rotate(
697
                angle: 1.0, // radians
698
                child: PhysicalShape(
699
                  clipper: const ShapeBorderClipper(
700
                    shape: BeveledRectangleBorder(
701
                      borderRadius: BorderRadius.all(Radius.circular(20.0)),
702 703 704 705
                    ),
                  ),
                  clipBehavior: clipBehavior,
                  color: Colors.red,
706
                  child: Container(
707
                    color: Colors.white,
708 709 710
                    child: RepaintBoundary(
                      child: Center(
                        child: Container(
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
                          color: Colors.black,
                          height: 10.0,
                          width: 10.0,
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  testWidgets('PhysicalShape painting with Clip.antiAlias', (WidgetTester tester) async {
    await tester.pumpWidget(genPhysicalShape(Clip.antiAlias));
    await expectLater(
      find.byType(RepaintBoundary).first,
731
      matchesGoldenFile('clip.PhysicalShape.antiAlias.png'),
732
    );
733
  });
734 735 736 737 738

  testWidgets('PhysicalShape painting with Clip.hardEdge', (WidgetTester tester) async {
    await tester.pumpWidget(genPhysicalShape(Clip.hardEdge));
    await expectLater(
      find.byType(RepaintBoundary).first,
739
      matchesGoldenFile('clip.PhysicalShape.hardEdge.png'),
740
    );
741
  });
742 743 744 745 746

  testWidgets('PhysicalShape painting with Clip.antiAliasWithSaveLayer', (WidgetTester tester) async {
    await tester.pumpWidget(genPhysicalShape(Clip.antiAliasWithSaveLayer));
    await expectLater(
      find.byType(RepaintBoundary).first,
747
      matchesGoldenFile('clip.PhysicalShape.antiAliasWithSaveLayer.png'),
748
    );
749
  });
750 751 752

  testWidgets('PhysicalShape painting', (WidgetTester tester) async {
    await tester.pumpWidget(
753 754 755
      Center(
        child: RepaintBoundary(
          child: Container(
756
            color: Colors.white,
757
            child: Padding(
758
              padding: const EdgeInsets.all(100.0),
759
              child: SizedBox(
760 761
                height: 100.0,
                width: 100.0,
762
                child: Transform.rotate(
763
                  angle: 1.0, // radians
764
                  child: PhysicalShape(
765
                    clipper: const ShapeBorderClipper(
766
                      shape: BeveledRectangleBorder(
767
                        borderRadius: BorderRadius.all(Radius.circular(20.0)),
768 769 770
                      ),
                    ),
                    color: Colors.red,
771
                    child: Container(
772
                      color: Colors.white,
773 774 775
                      child: RepaintBoundary(
                        child: Center(
                          child: Container(
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792
                            color: Colors.black,
                            height: 10.0,
                            width: 10.0,
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
    await expectLater(
      find.byType(RepaintBoundary).first,
793
      matchesGoldenFile('clip.PhysicalShape.default.png'),
794
    );
795
  });
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852

  testWidgets('ClipPath.shape', (WidgetTester tester) async {
    final List<String> logs = <String>[];
    final ShapeBorder shape = TestBorder((String message) { logs.add(message); });
    Widget buildClipPath() {
      return ClipPath.shape(
        shape: shape,
        child: const SizedBox(width: 100.0, height: 100.0),
      );
    }
    final Widget clipPath = buildClipPath();
    // verify that a regular clip works as one would expect
    logs.add('--0');
    await tester.pumpWidget(clipPath);
    // verify that pumping again doesn't recompute the clip
    // even though the widget itself is new (the shape doesn't change identity)
    logs.add('--1');
    await tester.pumpWidget(buildClipPath());
    // verify that ClipPath passes the TextDirection on to its shape
    logs.add('--2');
    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: clipPath,
    ));
    // verify that changing the text direction from LTR to RTL has an effect
    // even though the widget itself is identical
    logs.add('--3');
    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.rtl,
      child: clipPath,
    ));
    // verify that pumping again with a text direction has no effect
    logs.add('--4');
    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.rtl,
      child: buildClipPath(),
    ));
    logs.add('--5');
    // verify that changing the text direction and the widget at the same time
    // works as expected
    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: clipPath,
    ));
    expect(logs, <String>[
      '--0',
      'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) null',
      '--1',
      '--2',
      'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
      '--3',
      'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.rtl',
      '--4',
      '--5',
      'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
    ]);
  });
853 854 855 856 857 858 859

  testWidgets('CustomClipper reclips when notified', (WidgetTester tester) async {
    final ValueNotifier<Rect> clip = ValueNotifier<Rect>(const Rect.fromLTWH(50.0, 50.0, 100.0, 100.0));

    await tester.pumpWidget(
      ClipRect(
        clipper: NotifyClipper<Rect>(clip: clip),
860
        child: const Placeholder(),
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
      ),
    );

    expect(tester.renderObject(find.byType(ClipRect)).paint, paints
      ..save()
      ..clipRect(rect: const Rect.fromLTWH(50.0, 50.0, 100.0, 100.0))
      ..save()
      ..path() // Placeholder
      ..restore()
      ..restore(),
    );

    expect(tester.renderObject(find.byType(ClipRect)).debugNeedsPaint, isFalse);
    clip.value = const Rect.fromLTWH(50.0, 50.0, 150.0, 100.0);
    expect(tester.renderObject(find.byType(ClipRect)).debugNeedsPaint, isTrue);

    expect(tester.renderObject(find.byType(ClipRect)).paint, paints
      ..save()
      ..clipRect(rect: const Rect.fromLTWH(50.0, 50.0, 150.0, 100.0))
      ..save()
      ..path() // Placeholder
      ..restore()
      ..restore(),
    );
  });
Ian Hickson's avatar
Ian Hickson committed
886
}