custom_paint_test.dart 7.07 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// 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
import 'package:flutter_test/flutter_test.dart';
8

9
class TestCustomPainter extends CustomPainter {
10
  TestCustomPainter({ required this.log, this.name });
11

12 13
  final List<String?> log;
  final String? name;
14

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

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

24 25 26 27 28 29
class TestCustomPainterWithCustomSemanticsBuilder extends TestCustomPainter {
  TestCustomPainterWithCustomSemanticsBuilder() : super(log: <String>[]);

  @override
  SemanticsBuilderCallback get semanticsBuilder => (Size size) {
    const Key key = Key('0');
30
    const Rect rect = Rect.zero;
31 32 33 34 35 36 37 38
    const SemanticsProperties semanticsProperties = SemanticsProperties();
    return <CustomPainterSemantics>[
      const CustomPainterSemantics(key: key, rect: rect, properties: semanticsProperties),
      const CustomPainterSemantics(key: key, rect: rect, properties: semanticsProperties),
    ];
  };
}

39 40 41
class MockCanvas extends Fake implements Canvas {
  int saveCount = 0;
  int saveCountDelta = 1;
42

43 44 45 46 47 48 49 50 51 52 53 54 55
  @override
  int getSaveCount() {
    return saveCount += saveCountDelta;
  }

  @override
  void save() { }
}

class MockPaintingContext extends Fake implements PaintingContext {
  @override
  final MockCanvas canvas = MockCanvas();
}
56

57
void main() {
58
  testWidgets('Control test for custom painting', (WidgetTester tester) async {
59
    final List<String?> log = <String?>[];
60 61
    await tester.pumpWidget(CustomPaint(
      painter: TestCustomPainter(
62
        log: log,
63
        name: 'background',
64
      ),
65
      foregroundPainter: TestCustomPainter(
66
        log: log,
67
        name: 'foreground',
68
      ),
69 70
      child: CustomPaint(
        painter: TestCustomPainter(
71
          log: log,
72 73 74
          name: 'child',
        ),
      ),
75
    ));
76

77
    expect(log, equals(<String>['background', 'child', 'foreground']));
78
  });
Ian Hickson's avatar
Ian Hickson committed
79

80
  testWidgets('Throws FlutterError on custom painter incorrect restore/save calls', (WidgetTester tester) async {
81
    final GlobalKey target = GlobalKey();
82
    final List<String?> log = <String?>[];
83 84 85 86 87
    await tester.pumpWidget(CustomPaint(
      key: target,
      isComplex: true,
      painter: TestCustomPainter(log: log),
    ));
88
    final RenderCustomPaint renderCustom = target.currentContext!.findRenderObject()! as RenderCustomPaint;
89 90
    final MockPaintingContext paintingContext = MockPaintingContext();
    final MockCanvas canvas = paintingContext.canvas;
91 92

    FlutterError getError() {
93
      late FlutterError error;
94
      try {
95
        renderCustom.paint(paintingContext, Offset.zero);
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
      } 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'
111
      '   matching call to restore().\n',
112 113
    ));

114
    canvas.saveCountDelta = -1;
115 116 117 118 119 120 121 122 123
    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'
124
      '   saveLayer().\n',
125 126
    ));

127
    canvas.saveCountDelta = 2;
128 129 130
    error = getError();
    expect(error.toStringDeep(), contains('2 more times'));

131
    canvas.saveCountDelta = -2;
132 133 134 135
    error = getError();
    expect(error.toStringDeep(), contains('2 more times'));
  });

Ian Hickson's avatar
Ian Hickson committed
136
  testWidgets('CustomPaint sizing', (WidgetTester tester) async {
137
    final GlobalKey target = GlobalKey();
Ian Hickson's avatar
Ian Hickson committed
138

139
    await tester.pumpWidget(Center(
140
      child: CustomPaint(key: target),
Ian Hickson's avatar
Ian Hickson committed
141
    ));
142
    expect(target.currentContext!.size, Size.zero);
Ian Hickson's avatar
Ian Hickson committed
143

144
    await tester.pumpWidget(Center(
145
      child: CustomPaint(key: target, child: Container()),
Ian Hickson's avatar
Ian Hickson committed
146
    ));
147
    expect(target.currentContext!.size, const Size(800.0, 600.0));
Ian Hickson's avatar
Ian Hickson committed
148

149
    await tester.pumpWidget(Center(
150
      child: CustomPaint(key: target, size: const Size(20.0, 20.0)),
Ian Hickson's avatar
Ian Hickson committed
151
    ));
152
    expect(target.currentContext!.size, const Size(20.0, 20.0));
Ian Hickson's avatar
Ian Hickson committed
153

154
    await tester.pumpWidget(Center(
155
      child: CustomPaint(key: target, size: const Size(2000.0, 100.0)),
Ian Hickson's avatar
Ian Hickson committed
156
    ));
157
    expect(target.currentContext!.size, const Size(800.0, 100.0));
Ian Hickson's avatar
Ian Hickson committed
158

159
    await tester.pumpWidget(Center(
160
      child: CustomPaint(key: target, size: Size.zero, child: Container()),
Ian Hickson's avatar
Ian Hickson committed
161
    ));
162
    expect(target.currentContext!.size, const Size(800.0, 600.0));
Ian Hickson's avatar
Ian Hickson committed
163

164
    await tester.pumpWidget(Center(
165
      child: CustomPaint(key: target, child: const SizedBox(height: 0.0, width: 0.0)),
Ian Hickson's avatar
Ian Hickson committed
166
    ));
167
    expect(target.currentContext!.size, Size.zero);
Ian Hickson's avatar
Ian Hickson committed
168 169

  });
170 171

  testWidgets('Raster cache hints', (WidgetTester tester) async {
172
    final GlobalKey target = GlobalKey();
173

174
    final List<String?> log = <String?>[];
175
    await tester.pumpWidget(CustomPaint(
176 177
      key: target,
      isComplex: true,
178
      painter: TestCustomPainter(log: log),
179
    ));
180
    RenderCustomPaint renderCustom = target.currentContext!.findRenderObject()! as RenderCustomPaint;
181 182 183
    expect(renderCustom.isComplex, true);
    expect(renderCustom.willChange, false);

184
    await tester.pumpWidget(CustomPaint(
185 186
      key: target,
      willChange: true,
187
      foregroundPainter: TestCustomPainter(log: log),
188
    ));
189
    renderCustom = target.currentContext!.findRenderObject()! as RenderCustomPaint;
190 191 192
    expect(renderCustom.isComplex, false);
    expect(renderCustom.willChange, true);
  });
193 194 195 196 197

  test('Raster cache hints cannot be set with null painters', () {
    expect(() => CustomPaint(isComplex: true), throwsAssertionError);
    expect(() => CustomPaint(willChange: true), throwsAssertionError);
  });
198 199 200 201 202 203 204 205 206 207

  test('RenderCustomPaint consults preferred size for intrinsics when it has no child', () {
    final RenderCustomPaint inner = RenderCustomPaint(preferredSize: const Size(20, 30));
    expect(inner.getMinIntrinsicWidth(double.infinity), 20);
    expect(inner.getMaxIntrinsicWidth(double.infinity), 20);
    expect(inner.getMinIntrinsicHeight(double.infinity), 30);
    expect(inner.getMaxIntrinsicHeight(double.infinity), 30);
  });

  test('RenderCustomPaint does not return infinity for its intrinsics', () {
208
    final RenderCustomPaint inner = RenderCustomPaint(preferredSize: Size.infinite);
209 210 211 212 213
    expect(inner.getMinIntrinsicWidth(double.infinity), 0);
    expect(inner.getMaxIntrinsicWidth(double.infinity), 0);
    expect(inner.getMinIntrinsicHeight(double.infinity), 0);
    expect(inner.getMaxIntrinsicHeight(double.infinity), 0);
  });
214
}