From c07c4c9ff6d811ad42c51c7ea66cff75eb014b33 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Tue, 13 Feb 2024 13:39:43 -0600 Subject: [PATCH 1/6] Need to test --- packages/flutter_test/lib/src/controller.dart | 77 ++++++++++++------- .../test/multi_view_controller_test.dart | 16 ++++ 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart index 602a1b94b698..5a3cedd87a0d 100644 --- a/packages/flutter_test/lib/src/controller.dart +++ b/packages/flutter_test/lib/src/controller.dart @@ -183,8 +183,14 @@ class SemanticsController { FlutterView? view, }) { TestAsyncUtils.guardSync(); - assert(start == null || startNode == null, 'Cannot provide both start and startNode. Prefer startNode as start is deprecated.'); - assert(end == null || endNode == null, 'Cannot provide both end and endNode. Prefer endNode as end is deprecated.'); + assert( + start == null || startNode == null, + 'Cannot provide both start and startNode. Prefer startNode as start is deprecated.', + ); + assert( + end == null || endNode == null, + 'Cannot provide both end and endNode. Prefer endNode as end is deprecated.', + ); FlutterView? startView; if (start != null) { @@ -197,8 +203,7 @@ class SemanticsController { 'Specified view: $view' ); } - } - if (startNode != null) { + } else if (startNode != null) { final SemanticsOwner owner = startNode.evaluate().single.owner!; final RenderView renderView = _controller.binding.renderViews.firstWhere( (RenderView render) => render.owner!.semanticsOwner == owner, @@ -206,9 +211,9 @@ class SemanticsController { startView = renderView.flutterView; if (view != null && startView != view) { throw StateError( - 'The end node is not part of the provided view.\n' + 'The start node is not part of the provided view.\n' 'Finder: ${startNode.toString(describeSelf: true)}\n' - 'View of end node: $startView\n' + 'View of start node: $startView\n' 'Specified view: $view' ); } @@ -225,8 +230,7 @@ class SemanticsController { 'Specified view: $view' ); } - } - if (endNode != null) { + } else if (endNode != null) { final SemanticsOwner owner = endNode.evaluate().single.owner!; final RenderView renderView = _controller.binding.renderViews.firstWhere( (RenderView render) => render.owner!.semanticsOwner == owner, @@ -261,31 +265,48 @@ class SemanticsController { traversal, ); - int startIndex = 0; - int endIndex = traversal.length - 1; + // Setting the range + SemanticsNode? node; + String? errorString; + int startIndex; if (start != null) { - final SemanticsNode startNode = find(start); - startIndex = traversal.indexOf(startNode); - if (startIndex == -1) { - throw StateError( - 'The expected starting node was not found.\n' - 'Finder: ${start.toString(describeSelf: true)}\n\n' - 'Expected Start Node: $startNode\n\n' - 'Traversal: [\n ${traversal.join('\n ')}\n]'); - } + node = find(start); + startIndex = traversal.indexOf(node); + errorString = start.toString(describeSelf: true); + } else if (startNode != null) { + node = startNode.evaluate().single; + startIndex = traversal.indexOf(node); + errorString = startNode.toString(describeSelf: true); + } else { + startIndex = 0; + } + if (startIndex == -1) { + throw StateError( + 'The expected starting node was not found.\n' + 'Finder: $errorString\n\n' + 'Expected Start Node: $node\n\n' + 'Traversal: [\n ${traversal.join('\n ')}\n]'); } + int endIndex; if (end != null) { - final SemanticsNode endNode = find(end); - endIndex = traversal.indexOf(endNode); - if (endIndex == -1) { - throw StateError( - 'The expected ending node was not found.\n' - 'Finder: ${end.toString(describeSelf: true)}\n\n' - 'Expected End Node: $endNode\n\n' - 'Traversal: [\n ${traversal.join('\n ')}\n]'); - } + node = find(end); + endIndex = traversal.indexOf(node); + errorString = end.toString(describeSelf: true); + } else if (endNode != null) { + node = endNode.evaluate().single; + endIndex = traversal.indexOf(node); + errorString = endNode.toString(describeSelf: true); + } else { + endIndex = traversal.length - 1; + } + if (endIndex == -1) { + throw StateError( + 'The expected ending node was not found.\n' + 'Finder: $errorString\n\n' + 'Expected End Node: $node\n\n' + 'Traversal: [\n ${traversal.join('\n ')}\n]'); } return traversal.getRange(startIndex, endIndex + 1); diff --git a/packages/flutter_test/test/multi_view_controller_test.dart b/packages/flutter_test/test/multi_view_controller_test.dart index fc9ec1760951..e21b443b56de 100644 --- a/packages/flutter_test/test/multi_view_controller_test.dart +++ b/packages/flutter_test/test/multi_view_controller_test.dart @@ -24,6 +24,22 @@ void main() { ); }); + testWidgets('simulatedAccessibilityTraversal - startNode and endNode in same view', (WidgetTester tester) async { + tester.binding.ensureSemantics(); + await pumpViews(tester: tester); + expect( + tester.semantics.simulatedAccessibilityTraversal( + startNode: find.semantics.byValue('View2Child1'), + endNode: find.semantics.byValue('View2Child3'), + ).map((SemanticsNode node) => node.label), + [ + 'View2Child1', + 'View2Child2', + 'View2Child3', + ], + ); + }); + testWidgets('simulatedAccessibilityTraversal - start not specified', (WidgetTester tester) async { await pumpViews(tester: tester); expect( From 07e86309f8c61b82c2f3eefe29562a47ab7fbf49 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Tue, 13 Feb 2024 13:41:26 -0600 Subject: [PATCH 2/6] dart fixes --- .../fix_semantics_controller.yaml | 79 +++++++++++++++++++ .../flutter_test/semantics_controller.dart | 19 +++++ .../semantics_controller.dart.expect | 19 +++++ 3 files changed, 117 insertions(+) create mode 100644 packages/flutter_test/lib/fix_data/fix_flutter_test/fix_semantics_controller.yaml create mode 100644 packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart create mode 100644 packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart.expect diff --git a/packages/flutter_test/lib/fix_data/fix_flutter_test/fix_semantics_controller.yaml b/packages/flutter_test/lib/fix_data/fix_flutter_test/fix_semantics_controller.yaml new file mode 100644 index 000000000000..6ad37c5d7d25 --- /dev/null +++ b/packages/flutter_test/lib/fix_data/fix_flutter_test/fix_semantics_controller.yaml @@ -0,0 +1,79 @@ +# Copyright 2014 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# For details regarding the *Flutter Fix* feature, see +# https://flutter.dev/docs/development/tools/flutter-fix + +# Please add new fixes to the top of the file, separated by one blank line +# from other fixes. In a comment, include a link to the PR where the change +# requiring the fix was made. + +# Every fix must be tested. See the +# flutter/packages/flutter_test/test_fixes/README.md file for instructions +# on testing these data driven fixes. + +# For documentation about this file format, see +# https://dart.dev/go/data-driven-fixes. + +# * Fixes in this file are for the flutter_test/controller.dart file. * + +version: 1 +transforms: + # Changes made in TBD + - title: "Migrate to startNode and endNode." + date: 2024-02-13 + element: + uris: [ 'flutter_test.dart' ] + method: simulatedAccessibilityTraversal + inClass: SemanticsController + oneOf: + - if: "start != '' && end != ''" + changes: + - kind: 'addParameter' + index: 2 + name: 'startNode' + style: optional_named + argumentValue: + expression: '{% start %}' + requiredIf: "start != '' && end != ''" + - kind: 'addParameter' + index: 3 + name: 'endNode' + style: optional_named + argumentValue: + expression: '{% end %}' + requiredIf: "start != '' && end != ''" + - kind: 'removeParameter' + name: 'start' + - kind: 'removeParameter' + name: 'end' + - if: "start != '' && end == ''" + changes: + - kind: 'addParameter' + index: 2 + name: 'startNode' + style: optional_named + argumentValue: + expression: '{% start %}' + requiredIf: "start != '' && end == ''" + - kind: 'removeParameter' + name: 'start' + - if: "start == '' && end != ''" + changes: + - kind: 'addParameter' + index: 2 + name: 'endNode' + style: optional_named + argumentValue: + expression: '{% end %}' + requiredIf: "start == '' && end != ''" + - kind: 'removeParameter' + name: 'end' + variables: + start: + kind: 'fragment' + value: 'arguments[start]' + end: + kind: 'fragment' + value: 'arguments[end]' diff --git a/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart b/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart new file mode 100644 index 000000000000..562ea839569b --- /dev/null +++ b/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart @@ -0,0 +1,19 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + // Generic reference variables. + finders.FinderBase theStart; + finders.FinderBase theEnd; + + testWidgets('simulatedAccessibilityTraversal', (WidgetTester tester) async { + // Changes made in TBD + tester.semantics.simulatedAccessibilityTraversal(); + tester.semantics.simulatedAccessibilityTraversal(start: theStart); + tester.semantics.simulatedAccessibilityTraversal(end: theEnd); + tester.semantics.simulatedAccessibilityTraversal(start: theStart, end: theEnd); + }); +} diff --git a/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart.expect b/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart.expect new file mode 100644 index 000000000000..8f18aa8919a6 --- /dev/null +++ b/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart.expect @@ -0,0 +1,19 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + // Generic reference variables. + finders.FinderBase theStart; + finders.FinderBase theEnd; + + testWidgets('simulatedAccessibilityTraversal', (WidgetTester tester) async { + // Changes made in TBD + tester.semantics.simulatedAccessibilityTraversal(); + tester.semantics.simulatedAccessibilityTraversal(startNode: theStart); + tester.semantics.simulatedAccessibilityTraversal(endNode: theEnd); + tester.semantics.simulatedAccessibilityTraversal(startNode: theStart, endNode: theEnd); + }); +} From c0fcf24ca605bc783cd939237c55b7338ab352eb Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Tue, 13 Feb 2024 16:18:17 -0600 Subject: [PATCH 3/6] Add tets --- .../flutter_test/test/controller_test.dart | 104 ++++++++++++++++++ .../test/multi_view_controller_test.dart | 16 --- 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/packages/flutter_test/test/controller_test.dart b/packages/flutter_test/test/controller_test.dart index 00e1fc8736d2..83fedf330ca1 100644 --- a/packages/flutter_test/test/controller_test.dart +++ b/packages/flutter_test/test/controller_test.dart @@ -920,6 +920,30 @@ void main() { orderedEquals(expectedMatchers)); }); + testWidgets('starts traversal at semantics node for `startNode`', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Center( + child: Column( + children: [ + for (int c = 0; c < 5; c++) + Semantics(container: true, child: Text('Child$c')), + ] + ), + ), + )); + expect( + tester.semantics.simulatedAccessibilityTraversal( + startNode: find.semantics.byLabel('Child1'), + ).map((SemanticsNode node) => node.label), + [ + 'Child1', + 'Child2', + 'Child3', + 'Child4', + ], + ); + }); + testWidgets('throws StateError if `start` not found in traversal', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); @@ -931,6 +955,23 @@ void main() { ); }); + testWidgets('throws StateError if `startNode` not found in traversal', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Center( + child: Column( + children: [ + for (int c = 0; c < 5; c++) + Semantics(container: true, child: Text('Child$c')), + ] + ), + ), + )); + expect( + () => tester.semantics.simulatedAccessibilityTraversal(startNode: find.semantics.byLabel('Child20')), + throwsA(isA()), + ); + }); + testWidgets('ends traversal at semantics node for `end`', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); @@ -942,6 +983,28 @@ void main() { orderedEquals(expectedMatchers)); }); + testWidgets('ends traversal at semantics node for `endNode`', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Center( + child: Column( + children: [ + for (int c = 0; c < 5; c++) + Semantics(container: true, child: Text('Child$c')), + ] + ), + ), + )); + expect( + tester.semantics.simulatedAccessibilityTraversal( + endNode: find.semantics.byLabel('Child1'), + ).map((SemanticsNode node) => node.label), + [ + 'Child0', + 'Child1', + ], + ); + }); + testWidgets('throws StateError if `end` not found in traversal', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); @@ -953,6 +1016,23 @@ void main() { ); }); + testWidgets('throws StateError if `endNode` not found in traversal', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Center( + child: Column( + children: [ + for (int c = 0; c < 5; c++) + Semantics(container: true, child: Text('Child$c')), + ] + ), + ), + )); + expect( + () => tester.semantics.simulatedAccessibilityTraversal(endNode: find.semantics.byLabel('Child20')), + throwsA(isA()), + ); + }); + testWidgets('returns traversal between `start` and `end` if both are provided', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); @@ -967,6 +1047,30 @@ void main() { orderedEquals(expectedMatchers)); }); + testWidgets('returns traversal between `startNode` and `endNode` if both are provided', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Center( + child: Column( + children: [ + for (int c = 0; c < 5; c++) + Semantics(container: true, child: Text('Child$c')), + ] + ), + ), + )); + expect( + tester.semantics.simulatedAccessibilityTraversal( + startNode: find.semantics.byLabel('Child1'), + endNode: find.semantics.byLabel('Child3'), + ).map((SemanticsNode node) => node.label), + [ + 'Child1', + 'Child2', + 'Child3', + ], + ); + }); + testWidgets('can do fuzzy traversal match with `containsAllInOrder`', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); diff --git a/packages/flutter_test/test/multi_view_controller_test.dart b/packages/flutter_test/test/multi_view_controller_test.dart index e21b443b56de..fc9ec1760951 100644 --- a/packages/flutter_test/test/multi_view_controller_test.dart +++ b/packages/flutter_test/test/multi_view_controller_test.dart @@ -24,22 +24,6 @@ void main() { ); }); - testWidgets('simulatedAccessibilityTraversal - startNode and endNode in same view', (WidgetTester tester) async { - tester.binding.ensureSemantics(); - await pumpViews(tester: tester); - expect( - tester.semantics.simulatedAccessibilityTraversal( - startNode: find.semantics.byValue('View2Child1'), - endNode: find.semantics.byValue('View2Child3'), - ).map((SemanticsNode node) => node.label), - [ - 'View2Child1', - 'View2Child2', - 'View2Child3', - ], - ); - }); - testWidgets('simulatedAccessibilityTraversal - start not specified', (WidgetTester tester) async { await pumpViews(tester: tester); expect( From 874318335287b59cee9c6ab2ffea5718d5c2c413 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Tue, 13 Feb 2024 16:29:24 -0600 Subject: [PATCH 4/6] Self review --- .../test_fixes/flutter_test/semantics_controller.dart.expect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart.expect b/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart.expect index 8f18aa8919a6..d00687969b3e 100644 --- a/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart.expect +++ b/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart.expect @@ -10,7 +10,7 @@ void main() { finders.FinderBase theEnd; testWidgets('simulatedAccessibilityTraversal', (WidgetTester tester) async { - // Changes made in TBD + // Changes made in https://github.com/flutter/flutter/pull/143386 tester.semantics.simulatedAccessibilityTraversal(); tester.semantics.simulatedAccessibilityTraversal(startNode: theStart); tester.semantics.simulatedAccessibilityTraversal(endNode: theEnd); From 1e6853291eba42ee7005f284265118be33c99673 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Thu, 15 Feb 2024 11:12:04 -0600 Subject: [PATCH 5/6] fix out of range error in end index --- packages/flutter_test/lib/src/controller.dart | 5 ++--- packages/flutter_test/test/controller_test.dart | 12 ++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart index 5a3cedd87a0d..eb737d283e20 100644 --- a/packages/flutter_test/lib/src/controller.dart +++ b/packages/flutter_test/lib/src/controller.dart @@ -289,7 +289,7 @@ class SemanticsController { 'Traversal: [\n ${traversal.join('\n ')}\n]'); } - int endIndex; + int? endIndex; if (end != null) { node = find(end); endIndex = traversal.indexOf(node); @@ -298,8 +298,6 @@ class SemanticsController { node = endNode.evaluate().single; endIndex = traversal.indexOf(node); errorString = endNode.toString(describeSelf: true); - } else { - endIndex = traversal.length - 1; } if (endIndex == -1) { throw StateError( @@ -308,6 +306,7 @@ class SemanticsController { 'Expected End Node: $node\n\n' 'Traversal: [\n ${traversal.join('\n ')}\n]'); } + endIndex ??= traversal.length - 1; return traversal.getRange(startIndex, endIndex + 1); } diff --git a/packages/flutter_test/test/controller_test.dart b/packages/flutter_test/test/controller_test.dart index 83fedf330ca1..afc10dd576f0 100644 --- a/packages/flutter_test/test/controller_test.dart +++ b/packages/flutter_test/test/controller_test.dart @@ -920,6 +920,18 @@ void main() { orderedEquals(expectedMatchers)); }); + testWidgets('simulatedAccessibilityTraversal end Index supports empty traversal', (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp( + home: Center( + child: Column(), // No nodes! + ), + )); + expect( + tester.semantics.simulatedAccessibilityTraversal().map((SemanticsNode node) => node.label), + [], + ); + }); + testWidgets('starts traversal at semantics node for `startNode`', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Center( From d62e71ec478dec95eccc88343bb1987e3f2538a1 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Thu, 15 Feb 2024 13:27:50 -0600 Subject: [PATCH 6/6] Update packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart --- .../test_fixes/flutter_test/semantics_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart b/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart index 562ea839569b..7dec14cc7c19 100644 --- a/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart +++ b/packages/flutter_test/test_fixes/flutter_test/semantics_controller.dart @@ -10,7 +10,7 @@ void main() { finders.FinderBase theEnd; testWidgets('simulatedAccessibilityTraversal', (WidgetTester tester) async { - // Changes made in TBD + // Changes made in https://github.com/flutter/flutter/pull/143386 tester.semantics.simulatedAccessibilityTraversal(); tester.semantics.simulatedAccessibilityTraversal(start: theStart); tester.semantics.simulatedAccessibilityTraversal(end: theEnd);