diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart index dd3dbdb288bc8..36a0983d840a7 100644 --- a/packages/flutter/lib/src/material/search.dart +++ b/packages/flutter/lib/src/material/search.dart @@ -8,6 +8,8 @@ /// @docImport 'list_tile.dart'; library; +import 'dart:ui'; + import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -615,16 +617,19 @@ class _SearchPageState extends State<_SearchPage> { leadingWidth: widget.delegate.leadingWidth, automaticallyImplyLeading: widget.delegate.automaticallyImplyLeading ?? true, leading: widget.delegate.buildLeading(context), - title: TextField( - controller: widget.delegate._queryTextController, - focusNode: focusNode, - style: widget.delegate.searchFieldStyle ?? theme.textTheme.titleLarge, - textInputAction: widget.delegate.textInputAction, - autocorrect: widget.delegate.autocorrect, - enableSuggestions: widget.delegate.enableSuggestions, - keyboardType: widget.delegate.keyboardType, - onSubmitted: (String _) => widget.delegate.showResults(context), - decoration: InputDecoration(hintText: searchFieldLabel), + title: Semantics( + inputType: SemanticsInputType.search, + child: TextField( + controller: widget.delegate._queryTextController, + focusNode: focusNode, + style: widget.delegate.searchFieldStyle ?? theme.textTheme.titleLarge, + textInputAction: widget.delegate.textInputAction, + autocorrect: widget.delegate.autocorrect, + enableSuggestions: widget.delegate.enableSuggestions, + keyboardType: widget.delegate.keyboardType, + onSubmitted: (String _) => widget.delegate.showResults(context), + decoration: InputDecoration(hintText: searchFieldLabel), + ), ), flexibleSpace: widget.delegate.buildFlexibleSpace(context), actions: widget.delegate.buildActions(context), diff --git a/packages/flutter/lib/src/rendering/custom_paint.dart b/packages/flutter/lib/src/rendering/custom_paint.dart index 03bf08d014926..bb05ac7d0865e 100644 --- a/packages/flutter/lib/src/rendering/custom_paint.dart +++ b/packages/flutter/lib/src/rendering/custom_paint.dart @@ -1017,12 +1017,33 @@ class RenderCustomPaint extends RenderProxyBox { if (properties.hint != null) { config.hint = properties.hint!; } + if (properties.identifier != null) { + config.identifier = properties.identifier!; + } + if (properties.tooltip != null) { + config.tooltip = properties.tooltip!; + } + if (properties.hintOverrides != null) { + config.hintOverrides = properties.hintOverrides; + } + if (properties.tagForChildren != null) { + config.addTagForChildren(properties.tagForChildren!); + } + if (properties.controlsNodes != null) { + config.controlsNodes = properties.controlsNodes; + } + if (properties.hint != null) { + config.hint = properties.hint!; + } if (properties.textDirection != null) { config.textDirection = properties.textDirection; } if (config.validationResult != properties.validationResult) { config.validationResult = properties.validationResult; } + if (properties.inputType != null) { + config.inputType = properties.inputType!; + } if (properties.onTap != null) { config.onTap = properties.onTap; } diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 1fa5033d6368b..1d2ff8e54d141 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -1295,6 +1295,7 @@ class SemanticsProperties extends DiagnosticableTree { this.role, this.controlsNodes, this.inputType, + this.validationResult = SemanticsValidationResult.none, this.onTap, this.onLongPress, this.onScrollLeft, @@ -1317,7 +1318,6 @@ class SemanticsProperties extends DiagnosticableTree { this.onFocus, this.onDismiss, this.customSemanticsActions, - this.validationResult = SemanticsValidationResult.none, }) : assert( label == null || attributedLabel == null, 'Only one of label or attributedLabel should be provided', diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 00b6b590c5041..8db15f582f048 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -8,7 +8,7 @@ @Tags(['reduced-test-set']) library; -import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle, Color; +import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle, Color, SemanticsInputType; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -566,6 +566,7 @@ void main() { children: [ TestSemantics( id: 4, + inputType: ui.SemanticsInputType.text, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, @@ -9757,6 +9758,7 @@ void main() { children: [ TestSemantics( id: 4, + inputType: ui.SemanticsInputType.text, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, @@ -9823,6 +9825,7 @@ void main() { children: [ TestSemantics( id: 4, + inputType: ui.SemanticsInputType.text, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, diff --git a/packages/flutter/test/material/bottom_sheet_test.dart b/packages/flutter/test/material/bottom_sheet_test.dart index 1c58dd9a4e8c4..ee195f2dec0d2 100644 --- a/packages/flutter/test/material/bottom_sheet_test.dart +++ b/packages/flutter/test/material/bottom_sheet_test.dart @@ -6,6 +6,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; @@ -868,6 +869,7 @@ void main() { TestSemantics( actions: [SemanticsAction.tap, SemanticsAction.dismiss], label: 'Scrim', + hintOverrides: const SemanticsHintOverrides(onTapHint: 'Close Bottom Sheet'), textDirection: TextDirection.ltr, ), ], @@ -1040,6 +1042,7 @@ void main() { TestSemantics( actions: [SemanticsAction.tap, SemanticsAction.dismiss], label: 'Scrim', + hintOverrides: const SemanticsHintOverrides(onTapHint: 'Close Bottom Sheet'), textDirection: TextDirection.ltr, ), ], @@ -1117,6 +1120,7 @@ void main() { TestSemantics( actions: [SemanticsAction.tap, SemanticsAction.dismiss], label: 'Scrim', + hintOverrides: const SemanticsHintOverrides(onTapHint: 'Close Bottom Sheet'), textDirection: TextDirection.ltr, ), ], @@ -1189,6 +1193,7 @@ void main() { TestSemantics( actions: [SemanticsAction.tap, SemanticsAction.dismiss], label: 'Scrim', + hintOverrides: const SemanticsHintOverrides(onTapHint: 'Close Bottom Sheet'), textDirection: TextDirection.ltr, ), ], diff --git a/packages/flutter/test/material/dropdown_menu_test.dart b/packages/flutter/test/material/dropdown_menu_test.dart index bdc0c6d2fdb3b..db8e56ee6632b 100644 --- a/packages/flutter/test/material/dropdown_menu_test.dart +++ b/packages/flutter/test/material/dropdown_menu_test.dart @@ -4174,6 +4174,7 @@ void main() { children: [ TestSemantics( id: 5, + inputType: SemanticsInputType.text, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, @@ -4182,6 +4183,7 @@ void main() { ], actions: [SemanticsAction.focus], textDirection: TextDirection.ltr, + currentValueLength: 0, children: [ TestSemantics( id: 6, diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index 8ee6da85bcfb6..436a5dfe907d2 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -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:ui'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -677,6 +679,8 @@ void main() { SemanticsAction.paste, ], label: 'Search', + currentValueLength: 0, + inputType: SemanticsInputType.search, textDirection: TextDirection.ltr, textSelection: const TextSelection(baseOffset: 0, extentOffset: 0), ), @@ -702,6 +706,8 @@ void main() { SemanticsAction.paste, ], label: 'Search', + currentValueLength: 0, + inputType: SemanticsInputType.search, textDirection: TextDirection.ltr, textSelection: const TextSelection(baseOffset: 0, extentOffset: 0), ); @@ -877,6 +883,8 @@ void main() { SemanticsAction.paste, ], label: 'Search', + inputType: SemanticsInputType.search, + currentValueLength: 0, textDirection: TextDirection.ltr, textSelection: const TextSelection(baseOffset: 0, extentOffset: 0), ), @@ -902,6 +910,8 @@ void main() { SemanticsAction.paste, ], label: 'Search', + inputType: SemanticsInputType.search, + currentValueLength: 0, textDirection: TextDirection.ltr, textSelection: const TextSelection(baseOffset: 0, extentOffset: 0), ); diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 7e2671b905178..04f37f93ef620 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -14,7 +14,7 @@ library; import 'dart:math' as math; -import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle; +import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle, SemanticsInputType; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -723,6 +723,8 @@ void main() { SemanticsAction.didGainAccessibilityFocus, SemanticsAction.didLoseAccessibilityFocus, ], + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, textDirection: TextDirection.ltr, ), ], @@ -1963,6 +1965,8 @@ void main() { SemanticsAction.moveCursorBackwardByWord, ], value: 'abcdefghi', + inputType: ui.SemanticsInputType.text, + currentValueLength: 9, textDirection: TextDirection.ltr, textSelection: const TextSelection.collapsed(offset: 9), ), @@ -5312,6 +5316,8 @@ void main() { textDirection: TextDirection.ltr, value: 'some text', actions: [SemanticsAction.tap, SemanticsAction.focus], + inputType: ui.SemanticsInputType.text, + currentValueLength: 9, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, @@ -6572,6 +6578,8 @@ void main() { expect( semantics, includesNodeWith( + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, @@ -6980,13 +6988,14 @@ void main() { expect( semantics, includesNodeWith( + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, ], maxValueLength: 10, - currentValueLength: 0, ), ); @@ -7004,6 +7013,7 @@ void main() { expect( semantics, includesNodeWith( + inputType: ui.SemanticsInputType.text, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, @@ -7032,6 +7042,8 @@ void main() { expect( semantics, includesNodeWith( + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, @@ -7994,6 +8006,8 @@ void main() { TestSemantics.rootChild( id: 1, textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, actions: [SemanticsAction.tap, SemanticsAction.focus], flags: [ SemanticsFlag.isTextField, @@ -8020,6 +8034,8 @@ void main() { id: 1, textDirection: TextDirection.ltr, value: 'Guten Tag', + inputType: ui.SemanticsInputType.text, + currentValueLength: 9, actions: [SemanticsAction.tap, SemanticsAction.focus], flags: [ SemanticsFlag.isTextField, @@ -8046,6 +8062,8 @@ void main() { id: 1, textDirection: TextDirection.ltr, value: 'Guten Tag', + inputType: ui.SemanticsInputType.text, + currentValueLength: 9, textSelection: const TextSelection.collapsed(offset: 9), actions: [ SemanticsAction.tap, @@ -8083,6 +8101,8 @@ void main() { textDirection: TextDirection.ltr, textSelection: const TextSelection.collapsed(offset: 4), value: 'Guten Tag', + inputType: ui.SemanticsInputType.text, + currentValueLength: 9, actions: [ SemanticsAction.tap, SemanticsAction.focus, @@ -8122,6 +8142,8 @@ void main() { textDirection: TextDirection.ltr, textSelection: const TextSelection.collapsed(offset: 0), value: 'Schönen Feierabend', + inputType: ui.SemanticsInputType.text, + currentValueLength: 18, actions: [ SemanticsAction.tap, SemanticsAction.focus, @@ -8168,6 +8190,7 @@ void main() { SemanticsFlag.isEnabled, ], value: 'Hello', + inputType: ui.SemanticsInputType.text, ), ); @@ -8179,6 +8202,7 @@ void main() { includesNodeWith( actions: [SemanticsAction.tap, SemanticsAction.focus], textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, @@ -8202,6 +8226,7 @@ void main() { SemanticsFlag.isEnabled, ], value: 'Hello', + inputType: ui.SemanticsInputType.text, ), ); @@ -8232,6 +8257,8 @@ void main() { TestSemantics.rootChild( id: 1, textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, actions: [ SemanticsAction.tap, SemanticsAction.focus, @@ -8274,6 +8301,8 @@ void main() { TestSemantics.rootChild( id: 1, value: 'Hello', + inputType: ui.SemanticsInputType.text, + currentValueLength: 5, textDirection: TextDirection.ltr, actions: [SemanticsAction.tap, SemanticsAction.focus], flags: [ @@ -8301,6 +8330,8 @@ void main() { TestSemantics.rootChild( id: 1, value: 'Hello', + inputType: ui.SemanticsInputType.text, + currentValueLength: 5, textSelection: const TextSelection.collapsed(offset: 5), textDirection: TextDirection.ltr, actions: [ @@ -8337,6 +8368,8 @@ void main() { TestSemantics.rootChild( id: 1, value: 'Hello', + inputType: ui.SemanticsInputType.text, + currentValueLength: 5, textSelection: const TextSelection(baseOffset: 5, extentOffset: 3), textDirection: TextDirection.ltr, actions: [ @@ -8395,6 +8428,8 @@ void main() { TestSemantics.rootChild( id: inputFieldId, value: 'Hello', + inputType: ui.SemanticsInputType.text, + currentValueLength: 5, textSelection: const TextSelection.collapsed(offset: 5), textDirection: TextDirection.ltr, actions: [ @@ -8451,6 +8486,8 @@ void main() { TestSemantics.rootChild( id: inputFieldId, value: 'Hello', + inputType: ui.SemanticsInputType.text, + currentValueLength: 5, textSelection: const TextSelection(baseOffset: 0, extentOffset: 5), textDirection: TextDirection.ltr, actions: [ @@ -8511,6 +8548,8 @@ void main() { ], actions: [SemanticsAction.tap, SemanticsAction.focus], value: textInTextField, + inputType: ui.SemanticsInputType.text, + currentValueLength: 5, textDirection: TextDirection.ltr, ), ], @@ -8546,6 +8585,8 @@ void main() { SemanticsAction.paste, ], value: textInTextField, + inputType: ui.SemanticsInputType.text, + currentValueLength: 5, textDirection: TextDirection.ltr, textSelection: const TextSelection( baseOffset: textInTextField.length, @@ -8591,6 +8632,8 @@ void main() { ], actions: [SemanticsAction.tap, SemanticsAction.focus], value: textInTextField, + inputType: ui.SemanticsInputType.text, + currentValueLength: 5, textDirection: TextDirection.ltr, ), ], @@ -8626,6 +8669,8 @@ void main() { // No paste option. ], value: textInTextField, + inputType: ui.SemanticsInputType.text, + currentValueLength: 5, textDirection: TextDirection.ltr, textSelection: const TextSelection( baseOffset: textInTextField.length, @@ -8769,6 +8814,9 @@ void main() { label: 'label', id: 1, textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, + maxValueLength: 10, + currentValueLength: 0, actions: [SemanticsAction.tap, SemanticsAction.focus], flags: [ SemanticsFlag.isTextField, @@ -8804,6 +8852,9 @@ void main() { id: 1, textDirection: TextDirection.ltr, textSelection: const TextSelection(baseOffset: 0, extentOffset: 0), + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, + maxValueLength: 10, actions: [ SemanticsAction.tap, SemanticsAction.focus, @@ -8870,6 +8921,8 @@ void main() { TestSemantics.rootChild( label: 'label', textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, actions: [SemanticsAction.tap, SemanticsAction.focus], flags: [ SemanticsFlag.isTextField, @@ -8925,6 +8978,8 @@ void main() { SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, ], + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, children: [ TestSemantics(label: 'oh no!', textDirection: TextDirection.ltr), ], @@ -17382,6 +17437,8 @@ void main() { children: [ TestSemantics( id: 4, + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, @@ -17450,6 +17507,8 @@ void main() { children: [ TestSemantics( id: 4, + inputType: ui.SemanticsInputType.text, + currentValueLength: 0, flags: [ SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, diff --git a/packages/flutter/test/widgets/custom_painter_test.dart b/packages/flutter/test/widgets/custom_painter_test.dart index 7fe3f0ef15c5f..7b3c810f233af 100644 --- a/packages/flutter/test/widgets/custom_painter_test.dart +++ b/packages/flutter/test/widgets/custom_painter_test.dart @@ -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:ui'; + import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -644,6 +646,78 @@ void _defineTests() { semantics.dispose(); }); + testWidgets('semantics properties', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + final SemanticsProperties properties = SemanticsProperties( + label: 'label', + value: 'value', + increasedValue: 'increasedValue', + decreasedValue: 'decreasedValue', + hint: 'hint', + link: true, + linkUrl: Uri.parse('http://google.com'), + headingLevel: 1, + maxValueLength: 3, + currentValueLength: 1, + identifier: 'id', + tooltip: 'tooltip', + hintOverrides: const SemanticsHintOverrides(onLongPressHint: 'long', onTapHint: 'tap'), + textDirection: TextDirection.rtl, + tagForChildren: const SemanticsTag('tag'), + role: SemanticsRole.alertDialog, + controlsNodes: const {'abc'}, + inputType: SemanticsInputType.phone, + validationResult: SemanticsValidationResult.invalid, + ); + await tester.pumpWidget( + CustomPaint( + painter: _PainterWithSemantics( + semantics: CustomPainterSemantics( + key: const ValueKey(1), + rect: const Rect.fromLTRB(1.0, 2.0, 3.0, 4.0), + properties: properties, + ), + ), + ), + ); + + const int expectedId = 2; + final TestSemantics expectedSemantics = TestSemantics.root( + children: [ + TestSemantics.rootChild( + id: 1, + children: [ + TestSemantics.rootChild( + id: expectedId, + rect: TestSemantics.fullScreen, + label: properties.label!, + value: properties.value!, + increasedValue: properties.increasedValue!, + decreasedValue: properties.decreasedValue!, + hint: properties.hint!, + linkUrl: properties.linkUrl, + headingLevel: properties.headingLevel, + maxValueLength: properties.maxValueLength, + currentValueLength: properties.currentValueLength, + identifier: properties.identifier!, + tooltip: properties.tooltip!, + hintOverrides: properties.hintOverrides, + textDirection: properties.textDirection, + tags: {properties.tagForChildren!}, + role: properties.role!, + controlsNodes: properties.controlsNodes, + inputType: properties.inputType!, + validationResult: properties.validationResult, + flags: [SemanticsFlag.isLink], + ), + ], + ), + ], + ); + expect(semantics, hasSemantics(expectedSemantics, ignoreRect: true, ignoreTransform: true)); + semantics.dispose(); + }); + group('diffing', () { testWidgets('complains about duplicate keys', (WidgetTester tester) async { final SemanticsTester semanticsTester = SemanticsTester(tester); diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index e79e3f3ebbdcf..36a14b8d1f9f2 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:convert' show jsonDecode; +import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -4710,6 +4711,7 @@ void main() { SemanticsFlag.isObscured, ], value: expectedValue, + inputType: SemanticsInputType.text, textDirection: TextDirection.ltr, ), ], @@ -4766,6 +4768,7 @@ void main() { TestSemantics( flags: [SemanticsFlag.isTextField], value: originalText, + inputType: SemanticsInputType.text, textDirection: TextDirection.ltr, ), ], @@ -4825,6 +4828,7 @@ void main() { SemanticsAction.moveCursorBackwardByWord, ], value: expectedValue, + inputType: SemanticsInputType.text, textDirection: TextDirection.ltr, // Focusing a single-line field on web selects it. textSelection: @@ -5080,6 +5084,7 @@ void main() { SemanticsAction.paste, ], value: 'test', + inputType: SemanticsInputType.text, textSelection: TextSelection.collapsed(offset: controller.text.length), textDirection: TextDirection.ltr, ), @@ -5200,6 +5205,7 @@ void main() { SemanticsAction.setSelection, SemanticsAction.setText, ], + inputType: SemanticsInputType.text, textSelection: TextSelection.collapsed(offset: controller.text.length), textDirection: TextDirection.ltr, ), diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index 8fc6daa2dc064..fea654d1ce89a 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -8,7 +8,7 @@ @TestOn('!chrome') library; -import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle; +import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle, SemanticsInputType; import 'dart:ui'; import 'package:flutter/cupertino.dart'; @@ -1616,6 +1616,7 @@ void main() { id: 1, actions: [SemanticsAction.longPress], textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, children: [ TestSemantics( id: 2, @@ -2165,6 +2166,7 @@ void main() { textDirection: TextDirection.ltr, value: 'Guten Tag', actions: [SemanticsAction.longPress], + inputType: ui.SemanticsInputType.text, flags: [ SemanticsFlag.isTextField, SemanticsFlag.isReadOnly, @@ -2194,6 +2196,7 @@ void main() { textDirection: TextDirection.ltr, value: 'Guten Tag', textSelection: const TextSelection.collapsed(offset: 9), + inputType: ui.SemanticsInputType.text, actions: [ SemanticsAction.longPress, SemanticsAction.moveCursorBackwardByCharacter, @@ -2227,6 +2230,7 @@ void main() { textDirection: TextDirection.ltr, textSelection: const TextSelection.collapsed(offset: 4), value: 'Guten Tag', + inputType: ui.SemanticsInputType.text, actions: [ SemanticsAction.longPress, SemanticsAction.moveCursorBackwardByCharacter, @@ -2262,6 +2266,7 @@ void main() { textDirection: TextDirection.ltr, textSelection: const TextSelection.collapsed(offset: 0), value: 'Guten Tag', + inputType: ui.SemanticsInputType.text, actions: [ SemanticsAction.longPress, SemanticsAction.moveCursorForwardByCharacter, @@ -2341,6 +2346,7 @@ void main() { id: 1, value: 'Guten Tag', textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, actions: [ SemanticsAction.longPress, // Absent the following because enableInteractiveSelection: false @@ -2386,6 +2392,7 @@ void main() { id: 1, value: 'Hello', textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, actions: [SemanticsAction.longPress], flags: [ SemanticsFlag.isReadOnly, @@ -2417,6 +2424,7 @@ void main() { value: 'Hello', textSelection: const TextSelection.collapsed(offset: 5), textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, actions: [ SemanticsAction.longPress, SemanticsAction.moveCursorBackwardByCharacter, @@ -2450,6 +2458,7 @@ void main() { value: 'Hello', textSelection: const TextSelection(baseOffset: 5, extentOffset: 3), textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, actions: [ SemanticsAction.longPress, SemanticsAction.moveCursorBackwardByCharacter, @@ -2526,6 +2535,7 @@ void main() { children: [ TestSemantics( actions: [SemanticsAction.longPress], + inputType: ui.SemanticsInputType.text, children: [ TestSemantics( children: [ @@ -2599,6 +2609,7 @@ void main() { value: 'Hello', textSelection: const TextSelection.collapsed(offset: 5), textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, actions: [ SemanticsAction.longPress, SemanticsAction.moveCursorBackwardByCharacter, @@ -2652,6 +2663,7 @@ void main() { value: 'Hello', textSelection: const TextSelection(baseOffset: 0, extentOffset: 5), textDirection: TextDirection.ltr, + inputType: ui.SemanticsInputType.text, actions: [ SemanticsAction.longPress, SemanticsAction.moveCursorBackwardByCharacter, @@ -2698,6 +2710,7 @@ void main() { children: [ TestSemantics( id: inputFieldId, + inputType: ui.SemanticsInputType.text, flags: [ SemanticsFlag.isReadOnly, SemanticsFlag.isTextField, @@ -2724,6 +2737,7 @@ void main() { children: [ TestSemantics( id: inputFieldId, + inputType: ui.SemanticsInputType.text, flags: [ SemanticsFlag.isReadOnly, SemanticsFlag.isTextField, diff --git a/packages/flutter/test/widgets/semantics_tester.dart b/packages/flutter/test/widgets/semantics_tester.dart index eb5f6bfa9b44d..6f526ea43a0d2 100644 --- a/packages/flutter/test/widgets/semantics_tester.dart +++ b/packages/flutter/test/widgets/semantics_tester.dart @@ -57,6 +57,13 @@ class TestSemantics { Iterable? tags, this.role = SemanticsRole.none, this.validationResult = SemanticsValidationResult.none, + this.inputType = SemanticsInputType.none, + this.controlsNodes, + this.linkUrl, + this.maxValueLength, + this.currentValueLength, + this.identifier = '', + this.hintOverrides, }) : assert(flags is int || flags is List), assert(actions is int || actions is List), tags = tags?.toSet() ?? {}; @@ -82,6 +89,13 @@ class TestSemantics { Iterable? tags, this.role = SemanticsRole.none, this.validationResult = SemanticsValidationResult.none, + this.inputType = SemanticsInputType.none, + this.controlsNodes, + this.linkUrl, + this.maxValueLength, + this.currentValueLength, + this.identifier = '', + this.hintOverrides, }) : id = 0, assert(flags is int || flags is List), assert(actions is int || actions is List), @@ -123,6 +137,13 @@ class TestSemantics { Iterable? tags, this.role = SemanticsRole.none, this.validationResult = SemanticsValidationResult.none, + this.inputType = SemanticsInputType.none, + this.controlsNodes, + this.linkUrl, + this.maxValueLength, + this.currentValueLength, + this.identifier = '', + this.hintOverrides, }) : assert(flags is int || flags is List), assert(actions is int || actions is List), transform = _applyRootChildScale(transform), @@ -233,6 +254,7 @@ class TestSemantics { /// The total number of semantic nodes within a scrollable. final int? scrollChildren; + /// The expected text selection. final TextSelection? textSelection; /// The validation result for this node, if any. @@ -243,6 +265,49 @@ class TestSemantics { /// for this field. final SemanticsValidationResult validationResult; + /// The expected heading level + final int? headingLevel; + + /// The expected role for the node. + /// + /// Defaults to SemanticsRole.none if not set. + final SemanticsRole role; + + /// The expected input type for the node. + /// + /// Defaults to SemanticsInputType.none if not set. + final SemanticsInputType inputType; + + /// The expected nodes that this node controls. + /// + /// Defaults to an empty set if not set. + final Set? controlsNodes; + + /// The expected url for the node. + /// + /// Defaults to null if not set. + final Uri? linkUrl; + + /// The expected max value length for the node. + /// + /// Defaults to null if not set. + final int? maxValueLength; + + /// The expected current value length for the node. + /// + /// Defaults to null if not set. + final int? currentValueLength; + + /// The expected identifier for the node. + /// + /// Defaults to an empty string if not set. + final String identifier; + + /// The expected hint overrides for the node. + /// + /// Defaults to null if not set. + final SemanticsHintOverrides? hintOverrides; + static Matrix4 _applyRootChildScale(Matrix4? transform) { final Matrix4 result = Matrix4.diagonal3Values(3.0, 3.0, 1.0); if (transform != null) { @@ -257,13 +322,6 @@ class TestSemantics { /// The tags of this node. final Set tags; - final int? headingLevel; - - /// The expected role for the node. - /// - /// Defaults to SemanticsRole.none if not set. - final SemanticsRole role; - bool _matches( SemanticsNode? node, Map matchState, { @@ -408,6 +466,43 @@ class TestSemantics { 'expected node id $id to have validationResult $validationResult but found validationResult ${node.validationResult}', ); } + if (inputType != node.inputType) { + return fail( + 'expected node id $id to have input type $inputType but found input type ${node.inputType}', + ); + } + + if (controlsNodes != controlsNodes && !setEquals(controlsNodes, node.controlsNodes)) { + return fail( + 'expected node id $id to controls nodes $controlsNodes but found controlling nodes ${node.controlsNodes}', + ); + } + + if (linkUrl?.toString() != node.linkUrl?.toString()) { + return fail( + 'expected node id $id to have link url $linkUrl but found link url ${node.linkUrl}', + ); + } + if (maxValueLength != node.maxValueLength) { + return fail( + 'expected node id $id to have max value length $maxValueLength but found max value length ${node.maxValueLength}', + ); + } + if (currentValueLength != node.currentValueLength) { + return fail( + 'expected node id $id to have current value length $currentValueLength but found current value length ${node.currentValueLength}', + ); + } + if (identifier != node.identifier) { + return fail( + 'expected node id $id to have identifier $identifier but found identifier ${node.identifier}', + ); + } + if (hintOverrides != node.hintOverrides) { + return fail( + 'expected node id $id to have hint overrides $hintOverrides but found hint overrides ${node.hintOverrides}', + ); + } if (children.isEmpty) { return true; @@ -494,6 +589,27 @@ class TestSemantics { if (thickness != null) { buf.writeln('$indent thickness: $thickness,'); } + if (inputType != SemanticsInputType.none) { + buf.writeln('$indent inputType: $inputType,'); + } + if (controlsNodes != null) { + buf.writeln('$indent controlsNodes: $controlsNodes,'); + } + if (linkUrl != null) { + buf.writeln('$indent linkUrl: $linkUrl,'); + } + if (maxValueLength != null) { + buf.writeln('$indent maxValueLength: $maxValueLength,'); + } + if (currentValueLength != null) { + buf.writeln('$indent currentValueLength: $currentValueLength,'); + } + if (identifier.isNotEmpty) { + buf.writeln('$indent identifier: $identifier,'); + } + if (hintOverrides != null) { + buf.writeln('$indent hintOverrides: $hintOverrides,'); + } buf.writeln('$indent children: ['); for (final TestSemantics child in children) { buf.writeln('${child.toString(indentAmount + 2)},'); @@ -827,6 +943,27 @@ class SemanticsTester { if (node.role != SemanticsRole.none) { buf.writeln(' role: ${node.role},'); } + if (node.inputType != SemanticsInputType.none) { + buf.writeln(' inputType: ${node.inputType},'); + } + if (node.controlsNodes != null) { + buf.writeln(' controlsNodes: ${node.controlsNodes},'); + } + if (node.linkUrl != null) { + buf.writeln(' linkUrl: ${node.linkUrl},'); + } + if (node.maxValueLength != null) { + buf.writeln(' maxValueLength: ${node.maxValueLength},'); + } + if (node.currentValueLength != null) { + buf.writeln(' currentValueLength: ${node.currentValueLength},'); + } + if (node.identifier.isNotEmpty) { + buf.writeln(' identifier: ${node.identifier},'); + } + if (node.hintOverrides != null) { + buf.writeln(' hintOverrides: ${node.hintOverrides},'); + } if (node.hasChildren) { buf.writeln(' children: ['); for (final SemanticsNode child in node.debugListChildrenInOrder(childOrder)) {