flutter/packages/flutter_test/test/mock_canvas_test.dart
Kostia Sokolovskyi 05de81ed7e
Add drawRSuperellipse support to mock_canvas. (#165744)
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.
2025-03-28 23:05:25 +00:00

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;
}
}