chore(mobile): Favorite provider unit test (#1874)

* Favorite provider tests

* Remove unused mock

* Add setUp function to avoid duplicate code
This commit is contained in:
Matthias Rupp 2023-02-27 21:15:25 +01:00 committed by GitHub
parent 807bdfeda9
commit c9a6820de7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 383 additions and 7 deletions

View File

@ -3,14 +3,15 @@ import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
class FavoriteSelectionNotifier extends StateNotifier<Set<String>> {
FavoriteSelectionNotifier(this.ref) : super({}) {
state = ref.watch(assetProvider).allAssets
FavoriteSelectionNotifier(this.assetsState, this.assetNotifier) : super({}) {
state = assetsState.allAssets
.where((asset) => asset.isFavorite)
.map((asset) => asset.id)
.toSet();
}
final Ref ref;
final AssetsState assetsState;
final AssetNotifier assetNotifier;
void _setFavoriteForAssetId(String id, bool favorite) {
if (!favorite) {
@ -29,7 +30,7 @@ class FavoriteSelectionNotifier extends StateNotifier<Set<String>> {
_setFavoriteForAssetId(asset.id, !_isFavorite(asset.id));
await ref.watch(assetProvider.notifier).toggleFavorite(
await assetNotifier.toggleFavorite(
asset,
state.contains(asset.id),
);
@ -38,7 +39,7 @@ class FavoriteSelectionNotifier extends StateNotifier<Set<String>> {
Future<void> addToFavorites(Iterable<Asset> assets) {
state = state.union(assets.map((a) => a.id).toSet());
final futures = assets.map((a) =>
ref.watch(assetProvider.notifier).toggleFavorite(
assetNotifier.toggleFavorite(
a,
true,
),
@ -50,7 +51,10 @@ class FavoriteSelectionNotifier extends StateNotifier<Set<String>> {
final favoriteProvider =
StateNotifierProvider<FavoriteSelectionNotifier, Set<String>>((ref) {
return FavoriteSelectionNotifier(ref);
return FavoriteSelectionNotifier(
ref.watch(assetProvider),
ref.watch(assetProvider.notifier),
);
});
final favoriteAssetProvider = StateProvider((ref) {

View File

@ -740,6 +740,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mockito:
dependency: "direct dev"
description:
name: mockito
sha256: "2a8a17b82b1bde04d514e75d90d634a0ac23f6cb4991f6098009dd56836aeafe"
url: "https://pub.dev"
source: hosted
version: "5.3.2"
nested:
dependency: transitive
description:

View File

@ -64,6 +64,7 @@ dev_dependencies:
flutter_launcher_icons: "^0.9.2"
flutter_native_splash: ^2.2.16
isar_generator: *isar_version
mockito: ^5.3.2
integration_test:
sdk: flutter

View File

@ -0,0 +1,104 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@GenerateNiceMocks([
MockSpec<AssetsState>(),
MockSpec<AssetNotifier>(),
])
import 'favorite_provider_test.mocks.dart';
Asset _getTestAsset(String id, bool favorite) {
return Asset(
remoteId: id,
deviceAssetId: '',
deviceId: '',
ownerId: '',
fileCreatedAt: DateTime.now(),
fileModifiedAt: DateTime.now(),
durationInSeconds: 0,
fileName: '',
isFavorite: favorite,
);
}
void main() {
group("Test favoriteProvider", () {
late MockAssetsState assetsState;
late MockAssetNotifier assetNotifier;
late ProviderContainer container;
late StateNotifierProvider<FavoriteSelectionNotifier, Set<String>> testFavoritesProvider;
setUp(() {
assetsState = MockAssetsState();
assetNotifier = MockAssetNotifier();
container = ProviderContainer();
testFavoritesProvider =
StateNotifierProvider<FavoriteSelectionNotifier, Set<String>>((ref) {
return FavoriteSelectionNotifier(
assetsState,
assetNotifier,
);
});
},);
test("Empty favorites provider", () {
when(assetsState.allAssets).thenReturn([]);
expect(<String>{}, container.read(testFavoritesProvider));
});
test("Non-empty favorites provider", () {
when(assetsState.allAssets).thenReturn([
_getTestAsset("001", false),
_getTestAsset("002", true),
_getTestAsset("003", false),
_getTestAsset("004", false),
_getTestAsset("005", true),
]);
expect(<String>{"002", "005"}, container.read(testFavoritesProvider));
});
test("Toggle favorite", () {
when(assetNotifier.toggleFavorite(null, false))
.thenAnswer((_) async => false);
final testAsset1 = _getTestAsset("001", false);
final testAsset2 = _getTestAsset("002", true);
when(assetsState.allAssets).thenReturn([testAsset1, testAsset2]);
expect(<String>{"002"}, container.read(testFavoritesProvider));
container.read(testFavoritesProvider.notifier).toggleFavorite(testAsset2);
expect(<String>{}, container.read(testFavoritesProvider));
container.read(testFavoritesProvider.notifier).toggleFavorite(testAsset1);
expect(<String>{"001"}, container.read(testFavoritesProvider));
});
test("Add favorites", () {
when(assetNotifier.toggleFavorite(null, false))
.thenAnswer((_) async => false);
when(assetsState.allAssets).thenReturn([]);
expect(<String>{}, container.read(testFavoritesProvider));
container.read(testFavoritesProvider.notifier).addToFavorites(
[
_getTestAsset("001", false),
_getTestAsset("002", false),
],
);
expect(<String>{"001", "002"}, container.read(testFavoritesProvider));
});
});
}

View File

@ -0,0 +1,259 @@
// Mocks generated by Mockito 5.3.2 from annotations
// in immich_mobile/test/favorite_provider_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i5;
import 'package:hooks_riverpod/hooks_riverpod.dart' as _i7;
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'
as _i6;
import 'package:immich_mobile/shared/models/asset.dart' as _i4;
import 'package:immich_mobile/shared/providers/asset.provider.dart' as _i2;
import 'package:logging/logging.dart' as _i3;
import 'package:mockito/mockito.dart' as _i1;
import 'package:state_notifier/state_notifier.dart' as _i8;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
class _FakeAssetsState_0 extends _i1.SmartFake implements _i2.AssetsState {
_FakeAssetsState_0(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeLogger_1 extends _i1.SmartFake implements _i3.Logger {
_FakeLogger_1(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
/// A class which mocks [AssetsState].
///
/// See the documentation for Mockito's code generation for more information.
class MockAssetsState extends _i1.Mock implements _i2.AssetsState {
@override
List<_i4.Asset> get allAssets => (super.noSuchMethod(
Invocation.getter(#allAssets),
returnValue: <_i4.Asset>[],
returnValueForMissingStub: <_i4.Asset>[],
) as List<_i4.Asset>);
@override
_i5.Future<_i2.AssetsState> withRenderDataStructure(
_i6.AssetGridLayoutParameters? layout) =>
(super.noSuchMethod(
Invocation.method(
#withRenderDataStructure,
[layout],
),
returnValue: _i5.Future<_i2.AssetsState>.value(_FakeAssetsState_0(
this,
Invocation.method(
#withRenderDataStructure,
[layout],
),
)),
returnValueForMissingStub:
_i5.Future<_i2.AssetsState>.value(_FakeAssetsState_0(
this,
Invocation.method(
#withRenderDataStructure,
[layout],
),
)),
) as _i5.Future<_i2.AssetsState>);
@override
_i2.AssetsState withAdditionalAssets(List<_i4.Asset>? toAdd) =>
(super.noSuchMethod(
Invocation.method(
#withAdditionalAssets,
[toAdd],
),
returnValue: _FakeAssetsState_0(
this,
Invocation.method(
#withAdditionalAssets,
[toAdd],
),
),
returnValueForMissingStub: _FakeAssetsState_0(
this,
Invocation.method(
#withAdditionalAssets,
[toAdd],
),
),
) as _i2.AssetsState);
}
/// A class which mocks [AssetNotifier].
///
/// See the documentation for Mockito's code generation for more information.
class MockAssetNotifier extends _i1.Mock implements _i2.AssetNotifier {
@override
_i3.Logger get log => (super.noSuchMethod(
Invocation.getter(#log),
returnValue: _FakeLogger_1(
this,
Invocation.getter(#log),
),
returnValueForMissingStub: _FakeLogger_1(
this,
Invocation.getter(#log),
),
) as _i3.Logger);
@override
set onError(_i7.ErrorListener? _onError) => super.noSuchMethod(
Invocation.setter(
#onError,
_onError,
),
returnValueForMissingStub: null,
);
@override
bool get mounted => (super.noSuchMethod(
Invocation.getter(#mounted),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override
_i5.Stream<_i2.AssetsState> get stream => (super.noSuchMethod(
Invocation.getter(#stream),
returnValue: _i5.Stream<_i2.AssetsState>.empty(),
returnValueForMissingStub: _i5.Stream<_i2.AssetsState>.empty(),
) as _i5.Stream<_i2.AssetsState>);
@override
_i2.AssetsState get state => (super.noSuchMethod(
Invocation.getter(#state),
returnValue: _FakeAssetsState_0(
this,
Invocation.getter(#state),
),
returnValueForMissingStub: _FakeAssetsState_0(
this,
Invocation.getter(#state),
),
) as _i2.AssetsState);
@override
set state(_i2.AssetsState? value) => super.noSuchMethod(
Invocation.setter(
#state,
value,
),
returnValueForMissingStub: null,
);
@override
_i2.AssetsState get debugState => (super.noSuchMethod(
Invocation.getter(#debugState),
returnValue: _FakeAssetsState_0(
this,
Invocation.getter(#debugState),
),
returnValueForMissingStub: _FakeAssetsState_0(
this,
Invocation.getter(#debugState),
),
) as _i2.AssetsState);
@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override
_i5.Future<void> rebuildAssetGridDataStructure() => (super.noSuchMethod(
Invocation.method(
#rebuildAssetGridDataStructure,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
void onNewAssetUploaded(_i4.Asset? newAsset) => super.noSuchMethod(
Invocation.method(
#onNewAssetUploaded,
[newAsset],
),
returnValueForMissingStub: null,
);
@override
dynamic deleteAssets(Set<_i4.Asset>? deleteAssets) => super.noSuchMethod(
Invocation.method(
#deleteAssets,
[deleteAssets],
),
returnValueForMissingStub: null,
);
@override
_i5.Future<bool> toggleFavorite(
_i4.Asset? asset,
bool? status,
) =>
(super.noSuchMethod(
Invocation.method(
#toggleFavorite,
[
asset,
status,
],
),
returnValue: _i5.Future<bool>.value(false),
returnValueForMissingStub: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
@override
bool updateShouldNotify(
_i2.AssetsState? old,
_i2.AssetsState? current,
) =>
(super.noSuchMethod(
Invocation.method(
#updateShouldNotify,
[
old,
current,
],
),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override
_i7.RemoveListener addListener(
_i8.Listener<_i2.AssetsState>? listener, {
bool? fireImmediately = true,
}) =>
(super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
{#fireImmediately: fireImmediately},
),
returnValue: () {},
returnValueForMissingStub: () {},
) as _i7.RemoveListener);
@override
void dispose() => super.noSuchMethod(
Invocation.method(
#dispose,
[],
),
returnValueForMissingStub: null,
);
}