mirror of
https://github.com/rrousselGit/riverpod.git
synced 2025-08-26 02:10:43 +08:00
Add devtool bindings (#356)
This commit is contained in:
@ -81,7 +81,7 @@ final paginatedQuestionsProvider = FutureProvider.autoDispose
|
|||||||
final page = parsed.copyWith(
|
final page = parsed.copyWith(
|
||||||
items: parsed.items.map((e) {
|
items: parsed.items.map((e) {
|
||||||
final document = parse(e.body);
|
final document = parse(e.body);
|
||||||
return e.copyWith(body: document.body.text.replaceAll('\n', ' '));
|
return e.copyWith(body: document.body!.text.replaceAll('\n', ' '));
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ dependencies:
|
|||||||
freezed_annotation: ^0.13.0-nullsafety.0
|
freezed_annotation: ^0.13.0-nullsafety.0
|
||||||
hooks_riverpod:
|
hooks_riverpod:
|
||||||
path: ../../packages/hooks_riverpod
|
path: ../../packages/hooks_riverpod
|
||||||
html: ^0.14.0
|
html: ^0.15.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^1.11.0
|
build_runner: ^1.11.0
|
||||||
|
@ -7,9 +7,9 @@ const _uuid = Uuid();
|
|||||||
class Todo {
|
class Todo {
|
||||||
Todo({
|
Todo({
|
||||||
required this.description,
|
required this.description,
|
||||||
|
required this.id,
|
||||||
this.completed = false,
|
this.completed = false,
|
||||||
String? id,
|
});
|
||||||
}) : id = id ?? _uuid.v4();
|
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final String description;
|
final String description;
|
||||||
@ -28,7 +28,10 @@ class TodoList extends StateNotifier<List<Todo>> {
|
|||||||
void add(String description) {
|
void add(String description) {
|
||||||
state = [
|
state = [
|
||||||
...state,
|
...state,
|
||||||
Todo(description: description),
|
Todo(
|
||||||
|
id: _uuid.v4(),
|
||||||
|
description: description,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ class _ProviderScopeElement extends StatefulElement {
|
|||||||
|
|
||||||
// filling the state properties here instead of inside State
|
// filling the state properties here instead of inside State
|
||||||
// so that it is more readable in the devtool (one less indentation)
|
// so that it is more readable in the devtool (one less indentation)
|
||||||
for (final entry in container.debugProviderValues!.entries) {
|
for (final entry in container.debugProviderValues.entries) {
|
||||||
final name = entry.key.name ?? describeIdentity(entry.key);
|
final name = entry.key.name ?? describeIdentity(entry.key);
|
||||||
properties.add(DiagnosticsProperty(name, entry.value));
|
properties.add(DiagnosticsProperty(name, entry.value));
|
||||||
}
|
}
|
||||||
@ -255,7 +255,7 @@ class UncontrolledProviderScope extends InheritedWidget {
|
|||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
for (final entry in container.debugProviderValues!.entries) {
|
for (final entry in container.debugProviderValues.entries) {
|
||||||
final name = entry.key.name ?? describeIdentity(entry.key);
|
final name = entry.key.name ?? describeIdentity(entry.key);
|
||||||
properties.add(DiagnosticsProperty(name, entry.value));
|
properties.add(DiagnosticsProperty(name, entry.value));
|
||||||
}
|
}
|
||||||
|
@ -86,10 +86,10 @@ void main() {
|
|||||||
|
|
||||||
container.read(provider0.state);
|
container.read(provider0.state);
|
||||||
container.read(provider1.state);
|
container.read(provider1.state);
|
||||||
final familyState0 = container.debugProviderElements!.firstWhere((p) {
|
final familyState0 = container.debugProviderElements.firstWhere((p) {
|
||||||
return p.provider == provider0.state;
|
return p.provider == provider0.state;
|
||||||
});
|
});
|
||||||
final familyState1 = container.debugProviderElements!.firstWhere((p) {
|
final familyState1 = container.debugProviderElements.firstWhere((p) {
|
||||||
return p.provider == provider1.state;
|
return p.provider == provider1.state;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -159,10 +159,10 @@ void main() {
|
|||||||
|
|
||||||
container.read(provider0.state);
|
container.read(provider0.state);
|
||||||
container.read(provider1.state);
|
container.read(provider1.state);
|
||||||
final familyState0 = container.debugProviderElements!.firstWhere((p) {
|
final familyState0 = container.debugProviderElements.firstWhere((p) {
|
||||||
return p.provider == provider0.state;
|
return p.provider == provider0.state;
|
||||||
});
|
});
|
||||||
final familyState1 = container.debugProviderElements!.firstWhere((p) {
|
final familyState1 = container.debugProviderElements.firstWhere((p) {
|
||||||
return p.provider == provider1.state;
|
return p.provider == provider1.state;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ void main() {
|
|||||||
.firstState<ProviderScopeState>(find.byKey(firstOwnerKey))
|
.firstState<ProviderScopeState>(find.byKey(firstOwnerKey))
|
||||||
.container;
|
.container;
|
||||||
|
|
||||||
final state1 = owner1.debugProviderElements!
|
final state1 = owner1.debugProviderElements
|
||||||
.firstWhere((s) => s.provider == provider.state);
|
.firstWhere((s) => s.provider == provider.state);
|
||||||
|
|
||||||
expect(state1.hasListeners, true);
|
expect(state1.hasListeners, true);
|
||||||
@ -384,7 +384,7 @@ void main() {
|
|||||||
.firstState<ProviderScopeState>(find.byKey(secondOwnerKey))
|
.firstState<ProviderScopeState>(find.byKey(secondOwnerKey))
|
||||||
.container;
|
.container;
|
||||||
|
|
||||||
final state2 = container2.debugProviderElements!
|
final state2 = container2.debugProviderElements
|
||||||
.firstWhere((s) => s.provider is StateNotifierStateProvider);
|
.firstWhere((s) => s.provider is StateNotifierStateProvider);
|
||||||
|
|
||||||
expect(find.text('0'), findsNothing);
|
expect(find.text('0'), findsNothing);
|
||||||
@ -418,7 +418,7 @@ void main() {
|
|||||||
.firstState<ProviderScopeState>(find.byType(ProviderScope))
|
.firstState<ProviderScopeState>(find.byType(ProviderScope))
|
||||||
.container;
|
.container;
|
||||||
|
|
||||||
final state = container.debugProviderElements!
|
final state = container.debugProviderElements
|
||||||
.firstWhere((s) => s.provider == provider.state);
|
.firstWhere((s) => s.provider == provider.state);
|
||||||
|
|
||||||
expect(state.hasListeners, true);
|
expect(state.hasListeners, true);
|
||||||
|
99
packages/riverpod/lib/src/devtool.dart
Normal file
99
packages/riverpod/lib/src/devtool.dart
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
part of 'provider.dart';
|
||||||
|
|
||||||
|
void Function(
|
||||||
|
String eventKind,
|
||||||
|
Map<Object?, Object?> event,
|
||||||
|
)? _debugPostEventOverride;
|
||||||
|
|
||||||
|
void debugPostEvent(
|
||||||
|
String eventKind, [
|
||||||
|
Map<Object?, Object?> event = const {},
|
||||||
|
]) {
|
||||||
|
if (_debugPostEventOverride != null) {
|
||||||
|
_debugPostEventOverride!(eventKind, event);
|
||||||
|
} else {
|
||||||
|
developer.postEvent(eventKind, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PostEventSpy spyPostEvent() {
|
||||||
|
assert(_debugPostEventOverride == null, 'postEvent is already spied');
|
||||||
|
|
||||||
|
final spy = PostEventSpy._();
|
||||||
|
_debugPostEventOverride = spy._postEvent;
|
||||||
|
return spy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
class PostEventCall {
|
||||||
|
PostEventCall._(this.eventKind, this.event);
|
||||||
|
final String eventKind;
|
||||||
|
final Map<Object?, Object?> event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
class PostEventSpy {
|
||||||
|
PostEventSpy._();
|
||||||
|
final logs = <PostEventCall>[];
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
assert(
|
||||||
|
_debugPostEventOverride == _postEvent,
|
||||||
|
'disposed a spy different from the current spy',
|
||||||
|
);
|
||||||
|
_debugPostEventOverride = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _postEvent(
|
||||||
|
String eventKind,
|
||||||
|
Map<Object?, Object?> event,
|
||||||
|
) {
|
||||||
|
logs.add(PostEventCall._(eventKind, event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
class RiverpodBinding {
|
||||||
|
RiverpodBinding._();
|
||||||
|
|
||||||
|
static final _instance = RiverpodBinding._();
|
||||||
|
|
||||||
|
static RiverpodBinding get debugInstance {
|
||||||
|
RiverpodBinding? binding;
|
||||||
|
assert(() {
|
||||||
|
binding = _instance;
|
||||||
|
return true;
|
||||||
|
}(), '');
|
||||||
|
|
||||||
|
return binding!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ProviderContainer> _containers = {};
|
||||||
|
Map<String, ProviderContainer> get containers => _containers;
|
||||||
|
set containers(Map<String, ProviderContainer> value) {
|
||||||
|
debugPostEvent('riverpod:container_list_changed');
|
||||||
|
_containers = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void providerListChangedFor({required String containerId}) {
|
||||||
|
debugPostEvent(
|
||||||
|
'riverpod:provider_list_changed',
|
||||||
|
{'container_id': containerId},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void providerChanged({
|
||||||
|
required String containerId,
|
||||||
|
required String providerId,
|
||||||
|
}) {
|
||||||
|
debugPostEvent(
|
||||||
|
'riverpod:provider_changed',
|
||||||
|
{
|
||||||
|
'container_id': containerId,
|
||||||
|
'provider_id': providerId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -103,7 +103,12 @@ abstract class AlwaysAliveProviderBase<Created, Listened>
|
|||||||
abstract class ProviderBase<Created, Listened>
|
abstract class ProviderBase<Created, Listened>
|
||||||
implements ProviderListenable<Listened> {
|
implements ProviderListenable<Listened> {
|
||||||
/// A base class for _all_ providers.
|
/// A base class for _all_ providers.
|
||||||
ProviderBase(this._create, this.name);
|
ProviderBase(this._create, this.name) {
|
||||||
|
assert(() {
|
||||||
|
debugId = '${_debugNextId++}';
|
||||||
|
return true;
|
||||||
|
}(), '');
|
||||||
|
}
|
||||||
|
|
||||||
final Created Function(ProviderReference ref) _create;
|
final Created Function(ProviderReference ref) _create;
|
||||||
|
|
||||||
@ -131,6 +136,11 @@ abstract class ProviderBase<Created, Listened>
|
|||||||
/// An internal method that defines how a provider behaves.
|
/// An internal method that defines how a provider behaves.
|
||||||
ProviderElement<Created, Listened> createElement();
|
ProviderElement<Created, Listened> createElement();
|
||||||
|
|
||||||
|
/// A unique identifier for this provider, used by devtools to differentiate providers
|
||||||
|
///
|
||||||
|
/// Available only during development.
|
||||||
|
late final String debugId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final content = {
|
final content = {
|
||||||
@ -705,6 +715,13 @@ class ProviderElement<Created, Listened> implements ProviderReference {
|
|||||||
if (!_didMount) {
|
if (!_didMount) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
assert(() {
|
||||||
|
RiverpodBinding.debugInstance.providerChanged(
|
||||||
|
containerId: container.debugId,
|
||||||
|
providerId: provider.debugId,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}(), '');
|
||||||
_notificationCount++;
|
_notificationCount++;
|
||||||
notifyMayHaveChanged();
|
notifyMayHaveChanged();
|
||||||
}
|
}
|
||||||
@ -792,6 +809,9 @@ but $provider does not depend on ${_debugCurrentlyBuildingElement!.provider}.
|
|||||||
_mounted = true;
|
_mounted = true;
|
||||||
state._element = this;
|
state._element = this;
|
||||||
assert(() {
|
assert(() {
|
||||||
|
RiverpodBinding.debugInstance
|
||||||
|
.providerListChangedFor(containerId: container._debugId);
|
||||||
|
|
||||||
_debugIsFlushing = true;
|
_debugIsFlushing = true;
|
||||||
return true;
|
return true;
|
||||||
}(), '');
|
}(), '');
|
||||||
@ -824,6 +844,12 @@ but $provider does not depend on ${_debugCurrentlyBuildingElement!.provider}.
|
|||||||
@protected
|
@protected
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
assert(() {
|
||||||
|
RiverpodBinding.debugInstance
|
||||||
|
.providerListChangedFor(containerId: container._debugId);
|
||||||
|
return true;
|
||||||
|
}(), '');
|
||||||
|
|
||||||
_mounted = false;
|
_mounted = false;
|
||||||
_runOnDispose();
|
_runOnDispose();
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ void _runBinaryGuarded<A, B>(void Function(A, B) cb, A value, B value2) {
|
|||||||
|
|
||||||
ProviderBase? _circularDependencyLock;
|
ProviderBase? _circularDependencyLock;
|
||||||
|
|
||||||
|
int _debugNextId = 0;
|
||||||
|
|
||||||
/// {@template riverpod.providercontainer}
|
/// {@template riverpod.providercontainer}
|
||||||
/// An object that stores the state of the providers and allows overriding the
|
/// An object that stores the state of the providers and allows overriding the
|
||||||
/// behavior of a specific provider.
|
/// behavior of a specific provider.
|
||||||
@ -43,6 +45,15 @@ class ProviderContainer {
|
|||||||
}) : _parent = parent,
|
}) : _parent = parent,
|
||||||
_localObservers = observers,
|
_localObservers = observers,
|
||||||
_root = parent?._root ?? parent {
|
_root = parent?._root ?? parent {
|
||||||
|
assert(() {
|
||||||
|
_debugId = '${_debugNextId++}';
|
||||||
|
RiverpodBinding.debugInstance.containers = {
|
||||||
|
...RiverpodBinding.debugInstance.containers,
|
||||||
|
_debugId: this,
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}(), '');
|
||||||
|
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
if (observers != null) {
|
if (observers != null) {
|
||||||
throw UnsupportedError(
|
throw UnsupportedError(
|
||||||
@ -74,6 +85,22 @@ class ProviderContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
late final String _debugId;
|
||||||
|
|
||||||
|
/// A unique ID for this object, used by the devtool to differentiate two [ProviderContainer].
|
||||||
|
///
|
||||||
|
/// Should not be used.
|
||||||
|
@visibleForTesting
|
||||||
|
String get debugId {
|
||||||
|
String? id;
|
||||||
|
assert(() {
|
||||||
|
id = _debugId;
|
||||||
|
return true;
|
||||||
|
}(), '');
|
||||||
|
|
||||||
|
return id!;
|
||||||
|
}
|
||||||
|
|
||||||
final ProviderContainer? _root;
|
final ProviderContainer? _root;
|
||||||
final ProviderContainer? _parent;
|
final ProviderContainer? _parent;
|
||||||
|
|
||||||
@ -342,6 +369,12 @@ class ProviderContainer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(() {
|
||||||
|
RiverpodBinding.debugInstance.containers =
|
||||||
|
Map.from(RiverpodBinding.debugInstance.containers)..remove(_debugId);
|
||||||
|
return true;
|
||||||
|
}(), '');
|
||||||
|
|
||||||
debugVsyncs.clear();
|
debugVsyncs.clear();
|
||||||
_parent?._children.remove(this);
|
_parent?._children.remove(this);
|
||||||
|
|
||||||
@ -395,8 +428,8 @@ class ProviderContainer {
|
|||||||
|
|
||||||
/// The states of the providers associated to this [ProviderContainer], sorted
|
/// The states of the providers associated to this [ProviderContainer], sorted
|
||||||
/// in order of dependency.
|
/// in order of dependency.
|
||||||
List<ProviderElement>? get debugProviderElements {
|
List<ProviderElement> get debugProviderElements {
|
||||||
List<ProviderElement>? result;
|
late List<ProviderElement> result;
|
||||||
assert(() {
|
assert(() {
|
||||||
result = _visitStatesInOrder().toList();
|
result = _visitStatesInOrder().toList();
|
||||||
return true;
|
return true;
|
||||||
@ -405,8 +438,8 @@ class ProviderContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The value exposed by all providers currently alive.
|
/// The value exposed by all providers currently alive.
|
||||||
Map<ProviderBase, Object?>? get debugProviderValues {
|
Map<ProviderBase, Object?> get debugProviderValues {
|
||||||
Map<ProviderBase, Object?>? res;
|
late Map<ProviderBase, Object?> res;
|
||||||
assert(() {
|
assert(() {
|
||||||
res = {
|
res = {
|
||||||
for (final entry in _stateReaders.entries)
|
for (final entry in _stateReaders.entries)
|
||||||
@ -423,6 +456,11 @@ class ProviderContainer {
|
|||||||
///
|
///
|
||||||
/// This can be used for logging or making devtools.
|
/// This can be used for logging or making devtools.
|
||||||
abstract class ProviderObserver {
|
abstract class ProviderObserver {
|
||||||
|
/// An object that listens to the changes of a [ProviderContainer].
|
||||||
|
///
|
||||||
|
/// This can be used for logging or making devtools.
|
||||||
|
const ProviderObserver();
|
||||||
|
|
||||||
/// A provider was initialized, and the value exposed is [value].
|
/// A provider was initialized, and the value exposed is [value].
|
||||||
void didAddProvider(ProviderBase provider, Object? value) {}
|
void didAddProvider(ProviderBase provider, Object? value) {}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer' as developer;
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import 'builders.dart';
|
import 'builders.dart';
|
||||||
@ -5,8 +7,9 @@ import 'framework.dart';
|
|||||||
import 'state_notifier_provider.dart';
|
import 'state_notifier_provider.dart';
|
||||||
import 'stream_provider.dart';
|
import 'stream_provider.dart';
|
||||||
|
|
||||||
part 'provider/base.dart';
|
part 'devtool.dart';
|
||||||
part 'provider/auto_dispose.dart';
|
part 'provider/auto_dispose.dart';
|
||||||
|
part 'provider/base.dart';
|
||||||
|
|
||||||
/// {@template riverpod.provider}
|
/// {@template riverpod.provider}
|
||||||
/// A provider that exposes a read-only value.
|
/// A provider that exposes a read-only value.
|
||||||
|
175
packages/riverpod/test/devtool_test.dart
Normal file
175
packages/riverpod/test/devtool_test.dart
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import 'package:riverpod/riverpod.dart';
|
||||||
|
import 'package:riverpod/src/provider.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'matchers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late PostEventSpy spy;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
spy = spyPostEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => spy.dispose());
|
||||||
|
|
||||||
|
test('calls postEvent whenever a provider is updated', () {
|
||||||
|
expect(RiverpodBinding.debugInstance.containers, isEmpty);
|
||||||
|
expect(spy.logs, isEmpty);
|
||||||
|
|
||||||
|
final container = ProviderContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
final provider = StateProvider((ref) => 42);
|
||||||
|
|
||||||
|
final state = container.read(provider);
|
||||||
|
spy.logs.clear();
|
||||||
|
|
||||||
|
expect(spy.logs, isEmpty);
|
||||||
|
|
||||||
|
state.state++;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
spy.logs,
|
||||||
|
[
|
||||||
|
isPostEventCall('riverpod:provider_changed', {
|
||||||
|
'container_id': container.debugId,
|
||||||
|
'provider_id': provider.debugId,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
spy.logs.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('RiverpodBinding contains the list of ProviderContainers', () {
|
||||||
|
expect(RiverpodBinding.debugInstance.containers, isEmpty);
|
||||||
|
expect(spy.logs, isEmpty);
|
||||||
|
|
||||||
|
final first = ProviderContainer();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
spy.logs,
|
||||||
|
[isPostEventCall('riverpod:container_list_changed', isEmpty)],
|
||||||
|
);
|
||||||
|
spy.logs.clear();
|
||||||
|
expect(
|
||||||
|
RiverpodBinding.debugInstance.containers,
|
||||||
|
{first.debugId: first},
|
||||||
|
);
|
||||||
|
|
||||||
|
final second = ProviderContainer();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
spy.logs,
|
||||||
|
[isPostEventCall('riverpod:container_list_changed', isEmpty)],
|
||||||
|
);
|
||||||
|
spy.logs.clear();
|
||||||
|
expect(
|
||||||
|
RiverpodBinding.debugInstance.containers,
|
||||||
|
{first.debugId: first, second.debugId: second},
|
||||||
|
);
|
||||||
|
|
||||||
|
first.dispose();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
spy.logs,
|
||||||
|
[isPostEventCall('riverpod:container_list_changed', isEmpty)],
|
||||||
|
);
|
||||||
|
spy.logs.clear();
|
||||||
|
expect(
|
||||||
|
RiverpodBinding.debugInstance.containers,
|
||||||
|
{second.debugId: second},
|
||||||
|
);
|
||||||
|
|
||||||
|
second.dispose();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
spy.logs,
|
||||||
|
[isPostEventCall('riverpod:container_list_changed', isEmpty)],
|
||||||
|
);
|
||||||
|
spy.logs.clear();
|
||||||
|
expect(RiverpodBinding.debugInstance.containers, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'ProviderContainer calls postEvent whenever it mounts/unmount a provider',
|
||||||
|
() async {
|
||||||
|
final container = ProviderContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
spy.logs.clear();
|
||||||
|
|
||||||
|
final provider = Provider.autoDispose((ref) => 0);
|
||||||
|
final provider2 = Provider((ref) => 0);
|
||||||
|
|
||||||
|
expect(spy.logs, isEmpty);
|
||||||
|
|
||||||
|
var sub = container.listen(provider);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
spy.logs,
|
||||||
|
[
|
||||||
|
isPostEventCall(
|
||||||
|
'riverpod:provider_list_changed',
|
||||||
|
<Object?, Object?>{'container_id': container.debugId},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
spy.logs.clear();
|
||||||
|
|
||||||
|
var sub2 = container.listen(provider2);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
spy.logs,
|
||||||
|
[
|
||||||
|
isPostEventCall(
|
||||||
|
'riverpod:provider_list_changed',
|
||||||
|
<Object?, Object?>{'container_id': container.debugId},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
spy.logs.clear();
|
||||||
|
|
||||||
|
sub.close();
|
||||||
|
|
||||||
|
expect(spy.logs, isEmpty);
|
||||||
|
await Future.value(null);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
spy.logs,
|
||||||
|
[
|
||||||
|
isPostEventCall(
|
||||||
|
'riverpod:provider_list_changed',
|
||||||
|
<Object?, Object?>{'container_id': container.debugId},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
spy.logs.clear();
|
||||||
|
|
||||||
|
sub2.close();
|
||||||
|
await Future.value(null);
|
||||||
|
|
||||||
|
expect(spy.logs, isEmpty);
|
||||||
|
await Future.value(null);
|
||||||
|
|
||||||
|
expect(spy.logs, isEmpty, reason: 'provider2 is not autoDispose');
|
||||||
|
|
||||||
|
// re-subscribe to the provider that was unmounted
|
||||||
|
sub = container.listen(provider);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
spy.logs,
|
||||||
|
[
|
||||||
|
isPostEventCall(
|
||||||
|
'riverpod:provider_list_changed',
|
||||||
|
<Object?, Object?>{'container_id': container.debugId},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
spy.logs.clear();
|
||||||
|
|
||||||
|
// re-subscribe to the provider that was no-longer listened but still mounted
|
||||||
|
sub2 = container.listen(provider2);
|
||||||
|
|
||||||
|
expect(spy.logs, isEmpty);
|
||||||
|
});
|
||||||
|
}
|
@ -18,6 +18,16 @@ Matcher isProvider(RootProvider provider) {
|
|||||||
void main() {
|
void main() {
|
||||||
// TODO flushing inside mayHaveChanged calls onChanged only after all mayHaveChanged were executed
|
// TODO flushing inside mayHaveChanged calls onChanged only after all mayHaveChanged were executed
|
||||||
|
|
||||||
|
test('ProviderObservers can have const constructors', () {
|
||||||
|
final root = ProviderContainer(
|
||||||
|
observers: [
|
||||||
|
const ConstObserver(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
root.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
test('disposing parent container when child container is not dispose throws',
|
test('disposing parent container when child container is not dispose throws',
|
||||||
() {
|
() {
|
||||||
final root = ProviderContainer();
|
final root = ProviderContainer();
|
||||||
@ -66,7 +76,7 @@ void main() {
|
|||||||
|
|
||||||
expect(container.read(provider), 42);
|
expect(container.read(provider), 42);
|
||||||
|
|
||||||
final state = container.debugProviderElements!.single;
|
final state = container.debugProviderElements.single;
|
||||||
|
|
||||||
expect(state.hasListeners, false);
|
expect(state.hasListeners, false);
|
||||||
|
|
||||||
@ -780,3 +790,7 @@ class MockMarkMayHaveChanged extends Mock {
|
|||||||
class MockDidUpdateProvider extends Mock {
|
class MockDidUpdateProvider extends Mock {
|
||||||
void call();
|
void call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ConstObserver extends ProviderObserver {
|
||||||
|
const ConstObserver();
|
||||||
|
}
|
||||||
|
13
packages/riverpod/test/matchers.dart
Normal file
13
packages/riverpod/test/matchers.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:riverpod/src/provider.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
Matcher isPostEventCall(Object kind, Object? event) {
|
||||||
|
var matcher =
|
||||||
|
isA<PostEventCall>().having((e) => e.eventKind, 'eventKind', kind);
|
||||||
|
|
||||||
|
if (event != null) {
|
||||||
|
matcher = matcher.having((e) => e.event, 'event', event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matcher;
|
||||||
|
}
|
@ -40,10 +40,10 @@ void main() {
|
|||||||
|
|
||||||
container.read(provider0.state);
|
container.read(provider0.state);
|
||||||
container.read(provider1.state);
|
container.read(provider1.state);
|
||||||
final familyState0 = container.debugProviderElements!.firstWhere((p) {
|
final familyState0 = container.debugProviderElements.firstWhere((p) {
|
||||||
return p.provider == provider0.state;
|
return p.provider == provider0.state;
|
||||||
});
|
});
|
||||||
final familyState1 = container.debugProviderElements!.firstWhere((p) {
|
final familyState1 = container.debugProviderElements.firstWhere((p) {
|
||||||
return p.provider == provider1.state;
|
return p.provider == provider1.state;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -108,10 +108,10 @@ void main() {
|
|||||||
|
|
||||||
container.read(provider0.state);
|
container.read(provider0.state);
|
||||||
container.read(provider1.state);
|
container.read(provider1.state);
|
||||||
final familyState0 = container.debugProviderElements!.firstWhere((p) {
|
final familyState0 = container.debugProviderElements.firstWhere((p) {
|
||||||
return p.provider == provider0.state;
|
return p.provider == provider0.state;
|
||||||
});
|
});
|
||||||
final familyState1 = container.debugProviderElements!.firstWhere((p) {
|
final familyState1 = container.debugProviderElements.firstWhere((p) {
|
||||||
return p.provider == provider1.state;
|
return p.provider == provider1.state;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -258,7 +258,7 @@ void main() {
|
|||||||
verifyNoMoreInteractions(listener);
|
verifyNoMoreInteractions(listener);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
container.debugProviderElements!.map((e) => e.provider),
|
container.debugProviderElements.map((e) => e.provider),
|
||||||
[provider, computed, provider2],
|
[provider, computed, provider2],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -295,7 +295,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<ProviderBase> compute(ProviderContainer container) {
|
List<ProviderBase> compute(ProviderContainer container) {
|
||||||
return container.debugProviderElements!.map((e) => e.provider).toList();
|
return container.debugProviderElements.map((e) => e.provider).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
class A {}
|
class A {}
|
||||||
|
Reference in New Issue
Block a user