mirror of
https://github.com/alibaba/flutter-go.git
synced 2025-07-13 00:22:23 +08:00
527 lines
20 KiB
Dart
527 lines
20 KiB
Dart
// Copyright 2018 The Chromium 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_web_test/flutter_web_test.dart';
|
|
|
|
import 'package:flutter_web/foundation.dart';
|
|
import 'package:flutter_web/material.dart';
|
|
|
|
// A simple "flat" InheritedModel: the data model is just 3 integer
|
|
// valued fields: a, b, c.
|
|
class ABCModel extends InheritedModel<String> {
|
|
const ABCModel({
|
|
Key key,
|
|
this.a,
|
|
this.b,
|
|
this.c,
|
|
this.aspects,
|
|
Widget child,
|
|
}) : super(key: key, child: child);
|
|
|
|
final int a;
|
|
final int b;
|
|
final int c;
|
|
|
|
// The aspects (fields) of this model that widgets can depend on with
|
|
// inheritFrom.
|
|
//
|
|
// This property is null by default, which means that the model supports
|
|
// all 3 fields.
|
|
final Set<String> aspects;
|
|
|
|
@override
|
|
bool isSupportedAspect(Object aspect) {
|
|
return aspect == null || aspects == null || aspects.contains(aspect);
|
|
}
|
|
|
|
@override
|
|
bool updateShouldNotify(ABCModel old) {
|
|
return !setEquals<String>(aspects, old.aspects) ||
|
|
a != old.a ||
|
|
b != old.b ||
|
|
c != old.c;
|
|
}
|
|
|
|
@override
|
|
bool updateShouldNotifyDependent(ABCModel old, Set<String> dependencies) {
|
|
return !setEquals<String>(aspects, old.aspects) ||
|
|
(a != old.a && dependencies.contains('a')) ||
|
|
(b != old.b && dependencies.contains('b')) ||
|
|
(c != old.c && dependencies.contains('c'));
|
|
}
|
|
|
|
static ABCModel of(BuildContext context, {String fieldName}) {
|
|
return InheritedModel.inheritFrom<ABCModel>(context, aspect: fieldName);
|
|
}
|
|
}
|
|
|
|
class ShowABCField extends StatefulWidget {
|
|
const ShowABCField({Key key, this.fieldName}) : super(key: key);
|
|
|
|
final String fieldName;
|
|
|
|
@override
|
|
_ShowABCFieldState createState() => _ShowABCFieldState();
|
|
}
|
|
|
|
class _ShowABCFieldState extends State<ShowABCField> {
|
|
int _buildCount = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ABCModel abc = ABCModel.of(context, fieldName: widget.fieldName);
|
|
final int value = widget.fieldName == 'a'
|
|
? abc.a
|
|
: (widget.fieldName == 'b' ? abc.b : abc.c);
|
|
return Text('${widget.fieldName}: $value [${_buildCount++}]');
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('InheritedModel basics', (WidgetTester tester) async {
|
|
int _a = 0;
|
|
int _b = 1;
|
|
int _c = 2;
|
|
|
|
final Widget abcPage = StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
const Widget showA = ShowABCField(fieldName: 'a');
|
|
const Widget showB = ShowABCField(fieldName: 'b');
|
|
const Widget showC = ShowABCField(fieldName: 'c');
|
|
|
|
// Unconditionally depends on the ABCModel: rebuilt when any
|
|
// aspect of the model changes.
|
|
final Widget showABC = Builder(builder: (BuildContext context) {
|
|
final ABCModel abc = ABCModel.of(context);
|
|
return Text('a: ${abc.a} b: ${abc.b} c: ${abc.c}');
|
|
});
|
|
|
|
return Scaffold(
|
|
body: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return ABCModel(
|
|
a: _a,
|
|
b: _b,
|
|
c: _c,
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
showA,
|
|
showB,
|
|
showC,
|
|
showABC,
|
|
RaisedButton(
|
|
child: const Text('Increment a'),
|
|
onPressed: () {
|
|
// Rebuilds the ABCModel which triggers a rebuild
|
|
// of showA because showA depends on the 'a' aspect
|
|
// of the ABCModel.
|
|
setState(() {
|
|
_a += 1;
|
|
});
|
|
},
|
|
),
|
|
RaisedButton(
|
|
child: const Text('Increment b'),
|
|
onPressed: () {
|
|
// Rebuilds the ABCModel which triggers a rebuild
|
|
// of showB because showB depends on the 'b' aspect
|
|
// of the ABCModel.
|
|
setState(() {
|
|
_b += 1;
|
|
});
|
|
},
|
|
),
|
|
RaisedButton(
|
|
child: const Text('Increment c'),
|
|
onPressed: () {
|
|
// Rebuilds the ABCModel which triggers a rebuild
|
|
// of showC because showC depends on the 'c' aspect
|
|
// of the ABCModel.
|
|
setState(() {
|
|
_c += 1;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
await tester.pumpWidget(MaterialApp(home: abcPage));
|
|
|
|
expect(find.text('a: 0 [0]'), findsOneWidget);
|
|
expect(find.text('b: 1 [0]'), findsOneWidget);
|
|
expect(find.text('c: 2 [0]'), findsOneWidget);
|
|
expect(find.text('a: 0 b: 1 c: 2'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Increment a'));
|
|
await tester.pumpAndSettle();
|
|
// Verify that field 'a' was incremented, but only the showA
|
|
// and showABC widgets were rebuilt.
|
|
expect(find.text('a: 1 [1]'), findsOneWidget);
|
|
expect(find.text('b: 1 [0]'), findsOneWidget);
|
|
expect(find.text('c: 2 [0]'), findsOneWidget);
|
|
expect(find.text('a: 1 b: 1 c: 2'), findsOneWidget);
|
|
|
|
// Verify that field 'a' was incremented, but only the showA
|
|
// and showABC widgets were rebuilt.
|
|
await tester.tap(find.text('Increment a'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 2 [2]'), findsOneWidget);
|
|
expect(find.text('b: 1 [0]'), findsOneWidget);
|
|
expect(find.text('c: 2 [0]'), findsOneWidget);
|
|
expect(find.text('a: 2 b: 1 c: 2'), findsOneWidget);
|
|
|
|
// Verify that field 'b' was incremented, but only the showB
|
|
// and showABC widgets were rebuilt.
|
|
await tester.tap(find.text('Increment b'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 2 [2]'), findsOneWidget);
|
|
expect(find.text('b: 2 [1]'), findsOneWidget);
|
|
expect(find.text('c: 2 [0]'), findsOneWidget);
|
|
expect(find.text('a: 2 b: 2 c: 2'), findsOneWidget);
|
|
|
|
// Verify that field 'c' was incremented, but only the showC
|
|
// and showABC widgets were rebuilt.
|
|
await tester.tap(find.text('Increment c'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 2 [2]'), findsOneWidget);
|
|
expect(find.text('b: 2 [1]'), findsOneWidget);
|
|
expect(find.text('c: 3 [1]'), findsOneWidget);
|
|
expect(find.text('a: 2 b: 2 c: 3'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'Looking up an non existent InherintedModel ancestor returns null',
|
|
(WidgetTester tester) async {
|
|
ABCModel inheritedModel;
|
|
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
inheritedModel = InheritedModel.inheritFrom(context);
|
|
return Container();
|
|
},
|
|
),
|
|
);
|
|
// Shouldn't crash first of all.
|
|
|
|
expect(inheritedModel, null);
|
|
});
|
|
|
|
testWidgets('Inner InheritedModel shadows the outer one',
|
|
(WidgetTester tester) async {
|
|
int _a = 0;
|
|
int _b = 1;
|
|
int _c = 2;
|
|
|
|
// Same as in abcPage in the "InheritedModel basics" test except:
|
|
// there are two ABCModels and the inner model's "a" and "b"
|
|
// properties shadow (override) the outer model. Further complicating
|
|
// matters: the inner model only supports the model's "a" aspect,
|
|
// so showB and showC will depend on the outer model.
|
|
final Widget abcPage = StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
const Widget showA = ShowABCField(fieldName: 'a');
|
|
const Widget showB = ShowABCField(fieldName: 'b');
|
|
const Widget showC = ShowABCField(fieldName: 'c');
|
|
|
|
// Unconditionally depends on the closest ABCModel ancestor.
|
|
// Which is the inner model, for which b,c are null.
|
|
final Widget showABC = Builder(builder: (BuildContext context) {
|
|
final ABCModel abc = ABCModel.of(context);
|
|
return Text('a: ${abc.a} b: ${abc.b} c: ${abc.c}',
|
|
style: Theme.of(context).textTheme.title);
|
|
});
|
|
|
|
return Scaffold(
|
|
body: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return ABCModel(
|
|
// The "outer" model
|
|
a: _a,
|
|
b: _b,
|
|
c: _c,
|
|
child: ABCModel(
|
|
// The "inner" model
|
|
a: 100 + _a,
|
|
b: 100 + _b,
|
|
aspects: Set<String>.of(<String>['a']),
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
showA,
|
|
showB,
|
|
showC,
|
|
const SizedBox(height: 24.0),
|
|
showABC,
|
|
const SizedBox(height: 24.0),
|
|
RaisedButton(
|
|
child: const Text('Increment a'),
|
|
onPressed: () {
|
|
setState(() {
|
|
_a += 1;
|
|
});
|
|
},
|
|
),
|
|
RaisedButton(
|
|
child: const Text('Increment b'),
|
|
onPressed: () {
|
|
setState(() {
|
|
_b += 1;
|
|
});
|
|
},
|
|
),
|
|
RaisedButton(
|
|
child: const Text('Increment c'),
|
|
onPressed: () {
|
|
setState(() {
|
|
_c += 1;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
await tester.pumpWidget(MaterialApp(home: abcPage));
|
|
expect(find.text('a: 100 [0]'), findsOneWidget);
|
|
expect(find.text('b: 1 [0]'), findsOneWidget);
|
|
expect(find.text('c: 2 [0]'), findsOneWidget);
|
|
expect(find.text('a: 100 b: 101 c: null'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Increment a'));
|
|
await tester.pumpAndSettle();
|
|
// Verify that field 'a' was incremented, but only the showA
|
|
// and showABC widgets were rebuilt.
|
|
expect(find.text('a: 101 [1]'), findsOneWidget);
|
|
expect(find.text('b: 1 [0]'), findsOneWidget);
|
|
expect(find.text('c: 2 [0]'), findsOneWidget);
|
|
expect(find.text('a: 101 b: 101 c: null'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Increment a'));
|
|
await tester.pumpAndSettle();
|
|
// Verify that field 'a' was incremented, but only the showA
|
|
// and showABC widgets were rebuilt.
|
|
expect(find.text('a: 102 [2]'), findsOneWidget);
|
|
expect(find.text('b: 1 [0]'), findsOneWidget);
|
|
expect(find.text('c: 2 [0]'), findsOneWidget);
|
|
expect(find.text('a: 102 b: 101 c: null'), findsOneWidget);
|
|
|
|
// Verify that field 'b' was incremented, but only the showB
|
|
// and showABC widgets were rebuilt.
|
|
await tester.tap(find.text('Increment b'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 102 [2]'), findsOneWidget);
|
|
expect(find.text('b: 2 [1]'), findsOneWidget);
|
|
expect(find.text('c: 2 [0]'), findsOneWidget);
|
|
expect(find.text('a: 102 b: 102 c: null'), findsOneWidget);
|
|
|
|
// Verify that field 'c' was incremented, but only the showC
|
|
// and showABC widgets were rebuilt.
|
|
await tester.tap(find.text('Increment c'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 102 [2]'), findsOneWidget);
|
|
expect(find.text('b: 2 [1]'), findsOneWidget);
|
|
expect(find.text('c: 3 [1]'), findsOneWidget);
|
|
expect(find.text('a: 102 b: 102 c: null'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('InheritedModel inner models supported aspect change',
|
|
(WidgetTester tester) async {
|
|
int _a = 0;
|
|
int _b = 1;
|
|
int _c = 2;
|
|
Set<String> _innerModelAspects = Set<String>.of(<String>['a']);
|
|
|
|
// Same as in abcPage in the "Inner InheritedModel shadows the outer one"
|
|
// test except: the "Add b aspect" changes adds 'b' to the set of
|
|
// aspects supported by the inner model.
|
|
final Widget abcPage = StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
const Widget showA = ShowABCField(fieldName: 'a');
|
|
const Widget showB = ShowABCField(fieldName: 'b');
|
|
const Widget showC = ShowABCField(fieldName: 'c');
|
|
|
|
// Unconditionally depends on the closest ABCModel ancestor.
|
|
// Which is the inner model, for which b,c are null.
|
|
final Widget showABC = Builder(builder: (BuildContext context) {
|
|
final ABCModel abc = ABCModel.of(context);
|
|
return Text('a: ${abc.a} b: ${abc.b} c: ${abc.c}',
|
|
style: Theme.of(context).textTheme.title);
|
|
});
|
|
|
|
return Scaffold(
|
|
body: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return ABCModel(
|
|
// The "outer" model
|
|
a: _a,
|
|
b: _b,
|
|
c: _c,
|
|
child: ABCModel(
|
|
// The "inner" model
|
|
a: 100 + _a,
|
|
b: 100 + _b,
|
|
aspects: _innerModelAspects,
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
showA,
|
|
showB,
|
|
showC,
|
|
const SizedBox(height: 24.0),
|
|
showABC,
|
|
const SizedBox(height: 24.0),
|
|
RaisedButton(
|
|
child: const Text('Increment a'),
|
|
onPressed: () {
|
|
setState(() {
|
|
_a += 1;
|
|
});
|
|
},
|
|
),
|
|
RaisedButton(
|
|
child: const Text('Increment b'),
|
|
onPressed: () {
|
|
setState(() {
|
|
_b += 1;
|
|
});
|
|
},
|
|
),
|
|
RaisedButton(
|
|
child: const Text('Increment c'),
|
|
onPressed: () {
|
|
setState(() {
|
|
_c += 1;
|
|
});
|
|
},
|
|
),
|
|
RaisedButton(
|
|
child: const Text('rebuild'),
|
|
onPressed: () {
|
|
setState(() {
|
|
// Rebuild both models
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
_innerModelAspects = Set<String>.of(<String>['a']);
|
|
await tester.pumpWidget(MaterialApp(home: abcPage));
|
|
expect(find.text('a: 100 [0]'),
|
|
findsOneWidget); // showA depends on the inner model
|
|
expect(find.text('b: 1 [0]'),
|
|
findsOneWidget); // showB depends on the outer model
|
|
expect(find.text('c: 2 [0]'), findsOneWidget);
|
|
expect(find.text('a: 100 b: 101 c: null'),
|
|
findsOneWidget); // inner model's a, b, c
|
|
|
|
_innerModelAspects = Set<String>.of(<String>['a', 'b']);
|
|
await tester.tap(find.text('rebuild'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 100 [1]'),
|
|
findsOneWidget); // rebuilt showA still depend on the inner model
|
|
expect(find.text('b: 101 [1]'),
|
|
findsOneWidget); // rebuilt showB now depends on the inner model
|
|
expect(find.text('c: 2 [1]'),
|
|
findsOneWidget); // rebuilt showC still depends on the outer model
|
|
expect(find.text('a: 100 b: 101 c: null'),
|
|
findsOneWidget); // inner model's a, b, c
|
|
|
|
// Verify that field 'a' was incremented, but only the showA
|
|
// and showABC widgets were rebuilt.
|
|
await tester.tap(find.text('Increment a'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 101 [2]'),
|
|
findsOneWidget); // rebuilt showA still depends on the inner model
|
|
expect(find.text('b: 101 [1]'), findsOneWidget);
|
|
expect(find.text('c: 2 [1]'), findsOneWidget);
|
|
expect(find.text('a: 101 b: 101 c: null'), findsOneWidget);
|
|
|
|
// Verify that field 'b' was incremented, but only the showB
|
|
// and showABC widgets were rebuilt.
|
|
await tester.tap(find.text('Increment b'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 101 [2]'),
|
|
findsOneWidget); // rebuilt showB still depends on the inner model
|
|
expect(find.text('b: 102 [2]'), findsOneWidget);
|
|
expect(find.text('c: 2 [1]'), findsOneWidget);
|
|
expect(find.text('a: 101 b: 102 c: null'), findsOneWidget);
|
|
|
|
// Verify that field 'c' was incremented, but only the showC
|
|
// and showABC widgets were rebuilt.
|
|
await tester.tap(find.text('Increment c'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 101 [2]'), findsOneWidget);
|
|
expect(find.text('b: 102 [2]'), findsOneWidget);
|
|
expect(find.text('c: 3 [2]'),
|
|
findsOneWidget); // rebuilt showC still depends on the outer model
|
|
expect(find.text('a: 101 b: 102 c: null'), findsOneWidget);
|
|
|
|
_innerModelAspects = Set<String>.of(<String>['a', 'b', 'c']);
|
|
await tester.tap(find.text('rebuild'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 101 [3]'),
|
|
findsOneWidget); // rebuilt showA still depend on the inner model
|
|
expect(find.text('b: 102 [3]'),
|
|
findsOneWidget); // rebuilt showB still depends on the inner model
|
|
expect(find.text('c: null [3]'),
|
|
findsOneWidget); // rebuilt showC now depends on the inner model
|
|
expect(find.text('a: 101 b: 102 c: null'),
|
|
findsOneWidget); // inner model's a, b, c
|
|
|
|
// Now the inner model supports no aspects
|
|
_innerModelAspects = Set<String>.of(<String>[]);
|
|
await tester.tap(find.text('rebuild'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 1 [4]'),
|
|
findsOneWidget); // rebuilt showA now depends on the outer model
|
|
expect(find.text('b: 2 [4]'),
|
|
findsOneWidget); // rebuilt showB now depends on the outer model
|
|
expect(find.text('c: 3 [4]'),
|
|
findsOneWidget); // rebuilt showC now depends on the outer model
|
|
expect(find.text('a: 101 b: 102 c: null'),
|
|
findsOneWidget); // inner model's a, b, c
|
|
|
|
// Now the inner model supports all aspects
|
|
_innerModelAspects = null;
|
|
await tester.tap(find.text('rebuild'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('a: 101 [5]'),
|
|
findsOneWidget); // rebuilt showA now depends on the inner model
|
|
expect(find.text('b: 102 [5]'),
|
|
findsOneWidget); // rebuilt showB now depends on the inner model
|
|
expect(find.text('c: null [5]'),
|
|
findsOneWidget); // rebuilt showC now depends on the inner model
|
|
expect(find.text('a: 101 b: 102 c: null'),
|
|
findsOneWidget); // inner model's a, b, c
|
|
});
|
|
}
|