// 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/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('LookupBoundary.dependOnInheritedWidgetOfExactType', () { testWidgets('respects boundary', (WidgetTester tester) async { InheritedWidget? containerThroughBoundary; InheritedWidget? containerStoppedAtBoundary; final Key inheritedKey = UniqueKey(); await tester.pumpWidget(MyInheritedWidget( value: 2, key: inheritedKey, child: LookupBoundary( child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>(); containerStoppedAtBoundary = LookupBoundary.dependOnInheritedWidgetOfExactType<MyInheritedWidget>(context); return const SizedBox.expand(); }, ), ), )); expect(containerThroughBoundary, equals(tester.widget(find.byKey(inheritedKey)))); expect(containerStoppedAtBoundary, isNull); }); testWidgets('ignores ancestor boundary', (WidgetTester tester) async { InheritedWidget? inheritedWidget; final Key inheritedKey = UniqueKey(); await tester.pumpWidget(LookupBoundary( child: MyInheritedWidget( value: 2, key: inheritedKey, child: Builder( builder: (BuildContext context) { inheritedWidget = LookupBoundary.dependOnInheritedWidgetOfExactType<MyInheritedWidget>(context); return const SizedBox.expand(); }, ), ), )); expect(inheritedWidget, equals(tester.widget(find.byKey(inheritedKey)))); }); testWidgets('finds widget before boundary', (WidgetTester tester) async { InheritedWidget? containerThroughBoundary; InheritedWidget? containerStoppedAtBoundary; final Key inheritedKey = UniqueKey(); await tester.pumpWidget(MyInheritedWidget( value: 2, child: LookupBoundary( child: MyInheritedWidget( key: inheritedKey, value: 1, child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>(); containerStoppedAtBoundary = LookupBoundary.dependOnInheritedWidgetOfExactType<MyInheritedWidget>(context); return const SizedBox.expand(); }, ), ), ), )); expect(containerThroughBoundary, equals(tester.widget(find.byKey(inheritedKey)))); expect(containerStoppedAtBoundary, equals(tester.widget(find.byKey(inheritedKey)))); }); testWidgets('creates dependency', (WidgetTester tester) async { MyInheritedWidget? inheritedWidget; final Widget widgetTree = DidChangeDependencySpy( onDidChangeDependencies: (BuildContext context) { inheritedWidget = LookupBoundary.dependOnInheritedWidgetOfExactType<MyInheritedWidget>(context); }, ); await tester.pumpWidget( MyInheritedWidget( value: 1, child: widgetTree, ), ); expect(inheritedWidget!.value, 1); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); await tester.pumpWidget( MyInheritedWidget( value: 2, child: widgetTree, ), ); expect(inheritedWidget!.value, 2); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 2); }); testWidgets('causes didChangeDependencies to be called on move even if dependency was not fulfilled due to boundary', (WidgetTester tester) async { MyInheritedWidget? inheritedWidget; final Key globalKey = GlobalKey(); final Widget widgetTree = DidChangeDependencySpy( key: globalKey, onDidChangeDependencies: (BuildContext context) { inheritedWidget = LookupBoundary.dependOnInheritedWidgetOfExactType<MyInheritedWidget>(context); }, ); await tester.pumpWidget( MyInheritedWidget( value: 1, child: LookupBoundary( child: widgetTree, ), ), ); expect(inheritedWidget, isNull); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); // Value of inherited widget changes, but there should be no dependency due to boundary. await tester.pumpWidget( MyInheritedWidget( value: 2, child: LookupBoundary( child: widgetTree, ), ), ); expect(inheritedWidget, isNull); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); // Widget is moved, didChangeDependencies is called, but dependency is still not found due to boundary. await tester.pumpWidget( SizedBox( child: MyInheritedWidget( value: 2, child: LookupBoundary( child: widgetTree, ), ), ), ); expect(inheritedWidget, isNull); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 2); await tester.pumpWidget( SizedBox( child: MyInheritedWidget( value: 2, child: LookupBoundary( child: MyInheritedWidget( value: 4, child: widgetTree, ), ), ), ), ); expect(inheritedWidget!.value, 4); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 3); }); testWidgets('causes didChangeDependencies to be called on move even if dependency was non-existant', (WidgetTester tester) async { MyInheritedWidget? inheritedWidget; final Key globalKey = GlobalKey(); final Widget widgetTree = DidChangeDependencySpy( key: globalKey, onDidChangeDependencies: (BuildContext context) { inheritedWidget = LookupBoundary.dependOnInheritedWidgetOfExactType<MyInheritedWidget>(context); }, ); await tester.pumpWidget(widgetTree); expect(inheritedWidget, isNull); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); // Widget moved, didChangeDependencies must be called. await tester.pumpWidget( SizedBox( child: widgetTree, ), ); expect(inheritedWidget, isNull); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 2); // Widget moved, didChangeDependencies must be called. await tester.pumpWidget( MyInheritedWidget( value: 6, child: SizedBox( child: widgetTree, ), ), ); expect(inheritedWidget!.value, 6); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 3); }); }); group('LookupBoundary.getElementForInheritedWidgetOfExactType', () { testWidgets('respects boundary', (WidgetTester tester) async { InheritedElement? containerThroughBoundary; InheritedElement? containerStoppedAtBoundary; final Key inheritedKey = UniqueKey(); await tester.pumpWidget(MyInheritedWidget( value: 2, key: inheritedKey, child: LookupBoundary( child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.getElementForInheritedWidgetOfExactType<MyInheritedWidget>(); containerStoppedAtBoundary = LookupBoundary.getElementForInheritedWidgetOfExactType<MyInheritedWidget>(context); return const SizedBox.expand(); }, ), ), )); expect(containerThroughBoundary, equals(tester.element(find.byKey(inheritedKey)))); expect(containerStoppedAtBoundary, isNull); }); testWidgets('ignores ancestor boundary', (WidgetTester tester) async { InheritedElement? inheritedWidget; final Key inheritedKey = UniqueKey(); await tester.pumpWidget(LookupBoundary( child: MyInheritedWidget( value: 2, key: inheritedKey, child: Builder( builder: (BuildContext context) { inheritedWidget = LookupBoundary.getElementForInheritedWidgetOfExactType<MyInheritedWidget>(context); return const SizedBox.expand(); }, ), ), )); expect(inheritedWidget, equals(tester.element(find.byKey(inheritedKey)))); }); testWidgets('finds widget before boundary', (WidgetTester tester) async { InheritedElement? containerThroughBoundary; InheritedElement? containerStoppedAtBoundary; final Key inheritedKey = UniqueKey(); await tester.pumpWidget(MyInheritedWidget( value: 2, child: LookupBoundary( child: MyInheritedWidget( key: inheritedKey, value: 1, child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.getElementForInheritedWidgetOfExactType<MyInheritedWidget>(); containerStoppedAtBoundary = LookupBoundary.getElementForInheritedWidgetOfExactType<MyInheritedWidget>(context); return const SizedBox.expand(); }, ), ), ), )); expect(containerThroughBoundary, equals(tester.element(find.byKey(inheritedKey)))); expect(containerStoppedAtBoundary, equals(tester.element(find.byKey(inheritedKey)))); }); testWidgets('does not creates dependency', (WidgetTester tester) async { final Widget widgetTree = DidChangeDependencySpy( onDidChangeDependencies: (BuildContext context) { LookupBoundary.getElementForInheritedWidgetOfExactType<MyInheritedWidget>(context); }, ); await tester.pumpWidget( MyInheritedWidget( value: 1, child: widgetTree, ), ); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); await tester.pumpWidget( MyInheritedWidget( value: 2, child: widgetTree, ), ); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); }); testWidgets('does not cause didChangeDependencies to be called on move when found', (WidgetTester tester) async { final Key globalKey = GlobalKey(); final Widget widgetTree = DidChangeDependencySpy( key: globalKey, onDidChangeDependencies: (BuildContext context) { LookupBoundary.getElementForInheritedWidgetOfExactType<MyInheritedWidget>(context); }, ); await tester.pumpWidget( MyInheritedWidget( value: 1, child: LookupBoundary( child: widgetTree, ), ), ); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); // Value of inherited widget changes, but there should be no dependency due to boundary. await tester.pumpWidget( MyInheritedWidget( value: 2, child: LookupBoundary( child: widgetTree, ), ), ); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); // Widget is moved, didChangeDependencies is called, but dependency is still not found due to boundary. await tester.pumpWidget( SizedBox( child: MyInheritedWidget( value: 2, child: LookupBoundary( child: widgetTree, ), ), ), ); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); await tester.pumpWidget( SizedBox( child: MyInheritedWidget( value: 2, child: LookupBoundary( child: MyInheritedWidget( value: 4, child: widgetTree, ), ), ), ), ); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); }); testWidgets('does not cause didChangeDependencies to be called on move when nothing was found', (WidgetTester tester) async { final Key globalKey = GlobalKey(); final Widget widgetTree = DidChangeDependencySpy( key: globalKey, onDidChangeDependencies: (BuildContext context) { LookupBoundary.getElementForInheritedWidgetOfExactType<MyInheritedWidget>(context); }, ); await tester.pumpWidget(widgetTree); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); // Widget moved, didChangeDependencies must be called. await tester.pumpWidget( SizedBox( child: widgetTree, ), ); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); // Widget moved, didChangeDependencies must be called. await tester.pumpWidget( MyInheritedWidget( value: 6, child: SizedBox( child: widgetTree, ), ), ); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); }); }); group('LookupBoundary.findAncestorWidgetOfExactType', () { testWidgets('respects boundary', (WidgetTester tester) async { Widget? containerThroughBoundary; Widget? containerStoppedAtBoundary; Widget? boundaryThroughBoundary; Widget? boundaryStoppedAtBoundary; final Key containerKey = UniqueKey(); final Key boundaryKey = UniqueKey(); await tester.pumpWidget(Container( key: containerKey, child: LookupBoundary( key: boundaryKey, child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.findAncestorWidgetOfExactType<Container>(); containerStoppedAtBoundary = LookupBoundary.findAncestorWidgetOfExactType<Container>(context); boundaryThroughBoundary = context.findAncestorWidgetOfExactType<LookupBoundary>(); boundaryStoppedAtBoundary = LookupBoundary.findAncestorWidgetOfExactType<LookupBoundary>(context); return const SizedBox.expand(); }, ), ), )); expect(containerThroughBoundary, equals(tester.widget(find.byKey(containerKey)))); expect(containerStoppedAtBoundary, isNull); expect(boundaryThroughBoundary, equals(tester.widget(find.byKey(boundaryKey)))); expect(boundaryStoppedAtBoundary, equals(tester.widget(find.byKey(boundaryKey)))); }); testWidgets('finds right widget before boundary', (WidgetTester tester) async { Widget? containerThroughBoundary; Widget? containerStoppedAtBoundary; final Key outerContainerKey = UniqueKey(); final Key innerContainerKey = UniqueKey(); await tester.pumpWidget(Container( key: outerContainerKey, child: LookupBoundary( child: Container( padding: const EdgeInsets.all(10), color: Colors.blue, child: Container( key: innerContainerKey, child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.findAncestorWidgetOfExactType<Container>(); containerStoppedAtBoundary = LookupBoundary.findAncestorWidgetOfExactType<Container>(context); return const SizedBox.expand(); }, ), ), ), ), )); expect(containerThroughBoundary, equals(tester.widget(find.byKey(innerContainerKey)))); expect(containerStoppedAtBoundary, equals(tester.widget(find.byKey(innerContainerKey)))); }); testWidgets('works if nothing is found', (WidgetTester tester) async { Widget? containerStoppedAtBoundary; await tester.pumpWidget(Builder( builder: (BuildContext context) { containerStoppedAtBoundary = LookupBoundary.findAncestorWidgetOfExactType<Container>(context); return const SizedBox.expand(); }, )); expect(containerStoppedAtBoundary, isNull); }); testWidgets('does not establish a dependency', (WidgetTester tester) async { Widget? containerThroughBoundary; Widget? containerStoppedAtBoundary; Widget? containerStoppedAtBoundaryUnfulfilled; final Key innerContainerKey = UniqueKey(); final Key globalKey = GlobalKey(); final Widget widgetTree = LookupBoundary( child: Container( key: innerContainerKey, child: DidChangeDependencySpy( key: globalKey, onDidChangeDependencies: (BuildContext context) { containerThroughBoundary = context.findAncestorWidgetOfExactType<Container>(); containerStoppedAtBoundary = LookupBoundary.findAncestorWidgetOfExactType<Container>(context); containerStoppedAtBoundaryUnfulfilled = LookupBoundary.findAncestorWidgetOfExactType<Material>(context); }, ), ), ); await tester.pumpWidget(widgetTree); expect(containerThroughBoundary, equals(tester.widget(find.byKey(innerContainerKey)))); expect(containerStoppedAtBoundary, equals(tester.widget(find.byKey(innerContainerKey)))); expect(containerStoppedAtBoundaryUnfulfilled, isNull); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); await tester.pumpWidget( SizedBox( // Changes tree structure, triggers global key move of DidChangeDependencySpy. child: widgetTree, ), ); // Tree restructuring above would have called didChangeDependencies if dependency had been established. expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); }); }); group('LookupBoundary.findAncestorStateOfType', () { testWidgets('respects boundary', (WidgetTester tester) async { State? containerThroughBoundary; State? containerStoppedAtBoundary; final Key containerKey = UniqueKey(); await tester.pumpWidget(MyStatefulContainer( key: containerKey, child: LookupBoundary( child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.findAncestorStateOfType<MyStatefulContainerState>(); containerStoppedAtBoundary = LookupBoundary.findAncestorStateOfType<MyStatefulContainerState>(context); return const SizedBox.expand(); }, ), ), )); expect(containerThroughBoundary, equals(tester.state(find.byKey(containerKey)))); expect(containerStoppedAtBoundary, isNull); }); testWidgets('finds right widget before boundary', (WidgetTester tester) async { State? containerThroughBoundary; State? containerStoppedAtBoundary; final Key outerContainerKey = UniqueKey(); final Key innerContainerKey = UniqueKey(); await tester.pumpWidget(MyStatefulContainer( key: outerContainerKey, child: LookupBoundary( child: MyStatefulContainer( child: MyStatefulContainer( key: innerContainerKey, child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.findAncestorStateOfType<MyStatefulContainerState>(); containerStoppedAtBoundary = LookupBoundary.findAncestorStateOfType<MyStatefulContainerState>(context); return const SizedBox.expand(); }, ), ), ), ), )); expect(containerThroughBoundary, equals(tester.state(find.byKey(innerContainerKey)))); expect(containerStoppedAtBoundary, equals(tester.state(find.byKey(innerContainerKey)))); }); testWidgets('works if nothing is found', (WidgetTester tester) async { State? containerStoppedAtBoundary; await tester.pumpWidget(Builder( builder: (BuildContext context) { containerStoppedAtBoundary = LookupBoundary.findAncestorStateOfType<MyStatefulContainerState>(context); return const SizedBox.expand(); }, )); expect(containerStoppedAtBoundary, isNull); }); testWidgets('does not establish a dependency', (WidgetTester tester) async { State? containerThroughBoundary; State? containerStoppedAtBoundary; State? containerStoppedAtBoundaryUnfulfilled; final Key innerContainerKey = UniqueKey(); final Key globalKey = GlobalKey(); final Widget widgetTree = LookupBoundary( child: MyStatefulContainer( key: innerContainerKey, child: DidChangeDependencySpy( key: globalKey, onDidChangeDependencies: (BuildContext context) { containerThroughBoundary = context.findAncestorStateOfType<MyStatefulContainerState>(); containerStoppedAtBoundary = LookupBoundary.findAncestorStateOfType<MyStatefulContainerState>(context); containerStoppedAtBoundaryUnfulfilled = LookupBoundary.findAncestorStateOfType<MyOtherStatefulContainerState>(context); }, ), ), ); await tester.pumpWidget(widgetTree); expect(containerThroughBoundary, equals(tester.state(find.byKey(innerContainerKey)))); expect(containerStoppedAtBoundary, equals(tester.state(find.byKey(innerContainerKey)))); expect(containerStoppedAtBoundaryUnfulfilled, isNull); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); await tester.pumpWidget( SizedBox( // Changes tree structure, triggers global key move of DidChangeDependencySpy. child: widgetTree, ), ); // Tree restructuring above would have called didChangeDependencies if dependency had been established. expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); }); }); group('LookupBoundary.findRootAncestorStateOfType', () { testWidgets('respects boundary', (WidgetTester tester) async { State? containerThroughBoundary; State? containerStoppedAtBoundary; final Key containerKey = UniqueKey(); await tester.pumpWidget(MyStatefulContainer( key: containerKey, child: LookupBoundary( child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.findRootAncestorStateOfType<MyStatefulContainerState>(); containerStoppedAtBoundary = LookupBoundary.findRootAncestorStateOfType<MyStatefulContainerState>(context); return const SizedBox.expand(); }, ), ), )); expect(containerThroughBoundary, equals(tester.state(find.byKey(containerKey)))); expect(containerStoppedAtBoundary, isNull); }); testWidgets('finds right widget before boundary', (WidgetTester tester) async { State? containerThroughBoundary; State? containerStoppedAtBoundary; final Key outerContainerKey = UniqueKey(); final Key innerContainerKey = UniqueKey(); await tester.pumpWidget(MyStatefulContainer( key: outerContainerKey, child: LookupBoundary( child: MyStatefulContainer( key: innerContainerKey, child: MyStatefulContainer( child: Builder( builder: (BuildContext context) { containerThroughBoundary = context.findRootAncestorStateOfType<MyStatefulContainerState>(); containerStoppedAtBoundary = LookupBoundary.findRootAncestorStateOfType<MyStatefulContainerState>(context); return const SizedBox.expand(); }, ), ), ), ), )); expect(containerThroughBoundary, equals(tester.state(find.byKey(outerContainerKey)))); expect(containerStoppedAtBoundary, equals(tester.state(find.byKey(innerContainerKey)))); }); testWidgets('works if nothing is found', (WidgetTester tester) async { State? containerStoppedAtBoundary; await tester.pumpWidget(Builder( builder: (BuildContext context) { containerStoppedAtBoundary = LookupBoundary.findRootAncestorStateOfType<MyStatefulContainerState>(context); return const SizedBox.expand(); }, )); expect(containerStoppedAtBoundary, isNull); }); testWidgets('does not establish a dependency', (WidgetTester tester) async { State? containerThroughBoundary; State? containerStoppedAtBoundary; State? containerStoppedAtBoundaryUnfulfilled; final Key innerContainerKey = UniqueKey(); final Key globalKey = GlobalKey(); final Widget widgetTree = LookupBoundary( child: MyStatefulContainer( key: innerContainerKey, child: DidChangeDependencySpy( key: globalKey, onDidChangeDependencies: (BuildContext context) { containerThroughBoundary = context.findRootAncestorStateOfType<MyStatefulContainerState>(); containerStoppedAtBoundary = LookupBoundary.findRootAncestorStateOfType<MyStatefulContainerState>(context); containerStoppedAtBoundaryUnfulfilled = LookupBoundary.findRootAncestorStateOfType<MyOtherStatefulContainerState>(context); }, ), ), ); await tester.pumpWidget(widgetTree); expect(containerThroughBoundary, equals(tester.state(find.byKey(innerContainerKey)))); expect(containerStoppedAtBoundary, equals(tester.state(find.byKey(innerContainerKey)))); expect(containerStoppedAtBoundaryUnfulfilled, isNull); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); await tester.pumpWidget( SizedBox( // Changes tree structure, triggers global key move of DidChangeDependencySpy. child: widgetTree, ), ); // Tree restructuring above would have called didChangeDependencies if dependency had been established. expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); }); }); group('LookupBoundary.findAncestorRenderObjectOfType', () { testWidgets('respects boundary', (WidgetTester tester) async { RenderPadding? paddingThroughBoundary; RenderPadding? passingStoppedAtBoundary; final Key paddingKey = UniqueKey(); await tester.pumpWidget(Padding( padding: EdgeInsets.zero, key: paddingKey, child: LookupBoundary( child: Builder( builder: (BuildContext context) { paddingThroughBoundary = context.findAncestorRenderObjectOfType<RenderPadding>(); passingStoppedAtBoundary = LookupBoundary.findAncestorRenderObjectOfType<RenderPadding>(context); return const SizedBox.expand(); }, ), ), )); expect(paddingThroughBoundary, equals(tester.renderObject(find.byKey(paddingKey)))); expect(passingStoppedAtBoundary, isNull); }); testWidgets('finds right widget before boundary', (WidgetTester tester) async { RenderPadding? paddingThroughBoundary; RenderPadding? paddingStoppedAtBoundary; final Key outerPaddingKey = UniqueKey(); final Key innerPaddingKey = UniqueKey(); await tester.pumpWidget(Padding( padding: EdgeInsets.zero, key: outerPaddingKey, child: LookupBoundary( child: Padding( padding: EdgeInsets.zero, child: Padding( padding: EdgeInsets.zero, key: innerPaddingKey, child: Builder( builder: (BuildContext context) { paddingThroughBoundary = context.findAncestorRenderObjectOfType<RenderPadding>(); paddingStoppedAtBoundary = LookupBoundary.findAncestorRenderObjectOfType<RenderPadding>(context); return const SizedBox.expand(); }, ), ), ), ), )); expect(paddingThroughBoundary, equals(tester.renderObject(find.byKey(innerPaddingKey)))); expect(paddingStoppedAtBoundary, equals(tester.renderObject(find.byKey(innerPaddingKey)))); }); testWidgets('works if nothing is found', (WidgetTester tester) async { RenderPadding? paddingStoppedAtBoundary; await tester.pumpWidget(Builder( builder: (BuildContext context) { paddingStoppedAtBoundary = LookupBoundary.findAncestorRenderObjectOfType<RenderPadding>(context); return const SizedBox.expand(); }, )); expect(paddingStoppedAtBoundary, isNull); }); testWidgets('does not establish a dependency', (WidgetTester tester) async { RenderPadding? paddingThroughBoundary; RenderPadding? paddingStoppedAtBoundary; RenderWrap? wrapStoppedAtBoundaryUnfulfilled; final Key innerPaddingKey = UniqueKey(); final Key globalKey = GlobalKey(); final Widget widgetTree = LookupBoundary( child: Padding( padding: EdgeInsets.zero, key: innerPaddingKey, child: DidChangeDependencySpy( key: globalKey, onDidChangeDependencies: (BuildContext context) { paddingThroughBoundary = context.findAncestorRenderObjectOfType<RenderPadding>(); paddingStoppedAtBoundary = LookupBoundary.findAncestorRenderObjectOfType<RenderPadding>(context); wrapStoppedAtBoundaryUnfulfilled = LookupBoundary.findAncestorRenderObjectOfType<RenderWrap>(context); }, ), ), ); await tester.pumpWidget(widgetTree); expect(paddingThroughBoundary, equals(tester.renderObject(find.byKey(innerPaddingKey)))); expect(paddingStoppedAtBoundary, equals(tester.renderObject(find.byKey(innerPaddingKey)))); expect(wrapStoppedAtBoundaryUnfulfilled, isNull); expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); await tester.pumpWidget( SizedBox( // Changes tree structure, triggers global key move of DidChangeDependencySpy. child: widgetTree, ), ); // Tree restructuring above would have called didChangeDependencies if dependency had been established. expect(tester.state<_DidChangeDependencySpyState>(find.byType(DidChangeDependencySpy)).didChangeDependenciesCount, 1); }); }); group('LookupBoundary.visitAncestorElements', () { testWidgets('respects boundary', (WidgetTester tester) async { final List<Element> throughBoundary = <Element>[]; final List<Element> stoppedAtBoundary = <Element>[]; final List<Element> stoppedAtBoundaryTerminatedEarly = <Element>[]; final Key level0 = UniqueKey(); final Key level1 = UniqueKey(); final Key level2 = UniqueKey(); final Key level3 = UniqueKey(); final Key level4 = UniqueKey(); await tester.pumpWidget(Container( key: level0, child: Container( key: level1, child: LookupBoundary( key: level2, child: Container( key: level3, child: Container( key: level4, child: Builder( builder: (BuildContext context) { context.visitAncestorElements((Element element) { throughBoundary.add(element); return element.widget.key != level0; }); LookupBoundary.visitAncestorElements(context, (Element element) { stoppedAtBoundary.add(element); return element.widget.key != level0; }); LookupBoundary.visitAncestorElements(context, (Element element) { stoppedAtBoundaryTerminatedEarly.add(element); return element.widget.key != level3; }); return const SizedBox(); } ) ) ) ) ), )); expect(throughBoundary, <Element>[ tester.element(find.byKey(level4)), tester.element(find.byKey(level3)), tester.element(find.byKey(level2)), tester.element(find.byKey(level1)), tester.element(find.byKey(level0)), ]); expect(stoppedAtBoundary, <Element>[ tester.element(find.byKey(level4)), tester.element(find.byKey(level3)), tester.element(find.byKey(level2)), ]); expect(stoppedAtBoundaryTerminatedEarly, <Element>[ tester.element(find.byKey(level4)), tester.element(find.byKey(level3)), ]); }); }); group('LookupBoundary.visitChildElements', () { testWidgets('respects boundary', (WidgetTester tester) async { final Key root = UniqueKey(); final Key child1 = UniqueKey(); final Key child2 = UniqueKey(); final Key child3 = UniqueKey(); await tester.pumpWidget(Column( key: root, children: <Widget>[ LookupBoundary( key: child1, child: Container(), ), Container( key: child2, child: LookupBoundary( child: Container(), ), ), Container( key: child3, ), ], )); final List<Element> throughBoundary = <Element>[]; final List<Element> stoppedAtBoundary = <Element>[]; final BuildContext context = tester.element(find.byKey(root)); context.visitChildElements((Element element) { throughBoundary.add(element); }); LookupBoundary.visitChildElements(context, (Element element) { stoppedAtBoundary.add(element); }); expect(throughBoundary, <Element>[ tester.element(find.byKey(child1)), tester.element(find.byKey(child2)), tester.element(find.byKey(child3)), ]); expect(stoppedAtBoundary, <Element>[ tester.element(find.byKey(child2)), tester.element(find.byKey(child3)), ]); }); }); group('LookupBoundary.debugIsHidingAncestorWidgetOfExactType', () { testWidgets('is hiding', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(Container( padding: const EdgeInsets.all(10), color: Colors.blue, child: LookupBoundary( child: Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<Container>(context); return Container(); }, ), ), )); expect(isHidden, isTrue); }); testWidgets('is not hiding entity within boundary', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(Container( padding: const EdgeInsets.all(10), color: Colors.blue, child: LookupBoundary( child: Container( padding: const EdgeInsets.all(10), color: Colors.red, child: Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<Container>(context); return Container(); }, ), ), ), )); expect(isHidden, isFalse); }); testWidgets('is not hiding if no boundary exists', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(Container( padding: const EdgeInsets.all(10), color: Colors.blue, child: Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<Container>(context); return Container(); }, ), )); expect(isHidden, isFalse); }); testWidgets('is not hiding if no boundary and no entity exists', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<Container>(context); return Container(); }, )); expect(isHidden, isFalse); }); }); group('LookupBoundary.debugIsHidingAncestorStateOfType', () { testWidgets('is hiding', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(MyStatefulContainer( child: LookupBoundary( child: Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorStateOfType<MyStatefulContainerState>(context); return Container(); }, ), ), )); expect(isHidden, isTrue); }); testWidgets('is not hiding entity within boundary', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(MyStatefulContainer( child: LookupBoundary( child: MyStatefulContainer( child: Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorStateOfType<MyStatefulContainerState>(context); return Container(); }, ), ), ), )); expect(isHidden, isFalse); }); testWidgets('is not hiding if no boundary exists', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(MyStatefulContainer( child: Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorStateOfType<MyStatefulContainerState>(context); return Container(); }, ), )); expect(isHidden, isFalse); }); testWidgets('is not hiding if no boundary and no entity exists', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorStateOfType<MyStatefulContainerState>(context); return Container(); }, )); expect(isHidden, isFalse); }); }); group('LookupBoundary.debugIsHidingAncestorRenderObjectOfType', () { testWidgets('is hiding', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(Padding( padding: EdgeInsets.zero, child: LookupBoundary( child: Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorRenderObjectOfType<RenderPadding>(context); return Container(); }, ), ), )); expect(isHidden, isTrue); }); testWidgets('is not hiding entity within boundary', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(Padding( padding: EdgeInsets.zero, child: LookupBoundary( child: Padding( padding: EdgeInsets.zero, child: Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorRenderObjectOfType<RenderPadding>(context); return Container(); }, ), ), ), )); expect(isHidden, isFalse); }); testWidgets('is not hiding if no boundary exists', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(Padding( padding: EdgeInsets.zero, child: Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorRenderObjectOfType<RenderPadding>(context); return Container(); }, ), )); expect(isHidden, isFalse); }); testWidgets('is not hiding if no boundary and no entity exists', (WidgetTester tester) async { bool? isHidden; await tester.pumpWidget(Builder( builder: (BuildContext context) { isHidden = LookupBoundary.debugIsHidingAncestorRenderObjectOfType<RenderPadding>(context); return Container(); }, )); expect(isHidden, isFalse); }); }); } class MyStatefulContainer extends StatefulWidget { const MyStatefulContainer({super.key, required this.child}); final Widget child; @override State<MyStatefulContainer> createState() => MyStatefulContainerState(); } class MyStatefulContainerState extends State<MyStatefulContainer> { @override Widget build(BuildContext context) { return widget.child; } } class MyOtherStatefulContainerState extends State<MyStatefulContainer> { @override Widget build(BuildContext context) { return widget.child; } } class MyInheritedWidget extends InheritedWidget { const MyInheritedWidget({super.key, required this.value, required super.child}); final int value; @override bool updateShouldNotify(MyInheritedWidget oldWidget) => oldWidget.value != value; } class DidChangeDependencySpy extends StatefulWidget { const DidChangeDependencySpy({super.key, required this.onDidChangeDependencies}); final OnDidChangeDependencies onDidChangeDependencies; @override State<DidChangeDependencySpy> createState() => _DidChangeDependencySpyState(); } class _DidChangeDependencySpyState extends State<DidChangeDependencySpy> { int didChangeDependenciesCount = 0; @override void didChangeDependencies() { super.didChangeDependencies(); didChangeDependenciesCount += 1; widget.onDidChangeDependencies(context); } @override Widget build(BuildContext context) { return Container(); } } typedef OnDidChangeDependencies = void Function(BuildContext context);