diff --git a/README.md b/README.md index 1369cd2..75d0e54 100644 --- a/README.md +++ b/README.md @@ -101,15 +101,13 @@ Each `StockResponse` includes an `origin` field which specifies where the event ```dart stock - .stream('key', refresh: true) + .stream('user_id', refresh: true) .listen((StockResponse> stockResponse) { - if (stockResponse is StockResponseLoading) { - _displayLoadingIndicator(); - } else if (stockResponse is StockResponseData) { - _displayTweetsInUI((stockResponse is StockResponseData).data); - } else { - _displayErrorInUi((stockResponse as StockResponseError).error); - } + stockResponse.when( + onLoading: (origin) => _displayLoadingIndicator(), + onData: (origin, data) => _displayTweetsInUI(data), + onError: (origin, error, stacktrace) => _displayErrorInUi(error), + ); }); ``` diff --git a/example/main.dart b/example/main.dart index 023207c..847c18f 100644 --- a/example/main.dart +++ b/example/main.dart @@ -25,13 +25,11 @@ void main() async { stock .stream('xmartlabs', refresh: true) .listen((StockResponse> stockResponse) { - if (stockResponse is StockResponseLoading) { - _displayLoadingIndicator(); - } else if (stockResponse is StockResponseData) { - _displayTweetsInUI(stockResponse.requireData()); - } else { - _displayErrorInUi((stockResponse as StockResponseError).error); - } + stockResponse.when( + onLoading: (_) => _displayLoadingIndicator(), + onData: (_, data) => _displayTweetsInUI(data), + onError: (_, error, __) => _displayErrorInUi(error), + ); }); // Get Xmartlabs tweets from the network and save them in the DB. diff --git a/lib/src/stock_response_extensions.dart b/lib/src/stock_response_extensions.dart index e422e93..6ed5d34 100644 --- a/lib/src/stock_response_extensions.dart +++ b/lib/src/stock_response_extensions.dart @@ -3,6 +3,45 @@ import 'package:stock/src/stock_response.dart'; /// Useful [StockResponse] extensions. extension StockResponseExtensions on StockResponse { + /// Returns the available data or throws error if there is no data. + T requireData() => when( + onData: (_, data) => data, + onLoading: (_) => throw StockError('There is no data in loading'), + // ignore: only_throw_errors + onError: (_, error, __) => throw error, + ); + + /// If there is data available, returns it; otherwise returns null. + T? getDataOrNull() => this is StockResponseData + ? (this as StockResponseData).value + : null; + + /// Returns the available data or throws error if there is no data. + Object requireError() => maybeMap( + onError: (response) => response.error, + orElse: () => throw StockError( + 'Response is not an StockResponseError. Response: $this', + ), + ); + + /// If this [StockResponse] is of type [StockResponseError], throws the + /// exception. Otherwise, does nothing. + void throwIfError() { + if (this is StockResponseError) { + // ignore: only_throw_errors + throw (this as StockResponseError).error; + } + } + + /// Returns if the response is a [StockResponseLoading] + bool get isLoading => this is StockResponseLoading; + + /// Returns if the response is a [StockResponseError] + bool get isError => this is StockResponseError; + + /// Returns if the response is a [StockResponseData] + bool get isData => this is StockResponseData; + /// Invoke [onData] if the response is successful, [onLoading] if the response /// is loading, and [onError] if the response is an error. E map({ @@ -36,6 +75,22 @@ extension StockResponseExtensions on StockResponse { onError: onError ?? (_) => orElse(), ); + /// Invoke [onData] if the response is successful, + /// [onLoading] if the response is loading, and + /// [onError] if the response is an error. + /// If the callback is not provided, null is returned. + E? mapOrNull({ + E Function(StockResponseLoading value)? onLoading, + E Function(StockResponseData value)? onData, + E Function(StockResponseError value)? onError, + }) => + maybeMap( + onLoading: onLoading, + onData: onData, + onError: onError, + orElse: () => null, + ); + /// Invoke [onData] if the response is successful, [onLoading] if the response /// is loading, and [onError] if the response is an error. E when({ @@ -58,6 +113,27 @@ extension StockResponseExtensions on StockResponse { ), ); + /// Invoke [onData] if the response is successful, + /// [onLoading] if the response is loading, and + /// [onError] if the response is an error. + /// If the callback is not provided, null is returned. + E? whenOrNull({ + E Function(ResponseOrigin origin)? onLoading, + E Function(ResponseOrigin origin, T data)? onData, + E Function( + ResponseOrigin origin, + Object error, + StackTrace? stackTrace, + )? + onError, + }) => + maybeWhen( + onLoading: onLoading, + onData: onData, + onError: onError, + orElse: (origin) => null, + ); + /// Invoke [onData] or [orElse] as fallback if the response is successful, /// [onLoading] or [orElse] as fallback if the response is loading, and /// [onError] or [orElse] as fallback if the response is an error. @@ -77,44 +153,4 @@ extension StockResponseExtensions on StockResponse { onData: onData ?? (origin, _) => orElse(origin), onError: onError ?? (origin, _, __) => orElse(origin), ); - - /// Returns the available data or throws error if there is no data. - T requireData() { - if (this is StockResponseData) { - return (this as StockResponseData).value; - } else if (isError) { - // ignore: only_throw_errors - throw (this as StockResponseError).error; - } else if (isLoading) { - throw StockError('There is no data in loading'); - } else { - throw StockError( - 'Type error requireData expect either Success, ' - 'Error but was given $runtimeType', - ); - } - } - - /// If there is data available, returns it; otherwise returns null. - T? getDataOrNull() => this is StockResponseData - ? (this as StockResponseData).value - : null; - - /// If this [StockResponse] is of type [StockResponseError], throws the - /// exception. Otherwise, does nothing. - void throwIfError() { - if (this is StockResponseError) { - // ignore: only_throw_errors - throw (this as StockResponseError).error; - } - } - - /// Returns if the response is a [StockResponseLoading] - bool get isLoading => this is StockResponseLoading; - - /// Returns if the response is a [StockResponseError] - bool get isError => this is StockResponseError; - - /// Returns if the response is a [StockResponseData] - bool get isData => this is StockResponseData; } diff --git a/scripts/checks.sh b/scripts/checks.sh index b6112ee..a3bb47a 100755 --- a/scripts/checks.sh +++ b/scripts/checks.sh @@ -7,9 +7,12 @@ dart pub get echo ':: Check code format ::' dart format --set-exit-if-changed . || { echo -e "${RED}Invalid format" ; exit 1; } -echo ':: Run linter ::' +echo ':: Run dart linter ::' dart analyze --fatal-infos || { echo -e "${RED}Linter error" ; exit 1; } +echo ':: Run flutter linter ::' +flutter analyze --fatal-infos || { echo -e "${RED}Linter error" ; exit 1; } + result=$(dart run dart_code_metrics:metrics analyze lib --fatal-style --fatal-performance --fatal-warnings) echo "$result" [[ $result == '✔ no issues found!' ]] || { echo -e "${RED}Linter error" ; exit 1; } diff --git a/test/common_mocks.dart b/test/common_mocks.dart index bdb090d..1995ff6 100644 --- a/test/common_mocks.dart +++ b/test/common_mocks.dart @@ -2,7 +2,11 @@ import 'package:mockito/annotations.dart'; import 'package:stock/src/implementations/factory_fetcher.dart'; import 'package:stock/src/source_of_truth.dart'; +import 'stock_response_extension_test.dart'; + @GenerateMocks([ + CallbackVoid, + CallbackInt, FutureFetcher, StreamFetcher, SourceOfTruth, diff --git a/test/common_mocks.mocks.dart b/test/common_mocks.mocks.dart index 2bf9f82..bac4229 100644 --- a/test/common_mocks.mocks.dart +++ b/test/common_mocks.mocks.dart @@ -3,11 +3,13 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i3; +import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:stock/src/source_of_truth.dart' as _i4; -import 'package:stock/src/implementations/factory_fetcher.dart' as _i2; +import 'package:stock/src/implementations/factory_fetcher.dart' as _i3; +import 'package:stock/src/source_of_truth.dart' as _i5; + +import 'stock_response_extension_test.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -20,22 +22,48 @@ import 'package:stock/src/implementations/factory_fetcher.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +/// A class which mocks [CallbackVoid]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCallbackVoid extends _i1.Mock implements _i2.CallbackVoid { + MockCallbackVoid() { + _i1.throwOnMissingStub(this); + } + + @override + void call() => super.noSuchMethod(Invocation.method(#call, []), + returnValueForMissingStub: null); +} + +/// A class which mocks [CallbackInt]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCallbackInt extends _i1.Mock implements _i2.CallbackInt { + MockCallbackInt() { + _i1.throwOnMissingStub(this); + } + + @override + int call() => + (super.noSuchMethod(Invocation.method(#call, []), returnValue: 0) as int); +} + /// A class which mocks [FutureFetcher]. /// /// See the documentation for Mockito's code generation for more information. class MockFutureFetcher extends _i1.Mock - implements _i2.FutureFetcher { + implements _i3.FutureFetcher { MockFutureFetcher() { _i1.throwOnMissingStub(this); } @override - _i3.Stream Function(Key) get factory => + _i4.Stream Function(Key) get factory => (super.noSuchMethod(Invocation.getter(#factory), - returnValue: (Key key) => _i3.Stream.empty()) - as _i3.Stream Function(Key)); + returnValue: (Key key) => _i4.Stream.empty()) + as _i4.Stream Function(Key)); @override - set factory(_i3.Stream Function(Key)? _factory) => + set factory(_i4.Stream Function(Key)? _factory) => super.noSuchMethod(Invocation.setter(#factory, _factory), returnValueForMissingStub: null); } @@ -44,18 +72,18 @@ class MockFutureFetcher extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockStreamFetcher extends _i1.Mock - implements _i2.StreamFetcher { + implements _i3.StreamFetcher { MockStreamFetcher() { _i1.throwOnMissingStub(this); } @override - _i3.Stream Function(Key) get factory => + _i4.Stream Function(Key) get factory => (super.noSuchMethod(Invocation.getter(#factory), - returnValue: (Key key) => _i3.Stream.empty()) - as _i3.Stream Function(Key)); + returnValue: (Key key) => _i4.Stream.empty()) + as _i4.Stream Function(Key)); @override - set factory(_i3.Stream Function(Key)? _factory) => + set factory(_i4.Stream Function(Key)? _factory) => super.noSuchMethod(Invocation.setter(#factory, _factory), returnValueForMissingStub: null); } @@ -64,18 +92,18 @@ class MockStreamFetcher extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockSourceOfTruth extends _i1.Mock - implements _i4.SourceOfTruth { + implements _i5.SourceOfTruth { MockSourceOfTruth() { _i1.throwOnMissingStub(this); } @override - _i3.Stream reader(Key? key) => + _i4.Stream reader(Key? key) => (super.noSuchMethod(Invocation.method(#reader, [key]), - returnValue: _i3.Stream.empty()) as _i3.Stream); + returnValue: _i4.Stream.empty()) as _i4.Stream); @override - _i3.Future write(Key? key, T? value) => (super.noSuchMethod( + _i4.Future write(Key? key, T? value) => (super.noSuchMethod( Invocation.method(#write, [key, value]), - returnValue: _i3.Future.value(), - returnValueForMissingStub: _i3.Future.value()) as _i3.Future); + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value()) as _i4.Future); } diff --git a/test/stock_response_extension_test.dart b/test/stock_response_extension_test.dart index c79ec7f..4f285d8 100644 --- a/test/stock_response_extension_test.dart +++ b/test/stock_response_extension_test.dart @@ -5,6 +5,8 @@ import 'package:stock/src/stock_response.dart'; import 'package:stock/src/stock_response_extensions.dart'; import 'package:test/test.dart'; +import 'common_mocks.mocks.dart'; + void main() { group('Require data extensions', () { test('requireData of an error throws the exception', () async { @@ -32,6 +34,40 @@ void main() { ); }); }); + + group('Require error extensions', () { + test('requireError of an error throws the exception', () async { + final customEx = Exception('Custom ex'); + expect( + StockResponseError(ResponseOrigin.fetcher, customEx) + .requireError(), + equals(customEx), + ); + }); + test('requireError of a loading response throws a exception', () async { + expect( + const StockResponseLoading(ResponseOrigin.fetcher) + .requireError, + throwsA( + (dynamic e) => + e is StockError && + e.toString().contains('Response is not an StockResponseError'), + ), + ); + }); + test('requireError of a data response throws a exception', () async { + expect( + const StockResponseData(ResponseOrigin.fetcher, 1) + .requireError, + throwsA( + (dynamic e) => + e is StockError && + e.toString().contains('Response is not an StockResponseError'), + ), + ); + }); + }); + group('throwIfError extension', () { test('throwIfError of an error throws the exception', () async { final customEx = Exception('Custom ex'); @@ -49,6 +85,7 @@ void main() { const StockResponse.data(ResponseOrigin.fetcher, 1).throwIfError(); }); }); + group('Get data extensions', () { test('getData of an error returns null', () async { final customEx = Exception('Custom ex'); @@ -72,6 +109,7 @@ void main() { ); }); }); + group('Property extensions', () { test('Loading returns true if loading', () async { expect( @@ -124,9 +162,7 @@ void main() { throwsA( (dynamic e) => e is StockError && - e.toString() == - 'StockError: Type error requireData expect either Success, ' - 'Error but was given _FakeType', + e.toString().contains('Unknown StockResponse type:'), ), ); }); @@ -147,9 +183,9 @@ void main() { group('Map', () { test('Map for loading', () { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponseLoading(ResponseOrigin.fetcher).map( onLoading: (_) => mockLoadingCallback(), onData: (_) => mockDataCallback(), @@ -161,9 +197,9 @@ void main() { }); test('Map for data', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponse.data(ResponseOrigin.fetcher, 1).map( onLoading: (_) => mockLoadingCallback(), onData: (_) => mockDataCallback(), @@ -175,9 +211,9 @@ void main() { }); test('Map for error', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); StockResponseError(ResponseOrigin.fetcher, Error()).map( onLoading: (_) => mockLoadingCallback(), onData: (_) => mockDataCallback(), @@ -189,9 +225,9 @@ void main() { }); test('Map with unknown type', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); expect( () { _FakeType().map( @@ -214,9 +250,9 @@ void main() { group('MaybeMap', () { test('MaybeMap for loading', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponseLoading(ResponseOrigin.fetcher).maybeMap( onLoading: (_) => mockLoadingCallback(), onData: (_) => mockDataCallback(), @@ -229,9 +265,9 @@ void main() { }); test('MaybeMap for loading with else', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponseLoading(ResponseOrigin.fetcher).maybeMap( onData: (_) => mockDataCallback(), onError: (_) => mockErrorCallback(), @@ -243,9 +279,9 @@ void main() { }); test('MaybeMap for data', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponse.data(ResponseOrigin.fetcher, 1).maybeMap( onLoading: (_) => mockLoadingCallback(), onData: (_) => mockDataCallback(), @@ -258,9 +294,9 @@ void main() { }); test('MaybeMap for data with else', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponse.data(ResponseOrigin.fetcher, 1).maybeMap( onLoading: (_) => mockLoadingCallback(), onError: (_) => mockErrorCallback(), @@ -272,9 +308,9 @@ void main() { }); test('MaybeMap for error', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); StockResponseError(ResponseOrigin.fetcher, Error()).maybeMap( onLoading: (_) => mockLoadingCallback(), onData: (_) => mockDataCallback(), @@ -287,9 +323,9 @@ void main() { }); test('MaybeMap for error with else', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); StockResponseError(ResponseOrigin.fetcher, Error()).maybeMap( onLoading: (_) => mockLoadingCallback(), onData: (_) => mockDataCallback(), @@ -303,9 +339,9 @@ void main() { group('When', () { test('When for loading', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponseLoading(ResponseOrigin.fetcher).when( onLoading: (_) => mockLoadingCallback(), onData: (_, __) => mockDataCallback(), @@ -317,9 +353,9 @@ void main() { }); test('When for data', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponse.data(ResponseOrigin.fetcher, 1).when( onLoading: (_) => mockLoadingCallback(), onData: (_, __) => mockDataCallback(), @@ -331,9 +367,9 @@ void main() { }); test('When for error', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); StockResponseError(ResponseOrigin.fetcher, Error()).when( onLoading: (_) => mockLoadingCallback(), onData: (_, __) => mockDataCallback(), @@ -347,9 +383,9 @@ void main() { group('MaybeWhen', () { test('MaybeWhen for loading', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponseLoading(ResponseOrigin.fetcher).maybeWhen( onLoading: (_) => mockLoadingCallback(), onData: (_, __) => mockDataCallback(), @@ -362,9 +398,9 @@ void main() { }); test('MaybeWhen for loading with else', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponseLoading(ResponseOrigin.fetcher).maybeWhen( onData: (_, __) => mockDataCallback(), onError: (_, __, ___) => mockErrorCallback(), @@ -376,9 +412,9 @@ void main() { }); test('MaybeWhen for data', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponse.data(ResponseOrigin.fetcher, 1).maybeWhen( onLoading: (_) => mockLoadingCallback(), onData: (_, __) => mockDataCallback(), @@ -391,9 +427,9 @@ void main() { }); test('MaybeWhen for data with else', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); const StockResponse.data(ResponseOrigin.fetcher, 1).maybeWhen( onLoading: (_) => mockLoadingCallback(), onError: (_, __, ___) => mockErrorCallback(), @@ -405,9 +441,9 @@ void main() { }); test('MaybeWhen for error', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); StockResponseError(ResponseOrigin.fetcher, Error()).maybeWhen( onLoading: (_) => mockLoadingCallback(), onData: (_, __) => mockDataCallback(), @@ -420,9 +456,9 @@ void main() { }); test('MaybeWhen for error with else', () async { - final mockLoadingCallback = MockCallback(); - final mockDataCallback = MockCallback(); - final mockErrorCallback = MockCallback(); + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); StockResponseError(ResponseOrigin.fetcher, Error()).maybeWhen( onLoading: (_) => mockLoadingCallback(), onData: (_, __) => mockDataCallback(), @@ -433,14 +469,195 @@ void main() { verify(mockErrorCallback()).called(1); }); }); + + group('WhenOrNull', () { + test('WhenOrNull for loading', () async { + final mockLoadingCallback = MockCallbackInt(); + final mockDataCallback = MockCallbackInt(); + final mockErrorCallback = MockCallbackInt(); + when(mockLoadingCallback.call()).thenAnswer((_) => 1); + + const StockResponseLoading(ResponseOrigin.fetcher).whenOrNull( + onLoading: (_) => mockLoadingCallback(), + onData: (_, __) => mockDataCallback(), + onError: (_, __, ___) => mockErrorCallback(), + ); + verify(mockLoadingCallback()).called(1); + verifyNever(mockDataCallback()); + verifyNever(mockErrorCallback()); + }); + + test('WhenOrNull for loading without callback', () async { + final mockLoadingCallback = MockCallbackInt(); + final mockDataCallback = MockCallbackInt(); + final mockErrorCallback = MockCallbackInt(); + final value = const StockResponseLoading(ResponseOrigin.fetcher) + .whenOrNull( + onData: (_, __) => mockDataCallback(), + onError: (_, __, ___) => mockErrorCallback(), + ); + expect(value, equals(null)); + verifyNever(mockLoadingCallback()); + verifyNever(mockDataCallback()); + verifyNever(mockErrorCallback()); + }); + + test('WhenOrNull for data', () async { + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); + const StockResponse.data(ResponseOrigin.fetcher, 1).whenOrNull( + onLoading: (_) => mockLoadingCallback(), + onData: (_, __) => mockDataCallback(), + onError: (_, __, ___) => mockErrorCallback(), + ); + verifyNever(mockLoadingCallback()); + verify(mockDataCallback()).called(1); + verifyNever(mockErrorCallback()); + }); + + test('WhenOrNull for data without callback', () async { + final mockLoadingCallback = MockCallbackInt(); + final mockErrorCallback = MockCallbackInt(); + final value = + const StockResponse.data(ResponseOrigin.fetcher, 1).whenOrNull( + onLoading: (_) => mockLoadingCallback(), + onError: (_, __, ___) => mockErrorCallback(), + ); + expect(value, equals(null)); + verifyNever(mockLoadingCallback()); + verifyNever(mockLoadingCallback()); + verifyNever(mockErrorCallback()); + }); + + test('WhenOrNull for error', () async { + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); + StockResponseError(ResponseOrigin.fetcher, Error()).whenOrNull( + onLoading: (_) => mockLoadingCallback(), + onData: (_, __) => mockDataCallback(), + onError: (_, __, ___) => mockErrorCallback(), + ); + verifyNever(mockLoadingCallback()); + verifyNever(mockDataCallback()); + verify(mockErrorCallback()).called(1); + }); + + test('WhenOrNull for error without callback', () async { + final mockLoadingCallback = MockCallbackInt(); + final mockDataCallback = MockCallbackInt(); + final value = StockResponseError(ResponseOrigin.fetcher, Error()) + .whenOrNull( + onLoading: (_) => mockLoadingCallback(), + onData: (_, __) => mockDataCallback(), + ); + expect(value, equals(null)); + verifyNever(mockLoadingCallback()); + verifyNever(mockLoadingCallback()); + verifyNever(mockDataCallback()); + }); + }); + + group('MapOrNull', () { + test('MapOrNull for loading', () async { + final mockLoadingCallback = MockCallbackInt(); + final mockDataCallback = MockCallbackInt(); + final mockErrorCallback = MockCallbackInt(); + when(mockLoadingCallback.call()).thenAnswer((_) => 1); + + const StockResponseLoading(ResponseOrigin.fetcher).mapOrNull( + onLoading: (_) => mockLoadingCallback(), + onData: (_) => mockDataCallback(), + onError: (_) => mockErrorCallback(), + ); + verify(mockLoadingCallback()).called(1); + verifyNever(mockDataCallback()); + verifyNever(mockErrorCallback()); + }); + + test('MapOrNull for loading without callback', () async { + final mockLoadingCallback = MockCallbackInt(); + final mockDataCallback = MockCallbackInt(); + final mockErrorCallback = MockCallbackInt(); + final value = + const StockResponseLoading(ResponseOrigin.fetcher).mapOrNull( + onData: (_) => mockDataCallback(), + onError: (_) => mockErrorCallback(), + ); + expect(value, equals(null)); + verifyNever(mockLoadingCallback()); + verifyNever(mockDataCallback()); + verifyNever(mockErrorCallback()); + }); + + test('MapOrNull for data', () async { + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); + const StockResponse.data(ResponseOrigin.fetcher, 1).mapOrNull( + onLoading: (_) => mockLoadingCallback(), + onData: (_) => mockDataCallback(), + onError: (_) => mockErrorCallback(), + ); + verifyNever(mockLoadingCallback()); + verify(mockDataCallback()).called(1); + verifyNever(mockErrorCallback()); + }); + + test('MapOrNull for data without callback', () async { + final mockLoadingCallback = MockCallbackInt(); + final mockErrorCallback = MockCallbackInt(); + final value = + const StockResponse.data(ResponseOrigin.fetcher, 1).mapOrNull( + onLoading: (_) => mockLoadingCallback(), + onError: (_) => mockErrorCallback(), + ); + expect(value, equals(null)); + verifyNever(mockLoadingCallback()); + verifyNever(mockLoadingCallback()); + verifyNever(mockErrorCallback()); + }); + + test('MapOrNull for error', () async { + final mockLoadingCallback = MockCallbackVoid(); + final mockDataCallback = MockCallbackVoid(); + final mockErrorCallback = MockCallbackVoid(); + StockResponseError(ResponseOrigin.fetcher, Error()).mapOrNull( + onLoading: (_) => mockLoadingCallback(), + onData: (_) => mockDataCallback(), + onError: (_) => mockErrorCallback(), + ); + verifyNever(mockLoadingCallback()); + verifyNever(mockDataCallback()); + verify(mockErrorCallback()).called(1); + }); + + test('MapOrNull for error without callback', () async { + final mockLoadingCallback = MockCallbackInt(); + final mockDataCallback = MockCallbackInt(); + final value = StockResponseError(ResponseOrigin.fetcher, Error()) + .mapOrNull( + onLoading: (_) => mockLoadingCallback(), + onData: (_) => mockDataCallback(), + ); + expect(value, equals(null)); + verifyNever(mockLoadingCallback()); + verifyNever(mockLoadingCallback()); + verifyNever(mockDataCallback()); + }); + }); } // ignore: one_member_abstracts -abstract class Callback { - void call(); +abstract class Callback { + T call(); } -class MockCallback extends Mock implements Callback {} +//abstract class CallbackVoid implements Callback {} +abstract class CallbackVoid implements Callback {} + +abstract class CallbackInt implements Callback {} class _FakeType implements StockResponse { @override