diff --git a/docs/src/content/docs/installing.md b/docs/src/content/docs/installing.md index 5a1be71..da6cebd 100644 --- a/docs/src/content/docs/installing.md +++ b/docs/src/content/docs/installing.md @@ -25,7 +25,7 @@ environment: flutter: ">=3.27.0" dependencies: - disco: ^1.0.0 + disco: ^2.1.0 flutter: sdk: flutter ``` diff --git a/packages/disco/CHANGELOG.md b/packages/disco/CHANGELOG.md index 68d8b21..1641093 100644 --- a/packages/disco/CHANGELOG.md +++ b/packages/disco/CHANGELOG.md @@ -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. diff --git a/packages/disco/lib/src/models/providers/provider_argument.dart b/packages/disco/lib/src/models/providers/provider_argument.dart index c64d52e..3741dab 100644 --- a/packages/disco/lib/src/models/providers/provider_argument.dart +++ b/packages/disco/lib/src/models/providers/provider_argument.dart @@ -11,7 +11,6 @@ typedef CreateArgProviderValueFn = /// A [Provider] that needs to be given an initial argument before /// it can be used. /// {@endtemplate} -@immutable class ArgProvider { /// {@macro ArgProvider} ArgProvider._( @@ -32,6 +31,11 @@ class ArgProvider { /// {@macro Provider.dispose} final DisposeProviderValueFn? _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? _overrideFn; + // --- // Overrides // --- @@ -41,6 +45,26 @@ class ArgProvider { ArgProviderOverride 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 fn) { + _overrideFn = fn; + } + // --- // DI methods // --- @@ -78,8 +102,10 @@ class ArgProvider { /// 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 _generateIntermediateProvider(A arg) => Provider( - (context) => _createValue(context, arg), + (context) => (_overrideFn ?? _createValue)(context, arg), dispose: _disposeValue, lazy: _lazy, ); diff --git a/packages/disco/pubspec.yaml b/packages/disco/pubspec.yaml index 3b90aa6..8e32359 100644 --- a/packages/disco/pubspec.yaml +++ b/packages/disco/pubspec.yaml @@ -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 diff --git a/packages/disco/test/disco_test.dart b/packages/disco/test/disco_test.dart index 5b90b53..9935b9a 100644 --- a/packages/disco/test/disco_test.dart +++ b/packages/disco/test/disco_test.dart @@ -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((_) => 0); await tester.pumpWidget(