custom_paint_test.dart 6.45 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
class MockCanvas extends Fake implements Canvas {
  int saveCount = 0;
  int saveCountDelta = 1;
27

28 29 30 31 32 33 34 35 36 37 38 39 40
  @override
  int getSaveCount() {
    return saveCount += saveCountDelta;
  }

  @override
  void save() { }
}

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

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

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

65
  testWidgets('Throws FlutterError on custom painter incorrect restore/save calls', (WidgetTester tester) async {
66
    final GlobalKey target = GlobalKey();
67
    final List<String?> log = <String?>[];
68 69 70 71 72
    await tester.pumpWidget(CustomPaint(
      key: target,
      isComplex: true,
      painter: TestCustomPainter(log: log),
    ));
73
    final RenderCustomPaint renderCustom = target.currentContext!.findRenderObject()! as RenderCustomPaint;
74 75
    final MockPaintingContext paintingContext = MockPaintingContext();
    final MockCanvas canvas = paintingContext.canvas;
76 77

    FlutterError getError() {
78
      late FlutterError error;
79
      try {
80
        renderCustom.paint(paintingContext, Offset.zero);
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
      } 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'
96
      '   matching call to restore().\n',
97 98
    ));

99
    canvas.saveCountDelta = -1;
100 101 102 103 104 105 106 107 108
    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'
109
      '   saveLayer().\n',
110 111
    ));

112
    canvas.saveCountDelta = 2;
113 114 115
    error = getError();
    expect(error.toStringDeep(), contains('2 more times'));

116
    canvas.saveCountDelta = -2;
117 118 119 120
    error = getError();
    expect(error.toStringDeep(), contains('2 more times'));
  });

Ian Hickson's avatar
Ian Hickson committed
121
  testWidgets('CustomPaint sizing', (WidgetTester tester) async {
122
    final GlobalKey target = GlobalKey();
Ian Hickson's avatar
Ian Hickson committed
123

124
    await tester.pumpWidget(Center(
125
      child: CustomPaint(key: target),
Ian Hickson's avatar
Ian Hickson committed
126
    ));
127
    expect(target.currentContext!.size, Size.zero);
Ian Hickson's avatar
Ian Hickson committed
128

129
    await tester.pumpWidget(Center(
130
      child: CustomPaint(key: target, child: Container()),
Ian Hickson's avatar
Ian Hickson committed
131
    ));
132
    expect(target.currentContext!.size, const Size(800.0, 600.0));
Ian Hickson's avatar
Ian Hickson committed
133

134
    await tester.pumpWidget(Center(
135
      child: CustomPaint(key: target, size: const Size(20.0, 20.0)),
Ian Hickson's avatar
Ian Hickson committed
136
    ));
137
    expect(target.currentContext!.size, const Size(20.0, 20.0));
Ian Hickson's avatar
Ian Hickson committed
138

139
    await tester.pumpWidget(Center(
140
      child: CustomPaint(key: target, size: const Size(2000.0, 100.0)),
Ian Hickson's avatar
Ian Hickson committed
141
    ));
142
    expect(target.currentContext!.size, const Size(800.0, 100.0));
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, child: const SizedBox.shrink()),
Ian Hickson's avatar
Ian Hickson committed
151
    ));
152
    expect(target.currentContext!.size, Size.zero);
Ian Hickson's avatar
Ian Hickson committed
153 154

  });
155 156

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

159
    final List<String?> log = <String?>[];
160
    await tester.pumpWidget(CustomPaint(
161 162
      key: target,
      isComplex: true,
163
      painter: TestCustomPainter(log: log),
164
    ));
165
    RenderCustomPaint renderCustom = target.currentContext!.findRenderObject()! as RenderCustomPaint;
166 167 168
    expect(renderCustom.isComplex, true);
    expect(renderCustom.willChange, false);

169
    await tester.pumpWidget(CustomPaint(
170 171
      key: target,
      willChange: true,
172
      foregroundPainter: TestCustomPainter(log: log),
173
    ));
174
    renderCustom = target.currentContext!.findRenderObject()! as RenderCustomPaint;
175 176 177
    expect(renderCustom.isComplex, false);
    expect(renderCustom.willChange, true);
  });
178 179 180 181 182

  test('Raster cache hints cannot be set with null painters', () {
    expect(() => CustomPaint(isComplex: true), throwsAssertionError);
    expect(() => CustomPaint(willChange: true), throwsAssertionError);
  });
183 184 185 186 187 188 189 190 191 192

  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', () {
193
    final RenderCustomPaint inner = RenderCustomPaint(preferredSize: Size.infinite);
194 195 196 197 198
    expect(inner.getMinIntrinsicWidth(double.infinity), 0);
    expect(inner.getMaxIntrinsicWidth(double.infinity), 0);
    expect(inner.getMinIntrinsicHeight(double.infinity), 0);
    expect(inner.getMaxIntrinsicHeight(double.infinity), 0);
  });
199
}