| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| class TestCustomPainter extends CustomPainter { |
| TestCustomPainter({ required this.log, this.name }); |
| |
| final List<String?> log; |
| final String? name; |
| |
| @override |
| void paint(Canvas canvas, Size size) { |
| log.add(name); |
| } |
| |
| @override |
| bool shouldRepaint(TestCustomPainter oldPainter) => true; |
| } |
| |
| class TestCustomPainterWithCustomSemanticsBuilder extends TestCustomPainter { |
| TestCustomPainterWithCustomSemanticsBuilder() : super(log: <String>[]); |
| |
| @override |
| SemanticsBuilderCallback get semanticsBuilder => (Size size) { |
| const Key key = Key('0'); |
| const Rect rect = Rect.zero; |
| 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 Fake implements Canvas { |
| int saveCount = 0; |
| int saveCountDelta = 1; |
| |
| @override |
| int getSaveCount() { |
| return saveCount += saveCountDelta; |
| } |
| |
| @override |
| void save() { } |
| } |
| |
| class MockPaintingContext extends Fake implements PaintingContext { |
| @override |
| final MockCanvas canvas = MockCanvas(); |
| } |
| |
| void main() { |
| testWidgets('Control test for custom painting', (WidgetTester tester) async { |
| final List<String?> log = <String?>[]; |
| await tester.pumpWidget(CustomPaint( |
| painter: TestCustomPainter( |
| log: log, |
| name: 'background', |
| ), |
| foregroundPainter: TestCustomPainter( |
| log: log, |
| name: 'foreground', |
| ), |
| child: CustomPaint( |
| painter: TestCustomPainter( |
| log: log, |
| name: 'child', |
| ), |
| ), |
| )); |
| |
| expect(log, equals(<String>['background', 'child', 'foreground'])); |
| }); |
| |
| 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()! as RenderCustomPaint; |
| final MockPaintingContext paintingContext = MockPaintingContext(); |
| final MockCanvas canvas = paintingContext.canvas; |
| |
| FlutterError getError() { |
| late FlutterError error; |
| try { |
| renderCustom.paint(paintingContext, Offset.zero); |
| } 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', |
| )); |
| |
| canvas.saveCountDelta = -1; |
| 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', |
| )); |
| |
| canvas.saveCountDelta = 2; |
| error = getError(); |
| expect(error.toStringDeep(), contains('2 more times')); |
| |
| canvas.saveCountDelta = -2; |
| error = getError(); |
| expect(error.toStringDeep(), contains('2 more times')); |
| }); |
| |
| testWidgets('CustomPaint sizing', (WidgetTester tester) async { |
| final GlobalKey target = GlobalKey(); |
| |
| await tester.pumpWidget(Center( |
| child: CustomPaint(key: target), |
| )); |
| expect(target.currentContext!.size, Size.zero); |
| |
| await tester.pumpWidget(Center( |
| child: CustomPaint(key: target, child: Container()), |
| )); |
| expect(target.currentContext!.size, const Size(800.0, 600.0)); |
| |
| await tester.pumpWidget(Center( |
| child: CustomPaint(key: target, size: const Size(20.0, 20.0)), |
| )); |
| expect(target.currentContext!.size, const Size(20.0, 20.0)); |
| |
| await tester.pumpWidget(Center( |
| child: CustomPaint(key: target, size: const Size(2000.0, 100.0)), |
| )); |
| expect(target.currentContext!.size, const Size(800.0, 100.0)); |
| |
| await tester.pumpWidget(Center( |
| child: CustomPaint(key: target, child: Container()), |
| )); |
| expect(target.currentContext!.size, const Size(800.0, 600.0)); |
| |
| await tester.pumpWidget(Center( |
| child: CustomPaint(key: target, child: const SizedBox(height: 0.0, width: 0.0)), |
| )); |
| expect(target.currentContext!.size, Size.zero); |
| |
| }); |
| |
| testWidgets('Raster cache hints', (WidgetTester tester) async { |
| final GlobalKey target = GlobalKey(); |
| |
| final List<String?> log = <String?>[]; |
| await tester.pumpWidget(CustomPaint( |
| key: target, |
| isComplex: true, |
| painter: TestCustomPainter(log: log), |
| )); |
| RenderCustomPaint renderCustom = target.currentContext!.findRenderObject()! as RenderCustomPaint; |
| expect(renderCustom.isComplex, true); |
| expect(renderCustom.willChange, false); |
| |
| await tester.pumpWidget(CustomPaint( |
| key: target, |
| willChange: true, |
| foregroundPainter: TestCustomPainter(log: log), |
| )); |
| renderCustom = target.currentContext!.findRenderObject()! as RenderCustomPaint; |
| expect(renderCustom.isComplex, false); |
| expect(renderCustom.willChange, true); |
| }); |
| |
| test('Raster cache hints cannot be set with null painters', () { |
| expect(() => CustomPaint(isComplex: true), throwsAssertionError); |
| expect(() => CustomPaint(willChange: true), throwsAssertionError); |
| }); |
| |
| 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', () { |
| final RenderCustomPaint inner = RenderCustomPaint(preferredSize: Size.infinite); |
| expect(inner.getMinIntrinsicWidth(double.infinity), 0); |
| expect(inner.getMaxIntrinsicWidth(double.infinity), 0); |
| expect(inner.getMinIntrinsicHeight(double.infinity), 0); |
| expect(inner.getMaxIntrinsicHeight(double.infinity), 0); |
| }); |
| } |