// 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'; import 'package:flutter/material.dart'; import 'test_widgets.dart'; class TestInherited extends InheritedWidget { const TestInherited({ Key key, Widget child, this.shouldNotify = true }) : super(key: key, child: child); final bool shouldNotify; @override bool updateShouldNotify(InheritedWidget oldWidget) { return shouldNotify; } } class ValueInherited extends InheritedWidget { const ValueInherited({ Key key, Widget child, this.value }) : super(key: key, child: child); final int value; @override bool updateShouldNotify(ValueInherited oldWidget) => value != oldWidget.value; } class ExpectFail extends StatefulWidget { const ExpectFail(this.onError, { Key key }) : super(key: key); final VoidCallback onError; @override ExpectFailState createState() => ExpectFailState(); } class ExpectFailState extends State<ExpectFail> { @override void initState() { super.initState(); try { context.dependOnInheritedWidgetOfExactType<TestInherited>(); // should fail } catch (e) { widget.onError(); } } @override Widget build(BuildContext context) => Container(); } class ChangeNotifierInherited extends InheritedNotifier<ChangeNotifier> { const ChangeNotifierInherited({ Key key, Widget child, ChangeNotifier notifier }) : super(key: key, child: child, notifier: notifier); } void main() { testWidgets('Inherited notifies dependents', (WidgetTester tester) async { final List<TestInherited> log = <TestInherited>[]; final Builder builder = Builder( builder: (BuildContext context) { log.add(context.dependOnInheritedWidgetOfExactType<TestInherited>()); return Container(); } ); final TestInherited first = TestInherited(child: builder); await tester.pumpWidget(first); expect(log, equals(<TestInherited>[first])); final TestInherited second = TestInherited(child: builder, shouldNotify: false); await tester.pumpWidget(second); expect(log, equals(<TestInherited>[first])); final TestInherited third = TestInherited(child: builder, shouldNotify: true); await tester.pumpWidget(third); expect(log, equals(<TestInherited>[first, third])); }); testWidgets('Update inherited when reparenting state', (WidgetTester tester) async { final GlobalKey globalKey = GlobalKey(); final List<TestInherited> log = <TestInherited>[]; TestInherited build() { return TestInherited( key: UniqueKey(), child: Container( key: globalKey, child: Builder( builder: (BuildContext context) { log.add(context.dependOnInheritedWidgetOfExactType<TestInherited>()); return Container(); } ), ), ); } final TestInherited first = build(); await tester.pumpWidget(first); expect(log, equals(<TestInherited>[first])); final TestInherited second = build(); await tester.pumpWidget(second); expect(log, equals(<TestInherited>[first, second])); }); testWidgets('Update inherited when removing node', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget( Container( child: ValueInherited( value: 1, child: Container( child: FlipWidget( left: Container( child: ValueInherited( value: 2, child: Container( child: ValueInherited( value: 3, child: Container( child: Builder( builder: (BuildContext context) { final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>(); log.add('a: ${v.value}'); return const Text('', textDirection: TextDirection.ltr); } ), ), ), ), ), ), right: Container( child: ValueInherited( value: 2, child: Container( child: Container( child: Builder( builder: (BuildContext context) { final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>(); log.add('b: ${v.value}'); return const Text('', textDirection: TextDirection.ltr); } ), ), ), ), ), ), ), ), ), ); expect(log, equals(<String>['a: 3'])); log.clear(); await tester.pump(); expect(log, equals(<String>[])); log.clear(); flipStatefulWidget(tester); await tester.pump(); expect(log, equals(<String>['b: 2'])); log.clear(); flipStatefulWidget(tester); await tester.pump(); expect(log, equals(<String>['a: 3'])); log.clear(); }); testWidgets('Update inherited when removing node and child has global key', (WidgetTester tester) async { final List<String> log = <String>[]; final Key key = GlobalKey(); await tester.pumpWidget( Container( child: ValueInherited( value: 1, child: Container( child: FlipWidget( left: Container( child: ValueInherited( value: 2, child: Container( child: ValueInherited( value: 3, child: Container( key: key, child: Builder( builder: (BuildContext context) { final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>(); log.add('a: ${v.value}'); return const Text('', textDirection: TextDirection.ltr); } ), ), ), ), ), ), right: Container( child: ValueInherited( value: 2, child: Container( child: Container( key: key, child: Builder( builder: (BuildContext context) { final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>(); log.add('b: ${v.value}'); return const Text('', textDirection: TextDirection.ltr); } ), ), ), ), ), ), ), ), ), ); expect(log, equals(<String>['a: 3'])); log.clear(); await tester.pump(); expect(log, equals(<String>[])); log.clear(); flipStatefulWidget(tester); await tester.pump(); expect(log, equals(<String>['b: 2'])); log.clear(); flipStatefulWidget(tester); await tester.pump(); expect(log, equals(<String>['a: 3'])); log.clear(); }); testWidgets('Update inherited when removing node and child has global key with constant child', (WidgetTester tester) async { final List<int> log = <int>[]; final Key key = GlobalKey(); final Widget child = Builder( builder: (BuildContext context) { final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>(); log.add(v.value); return const Text('', textDirection: TextDirection.ltr); } ); await tester.pumpWidget( Container( child: ValueInherited( value: 1, child: Container( child: FlipWidget( left: Container( child: ValueInherited( value: 2, child: Container( child: ValueInherited( value: 3, child: Container( key: key, child: child, ), ), ), ), ), right: Container( child: ValueInherited( value: 2, child: Container( child: Container( key: key, child: child, ), ), ), ), ), ), ), ), ); expect(log, equals(<int>[3])); log.clear(); await tester.pump(); expect(log, equals(<int>[])); log.clear(); flipStatefulWidget(tester); await tester.pump(); expect(log, equals(<int>[2])); log.clear(); flipStatefulWidget(tester); await tester.pump(); expect(log, equals(<int>[3])); log.clear(); }); testWidgets('Update inherited when removing node and child has global key with constant child, minimised', (WidgetTester tester) async { final List<int> log = <int>[]; final Widget child = Builder( key: GlobalKey(), builder: (BuildContext context) { final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>(); log.add(v.value); return const Text('', textDirection: TextDirection.ltr); }, ); await tester.pumpWidget( ValueInherited( value: 2, child: FlipWidget( left: ValueInherited( value: 3, child: child, ), right: child, ), ), ); expect(log, equals(<int>[3])); log.clear(); await tester.pump(); expect(log, equals(<int>[])); log.clear(); flipStatefulWidget(tester); await tester.pump(); expect(log, equals(<int>[2])); log.clear(); flipStatefulWidget(tester); await tester.pump(); expect(log, equals(<int>[3])); log.clear(); }); testWidgets('Inherited widget notifies descendants when descendant previously failed to find a match', (WidgetTester tester) async { int inheritedValue = -1; final Widget inner = Container( key: GlobalKey(), child: Builder( builder: (BuildContext context) { final ValueInherited widget = context.dependOnInheritedWidgetOfExactType<ValueInherited>(); inheritedValue = widget?.value; return Container(); } ), ); await tester.pumpWidget( inner ); expect(inheritedValue, isNull); inheritedValue = -2; await tester.pumpWidget( ValueInherited( value: 3, child: inner, ), ); expect(inheritedValue, equals(3)); }); testWidgets('Inherited widget doesn\'t notify descendants when descendant did not previously fail to find a match and had no dependencies', (WidgetTester tester) async { int buildCount = 0; final Widget inner = Container( key: GlobalKey(), child: Builder( builder: (BuildContext context) { buildCount += 1; return Container(); } ), ); await tester.pumpWidget( inner ); expect(buildCount, equals(1)); await tester.pumpWidget( ValueInherited( value: 3, child: inner, ), ); expect(buildCount, equals(1)); }); testWidgets('Inherited widget does notify descendants when descendant did not previously fail to find a match but did have other dependencies', (WidgetTester tester) async { int buildCount = 0; final Widget inner = Container( key: GlobalKey(), child: TestInherited( shouldNotify: false, child: Builder( builder: (BuildContext context) { context.dependOnInheritedWidgetOfExactType<TestInherited>(); buildCount += 1; return Container(); } ), ), ); await tester.pumpWidget( inner ); expect(buildCount, equals(1)); await tester.pumpWidget( ValueInherited( value: 3, child: inner, ), ); expect(buildCount, equals(2)); }); testWidgets('initState() dependency on Inherited asserts', (WidgetTester tester) async { // This is a regression test for https://github.com/flutter/flutter/issues/5491 bool exceptionCaught = false; final TestInherited parent = TestInherited(child: ExpectFail(() { exceptionCaught = true; })); await tester.pumpWidget(parent); expect(exceptionCaught, isTrue); }); testWidgets('InheritedNotifier', (WidgetTester tester) async { int buildCount = 0; final ChangeNotifier notifier = ChangeNotifier(); final Widget builder = Builder( builder: (BuildContext context) { context.dependOnInheritedWidgetOfExactType<ChangeNotifierInherited>(); buildCount += 1; return Container(); } ); final Widget inner = ChangeNotifierInherited( notifier: notifier, child: builder, ); await tester.pumpWidget(inner); expect(buildCount, equals(1)); await tester.pumpWidget(inner); expect(buildCount, equals(1)); await tester.pump(); expect(buildCount, equals(1)); notifier.notifyListeners(); await tester.pump(); expect(buildCount, equals(2)); await tester.pumpWidget(inner); expect(buildCount, equals(2)); await tester.pumpWidget(ChangeNotifierInherited( notifier: null, child: builder, )); expect(buildCount, equals(3)); }); }