custom_paint_test.dart 7.39 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/rendering.dart';
6
import 'package:flutter/widgets.dart';
7 8
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
9 10 11 12

class TestCustomPainter extends CustomPainter {
  TestCustomPainter({ this.log, this.name });

13 14
  final List<String> log;
  final String name;
15

16
  @override
17 18 19 20
  void paint(Canvas canvas, Size size) {
    log.add(name);
  }

21
  @override
22 23 24
  bool shouldRepaint(TestCustomPainter oldPainter) => true;
}

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
class TestCustomPainterWithCustomSemanticsBuilder extends TestCustomPainter {
  TestCustomPainterWithCustomSemanticsBuilder() : super(log: <String>[]);

  @override
  SemanticsBuilderCallback get semanticsBuilder => (Size size) {
    const Key key = Key('0');
    const Rect rect = Rect.fromLTRB(0, 0, 0, 0);
    const SemanticsProperties semanticsProperties = SemanticsProperties();
    return <CustomPainterSemantics>[
      const CustomPainterSemantics(key: key, rect: rect, properties: semanticsProperties),
      const CustomPainterSemantics(key: key, rect: rect, properties: semanticsProperties),
    ];
  };
}

class MockCanvas extends Mock implements Canvas {}

class MockPaintingContext extends Mock implements PaintingContext {}

44
void main() {
45
  testWidgets('Control test for custom painting', (WidgetTester tester) async {
46
    final List<String> log = <String>[];
47 48
    await tester.pumpWidget(CustomPaint(
      painter: TestCustomPainter(
49
        log: log,
50
        name: 'background',
51
      ),
52
      foregroundPainter: TestCustomPainter(
53
        log: log,
54
        name: 'foreground',
55
      ),
56 57
      child: CustomPaint(
        painter: TestCustomPainter(
58
          log: log,
59 60 61
          name: 'child',
        ),
      ),
62
    ));
63

64
    expect(log, equals(<String>['background', 'child', 'foreground']));
65
  });
Ian Hickson's avatar
Ian Hickson committed
66

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
  testWidgets('Throws FlutterError on custom painter incorrect restore/save calls', (
      WidgetTester tester) async {
    final GlobalKey target = GlobalKey();
    final List<String> log = <String>[];
    await tester.pumpWidget(CustomPaint(
      key: target,
      isComplex: true,
      painter: TestCustomPainter(log: log),
    ));
    final RenderCustomPaint renderCustom = target.currentContext.findRenderObject();
    final Canvas canvas = MockCanvas();
    int saveCount = 0;
    when(canvas.getSaveCount()).thenAnswer((_) => saveCount++);
    final PaintingContext paintingContext = MockPaintingContext();
    when(paintingContext.canvas).thenReturn(canvas);

    FlutterError getError() {
      FlutterError error;
      try {
        renderCustom.paint(paintingContext, const Offset(0, 0));
      } on FlutterError catch (e) {
        error = e;
      }
      return error;
    }

    FlutterError error = getError();
    expect(error.toStringDeep(), equalsIgnoringHashCodes(
      'FlutterError\n'
      '   The TestCustomPainter#00000() custom painter called canvas.save()\n'
      '   or canvas.saveLayer() at least 1 more time than it called\n'
      '   canvas.restore().\n'
      '   This leaves the canvas in an inconsistent state and will probably\n'
      '   result in a broken display.\n'
      '   You must pair each call to save()/saveLayer() with a later\n'
      '   matching call to restore().\n'
    ));

    when(canvas.getSaveCount()).thenAnswer((_) => saveCount--);
    error = getError();
    expect(error.toStringDeep(), equalsIgnoringHashCodes(
      'FlutterError\n'
      '   The TestCustomPainter#00000() custom painter called\n'
      '   canvas.restore() 1 more time than it called canvas.save() or\n'
      '   canvas.saveLayer().\n'
      '   This leaves the canvas in an inconsistent state and will result\n'
      '   in a broken display.\n'
      '   You should only call restore() if you first called save() or\n'
      '   saveLayer().\n'
    ));

    when(canvas.getSaveCount()).thenAnswer((_) => saveCount += 2);
    error = getError();
    expect(error.toStringDeep(), contains('2 more times'));

    when(canvas.getSaveCount()).thenAnswer((_) => saveCount -= 2);
    error = getError();
    expect(error.toStringDeep(), contains('2 more times'));
  });

  testWidgets('assembleSemanticsNode throws FlutterError', (WidgetTester tester) async {
    final List<String> log = <String>[];
    final GlobalKey target = GlobalKey();
    await tester.pumpWidget(CustomPaint(
      key: target,
      isComplex: true,
      painter: TestCustomPainter(log: log),
    ));
    final RenderCustomPaint renderCustom = target.currentContext.findRenderObject();
    FlutterError error;
    try {
      renderCustom.assembleSemanticsNode(
        null,
        null,
        <SemanticsNode>[SemanticsNode()],
      );
    } on FlutterError catch (e) {
      error = e;
    }
    expect(error, isNotNull);
    expect(error.toStringDeep(), equalsIgnoringHashCodes(
      'FlutterError\n'
      '   RenderCustomPaint does not have a child widget but received a\n'
      '   non-empty list of child SemanticsNode:\n'
      '   SemanticsNode#1(Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible)\n'
    ));

    await tester.pumpWidget(CustomPaint(
      key: target,
      isComplex: true,
      painter: TestCustomPainterWithCustomSemanticsBuilder(),
    ));
    final dynamic exception = tester.takeException();
    expect(exception, isFlutterError);
    error = exception;
    expect(error.toStringDeep(), equalsIgnoringHashCodes(
      'FlutterError\n'
      '   Failed to update the list of CustomPainterSemantics:\n'
      '   - duplicate key [<\'0\'>] found at position 1\n'
    ));
  });

Ian Hickson's avatar
Ian Hickson committed
169
  testWidgets('CustomPaint sizing', (WidgetTester tester) async {
170
    final GlobalKey target = GlobalKey();
Ian Hickson's avatar
Ian Hickson committed
171

172
    await tester.pumpWidget(Center(
173
      child: CustomPaint(key: target),
Ian Hickson's avatar
Ian Hickson committed
174 175 176
    ));
    expect(target.currentContext.size, Size.zero);

177
    await tester.pumpWidget(Center(
178
      child: CustomPaint(key: target, child: Container()),
Ian Hickson's avatar
Ian Hickson committed
179 180 181
    ));
    expect(target.currentContext.size, const Size(800.0, 600.0));

182
    await tester.pumpWidget(Center(
183
      child: CustomPaint(key: target, size: const Size(20.0, 20.0)),
Ian Hickson's avatar
Ian Hickson committed
184 185 186
    ));
    expect(target.currentContext.size, const Size(20.0, 20.0));

187
    await tester.pumpWidget(Center(
188
      child: CustomPaint(key: target, size: const Size(2000.0, 100.0)),
Ian Hickson's avatar
Ian Hickson committed
189 190 191
    ));
    expect(target.currentContext.size, const Size(800.0, 100.0));

192
    await tester.pumpWidget(Center(
193
      child: CustomPaint(key: target, size: Size.zero, child: Container()),
Ian Hickson's avatar
Ian Hickson committed
194 195 196
    ));
    expect(target.currentContext.size, const Size(800.0, 600.0));

197
    await tester.pumpWidget(Center(
198
      child: CustomPaint(key: target, child: Container(height: 0.0, width: 0.0)),
Ian Hickson's avatar
Ian Hickson committed
199 200 201 202
    ));
    expect(target.currentContext.size, Size.zero);

  });
203 204

  testWidgets('Raster cache hints', (WidgetTester tester) async {
205
    final GlobalKey target = GlobalKey();
206 207

    final List<String> log = <String>[];
208
    await tester.pumpWidget(CustomPaint(
209 210
      key: target,
      isComplex: true,
211
      painter: TestCustomPainter(log: log),
212 213 214 215 216
    ));
    RenderCustomPaint renderCustom = target.currentContext.findRenderObject();
    expect(renderCustom.isComplex, true);
    expect(renderCustom.willChange, false);

217
    await tester.pumpWidget(CustomPaint(
218 219
      key: target,
      willChange: true,
220
      foregroundPainter: TestCustomPainter(log: log),
221 222 223 224 225
    ));
    renderCustom = target.currentContext.findRenderObject();
    expect(renderCustom.isComplex, false);
    expect(renderCustom.willChange, true);
  });
226
}