From 4f786841f98de325fc9195f3256ded5dec1b35c3 Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:17:07 -0700 Subject: [PATCH] Revert "Add `FocusNode.focusabilityListenable` (#144280)" since the feature is no longer needed (#145102) This reverts commit 726e5d28c088260b507067a6890a69b6ccb0f103. *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* *List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.* *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* --- .../lib/src/widgets/focus_manager.dart | 176 +----------- .../test/widgets/focus_manager_test.dart | 269 ------------------ .../test/widgets/focus_scope_test.dart | 163 ----------- 3 files changed, 10 insertions(+), 598 deletions(-) diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index 61413b68a06..19e6294a5ae 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -517,76 +517,21 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { /// focus traversal policy for a widget subtree. /// * [FocusTraversalPolicy], a class that can be extended to describe a /// traversal policy. - bool get canRequestFocus => _canRequestFocus && (_focusabilityListenable?.value ?? _computeAncestorsAllowFocus()); - bool _computeAncestorsAllowFocus() => ancestors.every(_allowDescendantsToBeFocused); + bool get canRequestFocus => _canRequestFocus && ancestors.every(_allowDescendantsToBeFocused); static bool _allowDescendantsToBeFocused(FocusNode ancestor) => ancestor.descendantsAreFocusable; bool _canRequestFocus; @mustCallSuper set canRequestFocus(bool value) { - if (value == _canRequestFocus) { - return; - } - // Have to set this first before unfocusing, since it checks this to cull - // unfocusable, previously-focused children. - _canRequestFocus = value; - if (hasFocus && !value) { - unfocus(disposition: UnfocusDisposition.previouslyFocusedChild); - } - - final _FocusabilityListenable? focusabilityListenable = _focusabilityListenable; - if (focusabilityListenable != null && focusabilityListenable.hasListeners) { - final bool ancestorsAllowFocus = focusabilityListenable._ancestorsAllowFocus = _adjustListeningNodeCountForAncestors(value ? 1 : -1); - if (ancestorsAllowFocus) { - focusabilityListenable.notifyListeners(); + if (value != _canRequestFocus) { + // Have to set this first before unfocusing, since it checks this to cull + // unfocusable, previously-focused children. + _canRequestFocus = value; + if (hasFocus && !value) { + unfocus(disposition: UnfocusDisposition.previouslyFocusedChild); } + _manager?._markPropertiesChanged(this); } - _manager?._markPropertiesChanged(this); - } - - // The number of descendant focus nodes whose focusability must be - // re-evaluated, when this node's `descentantsAreFocusable` value changes. - // This does not include nodes with `_canRequestFocus` set to false, even when - // their focusability listenable has listeners. - int _focusabilityListeningDescendantCount = 0; - - /// A [ValueListenable] that notifies registered listeners when the - /// focusability of this [FocusNode] changes. - /// - /// The [ValueListenable]'s `value` indicates whether this [FocusNode] can - /// request primary focus. It's value is always consistent with the return - /// value of the [canRequestFocus] getter, which only returns true when the - /// [FocusNode]'s [canRequestFocus] setter is set to true, and all of its - /// ancestors in the focus tree have [FocusNode.descendantsAreFocusable] set to - /// true. - /// - /// Unlike listeners added to the [FocusNode] itself, which won't be notified - /// until focus changes are applied in microtasks, listeners added to - /// [focusabilityListenable] are notified immediately as the [FocusNode]'s - /// focusability changes. - /// - /// This can be used to monitor, for example, whether a text field is currently - /// disabled, or in an inactive route, thus isn't receiving user interactions, - /// so that text field can unsubscribe itself from system services such as - /// scribble and autofill when it becomes unfocusable, and re-subscribe when it - /// becomes focusable again. - /// - /// This [ValueListenable] is managed by this [FocusNode]. It must not be used - /// after the [FocusNode] itself is disposed. - ValueListenable get focusabilityListenable => _focusabilityListenable ??= _FocusabilityListenable(this); - _FocusabilityListenable? _focusabilityListenable; - - // Returns whether all ancestors have `descendantsAreFocusable` set to true. - bool _adjustListeningNodeCountForAncestors(int delta) { - assert(delta != 0); - for (FocusNode? node = parent; node != null; node = node.parent) { - node._focusabilityListeningDescendantCount += delta; - assert(node._focusabilityListeningDescendantCount >= 0); - if (!node.descendantsAreFocusable) { - return false; - } - } - return true; } /// If false, will disable focus for all of this node's descendants. @@ -629,47 +574,9 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { if (!value && hasFocus) { unfocus(disposition: UnfocusDisposition.previouslyFocusedChild); } - _onDescendantsAreFocusableChanged(value); _manager?._markPropertiesChanged(this); } - void _onDescendantsAreFocusableChanged(bool newValue) { - assert(_focusabilityListeningDescendantCount >= 0); - final int ancestorListenerAdjustment = newValue ? _focusabilityListeningDescendantCount : -_focusabilityListeningDescendantCount; - - // If there's an ancestor that disallows focus, changing the - // `descendantsAreFocusable` value of this node never affects the - // focusability of the descendants. Notify _focusabilityListenerCount - // listeners only when this is not the case. - final bool notifyChildren = ancestorListenerAdjustment != 0 - && _adjustListeningNodeCountForAncestors(ancestorListenerAdjustment); - if (notifyChildren) { - assert(children.isNotEmpty); - for (final FocusNode child in children) { - child._notifyFocusabilityListenersInSubtree(newValue); - } - } - } - - void _notifyFocusabilityListenersInSubtree(bool ancestorsAllowFocus) { - final _FocusabilityListenable? focusabilityListenable = _focusabilityListenable; - if (_canRequestFocus && focusabilityListenable != null && focusabilityListenable.hasListeners) { - assert(ancestorsAllowFocus == _computeAncestorsAllowFocus()); - assert(ancestorsAllowFocus != focusabilityListenable._ancestorsAllowFocus); - focusabilityListenable._ancestorsAllowFocus = ancestorsAllowFocus; - focusabilityListenable.notifyListeners(); - } - - if (_focusabilityListeningDescendantCount > 0 && descendantsAreFocusable) { - // Further propagate to children whose focusability is determined by this - // node's ancestors. - assert(children.isNotEmpty); - for (final FocusNode child in children) { - child._notifyFocusabilityListenersInSubtree(ancestorsAllowFocus); - } - } - } - /// If false, tells the focus traversal policy to skip over for all of this /// node's descendants for purposes of the traversal algorithm. /// @@ -1104,8 +1011,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { @mustCallSuper void _reparent(FocusNode child) { assert(child != this, 'Tried to make a child into a parent of itself.'); - final FocusNode? oldParent = child._parent; - if (oldParent == this) { + if (child._parent == this) { assert(_children.contains(child), "Found a node that says it's a child, but doesn't appear in the child list."); // The child is already a child of this parent. return; @@ -1114,15 +1020,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { assert(!ancestors.contains(child), 'The supplied child is already an ancestor of this node. Loops are not allowed.'); final FocusScopeNode? oldScope = child.enclosingScope; final bool hadFocus = child.hasFocus; - - final _FocusabilityListenable? childFocusabilityListenable = child._focusabilityListenable; - final int childSubtreeListenerCount = (child.descendantsAreFocusable ? child._focusabilityListeningDescendantCount : 0) - + (child._canRequestFocus && childFocusabilityListenable != null && childFocusabilityListenable.hasListeners ? 1 : 0); - // If childSubtreeListenerCount == 0, we don't care about focusability since there are no listeners. - final bool childCouldFocus = childSubtreeListenerCount > 0 - && child._adjustListeningNodeCountForAncestors(-childSubtreeListenerCount); - oldParent?._removeChild(child, removeScopeFocus: oldScope != nearestScope); - + child._parent?._removeChild(child, removeScopeFocus: oldScope != nearestScope); _children.add(child); child._parent = this; child._ancestors = null; @@ -1130,14 +1028,6 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { for (final FocusNode ancestor in child.ancestors) { ancestor._descendants = null; } - - if (childSubtreeListenerCount > 0) { - final bool childCanFocus = child._adjustListeningNodeCountForAncestors(childSubtreeListenerCount); - if (childCanFocus != childCouldFocus) { - child._notifyFocusabilityListenersInSubtree(childCanFocus); - } - } - if (hadFocus) { // Update the focus chain for the current focus without changing it. _manager?.primaryFocus?._setAsFocusedChildForScope(); @@ -1183,8 +1073,6 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { @override void dispose() { - _focusabilityListenable?.dispose(); - _focusabilityListenable = null; // Detaching will also unfocus and clean up the manager's data structures. _attachment?.detach(); super.dispose(); @@ -1383,14 +1271,6 @@ class FocusScopeNode extends FocusNode { this.traversalEdgeBehavior = TraversalEdgeBehavior.closedLoop, }) : super(descendantsAreFocusable: true); - @override - set canRequestFocus(bool value) { - if (value != _canRequestFocus) { - super.canRequestFocus = value; - _onDescendantsAreFocusableChanged(value); - } - } - @override FocusScopeNode get nearestScope => this; @@ -1534,42 +1414,6 @@ class FocusScopeNode extends FocusNode { } } -class _FocusabilityListenable extends ChangeNotifier implements ValueListenable { - _FocusabilityListenable(this.node); - - final FocusNode node; - - @override - bool get value { - assert(!hasListeners || _ancestorsAllowFocus == node._computeAncestorsAllowFocus()); - return node._canRequestFocus && (hasListeners ? _ancestorsAllowFocus : node._computeAncestorsAllowFocus()); - } - - // True if all ancestors of `node` have `descentantsAreFocusable` set to - // true. The value is only maintained when there are listeners, and - // `node._canRequestFocus` is true. - bool _ancestorsAllowFocus = true; - - @override - void addListener(VoidCallback listener) { - final bool hadListener = hasListeners; - super.addListener(listener); - assert(hasListeners); - if (!hadListener && node._canRequestFocus) { - _ancestorsAllowFocus = node._adjustListeningNodeCountForAncestors(1); - } - } - - @override - void removeListener(VoidCallback listener) { - final bool hadListener = hasListeners; - super.removeListener(listener); - if (node._canRequestFocus && hadListener && !hasListeners) { - _ancestorsAllowFocus = node._adjustListeningNodeCountForAncestors(-1); - } - } -} - /// An enum to describe which kind of focus highlight behavior to use when /// displaying focus information. enum FocusHighlightMode { diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index 0d1477d11d5..c5fd810e743 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -2143,275 +2143,6 @@ void main() { tester.binding.focusManager.removeListener(handleFocusChange); }); - group('focusability listener', () { - int focusabilityChangeCount = 0; - void focusabilityCallback() { - focusabilityChangeCount += 1; - } - - setUp(() { focusabilityChangeCount = 0; }); - - testWidgets('canRequestFocus affects focusability of the node', (WidgetTester tester) async { - int node2CallbackCounter = 0; - void node2Callback() { node2CallbackCounter += 1; } - final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback); - final FocusNode node2 = FocusNode(debugLabel: 'node 2')..focusabilityListenable.addListener(node2Callback); - - addTearDown(node1.dispose); - addTearDown(node2.dispose); - - await tester.pumpWidget( - Focus( - focusNode: node1, - child: Focus( - focusNode: node2, - child: const SizedBox(), - ), - ), - ); - - expect(node1.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 0); - expect(node2.focusabilityListenable.value, isTrue); - expect(node2CallbackCounter, 0); - - node1.canRequestFocus = false; - expect(node1.focusabilityListenable.value, isFalse); - expect(focusabilityChangeCount, 1); - expect(node2.focusabilityListenable.value, isTrue); - expect(node2CallbackCounter, 0); - - node1.canRequestFocus = true; - expect(node1.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 2); - expect(node2.focusabilityListenable.value, isTrue); - expect(node2CallbackCounter, 0); - - node2.canRequestFocus = false; - expect(node1.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 2); - expect(node2.focusabilityListenable.value, isFalse); - expect(node2CallbackCounter, 1); - - node2.canRequestFocus = true; - expect(node1.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 2); - expect(node2.focusabilityListenable.value, isTrue); - expect(node2CallbackCounter, 2); - }); - - testWidgets('descendantsAreFocusable affects focusability of the descendants', (WidgetTester tester) async { - int node2CallbackCounter = 0; - void node2Callback() { node2CallbackCounter += 1; } - final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback); - final FocusNode node2 = FocusNode(debugLabel: 'node 2', descendantsAreFocusable: false)..focusabilityListenable.addListener(node2Callback); - - addTearDown(node1.dispose); - addTearDown(node2.dispose); - - await tester.pumpWidget( - Focus( - focusNode: node1, - child: Focus( - focusNode: node2, - child: const SizedBox(), - ), - ), - ); - - expect(node1.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 0); - expect(node2.focusabilityListenable.value, isTrue); - expect(node2CallbackCounter, 0); - - node1.descendantsAreFocusable = false; - expect(node1.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 0); - expect(node2.focusabilityListenable.value, isFalse); - expect(node2CallbackCounter, 1); - - node1.descendantsAreFocusable = true; - expect(node1.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 0); - expect(node2.focusabilityListenable.value, isTrue); - expect(node2CallbackCounter, 2); - - node2.descendantsAreFocusable = false; - expect(node1.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 0); - expect(node2.focusabilityListenable.value, isTrue); - expect(node2CallbackCounter, 2); - }); - - testWidgets('Reparenting affects focusability of the node', (WidgetTester tester) async { - int node3CallbackCounter = 0; - void node3Callback() { node3CallbackCounter += 1; } - final FocusNode node1 = FocusNode(debugLabel: 'node 1'); - final FocusNode node2 = FocusNode(debugLabel: 'node 2', descendantsAreFocusable: false); - final FocusNode node3 = FocusNode(debugLabel: 'node 3')..focusabilityListenable.addListener(node3Callback); - final FocusNode node4 = FocusNode(debugLabel: 'node 4')..focusabilityListenable.addListener(focusabilityCallback); - addTearDown(node1.dispose); - addTearDown(node2.dispose); - addTearDown(node3.dispose); - addTearDown(node4.dispose); - - await tester.pumpWidget( - Focus( - focusNode: node1, - child: Focus( - focusNode: node2, - child: Column( - children: [ - Focus(focusNode: node3, child: Container()), - Focus(focusNode: node4, child: Container()), - ], - ) - ), - ), - ); - - // The listeners are notified on reparent. - expect(node4.focusabilityListenable.value, isFalse); - expect(focusabilityChangeCount, 1); - expect(node3.focusabilityListenable.value, isFalse); - expect(node3CallbackCounter, 1); - - // Swap node 1 and node 3. - await tester.pumpWidget( - Focus( - focusNode: node3, - child: Focus( - focusNode: node2, - child: Column( - children: [ - Focus(focusNode: node1, child: Container()), - Focus(focusNode: node4, child: Container()), - ], - ) - ), - ), - ); - - expect(node4.focusabilityListenable.value, isFalse); - expect(focusabilityChangeCount, 1); - expect(node3.focusabilityListenable.value, isTrue); - expect(node3CallbackCounter, 2); - - // Swap node 1 and node 2. - await tester.pumpWidget( - Focus( - focusNode: node3, - child: Focus( - focusNode: node1, - child: Column( - children: [ - Focus(focusNode: node2, child: Container()), - Focus(focusNode: node4, child: Container()), - ], - ) - ), - ), - ); - - expect(node4.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 2); - expect(node3.focusabilityListenable.value, isTrue); - expect(node3CallbackCounter, 2); - - // Swap node 2 and node 4. - await tester.pumpWidget( - Focus( - focusNode: node3, - child: Focus( - focusNode: node1, - child: Column( - children: [ - Focus(focusNode: node4, child: Container()), - Focus(focusNode: node2, child: Container()), - ], - ) - ), - ), - ); - - expect(node4.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 2); - expect(node3.focusabilityListenable.value, isTrue); - expect(node3CallbackCounter, 2); - - // Return to the initial state - await tester.pumpWidget( - Focus( - focusNode: node1, - child: Focus( - focusNode: node2, - child: Column( - children: [ - Focus(focusNode: node3, child: Container()), - Focus(focusNode: node4, child: Container()), - ], - ) - ), - ), - ); - - expect(node4.focusabilityListenable.value, isFalse); - expect(focusabilityChangeCount, 3); - expect(node3.focusabilityListenable.value, isFalse); - expect(node3CallbackCounter, 3); - }); - - testWidgets('does not get called in dispose', (WidgetTester tester) async { - final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback); - final FocusNode node2 = FocusNode(debugLabel: 'node 2')..focusabilityListenable.addListener(focusabilityCallback); - - await tester.pumpWidget( - Focus( - descendantsAreFocusable: false, - child: Column( - children: [ - Focus(focusNode: node1, child: Container()), - Focus(focusNode: node2, child: Container()), - ], - ), - ), - ); - expect(focusabilityChangeCount, 2); - - await tester.pumpWidget(const SizedBox()); - expect(focusabilityChangeCount, 2); - }); - - testWidgets('Adding removing listeners many times', (WidgetTester tester) async { - final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback); - final FocusNode node2 = FocusNode(debugLabel: 'node 2'); - - for (int i = 0; i < 100; i += 1) { - node1.focusabilityListenable.removeListener(focusabilityCallback); - node1.focusabilityListenable.removeListener(focusabilityCallback); - node1.focusabilityListenable.addListener(focusabilityCallback); - node1.focusabilityListenable.removeListener(focusabilityCallback); - } - node1.focusabilityListenable.addListener(focusabilityCallback); - node1.focusabilityListenable.addListener(focusabilityCallback); - node2.focusabilityListenable.addListener(focusabilityCallback); - expect(focusabilityChangeCount, 0); - - await tester.pumpWidget( - Focus( - descendantsAreFocusable: false, - child: Column( - children: [ - Focus(focusNode: node1, child: Container()), - Focus(focusNode: node2, child: Container()), - ], - ), - ), - ); - expect(focusabilityChangeCount, 3); - }); - }); - testWidgets('debugFocusChanges causes logging of focus changes', (WidgetTester tester) async { final bool oldDebugFocusChanges = debugFocusChanges; final DebugPrintCallback oldDebugPrint = debugPrint; diff --git a/packages/flutter/test/widgets/focus_scope_test.dart b/packages/flutter/test/widgets/focus_scope_test.dart index ad2a3c91e99..9203c6a9065 100644 --- a/packages/flutter/test/widgets/focus_scope_test.dart +++ b/packages/flutter/test/widgets/focus_scope_test.dart @@ -2141,169 +2141,6 @@ void main() { expect(childFocusNode.canRequestFocus, isTrue); }); }); - - group('focusability listener', () { - int focusabilityChangeCount = 0; - void focusabilityCallback() { - focusabilityChangeCount += 1; - } - - setUp(() { focusabilityChangeCount = 0; }); - - testWidgets('canRequestFocus affects child focusability', (WidgetTester tester) async { - final FocusScopeNode scopeNode1 = FocusScopeNode(debugLabel: 'scope1'); - final FocusScopeNode scopeNode2 = FocusScopeNode(debugLabel: 'scope2'); - final FocusNode node1 = FocusNode(debugLabel: 'node 1'); - final FocusNode node2 = FocusNode(debugLabel: 'node 2'); - final FocusNode node3 = FocusNode(debugLabel: 'node 3'); - addTearDown(scopeNode1.dispose); - addTearDown(scopeNode2.dispose); - addTearDown(node1.dispose); - addTearDown(node2.dispose); - addTearDown(node3.dispose); - - await tester.pumpWidget( - FocusScope( - node: scopeNode1, - child: Column( - children: [ - Focus( - focusNode: node1, - child: Container(), - ), - Focus( - focusNode: node2, - child: FocusScope( - node: scopeNode2, - child: Focus(focusNode: node3, child: const SizedBox()), - ), - ), - ], - ), - ), - ); - - node3.focusabilityListenable.addListener(focusabilityCallback); - int node1FocusabilityCallbackCount = 0; - node1.focusabilityListenable.addListener(() => node1FocusabilityCallbackCount += 1); - - scopeNode1.canRequestFocus = false; - expect(node3.focusabilityListenable.value, isFalse); - expect(focusabilityChangeCount, 1); - expect(node1.focusabilityListenable.value, isFalse); - expect(node1FocusabilityCallbackCount, 1); - - scopeNode2.canRequestFocus = false; - expect(node3.focusabilityListenable.value, isFalse); - expect(focusabilityChangeCount, 1); - expect(node1.focusabilityListenable.value, isFalse); - expect(node1FocusabilityCallbackCount, 1); - - scopeNode1.canRequestFocus = true; - expect(node3.focusabilityListenable.value, isFalse); - expect(focusabilityChangeCount, 1); - expect(node1.focusabilityListenable.value, isTrue); - expect(node1FocusabilityCallbackCount, 2); - - scopeNode2.canRequestFocus = true; - expect(node3.focusabilityListenable.value, isTrue); - expect(focusabilityChangeCount, 2); - expect(node1.focusabilityListenable.value, isTrue); - expect(node1FocusabilityCallbackCount, 2); - }); - - testWidgets('onFocusabilityCallback invoked on mount, if not focusable', (WidgetTester tester) async { - final FocusScopeNode scopeNode1 = FocusScopeNode(debugLabel: 'scope1', canRequestFocus: false); - final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback); - addTearDown(scopeNode1.dispose); - addTearDown(node1.dispose); - - await tester.pumpWidget( - FocusScope( - node: scopeNode1, - child: Column( - children: [ - Focus( - focusNode: node1, - child: Container(), - ), - ], - ), - ), - ); - - expect(node1.focusabilityListenable.value, isFalse); - expect(focusabilityChangeCount, 1); - }); - - testWidgets('onFocusabilityCallback is not invoked on mount, if focusable', (WidgetTester tester) async { - final FocusScopeNode scopeNode1 = FocusScopeNode(debugLabel: 'scope1'); - final FocusNode node1 = FocusNode(debugLabel: 'node 1')..focusabilityListenable.addListener(focusabilityCallback); - addTearDown(scopeNode1.dispose); - addTearDown(node1.dispose); - - await tester.pumpWidget( - FocusScope( - node: scopeNode1, - child: Column( - children: [ - Focus( - focusNode: node1, - child: Container(), - ), - ], - ), - ), - ); - - expect(focusabilityChangeCount, 0); - }); - - testWidgets('onFocusabilityCallback on scope node', (WidgetTester tester) async { - final FocusScopeNode scopeNode1 = FocusScopeNode(debugLabel: 'scope1'); - final FocusScopeNode scopeNode2 = FocusScopeNode(debugLabel: 'scope2')..focusabilityListenable.addListener(focusabilityCallback); - addTearDown(scopeNode1.dispose); - addTearDown(scopeNode2.dispose); - - await tester.pumpWidget( - FocusScope( - node: scopeNode1, - child: FocusScope(node: scopeNode2, child: Container()) - ), - ); - - expect(focusabilityChangeCount, 0); - - scopeNode2.canRequestFocus = false; - expect(focusabilityChangeCount, 1); - expect(scopeNode2.focusabilityListenable.value, isFalse); - - scopeNode2.canRequestFocus = true; - expect(focusabilityChangeCount, 2); - expect(scopeNode2.focusabilityListenable.value, isTrue); - - // scope 2 has no descendants. - scopeNode2.descendantsAreFocusable = false; - expect(focusabilityChangeCount, 2); - expect(scopeNode2.focusabilityListenable.value, isTrue); - - scopeNode1.descendantsAreFocusable = false; - expect(focusabilityChangeCount, 3); - expect(scopeNode2.focusabilityListenable.value, isFalse); - - scopeNode1.descendantsAreFocusable = true; - expect(focusabilityChangeCount, 4); - expect(scopeNode2.focusabilityListenable.value, isTrue); - - scopeNode1.canRequestFocus = false; - expect(focusabilityChangeCount, 5); - expect(scopeNode2.focusabilityListenable.value, isFalse); - - scopeNode1.canRequestFocus = true; - expect(focusabilityChangeCount, 6); - expect(scopeNode2.focusabilityListenable.value, isTrue); - }); - }); } class TestFocus extends StatefulWidget {