blob: 362acd77860647fadc953531b0bb30972a60f8b7 [file] [log] [blame]
// 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_test/flutter_test.dart';
import 'test_widgets.dart';
class TestInherited extends InheritedWidget {
const TestInherited({ Key? key, required 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, required Widget child, required 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, required 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(shouldNotify: false, child: builder);
await tester.pumpWidget(second);
expect(log, equals(<TestInherited>[first]));
final TestInherited third = TestInherited(child: builder);
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(
ValueInherited(
value: 1,
child: FlipWidget(
left: ValueInherited(
value: 2,
child: ValueInherited(
value: 3,
child: Builder(
builder: (BuildContext context) {
final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
log.add('a: ${v.value}');
return const Text('', textDirection: TextDirection.ltr);
},
),
),
),
right: ValueInherited(
value: 2,
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(
ValueInherited(
value: 1,
child: FlipWidget(
left: ValueInherited(
value: 2,
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: ValueInherited(
value: 2,
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(
ValueInherited(
value: 1,
child: FlipWidget(
left: ValueInherited(
value: 2,
child: ValueInherited(
value: 3,
child: Container(
key: key,
child: child,
),
),
),
right: ValueInherited(
value: 2,
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(
child: builder,
));
expect(buildCount, equals(3));
});
}