import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/activities/models/activity.model.dart'; import 'package:immich_mobile/modules/activities/providers/activity.provider.dart'; import 'package:immich_mobile/modules/activities/providers/activity_service.provider.dart'; import 'package:immich_mobile/modules/activities/providers/activity_statistics.provider.dart'; import 'package:mocktail/mocktail.dart'; import '../../fixtures/user.stub.dart'; import '../../test_utils.dart'; import 'activity_mocks.dart'; final _activities = [ Activity( id: '1', createdAt: DateTime(100), type: ActivityType.comment, comment: 'First Activity', assetId: 'asset-2', user: UserStub.admin, ), Activity( id: '2', createdAt: DateTime(200), type: ActivityType.comment, comment: 'Second Activity', user: UserStub.user1, ), Activity( id: '3', createdAt: DateTime(300), type: ActivityType.like, assetId: 'asset-1', user: UserStub.admin, ), Activity( id: '4', createdAt: DateTime(400), type: ActivityType.like, user: UserStub.user1, ), ]; void main() { late ActivityServiceMock activityMock; late ActivityStatisticsMock activityStatisticsMock; late ProviderContainer container; late AlbumActivityProvider provider; late ListenerMock>> listener; setUpAll(() { registerFallbackValue(AsyncData>([..._activities])); }); setUp(() async { activityMock = ActivityServiceMock(); activityStatisticsMock = ActivityStatisticsMock(); container = TestUtils.createContainer( overrides: [ activityServiceProvider.overrideWith((ref) => activityMock), activityStatisticsProvider('test-album', 'test-asset') .overrideWith(() => activityStatisticsMock), ], ); // Mock values when( () => activityMock.getAllActivities('test-album', assetId: 'test-asset'), ).thenAnswer((_) async => [..._activities]); // Init and wait for providers future to complete provider = albumActivityProvider('test-album', 'test-asset'); listener = ListenerMock(); container.listen( provider, listener.call, fireImmediately: true, ); await container.read(provider.future); }); test('Returns a list of activity', () async { verifyInOrder([ () => listener.call(null, const AsyncLoading()), () => listener.call( const AsyncLoading(), any( that: allOf( [ isA>>(), predicate( (AsyncData> ad) => ad.requireValue.every((e) => _activities.contains(e)), ), ], ), ), ), ]); verifyNoMoreInteractions(listener); }); group('addLike()', () { test('Like successfully added', () async { final like = Activity( id: '5', createdAt: DateTime(2023), type: ActivityType.like, user: UserStub.admin, ); when( () => activityMock.addActivity( 'test-album', ActivityType.like, assetId: 'test-asset', ), ).thenAnswer((_) async => AsyncData(like)); await container.read(provider.notifier).addLike(); verify( () => activityMock.addActivity( 'test-album', ActivityType.like, assetId: 'test-asset', ), ); final activities = await container.read(provider.future); expect(activities, hasLength(5)); expect(activities, contains(like)); // Never bump activity count for new likes verifyNever(() => activityStatisticsMock.addActivity()); }); test('Like failed', () async { final like = Activity( id: '5', createdAt: DateTime(2023), type: ActivityType.like, user: UserStub.admin, ); when( () => activityMock.addActivity( 'test-album', ActivityType.like, assetId: 'test-asset', ), ).thenAnswer( (_) async => AsyncError(Exception('Mock'), StackTrace.current), ); await container.read(provider.notifier).addLike(); verify( () => activityMock.addActivity( 'test-album', ActivityType.like, assetId: 'test-asset', ), ); final activities = await container.read(provider.future); expect(activities, hasLength(4)); expect(activities, isNot(contains(like))); }); }); group('removeActivity()', () { test('Like successfully removed', () async { when(() => activityMock.removeActivity('3')) .thenAnswer((_) async => true); await container.read(provider.notifier).removeActivity('3'); verify( () => activityMock.removeActivity('3'), ); final activities = await container.read(provider.future); expect(activities, hasLength(3)); expect( activities, isNot(anyElement(predicate((Activity a) => a.id == '3'))), ); verifyNever(() => activityStatisticsMock.removeActivity()); }); test('Remove Like failed', () async { when(() => activityMock.removeActivity('3')) .thenAnswer((_) async => false); await container.read(provider.notifier).removeActivity('3'); final activities = await container.read(provider.future); expect(activities, hasLength(4)); expect( activities, anyElement(predicate((Activity a) => a.id == '3')), ); }); test('Comment successfully removed', () async { when(() => activityMock.removeActivity('1')) .thenAnswer((_) async => true); await container.read(provider.notifier).removeActivity('1'); final activities = await container.read(provider.future); expect( activities, isNot(anyElement(predicate((Activity a) => a.id == '1'))), ); verify(() => activityStatisticsMock.removeActivity()); }); }); group('addComment()', () { late ActivityStatisticsMock albumActivityStatisticsMock; setUp(() { albumActivityStatisticsMock = ActivityStatisticsMock(); container = TestUtils.createContainer( overrides: [ activityServiceProvider.overrideWith((ref) => activityMock), activityStatisticsProvider('test-album', 'test-asset') .overrideWith(() => activityStatisticsMock), activityStatisticsProvider('test-album') .overrideWith(() => albumActivityStatisticsMock), ], ); }); test('Comment successfully added', () async { final comment = Activity( id: '5', createdAt: DateTime(2023), type: ActivityType.comment, user: UserStub.admin, comment: 'Test-Comment', assetId: 'test-asset', ); when( () => activityMock.addActivity( 'test-album', ActivityType.comment, assetId: 'test-asset', comment: 'Test-Comment', ), ).thenAnswer((_) async => AsyncData(comment)); when(() => activityStatisticsMock.build('test-album', 'test-asset')) .thenReturn(4); when(() => albumActivityStatisticsMock.build('test-album')).thenReturn(2); await container.read(provider.notifier).addComment('Test-Comment'); verify( () => activityMock.addActivity( 'test-album', ActivityType.comment, assetId: 'test-asset', comment: 'Test-Comment', ), ); final activities = await container.read(provider.future); expect(activities, hasLength(5)); expect(activities, contains(comment)); verify(() => activityStatisticsMock.addActivity()); verify(() => albumActivityStatisticsMock.addActivity()); }); test('Comment successfully added without assetId', () async { final comment = Activity( id: '5', createdAt: DateTime(2023), type: ActivityType.comment, user: UserStub.admin, assetId: 'test-asset', comment: 'Test-Comment', ); when( () => activityMock.addActivity( 'test-album', ActivityType.comment, comment: 'Test-Comment', ), ).thenAnswer((_) async => AsyncData(comment)); when(() => albumActivityStatisticsMock.build('test-album')).thenReturn(2); when(() => activityMock.getAllActivities('test-album')) .thenAnswer((_) async => [..._activities]); final albumProvider = albumActivityProvider('test-album'); await container.read(albumProvider.notifier).addComment('Test-Comment'); verify( () => activityMock.addActivity( 'test-album', ActivityType.comment, assetId: null, comment: 'Test-Comment', ), ); final activities = await container.read(albumProvider.future); expect(activities, hasLength(5)); expect(activities, contains(comment)); verifyNever(() => activityStatisticsMock.addActivity()); verify(() => albumActivityStatisticsMock.addActivity()); }); test('Comment failed', () async { final comment = Activity( id: '5', createdAt: DateTime(2023), type: ActivityType.comment, user: UserStub.admin, comment: 'Test-Comment', assetId: 'test-asset', ); when( () => activityMock.addActivity( 'test-album', ActivityType.comment, assetId: 'test-asset', comment: 'Test-Comment', ), ).thenAnswer( (_) async => AsyncError(Exception('Error'), StackTrace.current), ); await container.read(provider.notifier).addComment('Test-Comment'); final activities = await container.read(provider.future); expect(activities, hasLength(4)); expect(activities, isNot(contains(comment))); verifyNever(() => activityStatisticsMock.addActivity()); verifyNever(() => albumActivityStatisticsMock.addActivity()); }); }); }