From a0b5448b84f1220d0ea07fb2879d8c6d0e1d179c Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Sat, 4 Aug 2018 08:50:10 -0700 Subject: [PATCH] Add route/routeName to search page (#20190) --- packages/flutter/lib/src/material/search.dart | 63 +++++++---- .../flutter/test/material/search_test.dart | 106 ++++++++++++++++++ 2 files changed, 146 insertions(+), 23 deletions(-) diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart index 64e4b325b2..1d2e801da9 100644 --- a/packages/flutter/lib/src/material/search.dart +++ b/packages/flutter/lib/src/material/search.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'app_bar.dart'; @@ -389,6 +390,7 @@ class _SearchPageState extends State<_SearchPage> { @override Widget build(BuildContext context) { final ThemeData theme = widget.delegate.appBarTheme(context); + final String searchFieldLabel = MaterialLocalizations.of(context).searchFieldLabel; Widget body; switch(widget.delegate._currentBody) { case _SearchBody.suggestions: @@ -404,32 +406,47 @@ class _SearchPageState extends State<_SearchPage> { ); break; } + String routeName; + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + routeName = ''; + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + routeName = searchFieldLabel; + } - return new Scaffold( - appBar: new AppBar( - backgroundColor: theme.primaryColor, - iconTheme: theme.primaryIconTheme, - textTheme: theme.primaryTextTheme, - brightness: theme.primaryColorBrightness, - leading: widget.delegate.buildLeading(context), - title: new TextField( - controller: queryTextController, - focusNode: widget.delegate._focusNode, - style: theme.textTheme.title, - textInputAction: TextInputAction.search, - onSubmitted: (String _) { - widget.delegate.showResults(context); - }, - decoration: new InputDecoration( - border: InputBorder.none, - hintText: MaterialLocalizations.of(context).searchFieldLabel, + return new Semantics( + explicitChildNodes: true, + scopesRoute: true, + namesRoute: true, + label: routeName, + child: new Scaffold( + appBar: new AppBar( + backgroundColor: theme.primaryColor, + iconTheme: theme.primaryIconTheme, + textTheme: theme.primaryTextTheme, + brightness: theme.primaryColorBrightness, + leading: widget.delegate.buildLeading(context), + title: new TextField( + controller: queryTextController, + focusNode: widget.delegate._focusNode, + style: theme.textTheme.title, + textInputAction: TextInputAction.search, + onSubmitted: (String _) { + widget.delegate.showResults(context); + }, + decoration: new InputDecoration( + border: InputBorder.none, + hintText: searchFieldLabel, + ), ), + actions: widget.delegate.buildActions(context), + ), + body: new AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: body, ), - actions: widget.delegate.buildActions(context), - ), - body: new AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: body, ), ); } diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index 15767d6859..36c8e59f23 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -2,10 +2,13 @@ // 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 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/semantics_tester.dart'; + void main() { testWidgets('Can open and close search', (WidgetTester tester) async { final _TestSearchDelegate delegate = new _TestSearchDelegate(); @@ -477,6 +480,109 @@ void main() { expect(tester.testTextInput.setClientArgs['inputAction'], TextInputAction.search.toString()); }); + + group('contributes semantics', () { + TestSemantics buildExpected({String routeName}) { + return new TestSemantics.root( + children: [ + new TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + new TestSemantics( + id: 7, + flags: [ + SemanticsFlag.scopesRoute, + SemanticsFlag.namesRoute, + ], + label: routeName, + textDirection: TextDirection.ltr, + children: [ + new TestSemantics( + id: 8, + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], + actions: [SemanticsAction.tap], + label: 'Suggestions', + textDirection: TextDirection.ltr, + ), + new TestSemantics( + id: 9, + children: [ + new TestSemantics( + id: 10, + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], + actions: [SemanticsAction.tap], + label: 'Back', + textDirection: TextDirection.ltr, + ), + new TestSemantics( + id: 11, + flags: [ + SemanticsFlag.isTextField, + SemanticsFlag.isFocused, + SemanticsFlag.isHeader, + SemanticsFlag.namesRoute, + ], + actions: [ + SemanticsAction.tap, + SemanticsAction.setSelection, + SemanticsAction.paste, + ], + label: 'Search', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ); + } + + testWidgets('includes routeName on Android', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + final _TestSearchDelegate delegate = new _TestSearchDelegate(); + await tester.pumpWidget(new TestHomePage( + delegate: delegate, + )); + + await tester.tap(find.byTooltip('Search')); + await tester.pumpAndSettle(); + + expect(semantics, hasSemantics(buildExpected(routeName: 'Search'), + ignoreId: true, ignoreRect: true, ignoreTransform: true)); + + semantics.dispose(); + }); + + testWidgets('does not include routeName on iOS', (WidgetTester tester) async { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + final SemanticsTester semantics = new SemanticsTester(tester); + final _TestSearchDelegate delegate = new _TestSearchDelegate(); + await tester.pumpWidget(new TestHomePage( + delegate: delegate, + )); + + await tester.tap(find.byTooltip('Search')); + await tester.pumpAndSettle(); + + expect(semantics, hasSemantics(buildExpected(routeName: ''), + ignoreId: true, ignoreRect: true, ignoreTransform: true)); + + debugDefaultTargetPlatformOverride = null; + semantics.dispose(); + }); + }); } class TestHomePage extends StatelessWidget {