Unverified Commit 3eb40925 authored by Dan Field's avatar Dan Field Committed by GitHub

Always test semantics (#35110)

Default `semanticsEnabled` to true for `testWidgets` and fix associated bugs
parent 97127730
......@@ -520,6 +520,7 @@ class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChil
void performLayout() {
if (child == null || (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight)) {
size = constraints.constrain(Size.zero);
child?.layout(SectorConstraints(maxDeltaRadius: innerRadius), parentUsesSize: true);
return;
}
assert(child is RenderSector);
......
......@@ -2,6 +2,8 @@
// 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/scheduler.dart';
import 'package:flutter/widgets.dart';
......@@ -66,14 +68,25 @@ class _DatePickerLayoutDelegate extends MultiChildLayoutDelegate {
if (index == 0 || index == columnWidths.length - 1)
childWidth += remainingWidth / 2;
assert(
childWidth >= 0,
'Insufficient horizontal space to render the CupertinoDatePicker '
'because the parent is too narrow at ${size.width}px.\n'
'An additional ${-remainingWidth}px is needed to avoid overlapping '
'columns.',
);
layoutChild(index, BoxConstraints.tight(Size(childWidth, size.height)));
// We can't actually assert here because it would break things badly for
// semantics, which will expect that we laid things out here.
assert(() {
if (childWidth < 0) {
FlutterError.reportError(
FlutterErrorDetails(
exception: FlutterError(
'Insufficient horizontal space to render the '
'CupertinoDatePicker because the parent is too narrow at '
'${size.width}px.\n'
'An additional ${-remainingWidth}px is needed to avoid '
'overlapping columns.',
),
),
);
}
return true;
}());
layoutChild(index, BoxConstraints.tight(Size(math.max(0.0, childWidth), size.height)));
positionChild(index, Offset(currentHorizontalOffset, 0.0));
currentHorizontalOffset += childWidth;
......
......@@ -3584,9 +3584,10 @@ class _SemanticsGeometry {
} else {
_semanticsClipRect = _intersectRects(_semanticsClipRect, parent.describeApproximatePaintClip(child));
}
_semanticsClipRect = _transformRect(_semanticsClipRect, parent, child);
_paintClipRect = _transformRect(_paintClipRect, parent, child);
parent.applyPaintTransform(child, _transform);
_temporaryTransformHolder.setIdentity(); // clears data from previous call(s)
_applyIntermediatePaintTransforms(parent, child, _transform, _temporaryTransformHolder);
_semanticsClipRect = _transformRect(_semanticsClipRect, _temporaryTransformHolder);
_paintClipRect = _transformRect(_paintClipRect, _temporaryTransformHolder);
}
final RenderObject owner = ancestors.first;
......@@ -3609,14 +3610,41 @@ class _SemanticsGeometry {
static final Matrix4 _temporaryTransformHolder = Matrix4.zero();
/// From parent to child coordinate system.
static Rect _transformRect(Rect rect, RenderObject parent, RenderObject child) {
static Rect _transformRect(Rect rect, Matrix4 transform) {
assert(transform != null);
if (rect == null)
return null;
if (rect.isEmpty)
return Rect.zero;
_temporaryTransformHolder.setIdentity(); // clears data from a previous call
parent.applyPaintTransform(child, _temporaryTransformHolder);
return MatrixUtils.inverseTransformRect(_temporaryTransformHolder, rect);
return MatrixUtils.inverseTransformRect(transform, rect);
}
// Calls applyPaintTransform on all of the render objects between [child] and
// [ancestor]. This method handles cases where the immediate semantic parent
// is not the immediate render object parent of the child.
//
// It will mutate both transform and clipRectTransform.
static void _applyIntermediatePaintTransforms(
RenderObject ancestor,
RenderObject child,
Matrix4 transform,
Matrix4 clipRectTransform,
) {
assert(ancestor != null);
assert(child != null);
assert(transform != null);
assert(clipRectTransform != null);
assert(clipRectTransform.isIdentity());
RenderObject intermediateParent = child.parent;
assert(intermediateParent != null);
while (intermediateParent != ancestor) {
intermediateParent.applyPaintTransform(child, transform);
intermediateParent = intermediateParent.parent;
child = child.parent;
assert(intermediateParent != null);
}
ancestor.applyPaintTransform(child, transform);
ancestor.applyPaintTransform(child, clipRectTransform);
}
static Rect _intersectRects(Rect a, Rect b) {
......
......@@ -639,7 +639,7 @@ class RenderIndexedStack extends RenderStack {
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
if (index != null)
if (index != null && firstChild != null)
visitor(_childAtIndex());
}
......
......@@ -815,7 +815,7 @@ void main() {
);
final dynamic exception = tester.takeException();
expect(exception, isAssertionError);
expect(exception, isFlutterError);
expect(
exception.toString(),
contains('Insufficient horizontal space to render the CupertinoDatePicker'),
......
......@@ -2280,6 +2280,7 @@ void main() {
),
);
await tester.pumpAndSettle();
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 16.0));
expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 0.0);
......
......@@ -278,12 +278,15 @@ void main() {
double progressValue;
StateSetter setState;
await tester.pumpWidget(
Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
return CircularProgressIndicator(value: progressValue);
}
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
return CircularProgressIndicator(value: progressValue);
}
),
),
)
);
......
......@@ -1260,7 +1260,7 @@ void main() {
hasSemantics(
TestSemantics.root(children: <TestSemantics>[
TestSemantics.rootChild(
id: 2,
id: 1,
value: '50%',
increasedValue: '60%',
decreasedValue: '40%',
......@@ -1299,7 +1299,7 @@ void main() {
hasSemantics(
TestSemantics.root(children: <TestSemantics>[
TestSemantics.rootChild(
id: 3,
id: 1,
value: '40',
increasedValue: '60',
decreasedValue: '20',
......
......@@ -315,7 +315,7 @@ void _defineTests() {
expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull);
semantics.dispose();
});
}, semanticsEnabled: false);
testWidgets('Supports all actions', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
......
......@@ -297,12 +297,15 @@ Future<void> main() async {
final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage);
final TestImageProvider imageProvider = TestImageProvider(targetImage);
await tester.pumpWidget(FadeInImage(
placeholder: placeholderProvider,
image: imageProvider,
fadeOutDuration: animationDuration,
fadeInDuration: animationDuration,
imageSemanticLabel: imageSemanticText,
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: FadeInImage(
placeholder: placeholderProvider,
image: imageProvider,
fadeOutDuration: animationDuration,
fadeInDuration: animationDuration,
imageSemanticLabel: imageSemanticText,
),
));
placeholderProvider.complete();
......
......@@ -217,30 +217,30 @@ void main() {
);
// SemanticsNode#0()
// └SemanticsNode#8()
// ├SemanticsNode#5(selected, label: "node 1", textDirection: ltr)
// ├SemanticsNode#6(selected, label: "node 2", textDirection: ltr)
// └SemanticsNode#7(selected, label: "node 3", textDirection: ltr)
// └SemanticsNode#1()
// ├SemanticsNode#2(selected, label: "node 1", textDirection: ltr)
// ├SemanticsNode#3(selected, label: "node 2", textDirection: ltr)
// └SemanticsNode#4(selected, label: "node 3", textDirection: ltr)
expect(
semantics,
hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: 5,
id: 1,
children: <TestSemantics>[
TestSemantics(
id: 6,
id: 2,
flags: SemanticsFlag.isSelected.index,
label: 'node 1',
),
TestSemantics(
id: 7,
id: 3,
flags: SemanticsFlag.isSelected.index,
label: 'node 2',
),
TestSemantics(
id: 8,
id: 4,
flags: SemanticsFlag.isSelected.index,
label: 'node 3',
),
......
......@@ -238,6 +238,7 @@ void main() {
' │ needs compositing\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ semantics node: SemanticsNode#1\n'
' │ semantic boundary\n'
' │ size: Size(800.0, 600.0)\n'
' │\n'
......@@ -384,6 +385,7 @@ void main() {
' │ needs compositing\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ semantics node: SemanticsNode#1\n'
' │ semantic boundary\n'
' │ size: Size(800.0, 600.0)\n'
' │\n'
......
......@@ -406,7 +406,7 @@ void main() {
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true, ignoreRect: true, ignoreTransform: true));
semantics.dispose();
});
}, semanticsEnabled: false);
group('showOnScreen', () {
......
......@@ -62,7 +62,7 @@ void main() {
ignoreId: true,
));
semantics.dispose();
});
}, semanticsEnabled: false);
testWidgets('Detach and reattach assert', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
......
......@@ -55,16 +55,16 @@ void main() {
expect(semantics, hasSemantics(TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: 2,
id: 1,
rect: const Rect.fromLTWH(0.0, 0.0, 800.0, 600.0),
children: <TestSemantics>[
TestSemantics(
id: 3,
id: 2,
rect: const Rect.fromLTWH(0.0, 0.0, 800.0, 600.0),
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
id: 3,
label: 'Hello!',
textDirection: TextDirection.ltr,
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
......
......@@ -91,6 +91,7 @@ void main() {
' │ └─child: RenderParagraph#00000\n'
' │ │ parentData: <none> (can use size)\n'
' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ │ semantics node: SemanticsNode#2\n'
' │ │ size: Size(800.0, 600.0)\n'
' │ │ textAlign: start\n'
' │ │ textDirection: ltr\n'
......@@ -115,6 +116,7 @@ void main() {
' └─child: RenderParagraph#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ semantics node: SemanticsNode#3\n'
' │ size: Size(800.0, 600.0)\n'
' │ textAlign: start\n'
' │ textDirection: ltr\n'
......@@ -126,6 +128,7 @@ void main() {
' ║ <all styles inherited>\n'
' ║ "1"\n'
' ╚═══════════\n'
''
),
);
});
......
......@@ -103,7 +103,7 @@ void main() {
expect(response['isError'], true);
expect(response['response'], contains('Bad state: No semantics data found'));
});
}, semanticsEnabled: false);
testWidgets('throws state error multiple matches are found', (WidgetTester tester) async {
final SemanticsHandle semantics = RendererBinding.instance.pipelineOwner.ensureSemantics();
......
......@@ -71,7 +71,7 @@ typedef WidgetTesterCallback = Future<void> Function(WidgetTester widgetTester);
/// If the `semanticsEnabled` parameter is set to `true`,
/// [WidgetTester.ensureSemantics] will have been called before the tester is
/// passed to the `callback`, and that handle will automatically be disposed
/// after the callback is finished.
/// after the callback is finished. It defaults to true.
///
/// This function uses the [test] function in the test package to
/// register the given callback as a test. The callback, when run,
......@@ -100,7 +100,7 @@ void testWidgets(
bool skip = false,
test_package.Timeout timeout,
Duration initialTimeout,
bool semanticsEnabled = false,
bool semanticsEnabled = true,
}) {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
final WidgetTester tester = WidgetTester._(binding);
......@@ -115,6 +115,7 @@ void testWidgets(
test_package.addTearDown(binding.postTest);
return binding.runTest(
() async {
debugResetSemanticsIdCounter();
await callback(tester);
semanticsHandle?.dispose();
},
......
......@@ -123,7 +123,7 @@ void main() {
final Evaluation result = await textContrastGuideline.evaluate(tester);
expect(result.passed, false);
expect(result.reason,
'SemanticsNode#21(Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), label: "this is a test",'
'SemanticsNode#3(Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), label: "this is a test",'
' textDirection: ltr):\nExpected contrast ratio of at least '
'4.5 but found 1.17 for a font size of 14.0. The '
'computed light color was: Color(0xfffafafa), The computed dark color was:'
......@@ -256,7 +256,7 @@ void main() {
final Evaluation result = await androidTapTargetGuideline.evaluate(tester);
expect(result.passed, false);
expect(result.reason,
'SemanticsNode#41(Rect.fromLTRB(376.0, 276.5, 424.0, 323.5), actions: [tap]): expected tap '
'SemanticsNode#3(Rect.fromLTRB(376.0, 276.5, 424.0, 323.5), actions: [tap]): expected tap '
'target size of at least Size(48.0, 48.0), but found Size(48.0, 47.0)\n'
'See also: https://support.google.com/accessibility/android/answer/7101858?hl=en');
handle.dispose();
......
......@@ -33,7 +33,7 @@ void main() {
group('semantics', () {
testWidgets('Throws StateError if semantics are not enabled', (WidgetTester tester) async {
expect(() => find.bySemanticsLabel('Add'), throwsStateError);
});
}, semanticsEnabled: false);
testWidgets('finds Semantically labeled widgets', (WidgetTester tester) async {
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
......
......@@ -542,7 +542,7 @@ void main() {
expect(() => tester.getSemantics(find.text('hello')),
throwsA(isInstanceOf<StateError>()));
});
}, semanticsEnabled: false);
testWidgets('throws when there are multiple results from the finder', (WidgetTester tester) async {
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
......
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