NEW: added flutter_progress_button
This commit is contained in:
parent
974efb8aec
commit
e069755d2f
|
@ -0,0 +1,42 @@
|
|||
=======================================
|
||||
Flutter Web Preview Plugins Work Around
|
||||
=======================================
|
||||
|
||||
Intro
|
||||
-----
|
||||
Flutter for the web preview does NOT support plugins or the dart dependency management. One reason for this is because flutter web is a seperate repostory from flutter (main) with a seperate name space ("flutter_web"). As a work around I modify the source code of these plugins to be flutter web compatible. Next I add the modified plugins to this repository so that we can still add depdencies in the pubspec.yaml. This solution keeps our depedencies clean without the need to pull each plugin source into our flutter_web projects. The modifications are all cosmetic and 99% of the time it is just replacing "package:flutter/..." with "package:flutter_web" =). The pubspec.yaml also needs modifications. With that said more sophisticated plugins with dependencies can be complicated.
|
||||
|
||||
In the future flutter_web will be merged with flutter master. At that time this repository will be obsolete.
|
||||
|
||||
|
||||
Working Plugins
|
||||
---------------
|
||||
* provider
|
||||
- works as expected
|
||||
* flutter_progress_button
|
||||
- TBD
|
||||
|
||||
|
||||
Not Working Plugins (for your referrence)
|
||||
-----------------------------------------
|
||||
These packages are listed for referrence. These packages were an early attempt to get working, but failed for the reasons below:
|
||||
* firebase_core
|
||||
- cannot run on web because it is only a wrapper. this plugin depends on native code for Android/iOS. also developers intentionally added an exception that throws when trying to use this on flutter_web. "this is not compatible with flutter web preview".
|
||||
* cloud_firestore
|
||||
- cannot run on web because it is only a wrapper. this plugin depends on native code for Android/iOS. also developers intentionally added an exception that throws when trying to use this on flutter_web. "this is not compatible with flutter web preview".
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
::
|
||||
|
||||
dependencies:
|
||||
provider: any
|
||||
|
||||
dependency_overrides:
|
||||
flutter_web:
|
||||
git:
|
||||
url: https://github.com/j3g/flutter_web_plugins.git
|
||||
path: packages/provider
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
## 0.6.4 - 20190609
|
||||
README version update.
|
||||
|
||||
## 0.6.3 - 20190609
|
||||
Resolved Bug:
|
||||
Avoid a animate=false scenario that setState is called after dispose.
|
||||
|
||||
## 0.6.2 - 20190608
|
||||
Resolved Bug:
|
||||
Avoid a scenario that dispose is called on a null AnimationController.
|
||||
|
||||
## 0.6.1 - 20190604
|
||||
Improves package health.
|
||||
|
||||
## 0.6.0 - 20190604
|
||||
Support Customized content widgets.
|
||||
Support auto adjusts board radius.
|
||||
Add examples.
|
||||
|
||||
## 0.5.0 - 20190604
|
||||
Implement basic progress button function and animation.
|
||||
Use `onProgress` call back onPressed and returns a result handler function to indicate animation is completed and you are ready to go.
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Yang JIANG <jiangyang5157@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,73 @@
|
|||
# flutter_progress_button
|
||||
|
||||

|
||||

|
||||

|
||||
[](https://github.com/jiangyang5157/flutter_progress_button/issues)
|
||||
[](https://github.com/jiangyang5157/flutter_progress_button/blob/master/LICENSE)
|
||||
|
||||
**flutter_progress_button** is a free and open source (MIT license) Material Flutter Button that supports variety of buttons style demands. It is designed to be easy to use and customizable.
|
||||
|
||||
<img src="https://github.com/jiangyang5157/flutter_progress_button/blob/master/example/assets/example.gif" width="600">
|
||||
|
||||
|
||||
## Get started
|
||||
|
||||
### **Depend on it**
|
||||
|
||||
Add this to your package's pubspec.yaml file:
|
||||
|
||||
```yaml
|
||||
flutter_progress_button: '^0.6.4'
|
||||
```
|
||||
|
||||
### **Install it**
|
||||
|
||||
You can install packages from the command line:
|
||||
|
||||
```
|
||||
$ flutter pub get
|
||||
```
|
||||
|
||||
Alternatively, your editor might support flutter pub get.
|
||||
|
||||
### **Import it**
|
||||
|
||||
Now in your Dart code, you can use:
|
||||
|
||||
```dart
|
||||
import 'package:flutter_progress_button/flutter_progress_button.dart';
|
||||
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
Add `ProgressButton` to your widget tree:
|
||||
|
||||
```dart
|
||||
ProgressButton(
|
||||
normalWidget: const Text('I am a button'),
|
||||
progressWidget: const CircularProgressIndicator(),
|
||||
width: 196,
|
||||
height: 40,
|
||||
onPressed: () async {
|
||||
int score = await Future.delayed(
|
||||
const Duration(milliseconds: 3000), () => 42);
|
||||
// After [onPressed], it will trigger animation running backwards, from end to beginning
|
||||
return () {
|
||||
// Optional returns is returning a VoidCallback that will be called
|
||||
// after the animation is stopped at the beginning.
|
||||
// A best practice would be to do time-consuming task in [onPressed],
|
||||
// and do page navigation in the returned VoidCallback.
|
||||
// So that user won't missed out the reverse animation.
|
||||
};
|
||||
},
|
||||
),
|
||||
```
|
||||
|
||||
## Source
|
||||
Source code and examples of this library can be found in git:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/jiangyang5157/flutter_progress_button.git
|
||||
```
|
|
@ -0,0 +1,5 @@
|
|||
library flutter_progress_button;
|
||||
|
||||
import 'package:flutter_web/material.dart';
|
||||
|
||||
part 'src/widgets/progress_button.dart';
|
|
@ -0,0 +1,170 @@
|
|||
part of flutter_progress_button;
|
||||
|
||||
enum ProgressButtonState { Default, Processing }
|
||||
|
||||
class ProgressButton extends StatefulWidget {
|
||||
final Widget defaultWidget;
|
||||
final Widget progressWidget;
|
||||
final Function onPressed;
|
||||
final Color color;
|
||||
final double width;
|
||||
final double height;
|
||||
final double borderRadius;
|
||||
final bool animate;
|
||||
|
||||
ProgressButton({
|
||||
Key key,
|
||||
@required this.defaultWidget,
|
||||
this.progressWidget,
|
||||
this.onPressed,
|
||||
this.color,
|
||||
this.width = double.infinity,
|
||||
this.height = 40.0,
|
||||
this.borderRadius = 2.0,
|
||||
this.animate = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ProgressButtonState createState() => _ProgressButtonState();
|
||||
}
|
||||
|
||||
class _ProgressButtonState extends State<ProgressButton>
|
||||
with TickerProviderStateMixin {
|
||||
GlobalKey _globalKey = GlobalKey();
|
||||
Animation _anim;
|
||||
AnimationController _animController;
|
||||
Duration _duration = const Duration(milliseconds: 250);
|
||||
ProgressButtonState _state;
|
||||
double _width;
|
||||
double _height;
|
||||
double _borderRadius;
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
_animController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
_reset();
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_reset();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _reset() {
|
||||
_state = ProgressButtonState.Default;
|
||||
_width = widget.width;
|
||||
_height = widget.height;
|
||||
_borderRadius = widget.borderRadius;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PhysicalModel(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(_borderRadius),
|
||||
child: Container(
|
||||
key: _globalKey,
|
||||
height: _height,
|
||||
width: _width,
|
||||
child: RaisedButton(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
color: widget.color,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(_borderRadius)),
|
||||
child: _buildChildren(context),
|
||||
onPressed: widget.onPressed == null
|
||||
? null
|
||||
: () async {
|
||||
if (_state != ProgressButtonState.Default) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The result of widget.onPressed() will be called as VoidCallback after button status is back to default.
|
||||
VoidCallback onDefault;
|
||||
if (widget.animate) {
|
||||
_toProcessing();
|
||||
_forward((status) {
|
||||
if (status == AnimationStatus.dismissed) {
|
||||
_toDefault();
|
||||
if (onDefault != null && onDefault is VoidCallback) {
|
||||
onDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
onDefault = await widget.onPressed();
|
||||
_reverse();
|
||||
} else {
|
||||
_toProcessing();
|
||||
onDefault = await widget.onPressed();
|
||||
_toDefault();
|
||||
if (onDefault != null && onDefault is VoidCallback) {
|
||||
onDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChildren(BuildContext context) {
|
||||
Widget ret;
|
||||
switch (_state) {
|
||||
case ProgressButtonState.Default:
|
||||
ret = widget.defaultWidget;
|
||||
break;
|
||||
case ProgressButtonState.Processing:
|
||||
default:
|
||||
ret = widget.progressWidget ?? widget.defaultWidget;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void _toProcessing() {
|
||||
setState(() {
|
||||
_state = ProgressButtonState.Processing;
|
||||
});
|
||||
}
|
||||
|
||||
void _toDefault() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_state = ProgressButtonState.Default;
|
||||
});
|
||||
} else {
|
||||
_state = ProgressButtonState.Default;
|
||||
}
|
||||
}
|
||||
|
||||
void _forward(AnimationStatusListener stateListener) {
|
||||
double initialWidth = _globalKey.currentContext.size.width;
|
||||
double initialBorderRadius = widget.borderRadius;
|
||||
double targetWidth = _height;
|
||||
double targetBorderRadius = _height / 2;
|
||||
|
||||
_animController = AnimationController(duration: _duration, vsync: this);
|
||||
_anim = Tween(begin: 0.0, end: 1.0).animate(_animController)
|
||||
..addListener(() {
|
||||
setState(() {
|
||||
_width = initialWidth - ((initialWidth - targetWidth) * _anim.value);
|
||||
_borderRadius = initialBorderRadius -
|
||||
((initialBorderRadius - targetBorderRadius) * _anim.value);
|
||||
});
|
||||
})
|
||||
..addStatusListener(stateListener);
|
||||
|
||||
_animController.forward();
|
||||
}
|
||||
|
||||
void _reverse() {
|
||||
_animController.reverse();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
name: flutter_progress_button
|
||||
description: flutter_progress_button is a free and open source (MIT license) Material Flutter Button that supports variety of buttons style demands. It is designed to be easy to use and customizable.
|
||||
version: 0.6.4
|
||||
homepage: https://github.com/jiangyang5157/flutter_progress_button
|
||||
author: Yang JIANG <jiangyang5157@gmail.com>
|
||||
|
||||
environment:
|
||||
# You must be using Flutter >=1.5.0 or Dart >=2.3.0
|
||||
sdk: '>=2.3.0-dev.0.1 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter_web: any
|
||||
flutter_web_ui: any
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^1.4.0
|
||||
build_web_compilers: ^2.0.0
|
||||
pedantic: ^1.0.0
|
||||
|
||||
dependency_overrides:
|
||||
flutter_web:
|
||||
git:
|
||||
url: https://github.com/flutter/flutter_web
|
||||
path: packages/flutter_web
|
||||
flutter_web_ui:
|
||||
git:
|
||||
url: https://github.com/flutter/flutter_web
|
||||
path: packages/flutter_web_ui
|
|
@ -1,70 +0,0 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# Visual Studio Code related
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Android related
|
||||
**/android/**/gradle-wrapper.jar
|
||||
**/android/.gradle
|
||||
**/android/captures/
|
||||
**/android/gradlew
|
||||
**/android/gradlew.bat
|
||||
**/android/local.properties
|
||||
**/android/**/GeneratedPluginRegistrant.java
|
||||
|
||||
# iOS/XCode related
|
||||
**/ios/**/*.mode1v3
|
||||
**/ios/**/*.mode2v3
|
||||
**/ios/**/*.moved-aside
|
||||
**/ios/**/*.pbxuser
|
||||
**/ios/**/*.perspectivev3
|
||||
**/ios/**/*sync/
|
||||
**/ios/**/.sconsign.dblite
|
||||
**/ios/**/.tags*
|
||||
**/ios/**/.vagrant/
|
||||
**/ios/**/DerivedData/
|
||||
**/ios/**/Icon?
|
||||
**/ios/**/Pods/
|
||||
**/ios/**/.symlinks/
|
||||
**/ios/**/profile
|
||||
**/ios/**/xcuserdata
|
||||
**/ios/.generated/
|
||||
**/ios/Flutter/App.framework
|
||||
**/ios/Flutter/Flutter.framework
|
||||
**/ios/Flutter/Generated.xcconfig
|
||||
**/ios/Flutter/app.flx
|
||||
**/ios/Flutter/app.zip
|
||||
**/ios/Flutter/flutter_assets/
|
||||
**/ios/ServiceDefinitions.json
|
||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!**/ios/**/default.mode1v3
|
||||
!**/ios/**/default.mode2v3
|
||||
!**/ios/**/default.pbxuser
|
||||
!**/ios/**/default.perspectivev3
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
|
@ -1,10 +0,0 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: b593f5167bce84fb3cad5c258477bf3abc1b14eb
|
||||
channel: unknown
|
||||
|
||||
project_type: app
|
|
@ -1,125 +0,0 @@
|
|||
// ignore_for_file: public_member_api_docs
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
||||
class Counter with ChangeNotifier {
|
||||
int _count = 0;
|
||||
int get count => _count;
|
||||
|
||||
void increment() {
|
||||
_count++;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(builder: (_) => Counter()),
|
||||
],
|
||||
child: Consumer<Counter>(
|
||||
builder: (context, counter, _) {
|
||||
return MaterialApp(
|
||||
supportedLocales: const [Locale('en')],
|
||||
localizationsDelegates: [
|
||||
DefaultMaterialLocalizations.delegate,
|
||||
DefaultWidgetsLocalizations.delegate,
|
||||
_ExampleLocalizationsDelegate(counter.count),
|
||||
],
|
||||
home: const MyHomePage(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExampleLocalizations {
|
||||
static ExampleLocalizations of(BuildContext context) =>
|
||||
Localizations.of<ExampleLocalizations>(context, ExampleLocalizations);
|
||||
|
||||
const ExampleLocalizations(this._count);
|
||||
|
||||
final int _count;
|
||||
|
||||
String get title => 'Tapped $_count times';
|
||||
}
|
||||
|
||||
class _ExampleLocalizationsDelegate
|
||||
extends LocalizationsDelegate<ExampleLocalizations> {
|
||||
const _ExampleLocalizationsDelegate(this.count);
|
||||
|
||||
final int count;
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) => locale.languageCode == 'en';
|
||||
|
||||
@override
|
||||
Future<ExampleLocalizations> load(Locale locale) =>
|
||||
SynchronousFuture(ExampleLocalizations(count));
|
||||
|
||||
@override
|
||||
bool shouldReload(_ExampleLocalizationsDelegate old) => old.count != count;
|
||||
}
|
||||
|
||||
class MyHomePage extends StatelessWidget {
|
||||
const MyHomePage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Title()),
|
||||
body: const Center(child: CounterLabel()),
|
||||
floatingActionButton: const IncrementCounterButton(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IncrementCounterButton extends StatelessWidget {
|
||||
const IncrementCounterButton({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FloatingActionButton(
|
||||
onPressed: Provider.of<Counter>(context).increment,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CounterLabel extends StatelessWidget {
|
||||
const CounterLabel({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final counter = Provider.of<Counter>(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'${counter.count}',
|
||||
style: Theme.of(context).textTheme.display1,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Title extends StatelessWidget {
|
||||
const Title({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(ExampleLocalizations.of(context).title);
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
name: example
|
||||
version: 1.0.0
|
||||
homepage: https://github.com/rrousselGit/provider
|
||||
author: Remi Rousselet <darky12s@gmail.com>
|
||||
|
||||
environment:
|
||||
sdk: ">=2.1.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
provider:
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
dependency_overrides:
|
||||
provider:
|
||||
path: ../
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
|
@ -1,35 +0,0 @@
|
|||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:example/main.dart' as example;
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
|
||||
example.main();
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
expect(find.text('Tapped 0 times'), findsOneWidget);
|
||||
expect(find.text('Tapped 1 times'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
expect(find.text('Tapped 0 times'), findsNothing);
|
||||
expect(find.text('Tapped 1 times'), findsOneWidget);
|
||||
});
|
||||
}
|
|
@ -1,336 +0,0 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
group('ChangeNotifierProvider', () {
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
var notifier = ChangeNotifier();
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: notifier),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), notifier);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = ChangeNotifierProvider.value(
|
||||
key: const Key('42'),
|
||||
value: ChangeNotifier(),
|
||||
child: Container(),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = ChangeNotifierProvider<ChangeNotifier>(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
key: const Key('42'),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
});
|
||||
group('default constructor', () {
|
||||
testWidgets('pass down key', (tester) async {
|
||||
final notifier = ChangeNotifier();
|
||||
final keyProvider = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider.value(
|
||||
key: keyProvider,
|
||||
value: notifier,
|
||||
child: Container(),
|
||||
));
|
||||
expect(
|
||||
keyProvider.currentWidget,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
testWidgets('works with null (default)', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>.value(
|
||||
value: null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), null);
|
||||
});
|
||||
testWidgets('works with null (builder)', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>(
|
||||
builder: (_) => null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), null);
|
||||
});
|
||||
group('stateful constructor', () {
|
||||
testWidgets('called with context', (tester) async {
|
||||
final builder = ValueBuilderMock<ChangeNotifier>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>(
|
||||
key: key,
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
verify(builder(key.currentContext)).called(1);
|
||||
});
|
||||
test('throws if builder is null', () {
|
||||
expect(
|
||||
// ignore: prefer_const_constructors
|
||||
() => ChangeNotifierProvider(
|
||||
builder: null,
|
||||
),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
testWidgets('pass down key', (tester) async {
|
||||
final keyProvider = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
key: keyProvider,
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
expect(
|
||||
keyProvider.currentWidget,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
testWidgets('stateful builder called once', (tester) async {
|
||||
final notifier = MockNotifier();
|
||||
final builder = ValueBuilderMock<ChangeNotifier>();
|
||||
when(builder(any)).thenReturn(notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ChangeNotifierProvider>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
verifyNoMoreInteractions(builder);
|
||||
clearInteractions(notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyNoMoreInteractions(builder);
|
||||
verifyNoMoreInteractions(notifier);
|
||||
});
|
||||
testWidgets('dispose called on unmount', (tester) async {
|
||||
final notifier = MockNotifier();
|
||||
final builder = ValueBuilderMock<ChangeNotifier>();
|
||||
when(builder(any)).thenReturn(notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ChangeNotifierProvider>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
verifyNoMoreInteractions(builder);
|
||||
final listener = verify(notifier.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(notifier);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyInOrder([notifier.removeListener(listener), notifier.dispose()]);
|
||||
verifyNoMoreInteractions(builder);
|
||||
verifyNoMoreInteractions(notifier);
|
||||
});
|
||||
testWidgets('dispose can be null', (tester) async {
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from default to stateful constructor calls stateful builder',
|
||||
(tester) async {
|
||||
final notifier = MockNotifier();
|
||||
var notifier2 = ChangeNotifier();
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>.value(
|
||||
value: notifier,
|
||||
child: Container(),
|
||||
));
|
||||
final listener = verify(notifier.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>(
|
||||
builder: (_) {
|
||||
return notifier2;
|
||||
},
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), notifier2);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
verify(notifier.removeListener(listener)).called(1);
|
||||
verifyNoMoreInteractions(notifier);
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from stateful to default constructor dispose correctly stateful notifier',
|
||||
(tester) async {
|
||||
final ChangeNotifier notifier = MockNotifier();
|
||||
var notifier2 = ChangeNotifier();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: (_) => notifier,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final listener = verify(notifier.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(notifier);
|
||||
await tester.pumpWidget(ChangeNotifierProvider.value(
|
||||
value: notifier2,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), notifier2);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyInOrder([
|
||||
notifier.removeListener(listener),
|
||||
notifier.dispose(),
|
||||
]);
|
||||
verifyNoMoreInteractions(notifier);
|
||||
});
|
||||
testWidgets('dispose can be null', (tester) async {
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
testWidgets('changing notifier rebuilds descendants', (tester) async {
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
var notifier = ChangeNotifier();
|
||||
Widget build() {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: notifier,
|
||||
child: Builder(builder: (context) {
|
||||
Provider.of<ChangeNotifier>(context);
|
||||
return builder(context);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(build());
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(notifier.hasListeners, true);
|
||||
|
||||
var previousNotifier = notifier;
|
||||
notifier = ChangeNotifier();
|
||||
await tester.pumpWidget(build());
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(notifier.hasListeners, true);
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(previousNotifier.hasListeners, false);
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(notifier.hasListeners, false);
|
||||
});
|
||||
testWidgets("rebuilding with the same provider don't rebuilds descendants",
|
||||
(tester) async {
|
||||
final notifier = ChangeNotifier();
|
||||
final keyChild = GlobalKey();
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
final child = Builder(
|
||||
key: keyChild,
|
||||
builder: builder,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider.value(
|
||||
value: notifier,
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider.value(
|
||||
value: notifier,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), notifier);
|
||||
});
|
||||
testWidgets('notifylistener rebuilds descendants', (tester) async {
|
||||
final notifier = ChangeNotifier();
|
||||
final keyChild = GlobalKey();
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
final child = Builder(
|
||||
key: keyChild,
|
||||
builder: (context) {
|
||||
// subscribe
|
||||
Provider.of<ChangeNotifier>(context);
|
||||
return builder(context);
|
||||
},
|
||||
);
|
||||
var changeNotifierProvider = ChangeNotifierProvider.value(
|
||||
value: notifier,
|
||||
child: child,
|
||||
);
|
||||
await tester.pumpWidget(changeNotifierProvider);
|
||||
|
||||
clearInteractions(builder);
|
||||
// ignore: invalid_use_of_protected_member
|
||||
notifier.notifyListeners();
|
||||
await Future<void>.value();
|
||||
await tester.pump();
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), notifier);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:provider/src/proxy_provider.dart' show ProxyProviderBase;
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
class _ListenableCombined = Combined with ChangeNotifier;
|
||||
|
||||
void main() {
|
||||
final a = A();
|
||||
final b = B();
|
||||
final c = C();
|
||||
final d = D();
|
||||
final e = E();
|
||||
final f = F();
|
||||
|
||||
final combinedConsumerMock = ConsumerBuilderMock();
|
||||
setUp(() => when(combinedConsumerMock(any)).thenReturn(Container()));
|
||||
tearDown(() {
|
||||
clearInteractions(combinedConsumerMock);
|
||||
});
|
||||
|
||||
final mockConsumer = Consumer<_ListenableCombined>(
|
||||
builder: (context, combined, child) => combinedConsumerMock(combined),
|
||||
);
|
||||
|
||||
group('ChangeNotifierProxyProvider', () {
|
||||
test('throws if builder is missing', () {
|
||||
expect(
|
||||
() =>
|
||||
ChangeNotifierProxyProvider<A, _ListenableCombined>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('works with null', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ChangeNotifierProxyProvider<int, ChangeNotifier>(
|
||||
initialBuilder: (_) => null,
|
||||
builder: (_, __, value) => value,
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
|
||||
testWidgets('rebuilds dependendents when listeners are called',
|
||||
(tester) async {
|
||||
final notifier = ValueNotifier(0);
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ChangeNotifierProxyProvider<int, ValueNotifier<int>>(
|
||||
initialBuilder: (_) => notifier,
|
||||
builder: (_, count, value) => value..value = count,
|
||||
)
|
||||
],
|
||||
child: Consumer<ValueNotifier<int>>(builder: (_, value, __) {
|
||||
return Text(
|
||||
value.value.toString(),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
notifier.value++;
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
expect(find.text('0'), findsNothing);
|
||||
});
|
||||
testWidgets('disposes of created value', (tester) async {
|
||||
final notifier = MockNotifier();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ChangeNotifierProxyProvider<int, MockNotifier>(
|
||||
key: key,
|
||||
initialBuilder: (_) => notifier,
|
||||
builder: (_, count, value) => value,
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(notifier.dispose()).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('ChangeNotifierProxyProvider variants', () {
|
||||
Finder findProxyProvider() => find
|
||||
.byWidgetPredicate((widget) => widget is ProxyProviderBase<Combined>);
|
||||
testWidgets('ChangeNotifierProxyProvider2', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider2<A, B, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, previous) =>
|
||||
_ListenableCombined(context, previous, a, b),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ChangeNotifierProxyProvider3', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider3<A, B, C, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ChangeNotifierProxyProvider4', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider4<A, B, C, D, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c, d),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ChangeNotifierProxyProvider5', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider5<A, B, C, D, E, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d, e),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(context, _ListenableCombined(null, null, null), a,
|
||||
b, c, d, e, null),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ChangeNotifierProxyProvider6', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider6<A, B, C, D, E, F, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, f, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d, e, f),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c, d, e, f),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
Element findElementOfWidget<T extends Widget>() {
|
||||
return find.byType(T).first.evaluate().first;
|
||||
}
|
||||
|
||||
Type typeOf<T>() => T;
|
||||
|
||||
class ValueBuilderMock<T> extends Mock {
|
||||
T call(BuildContext context);
|
||||
}
|
||||
|
||||
class DisposerMock<T> extends Mock {
|
||||
void call(BuildContext context, T value);
|
||||
}
|
||||
|
||||
class MockNotifier extends Mock implements ChangeNotifier {}
|
||||
|
||||
class BuilderMock extends Mock {
|
||||
Widget call(BuildContext context);
|
||||
}
|
||||
|
||||
class UpdateShouldNotifyMock<T> extends Mock {
|
||||
bool call(T old, T newValue);
|
||||
}
|
||||
|
||||
class A with DiagnosticableTreeMixin {}
|
||||
|
||||
class B with DiagnosticableTreeMixin {}
|
||||
|
||||
class C with DiagnosticableTreeMixin {}
|
||||
|
||||
class D with DiagnosticableTreeMixin {}
|
||||
|
||||
class E with DiagnosticableTreeMixin {}
|
||||
|
||||
class F with DiagnosticableTreeMixin {}
|
||||
|
||||
class ConsumerBuilderMock extends Mock {
|
||||
Widget call(Combined foo);
|
||||
}
|
||||
|
||||
class CombinerMock extends Mock {
|
||||
Combined call(BuildContext context, A a, Combined foo);
|
||||
}
|
||||
|
||||
class ProviderBuilderMock extends Mock {
|
||||
Widget call(BuildContext context, Combined value, Widget child);
|
||||
}
|
||||
|
||||
class Combined extends DiagnosticableTree {
|
||||
final A a;
|
||||
final B b;
|
||||
final C c;
|
||||
final D d;
|
||||
final E e;
|
||||
final F f;
|
||||
final Combined previous;
|
||||
final BuildContext context;
|
||||
|
||||
Combined(this.context, this.previous, this.a,
|
||||
[this.b, this.c, this.d, this.e, this.f]);
|
||||
|
||||
@override
|
||||
// ignore: hash_and_equals
|
||||
bool operator ==(Object other) =>
|
||||
other is Combined &&
|
||||
other.context == context &&
|
||||
other.previous == previous &&
|
||||
other.a == a &&
|
||||
other.b == b &&
|
||||
other.c == c &&
|
||||
other.e == e &&
|
||||
other.f == f;
|
||||
|
||||
// fancy toString for debug purposes.
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.properties.addAll([
|
||||
DiagnosticsProperty('a', a, defaultValue: null),
|
||||
DiagnosticsProperty('b', b, defaultValue: null),
|
||||
DiagnosticsProperty('c', c, defaultValue: null),
|
||||
DiagnosticsProperty('d', d, defaultValue: null),
|
||||
DiagnosticsProperty('e', e, defaultValue: null),
|
||||
DiagnosticsProperty('f', f, defaultValue: null),
|
||||
DiagnosticsProperty('previous', previous, defaultValue: null),
|
||||
DiagnosticsProperty('context', context, defaultValue: null),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class MyListenable extends ChangeNotifier {}
|
||||
|
||||
class MyStream extends Stream<void> {
|
||||
@override
|
||||
StreamSubscription<void> listen(void Function(void event) onData,
|
||||
{Function onError, void Function() onDone, bool cancelOnError}) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
class ConsumerBuilderMock extends Mock {
|
||||
Widget call(Combined foo);
|
||||
}
|
||||
|
||||
class Combined {
|
||||
final A a;
|
||||
final B b;
|
||||
final C c;
|
||||
final D d;
|
||||
final E e;
|
||||
final F f;
|
||||
final Widget child;
|
||||
final BuildContext context;
|
||||
|
||||
Combined(this.context, this.child, this.a,
|
||||
[this.b, this.c, this.d, this.e, this.f]);
|
||||
|
||||
@override
|
||||
// ignore: hash_and_equals
|
||||
bool operator ==(Object other) =>
|
||||
other is Combined &&
|
||||
other.context == context &&
|
||||
other.child == child &&
|
||||
other.a == a &&
|
||||
other.b == b &&
|
||||
other.c == c &&
|
||||
other.e == e &&
|
||||
other.f == f;
|
||||
}
|
||||
|
||||
void main() {
|
||||
final a = A();
|
||||
final b = B();
|
||||
final c = C();
|
||||
final d = D();
|
||||
final e = E();
|
||||
final f = F();
|
||||
final provider = MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
],
|
||||
);
|
||||
|
||||
final mock = ConsumerBuilderMock();
|
||||
setUp(() {
|
||||
when(mock(any)).thenReturn(Container());
|
||||
});
|
||||
tearDown(() {
|
||||
clearInteractions(mock);
|
||||
});
|
||||
|
||||
group('consumer', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer<A>(
|
||||
key: key,
|
||||
builder: (context, value, child) =>
|
||||
mock(Combined(context, child, value)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer<int>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('consumer2', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer2<A, B>(
|
||||
key: key,
|
||||
builder: (context, value, v2, child) =>
|
||||
mock(Combined(context, child, value, v2)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer2<A, B>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
group('consumer3', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer3<A, B, C>(
|
||||
key: key,
|
||||
builder: (context, value, v2, v3, child) =>
|
||||
mock(Combined(context, child, value, v2, v3)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b, c)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer3<A, B, C>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
group('consumer4', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer4<A, B, C, D>(
|
||||
key: key,
|
||||
builder: (context, value, v2, v3, v4, child) =>
|
||||
mock(Combined(context, child, value, v2, v3, v4)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b, c, d)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer4<A, B, C, D>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
group('consumer5', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer5<A, B, C, D, E>(
|
||||
key: key,
|
||||
builder: (context, value, v2, v3, v4, v5, child) =>
|
||||
mock(Combined(context, child, value, v2, v3, v4, v5)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b, c, d, e)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer5<A, B, C, D, E>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
group('consumer6', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer6<A, B, C, D, E, F>(
|
||||
key: key,
|
||||
builder: (context, value, v2, v3, v4, v5, v6, child) =>
|
||||
mock(Combined(context, child, value, v2, v3, v4, v5, v6)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b, c, d, e, f)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer6<A, B, C, D, E, F>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,361 +0,0 @@
|
|||
// ignore_for_file: invalid_use_of_protected_member
|
||||
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
group('DelegateWidget', () {
|
||||
testWidgets(
|
||||
"can't call context.inheritFromWidgetOfExactType from first initDelegate",
|
||||
(tester) async {
|
||||
await tester.pumpWidget(Provider.value(
|
||||
value: 42,
|
||||
child: TestDelegateWidget(
|
||||
delegate: InitDelegate(),
|
||||
child: Container(),
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
});
|
||||
testWidgets(
|
||||
"can't call context.inheritFromWidgetOfExactType from initDelegate after an update",
|
||||
(tester) async {
|
||||
await tester.pumpWidget(Provider.value(
|
||||
value: 42,
|
||||
child: TestDelegateWidget(
|
||||
delegate: SingleValueDelegate(42),
|
||||
child: Container(),
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
|
||||
await tester.pumpWidget(Provider.value(
|
||||
value: 42,
|
||||
child: TestDelegateWidget(
|
||||
delegate: InitDelegate(),
|
||||
child: Container(),
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
});
|
||||
testWidgets('mount initializes setState and context and calls initDelegate',
|
||||
(tester) async {
|
||||
final state = MockStateDelegate<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
expect(state.context, isNull);
|
||||
expect(state.setState, isNull);
|
||||
verifyZeroInteractions(state.initDelegateMock);
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(state.context, key.currentContext);
|
||||
expect(state.setState, key.currentState.setState);
|
||||
|
||||
verify(state.initDelegateMock(
|
||||
key.currentContext,
|
||||
key.currentState.setState,
|
||||
)).called(1);
|
||||
verifyZeroInteractions(state.didUpdateDelegateMock);
|
||||
verifyZeroInteractions(state.disposeMock);
|
||||
});
|
||||
testWidgets(
|
||||
'rebuilding with delegate of the same type calls didUpdateDelegate',
|
||||
(tester) async {
|
||||
final state = MockStateDelegate<int>();
|
||||
final state2 = MockStateDelegate<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state,
|
||||
child: Container(),
|
||||
));
|
||||
clearInteractions(state.initDelegateMock);
|
||||
|
||||
final context = key.currentContext;
|
||||
final setState = key.currentState.setState;
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state2,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(state.context, isNull);
|
||||
expect(state.setState, isNull);
|
||||
verifyZeroInteractions(state.initDelegateMock);
|
||||
verifyZeroInteractions(state.didUpdateDelegateMock);
|
||||
verifyZeroInteractions(state.disposeMock);
|
||||
|
||||
expect(state2.context, context);
|
||||
expect(state2.setState, setState);
|
||||
verify(state2.didUpdateDelegate(state)).called(1);
|
||||
verifyZeroInteractions(state2.initDelegateMock);
|
||||
verifyNoMoreInteractions(state2.didUpdateDelegateMock);
|
||||
verifyZeroInteractions(state2.disposeMock);
|
||||
});
|
||||
testWidgets(
|
||||
'rebuilding with delegate of a different type disposes the previous and init the new one',
|
||||
(tester) async {
|
||||
final state = MockStateDelegate<int>();
|
||||
final state2 = MockStateDelegate<String>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state,
|
||||
child: Container(),
|
||||
));
|
||||
clearInteractions(state.initDelegateMock);
|
||||
|
||||
final context = key.currentContext;
|
||||
final setState = key.currentState.setState;
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state2,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(state.context, isNull);
|
||||
expect(state.setState, isNull);
|
||||
|
||||
verifyZeroInteractions(state.initDelegateMock);
|
||||
verifyZeroInteractions(state.didUpdateDelegateMock);
|
||||
verify(state.disposeMock(context, setState)).called(1);
|
||||
verifyNoMoreInteractions(state.disposeMock);
|
||||
|
||||
expect(state2.context, key.currentContext);
|
||||
expect(state2.setState, key.currentState.setState);
|
||||
|
||||
verify(state2.initDelegateMock(context, setState)).called(1);
|
||||
verifyNoMoreInteractions(state2.initDelegateMock);
|
||||
verifyNoMoreInteractions(state2.didUpdateDelegateMock);
|
||||
verifyZeroInteractions(state2.disposeMock);
|
||||
});
|
||||
|
||||
testWidgets('unmounting the widget calls delegate.dispose', (tester) async {
|
||||
final state = MockStateDelegate<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state,
|
||||
child: Container(),
|
||||
));
|
||||
clearInteractions(state.initDelegateMock);
|
||||
|
||||
final context = key.currentContext;
|
||||
final setState = key.currentState.setState;
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
expect(state.context, isNull);
|
||||
expect(state.setState, isNull);
|
||||
verifyZeroInteractions(state.initDelegateMock);
|
||||
verifyZeroInteractions(state.didUpdateDelegateMock);
|
||||
verify(state.disposeMock(context, setState)).called(1);
|
||||
verifyNoMoreInteractions(state.disposeMock);
|
||||
});
|
||||
|
||||
test('throws if delegate is null', () {
|
||||
expect(
|
||||
() => TestDelegateWidget(child: Container()),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('SingleValueDelegate', () {
|
||||
test('implements ValueStateDelegate', () {
|
||||
expect(
|
||||
SingleValueDelegate(0),
|
||||
isInstanceOf<ValueStateDelegate<int>>(),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('stores and update value', (tester) async {
|
||||
int value;
|
||||
BuildContext context;
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(BuilderDelegateWidget<SingleValueDelegate<int>>(
|
||||
key: key,
|
||||
delegate: SingleValueDelegate(0),
|
||||
builder: (c, d) {
|
||||
value = d.value;
|
||||
context = c;
|
||||
return Container();
|
||||
},
|
||||
));
|
||||
|
||||
expect(context, equals(key.currentContext));
|
||||
expect(value, equals(0));
|
||||
|
||||
await tester.pumpWidget(BuilderDelegateWidget<SingleValueDelegate<int>>(
|
||||
key: key,
|
||||
delegate: SingleValueDelegate(42),
|
||||
builder: (c, d) {
|
||||
value = d.value;
|
||||
context = c;
|
||||
return Container();
|
||||
},
|
||||
));
|
||||
|
||||
expect(context, equals(key.currentContext));
|
||||
expect(value, equals(42));
|
||||
});
|
||||
});
|
||||
|
||||
group('BuilderStateDelegate', () {
|
||||
test('implements ValueStateDelegate', () {
|
||||
expect(
|
||||
BuilderStateDelegate((_) => 42),
|
||||
isInstanceOf<ValueStateDelegate<int>>(),
|
||||
);
|
||||
});
|
||||
test('throws if builder is missing', () {
|
||||
expect(
|
||||
() => BuilderStateDelegate<dynamic>(null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('initialize value and never recreate it', (tester) async {
|
||||
int value;
|
||||
BuildContext context;
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester
|
||||
.pumpWidget(BuilderDelegateWidget<BuilderStateDelegate<int>>(
|
||||
key: key,
|
||||
delegate: BuilderStateDelegate((_) => 42),
|
||||
builder: (c, d) {
|
||||
value = d.value;
|
||||
context = c;
|
||||
return Container();
|
||||
},
|
||||
));
|
||||
|
||||
expect(context, equals(key.currentContext));
|
||||
expect(value, equals(42));
|
||||
|
||||
await tester
|
||||
.pumpWidget(BuilderDelegateWidget<BuilderStateDelegate<int>>(
|
||||
key: key,
|
||||
delegate: BuilderStateDelegate((_) => 0),
|
||||
builder: (c, d) {
|
||||
value = d.value;
|
||||
context = c;
|
||||
return Container();
|
||||
},
|
||||
));
|
||||
|
||||
expect(context, equals(key.currentContext));
|
||||
expect(value, equals(42));
|
||||
});
|
||||
|
||||
testWidgets('initialize value and never recreate it', (tester) async {
|
||||
final disposeMock = DisposerMock<int>();
|
||||
final key = GlobalKey();
|
||||
final delegate2 = BuilderStateDelegate(
|
||||
(_) => 42,
|
||||
dispose: disposeMock,
|
||||
);
|
||||
|
||||
await tester
|
||||
.pumpWidget(BuilderDelegateWidget<BuilderStateDelegate<int>>(
|
||||
key: key,
|
||||
delegate: delegate2,
|
||||
builder: (_, __) => Container(),
|
||||
));
|
||||
|
||||
final context = key.currentContext;
|
||||
|
||||
verifyZeroInteractions(disposeMock);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(disposeMock(context, 42)).called(1);
|
||||
verifyNoMoreInteractions(disposeMock);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class InitDelegate extends StateDelegate {
|
||||
@override
|
||||
void initDelegate() {
|
||||
super.initDelegate();
|
||||
Provider.of<int>(context);
|
||||
}
|
||||
}
|
||||
|
||||
class InitDelegateMock extends Mock {
|
||||
void call(BuildContext context, StateSetter setState);
|
||||
}
|
||||
|
||||
class DidUpdateDelegateMock extends Mock {
|
||||
void call(StateDelegate old);
|
||||
}
|
||||
|
||||
class DisposeMock extends Mock {
|
||||
void call(BuildContext context, StateSetter setState);
|
||||
}
|
||||
|
||||
class MockStateDelegate<T> extends StateDelegate {
|
||||
final disposeMock = DisposeMock();
|
||||
final initDelegateMock = InitDelegateMock();
|
||||
final didUpdateDelegateMock = DidUpdateDelegateMock();
|
||||
|
||||
@override
|
||||
void initDelegate() {
|
||||
super.initDelegate();
|
||||
initDelegateMock(context, setState);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateDelegate(StateDelegate old) {
|
||||
super.didUpdateDelegate(old);
|
||||
didUpdateDelegateMock(old);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposeMock(context, setState);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class BuilderDelegateWidget<T extends ValueStateDelegate<dynamic>>
|
||||
extends ValueDelegateWidget<dynamic> {
|
||||
BuilderDelegateWidget({Key key, this.builder, T delegate})
|
||||
: super(key: key, delegate: delegate);
|
||||
|
||||
final Widget Function(BuildContext context, T delegate) builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => builder(context, delegate as T);
|
||||
}
|
||||
|
||||
class TestDelegateWidget extends DelegateWidget {
|
||||
TestDelegateWidget({Key key, this.child, StateDelegate delegate})
|
||||
: super(key: key, delegate: delegate);
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => child;
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
// tests forked from stream_provider_test.dart
|
||||
// by replacing Stream with Future and StreamController with Completer
|
||||
|
||||
class ErrorBuilderMock<T> extends Mock {
|
||||
T call(BuildContext context, Object error);
|
||||
}
|
||||
|
||||
class MockFuture<T> extends Mock implements Future<T> {}
|
||||
|
||||
void main() {
|
||||
group('FutureProvider', () {
|
||||
testWidgets('update when value change', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
|
||||
completer.complete(0);
|
||||
// futures are asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 0);
|
||||
});
|
||||
|
||||
testWidgets("don't notify descendants when rebuilding by default",
|
||||
(tester) async {
|
||||
final completer = Completer<int>();
|
||||
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenAnswer((invocation) {
|
||||
final context = invocation.positionalArguments.first as BuildContext;
|
||||
Provider.of<int>(context);
|
||||
return Container();
|
||||
});
|
||||
final child = Builder(builder: builder);
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
child: child,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
});
|
||||
|
||||
testWidgets('pass down keys', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
key: key,
|
||||
value: completer.future,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(key.currentWidget, isInstanceOf<FutureProvider>());
|
||||
});
|
||||
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(null, 1)).thenReturn(true);
|
||||
|
||||
final completer = Completer<int>();
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
completer.complete(1);
|
||||
// futures are asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
verify(shouldNotify(null, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets("don't listen future again if it doesn't change",
|
||||
(tester) async {
|
||||
final future = MockFuture<int>();
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: future,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: future,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verify(future.then<void>(any, onError: anyNamed('onError'))).called(1);
|
||||
verifyNoMoreInteractions(future);
|
||||
});
|
||||
|
||||
testWidgets('future emits error and catchError is missing', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
completer.completeError(42);
|
||||
|
||||
await Future.microtask(tester.pump);
|
||||
final exception = tester.takeException() as Object;
|
||||
expect(exception, isFlutterError);
|
||||
expect(exception.toString(), equals('''
|
||||
An exception was throw by Future<int> listened by
|
||||
FutureProvider<int>, but no `catchError` was provided.
|
||||
|
||||
Exception:
|
||||
42
|
||||
'''));
|
||||
});
|
||||
testWidgets('calls catchError if future emits error', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
final key = GlobalKey();
|
||||
final catchError = ErrorBuilderMock<int>();
|
||||
when(catchError(any, 42)).thenReturn(0);
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
catchError: catchError,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
completer.completeError(42);
|
||||
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 0);
|
||||
|
||||
final context = findElementOfWidget<FutureProvider<int>>();
|
||||
|
||||
verify(catchError(context, 42));
|
||||
});
|
||||
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
FutureProvider<int>.value(value: Future<int>.value()),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = FutureProvider<int>.value(
|
||||
value: Future<int>.value(),
|
||||
initialData: 42,
|
||||
child: Container(),
|
||||
catchError: (_, __) => 42,
|
||||
key: const Key('42'),
|
||||
updateShouldNotify: (_, __) => true,
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialData, equals(provider.initialData));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.catchError, equals(provider.catchError));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = FutureProvider<int>(
|
||||
builder: (_) => Future<int>.value(),
|
||||
initialData: 42,
|
||||
child: Container(),
|
||||
catchError: (_, __) => 42,
|
||||
key: const Key('42'),
|
||||
updateShouldNotify: (_, __) => true,
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialData, equals(provider.initialData));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.catchError, equals(provider.catchError));
|
||||
});
|
||||
testWidgets('works with null', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(FutureProvider<int>.value(
|
||||
value: null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
});
|
||||
|
||||
group('stateful constructor', () {
|
||||
test('crashes if builder is null', () {
|
||||
expect(
|
||||
() => FutureProvider<int>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('works with null', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: (_) => null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
|
||||
testWidgets('create future with builder', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
|
||||
final builder = ValueBuilderMock<Future<int>>();
|
||||
when(builder(any)).thenAnswer((_) => completer.future);
|
||||
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<FutureProvider<int>>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
|
||||
// extra build to see if builder isn't called again
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyNoMoreInteractions(builder);
|
||||
});
|
||||
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(null, 1)).thenReturn(true);
|
||||
|
||||
var completer = Completer<int>();
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: (_) => completer.future,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
completer.complete(1);
|
||||
// futures are asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
verify(shouldNotify(null, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Changing from default to stateful constructor calls stateful builder',
|
||||
(tester) async {
|
||||
final key = GlobalKey();
|
||||
final completer = Completer<int>();
|
||||
await tester.pumpWidget(FutureProvider<int>.value(
|
||||
value: completer.future,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: (_) => Future.value(42),
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 42);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
testWidgets('Changing from stateful to default constructor',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: (_) => Future.value(0),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: Future.value(1),
|
||||
child: Container(key: key),
|
||||
));
|
||||
await tester.pump();
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,375 +0,0 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
group('ListenableProvider', () {
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
var listenable = ChangeNotifier();
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
ListenableProvider.value(value: listenable),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), listenable);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = ListenableProvider.value(
|
||||
key: const Key('42'),
|
||||
value: ChangeNotifier(),
|
||||
child: Container(),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = ListenableProvider<ChangeNotifier>(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
dispose: (_, n) {},
|
||||
child: Container(),
|
||||
key: const Key('42'),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
});
|
||||
|
||||
group('value constructor', () {
|
||||
testWidgets('pass down key', (tester) async {
|
||||
final listenable = ChangeNotifier();
|
||||
final keyProvider = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
key: keyProvider,
|
||||
value: listenable,
|
||||
child: Container(),
|
||||
));
|
||||
expect(
|
||||
keyProvider.currentWidget,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
testWidgets("don't listen again if listenable instance doesn't change",
|
||||
(tester) async {
|
||||
final listenable = MockNotifier();
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>.value(
|
||||
value: listenable,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>.value(
|
||||
value: listenable,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verify(listenable.addListener(any)).called(1);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
testWidgets('works with null (default)', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>.value(
|
||||
value: null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), null);
|
||||
});
|
||||
testWidgets('works with null (builder)', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>(
|
||||
builder: (_) => null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), null);
|
||||
});
|
||||
group('stateful constructor', () {
|
||||
testWidgets('called with context', (tester) async {
|
||||
final builder = ValueBuilderMock<ChangeNotifier>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>(
|
||||
key: key,
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
verify(builder(key.currentContext)).called(1);
|
||||
});
|
||||
test('throws if builder is null', () {
|
||||
expect(
|
||||
// ignore: prefer_const_constructors
|
||||
() => ListenableProvider(
|
||||
builder: null,
|
||||
),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
testWidgets('pass down key', (tester) async {
|
||||
final keyProvider = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
key: keyProvider,
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
expect(
|
||||
keyProvider.currentWidget,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
testWidgets('stateful builder called once', (tester) async {
|
||||
final listenable = MockNotifier();
|
||||
final builder = ValueBuilderMock<Listenable>();
|
||||
when(builder(any)).thenReturn(listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ListenableProvider>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
verifyNoMoreInteractions(builder);
|
||||
clearInteractions(listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyNoMoreInteractions(builder);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
testWidgets('dispose called on unmount', (tester) async {
|
||||
final listenable = MockNotifier();
|
||||
final builder = ValueBuilderMock<Listenable>();
|
||||
final disposer = DisposerMock<Listenable>();
|
||||
when(builder(any)).thenReturn(listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: builder,
|
||||
dispose: disposer,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ListenableProvider>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
verifyNoMoreInteractions(builder);
|
||||
final listener = verify(listenable.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(listenable);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyInOrder([
|
||||
listenable.removeListener(listener),
|
||||
disposer(context, listenable),
|
||||
]);
|
||||
verifyNoMoreInteractions(builder);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
testWidgets('dispose can be null', (tester) async {
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from default to stateful constructor calls stateful builder',
|
||||
(tester) async {
|
||||
final listenable = MockNotifier();
|
||||
var listenable2 = ChangeNotifier();
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>.value(
|
||||
value: listenable,
|
||||
child: Container(),
|
||||
));
|
||||
final listener = verify(listenable.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>(
|
||||
builder: (_) {
|
||||
return listenable2;
|
||||
},
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), listenable2);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
verify(listenable.removeListener(listener)).called(1);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from stateful to default constructor dispose correctly stateful listenable',
|
||||
(tester) async {
|
||||
final ChangeNotifier listenable = MockNotifier();
|
||||
final disposer = DisposerMock<Listenable>();
|
||||
var listenable2 = ChangeNotifier();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: (_) => listenable,
|
||||
dispose: disposer,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ListenableProvider<ChangeNotifier>>();
|
||||
|
||||
final listener = verify(listenable.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(listenable);
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable2,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), listenable2);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyInOrder([
|
||||
listenable.removeListener(listener),
|
||||
disposer(context, listenable),
|
||||
]);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
|
||||
testWidgets('changing listenable rebuilds descendants', (tester) async {
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
var listenable = ChangeNotifier();
|
||||
Widget build() {
|
||||
return ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: Builder(builder: (context) {
|
||||
Provider.of<ChangeNotifier>(context);
|
||||
return builder(context);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(build());
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(listenable.hasListeners, true);
|
||||
|
||||
var previousNotifier = listenable;
|
||||
listenable = ChangeNotifier();
|
||||
await tester.pumpWidget(build());
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(listenable.hasListeners, true);
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(previousNotifier.hasListeners, false);
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(listenable.hasListeners, false);
|
||||
});
|
||||
testWidgets("rebuilding with the same provider don't rebuilds descendants",
|
||||
(tester) async {
|
||||
final listenable = ChangeNotifier();
|
||||
final keyChild = GlobalKey();
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
final child = Builder(
|
||||
key: keyChild,
|
||||
builder: builder,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
|
||||
listenable.notifyListeners();
|
||||
await tester.pump();
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
});
|
||||
testWidgets('notifylistener rebuilds descendants', (tester) async {
|
||||
final listenable = ChangeNotifier();
|
||||
final keyChild = GlobalKey();
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
final child = Builder(
|
||||
key: keyChild,
|
||||
builder: (context) {
|
||||
// subscribe
|
||||
Provider.of<ChangeNotifier>(context);
|
||||
return builder(context);
|
||||
},
|
||||
);
|
||||
var changeNotifierProvider = ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
);
|
||||
await tester.pumpWidget(changeNotifierProvider);
|
||||
|
||||
clearInteractions(builder);
|
||||
// ignore: invalid_use_of_protected_member
|
||||
listenable.notifyListeners();
|
||||
await Future<void>.value();
|
||||
await tester.pump();
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:provider/src/proxy_provider.dart' show ProxyProviderBase;
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
class _ListenableCombined = Combined with ChangeNotifier;
|
||||
|
||||
void main() {
|
||||
final a = A();
|
||||
final b = B();
|
||||
final c = C();
|
||||
final d = D();
|
||||
final e = E();
|
||||
final f = F();
|
||||
|
||||
final combinedConsumerMock = ConsumerBuilderMock();
|
||||
setUp(() => when(combinedConsumerMock(any)).thenReturn(Container()));
|
||||
tearDown(() {
|
||||
clearInteractions(combinedConsumerMock);
|
||||
});
|
||||
|
||||
final mockConsumer = Consumer<_ListenableCombined>(
|
||||
builder: (context, combined, child) => combinedConsumerMock(combined),
|
||||
);
|
||||
|
||||
group('ListenableProxyProvider', () {
|
||||
test('throws if builder is missing', () {
|
||||
expect(
|
||||
() => ListenableProxyProvider<A, _ListenableCombined>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('rebuilds dependendents when listeners are called',
|
||||
(tester) async {
|
||||
final notifier = ValueNotifier(0);
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ListenableProxyProvider<int, ValueNotifier<int>>(
|
||||
initialBuilder: (_) => notifier,
|
||||
builder: (_, count, value) => value..value = count,
|
||||
)
|
||||
],
|
||||
child: Consumer<ValueNotifier<int>>(builder: (_, value, __) {
|
||||
return Text(
|
||||
value.value.toString(),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
notifier.value++;
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
expect(find.text('0'), findsNothing);
|
||||
});
|
||||
testWidgets('disposes of created value', (tester) async {
|
||||
final dispose = DisposerMock<ValueNotifier<int>>();
|
||||
final notifier = ValueNotifier(0);
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ListenableProxyProvider<int, ValueNotifier<int>>(
|
||||
key: key,
|
||||
initialBuilder: (_) => notifier,
|
||||
builder: (_, count, value) => value..value = count,
|
||||
dispose: dispose,
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
final context = key.currentContext;
|
||||
verifyZeroInteractions(dispose);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(dispose(context, notifier)).called(1);
|
||||
verifyNoMoreInteractions(dispose);
|
||||
});
|
||||
});
|
||||
|
||||
group('ListenableProxyProvider variants', () {
|
||||
Finder findProxyProvider() => find
|
||||
.byWidgetPredicate((widget) => widget is ProxyProviderBase<Combined>);
|
||||
testWidgets('ListenableProxyProvider2', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider2<A, B, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, previous) =>
|
||||
_ListenableCombined(context, previous, a, b),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ListenableProxyProvider3', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider3<A, B, C, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ListenableProxyProvider4', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider4<A, B, C, D, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c, d),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ListenableProxyProvider5', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider5<A, B, C, D, E, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d, e),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(context, _ListenableCombined(null, null, null), a,
|
||||
b, c, d, e, null),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ListenableProxyProvider6', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider6<A, B, C, D, E, F, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, f, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d, e, f),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c, d, e, f),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
import 'package:flutter/widgets.dart' hide TypeMatcher;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:matcher/matcher.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
Type _typeOf<T>() => T;
|
||||
|
||||
Matcher throwsProviderNotFound({Type widgetType, Type valueType}) {
|
||||
return throwsA(const TypeMatcher<ProviderNotFoundError>()
|
||||
.having((err) => err.valueType, 'valueType', valueType)
|
||||
.having((err) => err.widgetType, 'widgetType', widgetType));
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('MultiProvider', () {
|
||||
test('cloneWithChild works', () {
|
||||
final provider = MultiProvider(
|
||||
providers: [],
|
||||
child: Container(),
|
||||
key: const ValueKey(42),
|
||||
);
|
||||
|
||||
final newChild = Container();
|
||||
final clone = provider.cloneWithChild(newChild);
|
||||
expect(clone.child, newChild);
|
||||
expect(clone.providers, provider.providers);
|
||||
expect(clone.key, provider.key);
|
||||
});
|
||||
test('throw if providers is null', () {
|
||||
expect(
|
||||
() => MultiProvider(providers: null, child: Container()),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('MultiProvider with empty providers returns child',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(const MultiProvider(
|
||||
providers: [],
|
||||
child: Text(
|
||||
'Foo',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
));
|
||||
|
||||
expect(find.text('Foo'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('MultiProvider children can only access parent providers',
|
||||
(tester) async {
|
||||
final k1 = GlobalKey();
|
||||
final k2 = GlobalKey();
|
||||
final k3 = GlobalKey();
|
||||
final p1 = Provider.value(key: k1, value: 42);
|
||||
final p2 = Provider.value(key: k2, value: 'foo');
|
||||
final p3 = Provider.value(key: k3, value: 44.0);
|
||||
|
||||
final keyChild = GlobalKey();
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [p1, p2, p3],
|
||||
child: Text('Foo', key: keyChild, textDirection: TextDirection.ltr),
|
||||
));
|
||||
|
||||
expect(find.text('Foo'), findsOneWidget);
|
||||
|
||||
// p1 cannot access to p1/p2/p3
|
||||
expect(
|
||||
() => Provider.of<int>(k1.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: int, widgetType: _typeOf<Provider<int>>()),
|
||||
);
|
||||
expect(
|
||||
() => Provider.of<String>(k1.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: String, widgetType: _typeOf<Provider<int>>()),
|
||||
);
|
||||
expect(
|
||||
() => Provider.of<double>(k1.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: double, widgetType: _typeOf<Provider<int>>()),
|
||||
);
|
||||
|
||||
// p2 can access only p1
|
||||
expect(Provider.of<int>(k2.currentContext), 42);
|
||||
expect(
|
||||
() => Provider.of<String>(k2.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: String, widgetType: _typeOf<Provider<String>>()),
|
||||
);
|
||||
expect(
|
||||
() => Provider.of<double>(k2.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: double, widgetType: _typeOf<Provider<String>>()),
|
||||
);
|
||||
|
||||
// p3 can access both p1 and p2
|
||||
expect(Provider.of<int>(k3.currentContext), 42);
|
||||
expect(Provider.of<String>(k3.currentContext), 'foo');
|
||||
expect(
|
||||
() => Provider.of<double>(k3.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: double, widgetType: _typeOf<Provider<double>>()),
|
||||
);
|
||||
|
||||
// the child can access them all
|
||||
expect(Provider.of<int>(keyChild.currentContext), 42);
|
||||
expect(Provider.of<String>(keyChild.currentContext), 'foo');
|
||||
expect(Provider.of<double>(keyChild.currentContext), 44);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart' hide TypeMatcher;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:test_api/test_api.dart' show TypeMatcher;
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
group('Provider', () {
|
||||
testWidgets('throws if the provided value is a Listenable/Stream',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
Provider.value(
|
||||
value: MyListenable(),
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider.value(
|
||||
value: MyStream(),
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
});
|
||||
testWidgets('debugCheckInvalidValueType can be disabled', (tester) async {
|
||||
final previous = Provider.debugCheckInvalidValueType;
|
||||
Provider.debugCheckInvalidValueType = null;
|
||||
addTearDown(() => Provider.debugCheckInvalidValueType = previous);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider.value(
|
||||
value: MyListenable(),
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider.value(
|
||||
value: MyStream(),
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
});
|
||||
test('cloneWithChild works', () {
|
||||
final provider = Provider.value(
|
||||
value: 42,
|
||||
child: Container(),
|
||||
key: const ValueKey(42),
|
||||
updateShouldNotify: (int _, int __) => true,
|
||||
);
|
||||
|
||||
final newChild = Container();
|
||||
final clone = provider.cloneWithChild(newChild);
|
||||
expect(clone.child, equals(newChild));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(provider.updateShouldNotify, equals(clone.updateShouldNotify));
|
||||
});
|
||||
testWidgets('simple usage', (tester) async {
|
||||
var buildCount = 0;
|
||||
int value;
|
||||
double second;
|
||||
|
||||
// We voluntarily reuse the builder instance so that later call to pumpWidget
|
||||
// don't call builder again unless subscribed to an inheritedWidget
|
||||
final builder = Builder(
|
||||
builder: (context) {
|
||||
buildCount++;
|
||||
value = Provider.of(context);
|
||||
second = Provider.of(context, listen: false);
|
||||
return Container();
|
||||
},
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider<double>.value(
|
||||
value: 24.0,
|
||||
child: Provider<int>.value(
|
||||
value: 42,
|
||||
child: builder,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(value, equals(42));
|
||||
expect(second, equals(24.0));
|
||||
expect(buildCount, equals(1));
|
||||
|
||||
// nothing changed
|
||||
await tester.pumpWidget(
|
||||
Provider<double>.value(
|
||||
value: 24.0,
|
||||
child: Provider<int>.value(
|
||||
value: 42,
|
||||
child: builder,
|
||||
),
|
||||
),
|
||||
);
|
||||
// didn't rebuild
|
||||
expect(buildCount, equals(1));
|
||||
|
||||
// changed a value we are subscribed to
|
||||
await tester.pumpWidget(
|
||||
Provider<double>.value(
|
||||
value: 24.0,
|
||||
child: Provider<int>.value(
|
||||
value: 43,
|
||||
child: builder,
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(value, equals(43));
|
||||
expect(second, equals(24.0));
|
||||
// got rebuilt
|
||||
expect(buildCount, equals(2));
|
||||
|
||||
// changed a value we are _not_ subscribed to
|
||||
await tester.pumpWidget(
|
||||
Provider<double>.value(
|
||||
value: 20.0,
|
||||
child: Provider<int>.value(
|
||||
value: 43,
|
||||
child: builder,
|
||||
),
|
||||
),
|
||||
);
|
||||
// didn't get rebuilt
|
||||
expect(buildCount, equals(2));
|
||||
});
|
||||
|
||||
testWidgets('throws an error if no provider found', (tester) async {
|
||||
await tester.pumpWidget(Builder(builder: (context) {
|
||||
Provider.of<String>(context);
|
||||
return Container();
|
||||
}));
|
||||
|
||||
expect(
|
||||
tester.takeException(),
|
||||
const TypeMatcher<ProviderNotFoundError>()
|
||||
.having((err) => err.valueType, 'valueType', String)
|
||||
.having((err) => err.widgetType, 'widgetType', Builder)
|
||||
.having((err) => err.toString(), 'toString()', '''
|
||||
Error: Could not find the correct Provider<String> above this Builder Widget
|
||||
|
||||
To fix, please:
|
||||
|
||||
* Ensure the Provider<String> is an ancestor to this Builder Widget
|
||||
* Provide types to Provider<String>
|
||||
* Provide types to Consumer<String>
|
||||
* Provide types to Provider.of<String>()
|
||||
* Always use package imports. Ex: `import 'package:my_app/my_code.dart';
|
||||
* Ensure the correct `context` is being used.
|
||||
|
||||
If none of these solutions work, please file a bug at:
|
||||
https://github.com/rrousselGit/provider/issues
|
||||
'''),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('update should notify', (tester) async {
|
||||
int old;
|
||||
int curr;
|
||||
var callCount = 0;
|
||||
final updateShouldNotify = (int o, int c) {
|
||||
callCount++;
|
||||
old = o;
|
||||
curr = c;
|
||||
return o != c;
|
||||
};
|
||||
|
||||
var buildCount = 0;
|
||||
int buildValue;
|
||||
final builder = Builder(builder: (BuildContext context) {
|
||||
buildValue = Provider.of(context);
|
||||
buildCount++;
|
||||
return Container();
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider<int>.value(
|
||||
value: 24,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: builder,
|
||||
),
|
||||
);
|
||||
expect(callCount, equals(0));
|
||||
expect(buildCount, equals(1));
|
||||
expect(buildValue, equals(24));
|
||||
|
||||
// value changed
|
||||
await tester.pumpWidget(
|
||||
Provider<int>.value(
|
||||
value: 25,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: builder,
|
||||
),
|
||||
);
|
||||
expect(callCount, equals(1));
|
||||
expect(old, equals(24));
|
||||
expect(curr, equals(25));
|
||||
expect(buildCount, equals(2));
|
||||
expect(buildValue, equals(25));
|
||||
|
||||
// value didnt' change
|
||||
await tester.pumpWidget(
|
||||
Provider<int>.value(
|
||||
value: 25,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: builder,
|
||||
),
|
||||
);
|
||||
expect(callCount, equals(2));
|
||||
expect(old, equals(25));
|
||||
expect(curr, equals(25));
|
||||
expect(buildCount, equals(2));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,552 +0,0 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:provider/src/proxy_provider.dart'
|
||||
show NumericProxyProvider, Void;
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
Finder findProvider<T>() => find.byWidgetPredicate(
|
||||
// comparing `runtimeType` instead of using `is` because `is` accepts subclasses but InheritedWidgets don't.
|
||||
(widget) => widget.runtimeType == typeOf<InheritedProvider<T>>());
|
||||
|
||||
void main() {
|
||||
final a = A();
|
||||
final b = B();
|
||||
final c = C();
|
||||
final d = D();
|
||||
final e = E();
|
||||
final f = F();
|
||||
|
||||
final combinedConsumerMock = ConsumerBuilderMock();
|
||||
setUp(() => when(combinedConsumerMock(any)).thenReturn(Container()));
|
||||
tearDown(() {
|
||||
clearInteractions(combinedConsumerMock);
|
||||
});
|
||||
|
||||
final mockConsumer = Consumer<Combined>(
|
||||
builder: (context, combined, child) => combinedConsumerMock(combined),
|
||||
);
|
||||
|
||||
group('ProxyProvider', () {
|
||||
final combiner = CombinerMock();
|
||||
setUp(() {
|
||||
when(combiner(any, any, any)).thenAnswer((Invocation invocation) {
|
||||
return Combined(
|
||||
invocation.positionalArguments.first as BuildContext,
|
||||
invocation.positionalArguments[2] as Combined,
|
||||
invocation.positionalArguments[1] as A,
|
||||
);
|
||||
});
|
||||
});
|
||||
tearDown(() => clearInteractions(combiner));
|
||||
|
||||
Finder findProxyProvider<T>() => find.byWidgetPredicate(
|
||||
(widget) => widget is NumericProxyProvider<T, Void, Void, Void, Void,
|
||||
Void, Combined>,
|
||||
);
|
||||
|
||||
testWidgets('throws if the provided value is a Listenable/Stream',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, MyListenable>(
|
||||
builder: (_, __, ___) => MyListenable(),
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, MyStream>(
|
||||
builder: (_, __, ___) => MyStream(),
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
});
|
||||
testWidgets('debugCheckInvalidValueType can be disabled', (tester) async {
|
||||
final previous = Provider.debugCheckInvalidValueType;
|
||||
Provider.debugCheckInvalidValueType = null;
|
||||
addTearDown(() => Provider.debugCheckInvalidValueType = previous);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, MyListenable>(
|
||||
builder: (_, __, ___) => MyListenable(),
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, MyStream>(
|
||||
builder: (_, __, ___) => MyStream(),
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('initialBuilder creates initial value', (tester) async {
|
||||
final initialBuilder = ValueBuilderMock<Combined>();
|
||||
final key = GlobalKey();
|
||||
|
||||
when(initialBuilder(any)).thenReturn(Combined(null, null, null));
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: combiner,
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final details = verify(initialBuilder(captureAny))..called(1);
|
||||
expect(details.captured.first, equals(key.currentContext));
|
||||
|
||||
verify(combiner(key.currentContext, a, Combined(null, null, null)));
|
||||
});
|
||||
testWidgets('consume another providers', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: combiner,
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
verify(combinedConsumerMock(Combined(context, null, a))).called(1);
|
||||
verifyNoMoreInteractions(combinedConsumerMock);
|
||||
|
||||
verify(combiner(context, a, null)).called(1);
|
||||
verifyNoMoreInteractions(combiner);
|
||||
});
|
||||
|
||||
test('throws if builder is null', () {
|
||||
// ignore: prefer_const_constructors
|
||||
expect(() => ProxyProvider<A, Combined>(builder: null),
|
||||
throwsAssertionError);
|
||||
});
|
||||
|
||||
testWidgets('rebuild descendants if value change', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: combiner,
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final a2 = A();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a2),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: combiner,
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
verifyInOrder([
|
||||
combiner(context, a, null),
|
||||
combinedConsumerMock(Combined(context, null, a)),
|
||||
combiner(context, a2, Combined(context, null, a)),
|
||||
combinedConsumerMock(Combined(context, Combined(context, null, a), a2)),
|
||||
]);
|
||||
|
||||
verifyNoMoreInteractions(combiner);
|
||||
verifyNoMoreInteractions(combinedConsumerMock);
|
||||
});
|
||||
testWidgets('call dispose when unmounted with the latest result',
|
||||
(tester) async {
|
||||
final dispose = DisposerMock<Combined>();
|
||||
final dispose2 = DisposerMock<Combined>();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(builder: combiner, dispose: dispose)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final a2 = A();
|
||||
|
||||
// ProxyProvider creates a new Combined instance
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a2),
|
||||
ProxyProvider<A, Combined>(builder: combiner, dispose: dispose2)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyZeroInteractions(dispose);
|
||||
verify(
|
||||
dispose2(context, Combined(context, Combined(context, null, a), a2)));
|
||||
});
|
||||
|
||||
testWidgets("don't rebuild descendants if value doesn't change",
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: (c, a, p) => combiner(c, a, null),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(
|
||||
value: a,
|
||||
updateShouldNotify: (A _, A __) => true,
|
||||
),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: (c, a, p) {
|
||||
combiner(c, a, p);
|
||||
return p;
|
||||
},
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
verifyInOrder([
|
||||
combiner(context, a, null),
|
||||
combinedConsumerMock(Combined(context, null, a)),
|
||||
combiner(context, a, Combined(context, null, a)),
|
||||
]);
|
||||
|
||||
verifyNoMoreInteractions(combiner);
|
||||
verifyNoMoreInteractions(combinedConsumerMock);
|
||||
});
|
||||
|
||||
testWidgets('pass down updateShouldNotify', (tester) async {
|
||||
var buildCount = 0;
|
||||
final child = Builder(builder: (context) {
|
||||
buildCount++;
|
||||
|
||||
return Text(
|
||||
'$buildCount ${Provider.of<String>(context)}',
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
});
|
||||
|
||||
final shouldNotify = UpdateShouldNotifyMock<String>();
|
||||
when(shouldNotify('Hello', 'Hello')).thenReturn(false);
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider<String>.value(
|
||||
value: 'Hello', updateShouldNotify: (_, __) => true),
|
||||
ProxyProvider<String, String>(
|
||||
builder: (_, value, __) => value,
|
||||
updateShouldNotify: shouldNotify,
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider<String>.value(
|
||||
value: 'Hello', updateShouldNotify: (_, __) => true),
|
||||
ProxyProvider<String, String>(
|
||||
builder: (_, value, __) => value,
|
||||
updateShouldNotify: shouldNotify,
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(shouldNotify('Hello', 'Hello')).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
|
||||
expect(find.text('2 Hello'), findsNothing);
|
||||
expect(find.text('1 Hello'), findsOneWidget);
|
||||
});
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(builder: (c, a, p) => Combined(c, p, a)),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
expect(
|
||||
Provider.of<Combined>(key.currentContext),
|
||||
Combined(context, null, a),
|
||||
);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = ProxyProvider<A, B>(
|
||||
key: const Key('42'),
|
||||
initialBuilder: (_) => null,
|
||||
builder: (_, __, ___) => null,
|
||||
updateShouldNotify: (_, __) => null,
|
||||
dispose: (_, __) {},
|
||||
child: Container(),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialBuilder, equals(provider.initialBuilder));
|
||||
expect(clone.builder, equals(provider.builder));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.dispose, equals(provider.dispose));
|
||||
// expect(clone.providerBuilder, equals(provider.providerBuilder));
|
||||
});
|
||||
|
||||
// useful for libraries such as Mobx where events are synchronously dispatched
|
||||
testWidgets(
|
||||
'builder callback can trigger descendants setState synchronously',
|
||||
(tester) async {
|
||||
var statefulBuildCount = 0;
|
||||
void Function(VoidCallback) setState;
|
||||
|
||||
final statefulBuilder = StatefulBuilder(builder: (_, s) {
|
||||
setState = s;
|
||||
statefulBuildCount++;
|
||||
return Container();
|
||||
});
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(builder: (c, a, p) => Combined(c, p, a)),
|
||||
],
|
||||
child: statefulBuilder,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: A()),
|
||||
ProxyProvider<A, Combined>(builder: (c, a, p) {
|
||||
setState(() {});
|
||||
return Combined(c, p, a);
|
||||
}),
|
||||
],
|
||||
child: statefulBuilder,
|
||||
));
|
||||
|
||||
expect(
|
||||
statefulBuildCount,
|
||||
2,
|
||||
reason: 'builder must not be called asynchronously',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('ProxyProvider variants', () {
|
||||
Finder findProxyProvider<A, B, C, D, E, F>() => find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is NumericProxyProvider<A, B, C, D, E, F, Combined>,
|
||||
);
|
||||
testWidgets('ProxyProvider2', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider2<A, B, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, previous) =>
|
||||
Combined(context, previous, a, b),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context =
|
||||
tester.element(findProxyProvider<A, B, Void, Void, Void, Void>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ProxyProvider3', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider3<A, B, C, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, c, previous) =>
|
||||
Combined(context, previous, a, b, c),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context =
|
||||
tester.element(findProxyProvider<A, B, C, Void, Void, Void>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b, c),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ProxyProvider4', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider4<A, B, C, D, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, c, d, previous) =>
|
||||
Combined(context, previous, a, b, c, d),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context =
|
||||
tester.element(findProxyProvider<A, B, C, D, Void, Void>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b, c, d),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ProxyProvider5', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider5<A, B, C, D, E, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, previous) =>
|
||||
Combined(context, previous, a, b, c, d, e),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider<A, B, C, D, E, Void>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b, c, d, e, null),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ProxyProvider6', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider6<A, B, C, D, E, F, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, f, previous) =>
|
||||
Combined(context, previous, a, b, c, d, e, f),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider<A, B, C, D, E, F>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b, c, d, e, f),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('root/package/provider/README.md and root/README.mf are identical',
|
||||
() async {
|
||||
final root = await File.fromUri(Uri.parse(
|
||||
'${Directory.current.parent.parent.parent.path}/README.md'))
|
||||
.readAsString();
|
||||
final local = await File.fromUri(
|
||||
Uri.parse('${Directory.current.parent.path}/README.md'))
|
||||
.readAsString();
|
||||
|
||||
expect(root, equals(local));
|
||||
});
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/src/provider.dart';
|
||||
|
||||
class ValueBuilder extends Mock {
|
||||
int call(BuildContext context);
|
||||
}
|
||||
|
||||
class Dispose extends Mock {
|
||||
void call(BuildContext context, int value);
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('cloneWithChild works', () {
|
||||
final provider = Provider(
|
||||
builder: (_) => 42,
|
||||
child: Container(),
|
||||
key: const ValueKey(42),
|
||||
);
|
||||
|
||||
final newChild = Container();
|
||||
final clone = provider.cloneWithChild(newChild);
|
||||
expect(clone.child, equals(newChild));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(provider.updateShouldNotify, equals(clone.updateShouldNotify));
|
||||
});
|
||||
test('asserts', () {
|
||||
expect(
|
||||
() => Provider<dynamic>(builder: null, child: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
// don't throw
|
||||
Provider<dynamic>(builder: (_) => null, child: null);
|
||||
});
|
||||
|
||||
testWidgets('calls builder only once', (tester) async {
|
||||
final builder = ValueBuilder();
|
||||
await tester.pumpWidget(Provider<int>(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(Provider<int>(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
});
|
||||
|
||||
testWidgets('dispose', (tester) async {
|
||||
final dispose = Dispose();
|
||||
const key = ValueKey(42);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider<int>(
|
||||
key: key,
|
||||
builder: (_) => 42,
|
||||
dispose: dispose,
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(find.byKey(key));
|
||||
|
||||
verifyZeroInteractions(dispose);
|
||||
await tester.pumpWidget(Container());
|
||||
verify(dispose(context, 42)).called(1);
|
||||
});
|
||||
}
|
|
@ -1,377 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
class ErrorBuilderMock<T> extends Mock {
|
||||
T call(BuildContext context, Object error);
|
||||
}
|
||||
|
||||
class MockStreamController<T> extends Mock implements StreamController<T> {}
|
||||
|
||||
class MockStream<T> extends Mock implements Stream<T> {}
|
||||
|
||||
void main() {
|
||||
group('streamProvider', () {
|
||||
testWidgets('update when value change (default) ', (tester) async {
|
||||
final controller = StreamController<int>();
|
||||
final providerKey = GlobalKey();
|
||||
final childKey = GlobalKey();
|
||||
BuildContext context;
|
||||
|
||||
await tester.pumpWidget(StreamProvider(
|
||||
key: providerKey,
|
||||
builder: (c) {
|
||||
context = c;
|
||||
return controller.stream;
|
||||
},
|
||||
child: Container(key: childKey),
|
||||
));
|
||||
|
||||
expect(context, equals(providerKey.currentContext));
|
||||
expect(Provider.of<int>(childKey.currentContext), null);
|
||||
|
||||
controller.add(0);
|
||||
// adding to stream is asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(childKey.currentContext), 0);
|
||||
});
|
||||
testWidgets('update when value change (.value)', (tester) async {
|
||||
final controller = StreamController<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
|
||||
controller.add(0);
|
||||
// adding to stream is asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 0);
|
||||
});
|
||||
|
||||
testWidgets("don't notify descendants when rebuilding by default",
|
||||
(tester) async {
|
||||
final controller = StreamController<int>();
|
||||
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenAnswer((invocation) {
|
||||
final context = invocation.positionalArguments.first as BuildContext;
|
||||
Provider.of<int>(context);
|
||||
return Container();
|
||||
});
|
||||
final child = Builder(builder: builder);
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
child: child,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
});
|
||||
|
||||
testWidgets('pass down keys', (tester) async {
|
||||
final controller = StreamController<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
key: key,
|
||||
value: controller.stream,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(key.currentWidget, isInstanceOf<StreamProvider>());
|
||||
});
|
||||
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(null, 1)).thenReturn(true);
|
||||
|
||||
final controller = StreamController<int>();
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
controller.add(1);
|
||||
// adding to stream is asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
verify(shouldNotify(null, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets("don't listen again if stream instance doesn't change",
|
||||
(tester) async {
|
||||
final stream = MockStream<int>();
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: stream,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: stream,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verify(
|
||||
stream.listen(any,
|
||||
onError: anyNamed('onError'),
|
||||
onDone: anyNamed('onDone'),
|
||||
cancelOnError: anyNamed('cancelOnError')),
|
||||
).called(1);
|
||||
verifyNoMoreInteractions(stream);
|
||||
});
|
||||
|
||||
testWidgets('throws if stream has error and catchError is missing',
|
||||
(tester) async {
|
||||
final controller = StreamController<int>();
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
controller.addError(42);
|
||||
|
||||
await Future.microtask(tester.pump);
|
||||
final exception = tester.takeException() as Object;
|
||||
expect(exception, isFlutterError);
|
||||
expect(exception.toString(), equals('''
|
||||
An exception was throw by _ControllerStream<int> listened by
|
||||
StreamProvider<int>, but no `catchError` was provided.
|
||||
|
||||
Exception:
|
||||
42
|
||||
'''));
|
||||
});
|
||||
testWidgets('calls catchError if present and stream has error',
|
||||
(tester) async {
|
||||
final controller = StreamController<int>();
|
||||
final key = GlobalKey();
|
||||
final catchError = ErrorBuilderMock<int>();
|
||||
when(catchError(any, 42)).thenReturn(0);
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
catchError: catchError,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
controller.addError(42);
|
||||
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 0);
|
||||
|
||||
final context = findElementOfWidget<StreamProvider<int>>();
|
||||
|
||||
verify(catchError(context, 42));
|
||||
});
|
||||
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
StreamProvider<int>.value(value: const Stream<int>.empty()),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = StreamProvider<int>.value(
|
||||
value: const Stream<int>.empty(),
|
||||
initialData: 42,
|
||||
child: Container(),
|
||||
catchError: (_, __) => 42,
|
||||
key: const Key('42'),
|
||||
updateShouldNotify: (_, __) => true,
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialData, equals(provider.initialData));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.catchError, equals(provider.catchError));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = StreamProvider<int>.controller(
|
||||
builder: (_) => StreamController<int>(),
|
||||
initialData: 42,
|
||||
child: Container(),
|
||||
catchError: (_, __) => 42,
|
||||
key: const Key('42'),
|
||||
updateShouldNotify: (_, __) => true,
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialData, equals(provider.initialData));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.catchError, equals(provider.catchError));
|
||||
});
|
||||
testWidgets('works with null', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(StreamProvider<int>.value(
|
||||
value: null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
});
|
||||
|
||||
group('stateful constructor', () {
|
||||
test('crashes if builder is null', () {
|
||||
expect(
|
||||
() => StreamProvider<int>.controller(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('works with null', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: (_) => null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
|
||||
testWidgets('create and dispose stream with builder', (tester) async {
|
||||
final realController = StreamController<int>();
|
||||
final controller = MockStreamController<int>();
|
||||
when(controller.stream).thenAnswer((_) => realController.stream);
|
||||
|
||||
final builder = ValueBuilderMock<StreamController<int>>();
|
||||
when(builder(any)).thenReturn(controller);
|
||||
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<StreamProvider<int>>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
clearInteractions(controller);
|
||||
|
||||
// extra build to see if builder isn't called again
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyNoMoreInteractions(builder);
|
||||
verify(controller.close());
|
||||
});
|
||||
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(null, 1)).thenReturn(true);
|
||||
|
||||
var controller = StreamController<int>();
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: (_) => controller,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
controller.add(1);
|
||||
// adding to stream is asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
verify(shouldNotify(null, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Changing from default to stateful constructor calls stateful builder',
|
||||
(tester) async {
|
||||
final key = GlobalKey();
|
||||
final controller = StreamController<int>();
|
||||
await tester.pumpWidget(StreamProvider<int>.value(
|
||||
value: controller.stream,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final realController2 = StreamController<int>();
|
||||
final controller2 = MockStreamController<int>();
|
||||
when(controller2.stream).thenAnswer((_) => realController2.stream);
|
||||
|
||||
realController2.add(42);
|
||||
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: (_) => controller2,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 42);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(controller2.close()).called(1);
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from stateful to default constructor dispose correctly stateful stream',
|
||||
(tester) async {
|
||||
final realController = StreamController<int>();
|
||||
final controller = MockStreamController<int>();
|
||||
when(controller.stream).thenAnswer((_) => realController.stream);
|
||||
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: (_) => controller,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: Stream<int>.fromIterable([42]),
|
||||
child: Container(key: key),
|
||||
));
|
||||
await tester.pump();
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 42);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(controller.close()).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'common.dart';
|
||||
|
||||
class ValueNotifierMock<T> extends Mock implements ValueNotifier<T> {}
|
||||
|
||||
void main() {
|
||||
group('valueListenableProvider', () {
|
||||
testWidgets(
|
||||
'disposing ValueListenableProvider on a builder constructor disposes of the ValueNotifier',
|
||||
(tester) async {
|
||||
final mock = ValueNotifierMock<int>();
|
||||
await tester.pumpWidget(ValueListenableProvider<int>(
|
||||
builder: (_) => mock,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final listener =
|
||||
verify(mock.addListener(captureAny)).captured.first as VoidCallback;
|
||||
|
||||
clearInteractions(mock);
|
||||
await tester.pumpWidget(Container());
|
||||
verifyInOrder([
|
||||
mock.removeListener(listener),
|
||||
mock.dispose(),
|
||||
]);
|
||||
verifyNoMoreInteractions(mock);
|
||||
});
|
||||
testWidgets('rebuilds when value change', (tester) async {
|
||||
final listenable = ValueNotifier(0);
|
||||
|
||||
final child = Builder(
|
||||
builder: (context) => Text(Provider.of<int>(context).toString(),
|
||||
textDirection: TextDirection.ltr));
|
||||
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
listenable.value++;
|
||||
await tester.pump();
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("don't rebuild dependents by default", (tester) async {
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenAnswer((invocation) {
|
||||
final context = invocation.positionalArguments.first as BuildContext;
|
||||
Provider.of<int>(context);
|
||||
return Container();
|
||||
});
|
||||
|
||||
final listenable = ValueNotifier(0);
|
||||
final child = Builder(builder: builder);
|
||||
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
});
|
||||
|
||||
testWidgets('pass keys', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
key: key,
|
||||
value: ValueNotifier(42),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(key.currentWidget, isInstanceOf<ValueListenableProvider<int>>());
|
||||
});
|
||||
|
||||
testWidgets("don't listen again if stream instance doesn't change",
|
||||
(tester) async {
|
||||
final valueNotifier = ValueNotifierMock<int>();
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: valueNotifier,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: valueNotifier,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verify(valueNotifier.addListener(any)).called(1);
|
||||
verify(valueNotifier.value);
|
||||
verifyNoMoreInteractions(valueNotifier);
|
||||
});
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(0, 1)).thenReturn(true);
|
||||
|
||||
var notifier = ValueNotifier(0);
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: notifier,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
notifier.value++;
|
||||
await tester.pump();
|
||||
|
||||
verify(shouldNotify(0, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [ValueListenableProvider.value(value: ValueNotifier(42))],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 42);
|
||||
});
|
||||
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = ValueListenableProvider.value(
|
||||
key: const Key('42'),
|
||||
value: ValueNotifier<int>(42),
|
||||
child: Container(),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = ValueListenableProvider<int>(
|
||||
builder: (_) => ValueNotifier<int>(42),
|
||||
child: Container(),
|
||||
key: const Key('42'),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue