Commit ee632c39 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Exclude modal barrier from semantics on Android (#11024)

* Exclude modal barrier from semantics on Android

* Add tests
parent 47c4d64f
......@@ -272,6 +272,8 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
children: <Widget>[
new BlockSemantics(
child: new GestureDetector(
// On Android, the back button is used to dismiss a modal.
excludeFromSemantics: defaultTargetPlatform == TargetPlatform.android,
onTap: close,
child: new Container(
color: _color.evaluate(_controller)
......
......@@ -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 'package:flutter/foundation.dart';
import 'basic.dart';
import 'container.dart';
import 'framework.dart';
......@@ -28,7 +30,8 @@ class ModalBarrier extends StatelessWidget {
Widget build(BuildContext context) {
return new BlockSemantics(
child: new ExcludeSemantics(
excluding: !dismissible,
// On Android, the back button is used to dismiss a modal.
excluding: !dismissible || defaultTargetPlatform == TargetPlatform.android,
child: new Semantics(
container: true,
child: new GestureDetector(
......
......@@ -212,7 +212,7 @@ void main() {
),
);
expect(semantics, includesNodeWithLabel(buttonText));
expect(semantics, includesNodeWith(label: buttonText));
final BuildContext context = tester.element(find.text(buttonText));
......@@ -224,8 +224,8 @@ void main() {
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(semantics, includesNodeWithLabel(alertText));
expect(semantics, isNot(includesNodeWithLabel(buttonText)));
expect(semantics, includesNodeWith(label: alertText));
expect(semantics, isNot(includesNodeWith(label: buttonText)));
semantics.dispose();
});
......
......@@ -459,22 +459,22 @@ void main() {
drawer: new Drawer(child:new Semantics(label: drawerLabel, child: new Container())),
)));
expect(semantics, includesNodeWithLabel(bodyLabel));
expect(semantics, includesNodeWithLabel(persistentFooterButtonLabel));
expect(semantics, includesNodeWithLabel(bottomNavigationBarLabel));
expect(semantics, includesNodeWithLabel(floatingActionButtonLabel));
expect(semantics, isNot(includesNodeWithLabel(drawerLabel)));
expect(semantics, includesNodeWith(label: bodyLabel));
expect(semantics, includesNodeWith(label: persistentFooterButtonLabel));
expect(semantics, includesNodeWith(label: bottomNavigationBarLabel));
expect(semantics, includesNodeWith(label: floatingActionButtonLabel));
expect(semantics, isNot(includesNodeWith(label: drawerLabel)));
final ScaffoldState state = tester.firstState(find.byType(Scaffold));
state.openDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(semantics, isNot(includesNodeWithLabel(bodyLabel)));
expect(semantics, isNot(includesNodeWithLabel(persistentFooterButtonLabel)));
expect(semantics, isNot(includesNodeWithLabel(bottomNavigationBarLabel)));
expect(semantics, isNot(includesNodeWithLabel(floatingActionButtonLabel)));
expect(semantics, includesNodeWithLabel(drawerLabel));
expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
expect(semantics, isNot(includesNodeWith(label: persistentFooterButtonLabel)));
expect(semantics, isNot(includesNodeWith(label: bottomNavigationBarLabel)));
expect(semantics, isNot(includesNodeWith(label: floatingActionButtonLabel)));
expect(semantics, includesNodeWith(label: drawerLabel));
semantics.dispose();
});
......
......@@ -3,9 +3,13 @@
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'semantics_tester.dart';
void main() {
testWidgets('Drawer control test', (WidgetTester tester) async {
......@@ -172,4 +176,60 @@ void main() {
expect(buttonPressed, equals(true));
});
testWidgets('Dismissible ModalBarrier includes button in semantic tree on iOS', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
final SemanticsTester semantics = new SemanticsTester(tester);
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
await tester.pumpWidget(
new MaterialApp(
home: new Builder(
builder: (BuildContext context) {
return new Scaffold(
key: scaffoldKey,
drawer: const Drawer(),
);
}
)
)
);
// Open the drawer.
scaffoldKey.currentState.openDrawer();
await tester.pump(const Duration(milliseconds: 100));
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.tap]));
semantics.dispose();
debugDefaultTargetPlatformOverride = null;
});
testWidgets('Dismissible ModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
await tester.pumpWidget(
new MaterialApp(
home: new Builder(
builder: (BuildContext context) {
return new Scaffold(
key: scaffoldKey,
drawer: const Drawer(),
body: new Container()
);
}
)
)
);
// Open the drawer.
scaffoldKey.currentState.openDrawer();
await tester.pump(const Duration(milliseconds: 100));
expect(semantics, isNot(includesNodeWith(actions: <SemanticsAction>[SemanticsAction.tap])));
semantics.dispose();
});
}
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
......@@ -92,7 +93,9 @@ void main() {
semantics.dispose();
});
testWidgets('Dismissible ModalBarrier includes button in semantic tree', (WidgetTester tester) async {
testWidgets('Dismissible ModalBarrier includes button in semantic tree on iOS', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(const ModalBarrier(dismissible: true));
......@@ -113,6 +116,17 @@ void main() {
);
expect(semantics, hasSemantics(expectedSemantics));
semantics.dispose();
debugDefaultTargetPlatformOverride = null;
});
testWidgets('Dismissible ModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(const ModalBarrier(dismissible: true));
final TestSemantics expectedSemantics = new TestSemantics.root();
expect(semantics, hasSemantics(expectedSemantics));
semantics.dispose();
});
}
......
......@@ -28,7 +28,7 @@ void main() {
],
));
expect(semantics, isNot(includesNodeWithLabel('layer#1')));
expect(semantics, isNot(includesNodeWith(label: 'layer#1')));
await tester.pumpWidget(new Stack(
children: <Widget>[
......@@ -39,7 +39,7 @@ void main() {
],
));
expect(semantics, includesNodeWithLabel('layer#1'));
expect(semantics, includesNodeWith(label: 'layer#1'));
semantics.dispose();
});
......@@ -86,13 +86,13 @@ void main() {
],
));
expect(semantics, includesNodeWithLabel('#1'));
expect(semantics, includesNodeWithLabel('#2'));
expect(semantics, isNot(includesNodeWithLabel('NOT#2.1')));
expect(semantics, includesNodeWithLabel('#2.2'));
expect(semantics, includesNodeWithLabel('#2.2.1'));
expect(semantics, includesNodeWithLabel('#2.3'));
expect(semantics, includesNodeWithLabel('#3'));
expect(semantics, includesNodeWith(label: '#1'));
expect(semantics, includesNodeWith(label: '#2'));
expect(semantics, isNot(includesNodeWith(label:'NOT#2.1')));
expect(semantics, includesNodeWith(label: '#2.2'));
expect(semantics, includesNodeWith(label: '#2.2.1'));
expect(semantics, includesNodeWith(label: '#2.3'));
expect(semantics, includesNodeWith(label: '#3'));
semantics.dispose();
});
......@@ -121,9 +121,9 @@ void main() {
],
));
expect(semantics, isNot(includesNodeWithLabel('NOT#1')));
expect(semantics, includesNodeWithLabel('#2.1'));
expect(semantics, includesNodeWithLabel('#3'));
expect(semantics, isNot(includesNodeWith(label: 'NOT#1')));
expect(semantics, includesNodeWith(label: '#2.1'));
expect(semantics, includesNodeWith(label: '#3'));
semantics.dispose();
});
......
......@@ -240,17 +240,21 @@ class _HasSemantics extends Matcher {
/// Asserts that a [SemanticsTester] has a semantics tree that exactly matches the given semantics.
Matcher hasSemantics(TestSemantics semantics) => new _HasSemantics(semantics);
class _IncludesNodeWithLabel extends Matcher {
const _IncludesNodeWithLabel(this._label) : assert(_label != null);
class _IncludesNodeWith extends Matcher {
const _IncludesNodeWith({
this.label,
this.actions,
}) : assert(label != null || actions != null);
final String _label;
final String label;
final List<SemanticsAction> actions;
@override
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
bool result = false;
SemanticsNodeVisitor visitor;
visitor = (SemanticsNode node) {
if (node.label == _label) {
if (checkNode(node)) {
result = true;
} else {
node.visitChildren(visitor);
......@@ -262,16 +266,43 @@ class _IncludesNodeWithLabel extends Matcher {
return result;
}
bool checkNode(SemanticsNode node) {
if (label != null && node.label != label)
return false;
if (actions != null) {
final int expectedActions = actions.fold(0, (int value, SemanticsAction action) => value | action.index);
final int actualActions = node.getSemanticsData().actions;
if (expectedActions != actualActions)
return false;
}
return true;
}
@override
Description describe(Description description) {
return description.add('includes node with label "$_label"');
return description.add('includes node with $_configAsString');
}
@override
Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
return mismatchDescription.add('could not find node with label "$_label".\n$_matcherHelp');
return mismatchDescription.add('could not find node with $_configAsString.\n$_matcherHelp');
}
String get _configAsString {
String string = '';
if (label != null) {
string += 'label "$label"';
if (actions != null)
string += ' and ';
}
if (actions != null) {
string += ' actions "${actions.join(', ')}"';
}
return string;
}
}
/// Asserts that a node in the semantics tree of [SemanticsTester] has [label].
Matcher includesNodeWithLabel(String label) => new _IncludesNodeWithLabel(label);
/// Asserts that a node in the semantics tree of [SemanticsTester] has [label] and [actions].
///
/// If `null` is provided for either argument it will match against any value.
Matcher includesNodeWith({ String label, List<SemanticsAction> actions }) => new _IncludesNodeWith(label: label, actions: actions);
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