Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/content/docs/installing.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ environment:
flutter: ">=3.27.0"

dependencies:
disco: ^1.0.0
disco: ^2.1.0
flutter:
sdk: flutter
```
Expand Down
4 changes: 4 additions & 0 deletions packages/disco/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.1.0

- **FEAT**: Add `overrideWithFunction` to `ArgProvider` for dynamic test overrides. This allows passing a custom create function that receives the original argument, enabling inspection and custom logic during testing.

## 2.0.0

- **FEAT**: Allow providers in the same `ProviderScope` to depend on previously declared providers. This simplifies the development experience. This friendlier syntax does not introduce circular dependencies.
Expand Down
30 changes: 28 additions & 2 deletions packages/disco/lib/src/models/providers/provider_argument.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ typedef CreateArgProviderValueFn<T, A> =
/// A [Provider] that needs to be given an initial argument before
/// it can be used.
/// {@endtemplate}
@immutable
class ArgProvider<T extends Object, A> {
/// {@macro ArgProvider}
ArgProvider._(
Expand All @@ -32,6 +31,11 @@ class ArgProvider<T extends Object, A> {
/// {@macro Provider.dispose}
final DisposeProviderValueFn<T>? _disposeValue;

/// An optional function that overrides [_createValue] during testing.
/// When set, any argument passed to the provider is forwarded to this
/// function instead of the original [_createValue].
CreateArgProviderValueFn<T, A>? _overrideFn;

// ---
// Overrides
// ---
Expand All @@ -41,6 +45,26 @@ class ArgProvider<T extends Object, A> {
ArgProviderOverride<T, A> overrideWithValue(T value) =>
ArgProviderOverride._(this, value, debugName: debugName);

/// Overrides the create function of this provider with [fn] for testing.
///
/// Any argument passed to this provider in the widget tree will be forwarded
/// to [fn] instead of the original create function. This allows testing the
/// argument being passed to the provider while using a custom implementation.
///
/// Example:
/// ```dart
/// final numberProvider = Provider.withArgument((context, int arg) => arg * 2);
///
/// testWidgets('test', (tester) async {
/// numberProvider.overrideWithFunction((context, arg) => arg * 4);
/// // numberProvider(1) will now return 4 instead of 2
/// });
/// ```
@visibleForTesting
void overrideWithFunction(CreateArgProviderValueFn<T, A> fn) {
_overrideFn = fn;
}

// ---
// DI methods
// ---
Expand Down Expand Up @@ -78,8 +102,10 @@ class ArgProvider<T extends Object, A> {

/// Given an argument, creates a [Provider] with that argument.
/// This method is used internally by [ProviderScope].
/// If [_overrideFn] is set (via [overrideWithFunction]), it is used instead
/// of [_createValue].
Provider<T> _generateIntermediateProvider(A arg) => Provider<T>(
(context) => _createValue(context, arg),
(context) => (_overrideFn ?? _createValue)(context, arg),
dispose: _disposeValue,
lazy: _lazy,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/disco/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: disco
description: A Flutter library bringing a new concept of scoped providers for dependency injection, which are independent of any specific state management solution.
version: 2.0.0
version: 2.1.0
repository: https://github.com/our-creativity/disco
homepage: https://disco.mariuti.com
documentation: https://disco.mariuti.com
Expand Down
64 changes: 64 additions & 0 deletions packages/disco/test/disco_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,70 @@ void main() {
expect(find.text('16'), findsOneWidget);
});

testWidgets(
'overrideWithFunction replaces the create function of an ArgProvider',
(tester) async {
final numberProvider = Provider.withArgument(
(_, int arg) => arg * 2,
);

numberProvider.overrideWithFunction((_, arg) => arg * 4);

await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ProviderScope(
providers: [numberProvider(1)],
child: Builder(
builder: (context) {
final number = numberProvider.of(context);
return Text(number.toString());
},
),
),
),
),
);

// Without override: 1 * 2 = 2. With override: 1 * 4 = 4.
expect(find.text('4'), findsOneWidget);
},
);

testWidgets(
'overrideWithFunction forwards the arg to the overriding function',
(tester) async {
int? capturedArg;
final numberProvider = Provider.withArgument(
(_, int arg) => arg * 2,
);

numberProvider.overrideWithFunction((_, arg) {
capturedArg = arg;
return arg * 10;
});

await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ProviderScope(
providers: [numberProvider(7)],
child: Builder(
builder: (context) {
final number = numberProvider.of(context);
return Text(number.toString());
},
),
),
),
),
);

expect(find.text('70'), findsOneWidget);
expect(capturedArg, 7);
},
);

testWidgets('Only one ProviderScopeOverride can be present', (tester) async {
final numberProvider = Provider<int>((_) => 0);
await tester.pumpWidget(
Expand Down