| // 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. |
| |
| // @dart = 2.8 |
| |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:mockito/mockito.dart'; |
| |
| class TestCustomPainter extends CustomPainter { |
| TestCustomPainter({ 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.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 {} |
| |
| 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 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() as RenderCustomPaint; |
| dynamic 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" |
| )); |
| }); |
| |
| 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, size: Size.zero, 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); |
| }); |
| } |