Fix dropdown button semantics (#19932)
diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart
index c5c6ddc..1dea06d 100644
--- a/packages/flutter/lib/src/material/dropdown.dart
+++ b/packages/flutter/lib/src/material/dropdown.dart
@@ -139,6 +139,7 @@
//
// When the menu is dismissed we just fade the entire thing out
// in the first 0.25s.
+ final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final _DropdownRoute<T> route = widget.route;
final double unit = 0.5 / (route.items.length + 1.5);
final List<Widget> children = <Widget>[];
@@ -175,24 +176,30 @@
selectedIndex: route.selectedIndex,
resize: _resize,
),
- child: new Material(
- type: MaterialType.transparency,
- textStyle: route.style,
- child: new ScrollConfiguration(
- behavior: const _DropdownScrollBehavior(),
- child: new Scrollbar(
- child: new ListView(
- controller: widget.route.scrollController,
- padding: kMaterialListPadding,
- itemExtent: _kMenuItemHeight,
- shrinkWrap: true,
- children: children,
+ child: new Semantics(
+ scopesRoute: true,
+ namesRoute: true,
+ explicitChildNodes: true,
+ label: localizations.popupMenuLabel,
+ child: new Material(
+ type: MaterialType.transparency,
+ textStyle: route.style,
+ child: new ScrollConfiguration(
+ behavior: const _DropdownScrollBehavior(),
+ child: new Scrollbar(
+ child: new ListView(
+ controller: widget.route.scrollController,
+ padding: kMaterialListPadding,
+ itemExtent: _kMenuItemHeight,
+ shrinkWrap: true,
+ children: children,
+ ),
+ ),
),
),
),
),
- ),
- );
+ );
}
}
@@ -627,6 +634,7 @@
style: _textStyle.copyWith(color: Theme.of(context).hintColor),
child: new IgnorePointer(
child: widget.hint,
+ ignoringSemantics: false,
),
));
}
@@ -681,10 +689,13 @@
);
}
- return new GestureDetector(
- onTap: _handleTap,
- behavior: HitTestBehavior.opaque,
- child: result
+ return new Semantics(
+ button: true,
+ child: new GestureDetector(
+ onTap: _handleTap,
+ behavior: HitTestBehavior.opaque,
+ child: result
+ ),
);
}
}
diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart
index c8d4f43..83d90a9 100644
--- a/packages/flutter/lib/src/semantics/semantics.dart
+++ b/packages/flutter/lib/src/semantics/semantics.dart
@@ -365,7 +365,7 @@
scrollExtentMax,
scrollExtentMin,
transform,
- customSemanticsActionIds,
+ ui.hashList(customSemanticsActionIds),
);
}
diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart
index f6b40c3..ecffb4f 100644
--- a/packages/flutter/test/material/dropdown_test.dart
+++ b/packages/flutter/test/material/dropdown_test.dart
@@ -580,4 +580,102 @@
semantics.dispose();
});
+
+ testWidgets('Dropdown button includes semantics', (WidgetTester tester) async {
+ final SemanticsHandle handle = tester.ensureSemantics();
+ const Key key = const Key('test');
+ await tester.pumpWidget(buildFrame(
+ buttonKey: key,
+ value: null,
+ items: menuItems,
+ onChanged: (String _) {},
+ hint: const Text('test'),
+ ));
+
+ // By default the hint contributes the label.
+ expect(tester.getSemanticsData(find.byKey(key)), matchesSemanticsData(
+ isButton: true,
+ label: 'test',
+ hasTapAction: true,
+ ));
+
+ await tester.pumpWidget(buildFrame(
+ buttonKey: key,
+ value: 'three',
+ items: menuItems,
+ onChanged: null,
+ hint: const Text('test'),
+ ));
+
+ // Displays label of select item and is no longer tappable.
+ expect(tester.getSemanticsData(find.byKey(key)), matchesSemanticsData(
+ isButton: true,
+ label: 'three',
+ hasTapAction: true,
+ ));
+ handle.dispose();
+ });
+
+ testWidgets('Dropdown menu includes semantics', (WidgetTester tester) async {
+ final SemanticsTester semantics = new SemanticsTester(tester);
+ const Key key = const Key('test');
+ await tester.pumpWidget(buildFrame(
+ buttonKey: key,
+ value: null,
+ items: menuItems,
+ ));
+ await tester.tap(find.byKey(key));
+ await tester.pumpAndSettle();
+
+ expect(semantics, hasSemantics(new TestSemantics.root(
+ children: <TestSemantics>[
+ new TestSemantics.rootChild(
+ children: <TestSemantics>[
+ new TestSemantics(
+ flags: <SemanticsFlag>[
+ SemanticsFlag.scopesRoute,
+ SemanticsFlag.namesRoute,
+ ],
+ label: 'Popup menu',
+ children: <TestSemantics>[
+ new TestSemantics(
+ children: <TestSemantics>[
+ new TestSemantics(
+ children: <TestSemantics>[
+ new TestSemantics(
+ label: 'one',
+ textDirection: TextDirection.ltr,
+ tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
+ actions: <SemanticsAction>[SemanticsAction.tap],
+ ),
+ new TestSemantics(
+ label: 'two',
+ textDirection: TextDirection.ltr,
+ tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
+ actions: <SemanticsAction>[SemanticsAction.tap],
+ ),
+ new TestSemantics(
+ label: 'three',
+ textDirection: TextDirection.ltr,
+ tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
+ actions: <SemanticsAction>[SemanticsAction.tap],
+ ),
+ new TestSemantics(
+ label: 'four',
+ textDirection: TextDirection.ltr,
+ tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
+ actions: <SemanticsAction>[SemanticsAction.tap],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ), ignoreId: true, ignoreRect: true, ignoreTransform: true));
+ semantics.dispose();
+ });
}