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.
This commit is contained in:
Kostia Sokolovskyi
2025-03-29 00:05:25 +01:00
committed by GitHub
parent 713d906125
commit 05de81ed7e
2 changed files with 141 additions and 0 deletions

View File

@@ -289,6 +289,30 @@ abstract class PaintPattern {
PaintingStyle style,
});
/// Indicates that a rounded superellipse is expected next.
///
/// The next rounded superellipse is examined. Any arguments that are passed
/// to this method are compared to the actual [Canvas.drawRSuperellipse]
/// call's arguments and any mismatches result in failure.
///
/// If no call to [Canvas.drawRSuperellipse] was made, then this results in
/// failure.
///
/// Any calls made between the last matched call (if any) and the
/// [Canvas.drawRSuperellipse] call are ignored.
///
/// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`)
/// are compared against the state of the [Paint] object after the
/// painting has completed, not at the time of the call. If the same [Paint]
/// object is reused multiple times, then this may not match the actual
/// arguments as they were seen by the method.
void rsuperellipse({
RSuperellipse? rsuperellipse,
Color? color,
double? strokeWidth,
bool? hasMaskFilter,
});
/// Indicates that a rounded superellipse clip is expected next.
///
/// The next rounded superellipse clip is examined. Any arguments that are
@@ -916,6 +940,23 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher
);
}
@override
void rsuperellipse({
RSuperellipse? rsuperellipse,
Color? color,
double? strokeWidth,
bool? hasMaskFilter,
}) {
_predicates.add(
_RSuperellipsePaintPredicate(
rsuperellipse: rsuperellipse,
color: color,
strokeWidth: strokeWidth,
hasMaskFilter: hasMaskFilter,
),
);
}
@override
void clipRSuperellipse({RSuperellipse? rsuperellipse}) {
_predicates.add(_FunctionPaintPredicate(#clipRSuperellipse, <dynamic>[rsuperellipse]));
@@ -1450,6 +1491,38 @@ class _DRRectPaintPredicate extends _TwoParameterPaintPredicate<RRect, RRect> {
}) : super(#drawDRRect, 'a rounded rectangle outline', expected1: outer, expected2: inner);
}
class _RSuperellipsePaintPredicate extends _DrawCommandPaintPredicate {
_RSuperellipsePaintPredicate({
this.rsuperellipse,
super.color,
super.strokeWidth,
super.hasMaskFilter,
}) : super(#drawRSuperellipse, 'a rounded superellipse', 2, 1);
final RSuperellipse? rsuperellipse;
@override
void verifyArguments(List<dynamic> arguments) {
super.verifyArguments(arguments);
final RSuperellipse rsuperellipseArgument = arguments[0] as RSuperellipse;
if (rsuperellipse != null && rsuperellipseArgument != rsuperellipse) {
throw FlutterError(
'It called $methodName with a rounded superellipse, '
'$rsuperellipseArgument, which was not exactly the expected rounded '
'superellipse ($rsuperellipse).',
);
}
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (rsuperellipse != null) {
description.add('rounded superellipse $rsuperellipse');
}
}
}
class _CirclePaintPredicate extends _DrawCommandPaintPredicate {
_CirclePaintPredicate({
this.x,

View File

@@ -351,6 +351,56 @@ void main() {
);
});
});
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 {
@@ -379,3 +429,21 @@ class _ArcPainter extends CustomPainter {
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;
}
}