diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index feb77cd8bef53..f9abab6d10e71 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -821,11 +821,22 @@ class _SliderState extends State with TickerProviderStateMixin { // in range_slider.dart. Size screenSize() => MediaQuery.of(context).size; - void handleDidGainAccessibilityFocus() { - // Automatically activate the slider when it receives a11y focus. - if (!focusNode.hasFocus && focusNode.canRequestFocus) { - focusNode.requestFocus(); - } + VoidCallback? handleDidGainAccessibilityFocus; + switch (theme.platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + break; + case TargetPlatform.windows: + handleDidGainAccessibilityFocus = () { + // Automatically activate the slider when it receives a11y focus. + if (!focusNode.hasFocus && focusNode.canRequestFocus) { + focusNode.requestFocus(); + } + }; + break; } final Map shortcutMap; @@ -846,35 +857,38 @@ class _SliderState extends State with TickerProviderStateMixin { ? math.min(MediaQuery.of(context).textScaleFactor, 1.3) : MediaQuery.of(context).textScaleFactor; - return FocusableActionDetector( - actions: _actionMap, - shortcuts: shortcutMap, - focusNode: focusNode, - autofocus: widget.autofocus, - enabled: _enabled, - onShowFocusHighlight: _handleFocusHighlightChanged, - onShowHoverHighlight: _handleHoverChanged, - mouseCursor: effectiveMouseCursor, - includeFocusSemantics: false, - child: CompositedTransformTarget( - link: _layerLink, - child: _SliderRenderObjectWidget( - key: _renderObjectKey, - value: _convert(widget.value), - secondaryTrackValue: (widget.secondaryTrackValue != null) ? _convert(widget.secondaryTrackValue!) : null, - divisions: widget.divisions, - label: widget.label, - sliderTheme: sliderTheme, - textScaleFactor: textScaleFactor, - screenSize: screenSize(), - onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null, - onChangeStart: _handleDragStart, - onChangeEnd: _handleDragEnd, - state: this, - semanticFormatterCallback: widget.semanticFormatterCallback, - onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus, - hasFocus: _focused, - hovering: _hovering, + return Semantics( + container: true, + slider: true, + onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus, + child: FocusableActionDetector( + actions: _actionMap, + shortcuts: shortcutMap, + focusNode: focusNode, + autofocus: widget.autofocus, + enabled: _enabled, + onShowFocusHighlight: _handleFocusHighlightChanged, + onShowHoverHighlight: _handleHoverChanged, + mouseCursor: effectiveMouseCursor, + child: CompositedTransformTarget( + link: _layerLink, + child: _SliderRenderObjectWidget( + key: _renderObjectKey, + value: _convert(widget.value), + secondaryTrackValue: (widget.secondaryTrackValue != null) ? _convert(widget.secondaryTrackValue!) : null, + divisions: widget.divisions, + label: widget.label, + sliderTheme: sliderTheme, + textScaleFactor: textScaleFactor, + screenSize: screenSize(), + onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null, + onChangeStart: _handleDragStart, + onChangeEnd: _handleDragEnd, + state: this, + semanticFormatterCallback: widget.semanticFormatterCallback, + hasFocus: _focused, + hovering: _hovering, + ), ), ), ); @@ -935,7 +949,6 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { required this.onChangeEnd, required this.state, required this.semanticFormatterCallback, - required this.onDidGainAccessibilityFocus, required this.hasFocus, required this.hovering, }); @@ -951,7 +964,6 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { final ValueChanged? onChangeStart; final ValueChanged? onChangeEnd; final SemanticFormatterCallback? semanticFormatterCallback; - final VoidCallback? onDidGainAccessibilityFocus; final _SliderState state; final bool hasFocus; final bool hovering; @@ -972,7 +984,6 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { state: state, textDirection: Directionality.of(context), semanticFormatterCallback: semanticFormatterCallback, - onDidGainAccessibilityFocus: onDidGainAccessibilityFocus, platform: Theme.of(context).platform, hasFocus: hasFocus, hovering: hovering, @@ -997,7 +1008,6 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { ..onChangeEnd = onChangeEnd ..textDirection = Directionality.of(context) ..semanticFormatterCallback = semanticFormatterCallback - ..onDidGainAccessibilityFocus = onDidGainAccessibilityFocus ..platform = Theme.of(context).platform ..hasFocus = hasFocus ..hovering = hovering @@ -1019,7 +1029,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { required TargetPlatform platform, required ValueChanged? onChanged, required SemanticFormatterCallback? semanticFormatterCallback, - required this.onDidGainAccessibilityFocus, required this.onChangeStart, required this.onChangeEnd, required _SliderState state, @@ -1105,7 +1114,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { bool _active = false; double _currentDragValue = 0.0; Rect? overlayRect; - late Offset _thumbCenter; // This rect is used in gesture calculations, where the gesture coordinates // are relative to the sliders origin. Therefore, the offset is passed as @@ -1251,7 +1259,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { } } - VoidCallback? onDidGainAccessibilityFocus; ValueChanged? onChangeStart; ValueChanged? onChangeEnd; @@ -1575,10 +1582,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { sliderTheme: _sliderTheme, isDiscrete: isDiscrete, ); - _thumbCenter = Offset(trackRect.left + visualPosition * trackRect.width, trackRect.center.dy); + final Offset thumbCenter = Offset(trackRect.left + visualPosition * trackRect.width, trackRect.center.dy); if (isInteractive) { final Size overlaySize = sliderTheme.overlayShape!.getPreferredSize(isInteractive, false); - overlayRect = Rect.fromCircle(center: _thumbCenter, radius: overlaySize.width / 2.0); + overlayRect = Rect.fromCircle(center: thumbCenter, radius: overlaySize.width / 2.0); } final Offset? secondaryOffset = (secondaryVisualPosition != null) ? Offset(trackRect.left + secondaryVisualPosition * trackRect.width, trackRect.center.dy) : null; @@ -1589,7 +1596,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { sliderTheme: _sliderTheme, enableAnimation: _enableAnimation, textDirection: _textDirection, - thumbCenter: _thumbCenter, + thumbCenter: thumbCenter, secondaryOffset: secondaryOffset, isDiscrete: isDiscrete, isEnabled: isInteractive, @@ -1598,7 +1605,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { if (!_overlayAnimation.isDismissed) { _sliderTheme.overlayShape!.paint( context, - _thumbCenter, + thumbCenter, activationAnimation: _overlayAnimation, enableAnimation: _enableAnimation, isDiscrete: isDiscrete, @@ -1635,7 +1642,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { sliderTheme: _sliderTheme, enableAnimation: _enableAnimation, textDirection: _textDirection, - thumbCenter: _thumbCenter, + thumbCenter: thumbCenter, isEnabled: isInteractive, ); } @@ -1648,7 +1655,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { if (attached) { _sliderTheme.valueIndicatorShape!.paint( context, - offset + _thumbCenter, + offset + thumbCenter, activationAnimation: _valueIndicatorAnimation, enableAnimation: _enableAnimation, isDiscrete: isDiscrete, @@ -1667,7 +1674,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { _sliderTheme.thumbShape!.paint( context, - _thumbCenter, + thumbCenter, activationAnimation: _overlayAnimation, enableAnimation: _enableAnimation, isDiscrete: isDiscrete, @@ -1681,23 +1688,12 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ); } - @override - void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable children) { - node.rect = Rect.fromCenter( - center: _thumbCenter, - width: kMinInteractiveDimension, - height: kMinInteractiveDimension, - ); - - node.updateWith(config: config); - } - @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); // The Slider widget has its own Focus widget with semantics information, - // and want that semantics node to collect the semantics information here + // and we want that semantics node to collect the semantics information here // so that it's all in the same node: otherwise Talkback sees that the node // has focusable children, and it won't focus the Slider's Focus widget // because it thinks the Focus widget's node doesn't have anything to say @@ -1705,23 +1701,9 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { // information into one node means that Talkback will recognize that it has // something to say and focus it when it receives keyboard focus. // (See https://github.com/flutter/flutter/issues/57038 for context). - config.isSemanticBoundary = true; + config.isSemanticBoundary = false; config.isEnabled = isInteractive; - config.isSlider = true; - config.isFocusable = isInteractive; - config.isFocused = hasFocus; - switch (_platform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.iOS: - case TargetPlatform.linux: - case TargetPlatform.macOS: - break; - case TargetPlatform.windows: - config.onDidGainAccessibilityFocus = onDidGainAccessibilityFocus; - break; - } config.textDirection = textDirection; if (isInteractive) { config.onIncrease = increaseAction; diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart index ba31580cce0e2..84e55936f12af 100644 --- a/packages/flutter/test/material/slider_test.dart +++ b/packages/flutter/test/material/slider_test.dart @@ -15,6 +15,7 @@ import 'package:flutter/src/physics/utils.dart' show nearEqual; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; +import '../widgets/semantics_tester.dart'; // A thumb shape that also logs its repaint center. class LoggingThumbShape extends SliderComponentShape { @@ -1147,6 +1148,8 @@ void main() { }); testWidgets('Slider Semantics', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + await tester.pumpWidget(MaterialApp( home: Directionality( textDirection: TextDirection.ltr, @@ -1161,24 +1164,49 @@ void main() { await tester.pumpAndSettle(); - SemanticsNode semanticsNode = tester.getSemantics(find.byType(Slider)); expect( - semanticsNode, - matchesSemantics( - children: [ - matchesSemantics( - isEnabled: true, - isSlider: true, - isFocusable: true, - hasEnabledState: true, - hasIncreaseAction: true, - hasDecreaseAction: true, - value: '50%', - increasedValue: '55%', - decreasedValue: '45%', - textDirection: TextDirection.ltr, - ), - ], + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [ + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + SemanticsFlag.isFocusable, + SemanticsFlag.isSlider, + ], + actions: [ + SemanticsAction.increase, + SemanticsAction.decrease, + ], + value: '50%', + increasedValue: '55%', + decreasedValue: '45%', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, ), ); @@ -1195,27 +1223,95 @@ void main() { ), )); + expect( + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [ + SemanticsFlag.hasEnabledState, + // isFocusable is delayed by 1 frame. + SemanticsFlag.isFocusable, + SemanticsFlag.isSlider, + ], + value: '50%', + increasedValue: '55%', + decreasedValue: '45%', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, + ), + ); + await tester.pump(); - semanticsNode = tester.getSemantics(find.byType(Slider)); expect( - semanticsNode, - matchesSemantics( - scopesRoute: true, - children: [ - matchesSemantics( - isSlider: true, - hasEnabledState: true, - value: '50%', - increasedValue: '55%', - decreasedValue: '45%', - textDirection: TextDirection.ltr, - ), - ], + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [ + SemanticsFlag.hasEnabledState, + SemanticsFlag.isSlider, + ], + value: '50%', + increasedValue: '55%', + decreasedValue: '45%', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, ), ); + + semantics.dispose(); }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux })); testWidgets('Slider Semantics', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + await tester.pumpWidget( MaterialApp( home: Theme( @@ -1234,26 +1330,41 @@ void main() { ), ); - await tester.pumpAndSettle(); - - SemanticsNode semanticsNode = tester.getSemantics(find.byType(Slider)); expect( - semanticsNode, - matchesSemantics( - children: [ - matchesSemantics( - isEnabled: true, - isSlider: true, - isFocusable: true, - hasEnabledState: true, - hasIncreaseAction: true, - hasDecreaseAction: true, - value: '50%', - increasedValue: '60%', - decreasedValue: '40%', - textDirection: TextDirection.ltr, - ), - ], + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider], + actions: [SemanticsAction.increase, SemanticsAction.decrease], + value: '50%', + increasedValue: '60%', + decreasedValue: '40%', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, ), ); @@ -1270,26 +1381,48 @@ void main() { ), )); - semanticsNode = tester.getSemantics(find.byType(Slider)); expect( - semanticsNode, - matchesSemantics( - scopesRoute: true, - children: [ - matchesSemantics( - isSlider: true, - hasEnabledState: true, - value: '50%', - increasedValue: '60%', - decreasedValue: '40%', - textDirection: TextDirection.ltr, - ), - ], + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 5, + flags: [SemanticsFlag.hasEnabledState, SemanticsFlag.isSlider], + value: '50%', + increasedValue: '60%', + decreasedValue: '40%', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, ), ); + semantics.dispose(); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); testWidgets('Slider Semantics', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + await tester.pumpWidget(MaterialApp( home: Directionality( textDirection: TextDirection.ltr, @@ -1304,25 +1437,50 @@ void main() { await tester.pumpAndSettle(); - SemanticsNode semanticsNode = tester.getSemantics(find.byType(Slider)); expect( - semanticsNode, - matchesSemantics( - children: [ - matchesSemantics( - isEnabled: true, - isSlider: true, - isFocusable: true, - hasEnabledState: true, - hasIncreaseAction: true, - hasDecreaseAction: true, - hasDidGainAccessibilityFocusAction: true, - value: '50%', - increasedValue: '55%', - decreasedValue: '45%', - textDirection: TextDirection.ltr, - ), - ], + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [ + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + SemanticsFlag.isFocusable, + SemanticsFlag.isSlider, + ], + actions: [ + SemanticsAction.increase, + SemanticsAction.decrease, + SemanticsAction.didGainAccessibilityFocus, + ], + value: '50%', + increasedValue: '55%', + decreasedValue: '45%', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, ), ); @@ -1339,28 +1497,101 @@ void main() { ), )); + expect( + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [ + SemanticsFlag.hasEnabledState, + // isFocusable is delayed by 1 frame. + SemanticsFlag.isFocusable, + SemanticsFlag.isSlider, + ], + actions: [ + SemanticsAction.didGainAccessibilityFocus, + ], + value: '50%', + increasedValue: '55%', + decreasedValue: '45%', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, + ), + ); + await tester.pump(); - semanticsNode = tester.getSemantics(find.byType(Slider)); expect( - semanticsNode, - matchesSemantics( - scopesRoute: true, - children: [ - matchesSemantics( - isSlider: true, - hasEnabledState: true, - hasDidGainAccessibilityFocusAction: true, - value: '50%', - increasedValue: '55%', - decreasedValue: '45%', - textDirection: TextDirection.ltr, - ), - ], + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [ + SemanticsFlag.hasEnabledState, + SemanticsFlag.isSlider, + ], + actions: [ + SemanticsAction.didGainAccessibilityFocus, + ], + value: '50%', + increasedValue: '55%', + decreasedValue: '45%', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, ), ); + + semantics.dispose(); }, variant: const TargetPlatformVariant({ TargetPlatform.windows })); testWidgets('Slider semantics with custom formatter', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + await tester.pumpWidget(MaterialApp( home: Directionality( textDirection: TextDirection.ltr, @@ -1376,32 +1607,50 @@ void main() { ), )); - await tester.pumpAndSettle(); - - final SemanticsNode semanticsNode = tester.getSemantics(find.byType(Slider)); expect( - semanticsNode, - matchesSemantics( - children: [ - matchesSemantics( - isEnabled: true, - isSlider: true, - isFocusable: true, - hasEnabledState: true, - hasIncreaseAction: true, - hasDecreaseAction: true, - value: '40', - increasedValue: '60', - decreasedValue: '20', - textDirection: TextDirection.ltr, - ), - ], + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider], + actions: [SemanticsAction.increase, SemanticsAction.decrease], + value: '40', + increasedValue: '60', + decreasedValue: '20', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, ), ); + semantics.dispose(); }); // Regression test for https://github.com/flutter/flutter/issues/101868 testWidgets('Slider.label info should not write to semantic node', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + await tester.pumpWidget(MaterialApp( home: Directionality( textDirection: TextDirection.ltr, @@ -1418,28 +1667,44 @@ void main() { ), )); - await tester.pumpAndSettle(); - - final SemanticsNode semanticsNode = tester.getSemantics(find.byType(Slider)); expect( - semanticsNode, - matchesSemantics( - children: [ - matchesSemantics( - isEnabled: true, - isSlider: true, - isFocusable: true, - hasEnabledState: true, - hasIncreaseAction: true, - hasDecreaseAction: true, - value: '40', - increasedValue: '60', - decreasedValue: '20', - textDirection: TextDirection.ltr, - ), - ], + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider], + actions: [SemanticsAction.increase, SemanticsAction.decrease], + value: '40', + increasedValue: '60', + decreasedValue: '20', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, ), ); + semantics.dispose(); }); testWidgets('Slider is focusable and has correct focus color', (WidgetTester tester) async { @@ -2111,6 +2376,7 @@ void main() { }); testWidgets('Slider gains keyboard focus when it gains semantics focus on Windows', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!; final FocusNode focusNode = FocusNode(); await tester.pumpWidget( @@ -2125,32 +2391,55 @@ void main() { ), ); - final SemanticsNode semanticsNode = tester.getSemantics(find.byType(Slider)); - expect( - semanticsNode, - matchesSemantics( - children: [ - matchesSemantics( - isEnabled: true, - isSlider: true, - isFocusable: true, - hasEnabledState: true, - hasIncreaseAction: true, - hasDecreaseAction: true, - hasDidGainAccessibilityFocusAction: true, - value: '50%', - increasedValue: '55%', - decreasedValue: '45%', + expect(semantics, hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [ + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + SemanticsFlag.isFocusable, + SemanticsFlag.isSlider, + ], + actions: [ + SemanticsAction.increase, + SemanticsAction.decrease, + SemanticsAction.didGainAccessibilityFocus, + ], + value: '50%', + increasedValue: '55%', + decreasedValue: '45%', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], ), ], ), - ); + ignoreRect: true, + ignoreTransform: true, + )); expect(focusNode.hasFocus, isFalse); - semanticsOwner.performAction(5, SemanticsAction.didGainAccessibilityFocus); + semanticsOwner.performAction(4, SemanticsAction.didGainAccessibilityFocus); await tester.pumpAndSettle(); expect(focusNode.hasFocus, isTrue); + semantics.dispose(); }, variant: const TargetPlatformVariant({ TargetPlatform.windows })); testWidgets('Value indicator appears when it should', (WidgetTester tester) async { diff --git a/packages/flutter/test/widgets/semantics_debugger_test.dart b/packages/flutter/test/widgets/semantics_debugger_test.dart index 1de96fddcc552..7dd740abe8f8a 100644 --- a/packages/flutter/test/widgets/semantics_debugger_test.dart +++ b/packages/flutter/test/widgets/semantics_debugger_test.dart @@ -290,7 +290,7 @@ void main() { }); testWidgets('SemanticsDebugger slider', (WidgetTester tester) async { - double value = 0.50; + double value = 0.75; await tester.pumpWidget( Directionality( @@ -322,7 +322,7 @@ void main() { // interpreted as a gesture by the semantics debugger and sent to the widget // as a semantic action that always moves by 10% of the complete track. await tester.fling(find.byType(Slider), const Offset(-100.0, 0.0), 2000.0, warnIfMissed: false); // hitting the debugger - expect(value, equals(0.45)); + expect(value, equals(0.70)); }); testWidgets('SemanticsDebugger checkbox', (WidgetTester tester) async { diff --git a/packages/flutter_test/test/controller_test.dart b/packages/flutter_test/test/controller_test.dart index fc35c430d517f..d4b532b14d7b9 100644 --- a/packages/flutter_test/test/controller_test.dart +++ b/packages/flutter_test/test/controller_test.dart @@ -865,7 +865,7 @@ void main() { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); // We're expecting the traversal to start where the slider is. - final List expectedMatchers = [...fullTraversalMatchers]..removeRange(0, 7); + final List expectedMatchers = [...fullTraversalMatchers]..removeRange(0, 8); expect( tester.semantics.simulatedAccessibilityTraversal(start: find.byType(Slider)), @@ -887,7 +887,7 @@ void main() { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); // We're expecting the traversal to end where the slider is, inclusive. - final Iterable expectedMatchers = [...fullTraversalMatchers].getRange(0, 8); + final Iterable expectedMatchers = [...fullTraversalMatchers].getRange(0, 9); expect( tester.semantics.simulatedAccessibilityTraversal(end: find.byType(Slider)), @@ -909,7 +909,7 @@ void main() { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); // We're expecting the traversal to start at the text field and end at the slider. - final Iterable expectedMatchers = [...fullTraversalMatchers].getRange(1, 8); + final Iterable expectedMatchers = [...fullTraversalMatchers].getRange(1, 9); expect( tester.semantics.simulatedAccessibilityTraversal(