mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Closes https://github.com/flutter/flutter/issues/165743 ### Description - Adds `rsuperellipse` to `mock_canvas.dart` to verify `drawRSuperellipse` calls - Adds tests for `paints..rsuperellipse` ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing.
450 lines
13 KiB
Dart
450 lines
13 KiB
Dart
// 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 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
class MyPainter extends CustomPainter {
|
|
const MyPainter({required this.color});
|
|
|
|
final Color color;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
canvas.drawColor(color, BlendMode.color);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(MyPainter oldDelegate) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
class MethodAndArguments {
|
|
const MethodAndArguments(this.method, this.arguments);
|
|
|
|
final Symbol method;
|
|
final List<dynamic> arguments;
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (!(other is MethodAndArguments && other.method == method)) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < arguments.length; i++) {
|
|
if (arguments[i] != other.arguments[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => method.hashCode;
|
|
|
|
@override
|
|
String toString() => '$method, $arguments';
|
|
}
|
|
|
|
void main() {
|
|
group('something', () {
|
|
testWidgets('matches when the predicate returns true', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const CustomPaint(
|
|
painter: MyPainter(color: Colors.transparent),
|
|
child: SizedBox(width: 50, height: 50),
|
|
),
|
|
);
|
|
|
|
final List<MethodAndArguments> methodsAndArguments = <MethodAndArguments>[];
|
|
|
|
expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
paints..something((Symbol method, List<dynamic> arguments) {
|
|
methodsAndArguments.add(MethodAndArguments(method, arguments));
|
|
return method == #drawColor;
|
|
}),
|
|
);
|
|
|
|
expect(methodsAndArguments, <MethodAndArguments>[
|
|
const MethodAndArguments(#save, <dynamic>[]),
|
|
const MethodAndArguments(#drawColor, <dynamic>[Colors.transparent, BlendMode.color]),
|
|
// The #restore call is never evaluated
|
|
]);
|
|
});
|
|
|
|
testWidgets('fails when the predicate always returns false', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const CustomPaint(
|
|
painter: MyPainter(color: Colors.transparent),
|
|
child: SizedBox(width: 50, height: 50),
|
|
),
|
|
);
|
|
|
|
final List<MethodAndArguments> methodsAndArguments = <MethodAndArguments>[];
|
|
|
|
expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
isNot(
|
|
paints..something((Symbol method, List<dynamic> arguments) {
|
|
methodsAndArguments.add(MethodAndArguments(method, arguments));
|
|
return false;
|
|
}),
|
|
),
|
|
);
|
|
|
|
expect(methodsAndArguments, <MethodAndArguments>[
|
|
const MethodAndArguments(#save, <dynamic>[]),
|
|
const MethodAndArguments(#drawColor, <dynamic>[Colors.transparent, BlendMode.color]),
|
|
const MethodAndArguments(#restore, <dynamic>[]),
|
|
]);
|
|
});
|
|
|
|
testWidgets('fails when the predicate throws', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const CustomPaint(
|
|
painter: MyPainter(color: Colors.transparent),
|
|
child: SizedBox(width: 50, height: 50),
|
|
),
|
|
);
|
|
|
|
final List<MethodAndArguments> methodsAndArguments = <MethodAndArguments>[];
|
|
|
|
expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
isNot(
|
|
paints..something((Symbol method, List<dynamic> arguments) {
|
|
methodsAndArguments.add(MethodAndArguments(method, arguments));
|
|
if (method == #save) {
|
|
return false;
|
|
}
|
|
if (method == #drawColor) {
|
|
fail('fail');
|
|
}
|
|
return true;
|
|
}),
|
|
),
|
|
);
|
|
|
|
expect(methodsAndArguments, <MethodAndArguments>[
|
|
const MethodAndArguments(#save, <dynamic>[]),
|
|
const MethodAndArguments(#drawColor, <dynamic>[Colors.transparent, BlendMode.color]),
|
|
// The #restore call is never evaluated
|
|
]);
|
|
});
|
|
});
|
|
|
|
group('everything', () {
|
|
testWidgets('matches when the predicate always returns true', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const CustomPaint(
|
|
painter: MyPainter(color: Colors.transparent),
|
|
child: SizedBox(width: 50, height: 50),
|
|
),
|
|
);
|
|
|
|
final List<MethodAndArguments> methodsAndArguments = <MethodAndArguments>[];
|
|
|
|
expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
paints..everything((Symbol method, List<dynamic> arguments) {
|
|
methodsAndArguments.add(MethodAndArguments(method, arguments));
|
|
return true;
|
|
}),
|
|
);
|
|
|
|
expect(methodsAndArguments, <MethodAndArguments>[
|
|
const MethodAndArguments(#save, <dynamic>[]),
|
|
const MethodAndArguments(#drawColor, <dynamic>[Colors.transparent, BlendMode.color]),
|
|
const MethodAndArguments(#restore, <dynamic>[]),
|
|
]);
|
|
});
|
|
|
|
testWidgets('fails when the predicate returns false', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const CustomPaint(
|
|
painter: MyPainter(color: Colors.transparent),
|
|
child: SizedBox(width: 50, height: 50),
|
|
),
|
|
);
|
|
|
|
final List<MethodAndArguments> methodsAndArguments = <MethodAndArguments>[];
|
|
|
|
expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
isNot(
|
|
paints..everything((Symbol method, List<dynamic> arguments) {
|
|
methodsAndArguments.add(MethodAndArguments(method, arguments));
|
|
// returns false on #drawColor
|
|
return method == #restore || method == #save;
|
|
}),
|
|
),
|
|
);
|
|
|
|
expect(methodsAndArguments, <MethodAndArguments>[
|
|
const MethodAndArguments(#save, <dynamic>[]),
|
|
const MethodAndArguments(#drawColor, <dynamic>[Colors.transparent, BlendMode.color]),
|
|
// The #restore call is never evaluated
|
|
]);
|
|
});
|
|
|
|
testWidgets('fails if the predicate ever throws', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const CustomPaint(
|
|
painter: MyPainter(color: Colors.transparent),
|
|
child: SizedBox(width: 50, height: 50),
|
|
),
|
|
);
|
|
|
|
final List<MethodAndArguments> methodsAndArguments = <MethodAndArguments>[];
|
|
|
|
expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
isNot(
|
|
paints..everything((Symbol method, List<dynamic> arguments) {
|
|
methodsAndArguments.add(MethodAndArguments(method, arguments));
|
|
if (method == #drawColor) {
|
|
fail('failed ');
|
|
}
|
|
return true;
|
|
}),
|
|
),
|
|
);
|
|
|
|
expect(methodsAndArguments, <MethodAndArguments>[
|
|
const MethodAndArguments(#save, <dynamic>[]),
|
|
const MethodAndArguments(#drawColor, <dynamic>[Colors.transparent, BlendMode.color]),
|
|
// The #restore call is never evaluated
|
|
]);
|
|
});
|
|
});
|
|
|
|
group('arc', () {
|
|
final Rect rect = Offset.zero & const Size.square(50);
|
|
const double startAngle = math.pi / 4;
|
|
const double sweepAngle = math.pi / 2;
|
|
const bool useCenter = false;
|
|
final Paint paint = Paint()..color = Colors.blue;
|
|
|
|
Future<void> pumpPainter(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Center(
|
|
child: CustomPaint(
|
|
painter: _ArcPainter(
|
|
startAngle: startAngle,
|
|
sweepAngle: sweepAngle,
|
|
useCenter: useCenter,
|
|
paint: paint,
|
|
),
|
|
size: rect.size,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
testWidgets('matches when rect is correct', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
expect(tester.renderObject(find.byType(CustomPaint)), paints..arc(rect: rect));
|
|
});
|
|
|
|
testWidgets('does not match when rect is incorrect', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
|
|
expect(
|
|
() => expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
paints..arc(rect: rect.deflate(10)),
|
|
),
|
|
throwsA(
|
|
isA<TestFailure>().having(
|
|
(TestFailure failure) => failure.message,
|
|
'message',
|
|
contains(
|
|
'It called drawArc with a paint whose rect, '
|
|
'Rect.fromLTRB(0.0, 0.0, 50.0, 50.0), was not exactly the '
|
|
'expected rect (Rect.fromLTRB(10.0, 10.0, 40.0, 40.0)).',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('matches when startAngle is correct', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
expect(tester.renderObject(find.byType(CustomPaint)), paints..arc(startAngle: startAngle));
|
|
});
|
|
|
|
testWidgets('does not match when startAngle is incorrect', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
|
|
expect(
|
|
() => expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
paints..arc(startAngle: startAngle * 2),
|
|
),
|
|
throwsA(
|
|
isA<TestFailure>().having(
|
|
(TestFailure failure) => failure.message,
|
|
'message',
|
|
contains(
|
|
'It called drawArc with a start angle, 0.7853981633974483, which '
|
|
'was not exactly the expected start angle (1.5707963267948966).',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('matches when sweepAngle is correct', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
expect(tester.renderObject(find.byType(CustomPaint)), paints..arc(sweepAngle: sweepAngle));
|
|
});
|
|
|
|
testWidgets('does not match when sweepAngle is incorrect', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
|
|
expect(
|
|
() => expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
paints..arc(sweepAngle: sweepAngle * 2),
|
|
),
|
|
throwsA(
|
|
isA<TestFailure>().having(
|
|
(TestFailure failure) => failure.message,
|
|
'message',
|
|
contains(
|
|
'It called drawArc with a sweep angle, 1.5707963267948966, which '
|
|
'was not exactly the expected sweep angle (3.141592653589793).',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('matches when useCenter is correct', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
expect(tester.renderObject(find.byType(CustomPaint)), paints..arc(useCenter: useCenter));
|
|
});
|
|
|
|
testWidgets('does not match when useCenter is incorrect', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
|
|
expect(
|
|
() => expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
paints..arc(useCenter: !useCenter),
|
|
),
|
|
throwsA(
|
|
isA<TestFailure>().having(
|
|
(TestFailure failure) => failure.message,
|
|
'message',
|
|
contains(
|
|
'It called drawArc with a useCenter value, false, which was not '
|
|
'exactly the expected value (true)',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
});
|
|
|
|
group('rsuperellipse', () {
|
|
final RSuperellipse rsuperellipse = RSuperellipse.fromRectAndRadius(
|
|
Offset.zero & const Size.square(50),
|
|
const Radius.circular(5),
|
|
);
|
|
final Paint paint = Paint()..color = Colors.blue;
|
|
|
|
Future<void> pumpPainter(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Center(
|
|
child: CustomPaint(
|
|
painter: _RSuperellipsePainter(rsuperellipse: rsuperellipse, paint: paint),
|
|
size: rsuperellipse.outerRect.size,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
testWidgets('matches when rsuperellipse is correct', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
paints..rsuperellipse(rsuperellipse: rsuperellipse),
|
|
);
|
|
});
|
|
|
|
testWidgets('does not match when rsuperellipse is incorrect', (WidgetTester tester) async {
|
|
await pumpPainter(tester);
|
|
|
|
expect(
|
|
() => expect(
|
|
tester.renderObject(find.byType(CustomPaint)),
|
|
paints..rsuperellipse(rsuperellipse: rsuperellipse.deflate(10)),
|
|
),
|
|
throwsA(
|
|
isA<TestFailure>().having(
|
|
(TestFailure failure) => failure.message,
|
|
'message',
|
|
contains(
|
|
'It called drawRSuperellipse with a rounded superellipse, '
|
|
'RSuperellipse.fromLTRBR(0.0, 0.0, 50.0, 50.0, 5.0), which was '
|
|
'not exactly the expected rounded superellipse '
|
|
'(RSuperellipse.fromLTRBR(10.0, 10.0, 40.0, 40.0, 0.0))',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
class _ArcPainter extends CustomPainter {
|
|
const _ArcPainter({
|
|
required this.startAngle,
|
|
required this.sweepAngle,
|
|
required this.useCenter,
|
|
required Paint paint,
|
|
}) : _paint = paint;
|
|
|
|
final double startAngle;
|
|
|
|
final double sweepAngle;
|
|
|
|
final bool useCenter;
|
|
|
|
final Paint _paint;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
canvas.drawArc(Offset.zero & size, startAngle, sweepAngle, useCenter, _paint);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(MyPainter oldDelegate) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class _RSuperellipsePainter extends CustomPainter {
|
|
const _RSuperellipsePainter({required this.rsuperellipse, required Paint paint}) : _paint = paint;
|
|
|
|
final RSuperellipse rsuperellipse;
|
|
|
|
final Paint _paint;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
canvas.drawRSuperellipse(rsuperellipse, _paint);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(MyPainter oldDelegate) {
|
|
return true;
|
|
}
|
|
}
|