Unverified Commit b9438692 authored by Will Lockwood's avatar Will Lockwood Committed by GitHub

Fix `paints..something` and `paints..everything` succeeding when they should fail (#95993)

parent 76cf452f
......@@ -426,6 +426,10 @@ abstract class PaintPattern {
/// The predicate will be applied to each [Canvas] call until it returns false
/// or all of the method calls have been tested.
///
/// If the predicate returns false, then the [paints] [Matcher] is considered
/// to have failed. If all calls are tested without failing, then the [paints]
/// [Matcher] is considered a success.
///
/// If the predicate throws a [String], then the [paints] [Matcher] is
/// considered to have failed. The thrown string is used in the message
/// displayed from the test framework and should be complete sentence
......@@ -831,7 +835,11 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp
return false;
} on String catch (s) {
description.writeln(s);
description.write('The stack of the offending call was:\n${call.current.stackToString(indent: " ")}\n');
try {
description.write('The stack of the offending call was:\n${call.current.stackToString(indent: " ")}\n');
} on TypeError catch (_) {
// All calls have been evaluated
}
return false;
}
return true;
......@@ -1393,13 +1401,18 @@ class _SomethingPaintPredicate extends _PaintPredicate {
@override
void match(Iterator<RecordedInvocation> call) {
assert(predicate != null);
RecordedInvocation currentCall;
bool testedAllCalls = false;
do {
if (testedAllCalls) {
throw 'It painted methods that the predicate passed to a "something" step, '
'in the paint pattern, none of which were considered correct.';
}
currentCall = call.current;
if (!currentCall.invocation.isMethod)
throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call';
} while (call.moveNext() && !_runPredicate(currentCall.invocation.memberName, currentCall.invocation.positionalArguments));
testedAllCalls = !call.moveNext();
} while (!_runPredicate(currentCall.invocation.memberName, currentCall.invocation.positionalArguments));
}
bool _runPredicate(Symbol methodName, List<dynamic> arguments) {
......@@ -1422,14 +1435,14 @@ class _EverythingPaintPredicate extends _PaintPredicate {
@override
void match(Iterator<RecordedInvocation> call) {
assert(predicate != null);
while (call.moveNext()) {
do {
final RecordedInvocation currentCall = call.current;
if (!currentCall.invocation.isMethod)
throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call';
if (!_runPredicate(currentCall.invocation.memberName, currentCall.invocation.positionalArguments))
return;
}
throw 'It painted something that the predicate passed to an "everything" step '
'in the paint pattern considered incorrect.\n';
} while (call.moveNext());
}
bool _runPredicate(Symbol methodName, List<dynamic> arguments) {
......
// 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/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'mock_canvas.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) {
throw '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) {
throw 'failed ';
}
return true;
}),
),
);
expect(
methodsAndArguments,
<MethodAndArguments>[
const MethodAndArguments(#save, <dynamic>[]),
const MethodAndArguments(#drawColor, <dynamic>[Colors.transparent, BlendMode.color]),
// The #restore call is never evaluated
],
);
});
});
}
......@@ -2143,8 +2143,7 @@ void main() {
// Show prompt rect when told to.
verifyAutocorrectionRectVisibility(expectVisible: true);
// Text changed, prompt rect goes away.
controller.text = '12345';
await tester.enterText(find.byType(EditableText), '12345');
await tester.pump();
verifyAutocorrectionRectVisibility(expectVisible: false);
......@@ -2155,7 +2154,8 @@ void main() {
// Unfocus, prompt rect should go away.
focusNode.unfocus();
await tester.pump();
await tester.pumpAndSettle();
verifyAutocorrectionRectVisibility(expectVisible: false);
},
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment