chore: bump dart sdk to 3.8 (#20355)
* chore: bump dart sdk to 3.8 * chore: make build * make pigeon * chore: format files --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
@@ -21,10 +21,7 @@ void main() {
|
||||
late MockLocalAssetRepository mockAssetRepo;
|
||||
late MockStorageRepository mockStorageRepo;
|
||||
late MockNativeSyncApi mockNativeApi;
|
||||
final sortBy = {
|
||||
SortLocalAlbumsBy.backupSelection,
|
||||
SortLocalAlbumsBy.isIosSharedAlbum,
|
||||
};
|
||||
final sortBy = {SortLocalAlbumsBy.backupSelection, SortLocalAlbumsBy.isIosSharedAlbum};
|
||||
|
||||
setUp(() {
|
||||
mockAlbumRepo = MockLocalAlbumRepository();
|
||||
@@ -47,9 +44,9 @@ void main() {
|
||||
|
||||
group('HashService hashAssets', () {
|
||||
test('skips albums with no assets to hash', () async {
|
||||
when(() => mockAlbumRepo.getAll(sortBy: sortBy)).thenAnswer(
|
||||
(_) async => [LocalAlbumStub.recent.copyWith(assetCount: 0)],
|
||||
);
|
||||
when(
|
||||
() => mockAlbumRepo.getAll(sortBy: sortBy),
|
||||
).thenAnswer((_) async => [LocalAlbumStub.recent.copyWith(assetCount: 0)]);
|
||||
when(() => mockAlbumRepo.getAssetsToHash(LocalAlbumStub.recent.id)).thenAnswer((_) async => []);
|
||||
|
||||
await sut.hashAssets();
|
||||
@@ -84,9 +81,7 @@ void main() {
|
||||
when(() => mockAlbumRepo.getAll(sortBy: sortBy)).thenAnswer((_) async => [album]);
|
||||
when(() => mockAlbumRepo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset]);
|
||||
when(() => mockStorageRepo.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile);
|
||||
when(() => mockNativeApi.hashPaths(['image-path'])).thenAnswer(
|
||||
(_) async => [hash],
|
||||
);
|
||||
when(() => mockNativeApi.hashPaths(['image-path'])).thenAnswer((_) async => [hash]);
|
||||
|
||||
await sut.hashAssets();
|
||||
|
||||
|
||||
@@ -43,10 +43,7 @@ void main() {
|
||||
when(() => mockLogRepo.insert(any())).thenAnswer((_) async => true);
|
||||
when(() => mockLogRepo.insertAll(any())).thenAnswer((_) async => true);
|
||||
|
||||
sut = await LogService.create(
|
||||
logRepository: mockLogRepo,
|
||||
storeRepository: mockStoreRepo,
|
||||
);
|
||||
sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
@@ -72,9 +69,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('Updates the log level in store', () {
|
||||
final index = verify(
|
||||
() => mockStoreRepo.insert<int>(StoreKey.logLevel, captureAny()),
|
||||
).captured.firstOrNull;
|
||||
final index = verify(() => mockStoreRepo.insert<int>(StoreKey.logLevel, captureAny())).captured.firstOrNull;
|
||||
expect(index, LogLevel.shout.index);
|
||||
});
|
||||
|
||||
@@ -86,11 +81,7 @@ void main() {
|
||||
group("Log Service Buffer:", () {
|
||||
test('Buffers logs until timer elapses', () {
|
||||
TestUtils.fakeAsync((time) async {
|
||||
sut = await LogService.create(
|
||||
logRepository: mockLogRepo,
|
||||
storeRepository: mockStoreRepo,
|
||||
shouldBuffer: true,
|
||||
);
|
||||
sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: true);
|
||||
|
||||
final logger = Logger(_kInfoLog.logger!);
|
||||
logger.info(_kInfoLog.message);
|
||||
@@ -104,11 +95,7 @@ void main() {
|
||||
|
||||
test('Batch inserts all logs on timer', () {
|
||||
TestUtils.fakeAsync((time) async {
|
||||
sut = await LogService.create(
|
||||
logRepository: mockLogRepo,
|
||||
storeRepository: mockStoreRepo,
|
||||
shouldBuffer: true,
|
||||
);
|
||||
sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: true);
|
||||
|
||||
final logger = Logger(_kInfoLog.logger!);
|
||||
logger.info(_kInfoLog.message);
|
||||
@@ -125,11 +112,7 @@ void main() {
|
||||
|
||||
test('Does not buffer when off', () {
|
||||
TestUtils.fakeAsync((time) async {
|
||||
sut = await LogService.create(
|
||||
logRepository: mockLogRepo,
|
||||
storeRepository: mockStoreRepo,
|
||||
shouldBuffer: false,
|
||||
);
|
||||
sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: false);
|
||||
|
||||
final logger = Logger(_kInfoLog.logger!);
|
||||
logger.info(_kInfoLog.message);
|
||||
@@ -159,11 +142,7 @@ void main() {
|
||||
|
||||
test('Combines result from both DB + Buffer', () {
|
||||
TestUtils.fakeAsync((time) async {
|
||||
sut = await LogService.create(
|
||||
logRepository: mockLogRepo,
|
||||
storeRepository: mockStoreRepo,
|
||||
shouldBuffer: true,
|
||||
);
|
||||
sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: true);
|
||||
|
||||
final logger = Logger(_kWarnLog.logger!);
|
||||
logger.warning(_kWarnLog.message);
|
||||
|
||||
@@ -73,10 +73,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('Throws StoreKeyNotFoundException for nonexistent keys', () {
|
||||
expect(
|
||||
() => sut.get(StoreKey.currentUser),
|
||||
throwsA(isA<StoreKeyNotFoundException>()),
|
||||
);
|
||||
expect(() => sut.get(StoreKey.currentUser), throwsA(isA<StoreKeyNotFoundException>()));
|
||||
});
|
||||
|
||||
test('Returns the stored value for the given key or the defaultValue', () {
|
||||
@@ -91,17 +88,13 @@ void main() {
|
||||
|
||||
test('Skip insert when value is not modified', () async {
|
||||
await sut.put(StoreKey.accessToken, _kAccessToken);
|
||||
verifyNever(
|
||||
() => mockStoreRepo.insert<String>(StoreKey.accessToken, any()),
|
||||
);
|
||||
verifyNever(() => mockStoreRepo.insert<String>(StoreKey.accessToken, any()));
|
||||
});
|
||||
|
||||
test('Insert value when modified', () async {
|
||||
final newAccessToken = _kAccessToken.toUpperCase();
|
||||
await sut.put(StoreKey.accessToken, newAccessToken);
|
||||
verify(
|
||||
() => mockStoreRepo.insert<String>(StoreKey.accessToken, newAccessToken),
|
||||
).called(1);
|
||||
verify(() => mockStoreRepo.insert<String>(StoreKey.accessToken, newAccessToken)).called(1);
|
||||
expect(sut.tryGet(StoreKey.accessToken), newAccessToken);
|
||||
});
|
||||
});
|
||||
@@ -120,12 +113,7 @@ void main() {
|
||||
|
||||
test('Watches a specific key for changes', () async {
|
||||
final stream = sut.watch(StoreKey.accessToken);
|
||||
final events = <String?>[
|
||||
_kAccessToken,
|
||||
_kAccessToken.toUpperCase(),
|
||||
null,
|
||||
_kAccessToken.toLowerCase(),
|
||||
];
|
||||
final events = <String?>[_kAccessToken, _kAccessToken.toUpperCase(), null, _kAccessToken.toLowerCase()];
|
||||
|
||||
expectLater(stream, emitsInOrder(events));
|
||||
|
||||
|
||||
@@ -54,40 +54,25 @@ void main() {
|
||||
when(() => mockSyncStreamRepo.deletePartnerV1(any())).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.updateAssetsV1(any())).thenAnswer(successHandler);
|
||||
when(
|
||||
() => mockSyncStreamRepo.updateAssetsV1(
|
||||
any(),
|
||||
debugLabel: any(named: 'debugLabel'),
|
||||
),
|
||||
() => mockSyncStreamRepo.updateAssetsV1(any(), debugLabel: any(named: 'debugLabel')),
|
||||
).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.deleteAssetsV1(any())).thenAnswer(successHandler);
|
||||
when(
|
||||
() => mockSyncStreamRepo.deleteAssetsV1(
|
||||
any(),
|
||||
debugLabel: any(named: 'debugLabel'),
|
||||
),
|
||||
() => mockSyncStreamRepo.deleteAssetsV1(any(), debugLabel: any(named: 'debugLabel')),
|
||||
).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.updateAssetsExifV1(any())).thenAnswer(successHandler);
|
||||
when(
|
||||
() => mockSyncStreamRepo.updateAssetsExifV1(
|
||||
any(),
|
||||
debugLabel: any(named: 'debugLabel'),
|
||||
),
|
||||
() => mockSyncStreamRepo.updateAssetsExifV1(any(), debugLabel: any(named: 'debugLabel')),
|
||||
).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.updateMemoriesV1(any())).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.deleteMemoriesV1(any())).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.updateMemoryAssetsV1(any())).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.deleteMemoryAssetsV1(any())).thenAnswer(successHandler);
|
||||
when(
|
||||
() => mockSyncStreamRepo.updateStacksV1(
|
||||
any(),
|
||||
debugLabel: any(named: 'debugLabel'),
|
||||
),
|
||||
() => mockSyncStreamRepo.updateStacksV1(any(), debugLabel: any(named: 'debugLabel')),
|
||||
).thenAnswer(successHandler);
|
||||
when(
|
||||
() => mockSyncStreamRepo.deleteStacksV1(
|
||||
any(),
|
||||
debugLabel: any(named: 'debugLabel'),
|
||||
),
|
||||
() => mockSyncStreamRepo.deleteStacksV1(any(), debugLabel: any(named: 'debugLabel')),
|
||||
).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.updateUserMetadatasV1(any())).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.deleteUserMetadatasV1(any())).thenAnswer(successHandler);
|
||||
@@ -96,10 +81,7 @@ void main() {
|
||||
when(() => mockSyncStreamRepo.updateAssetFacesV1(any())).thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.deleteAssetFacesV1(any())).thenAnswer(successHandler);
|
||||
|
||||
sut = SyncStreamService(
|
||||
syncApiRepository: mockSyncApiRepo,
|
||||
syncStreamRepository: mockSyncStreamRepo,
|
||||
);
|
||||
sut = SyncStreamService(syncApiRepository: mockSyncApiRepo, syncStreamRepository: mockSyncStreamRepo);
|
||||
});
|
||||
|
||||
Future<void> simulateEvents(List<SyncEvent> events) async {
|
||||
@@ -108,41 +90,35 @@ void main() {
|
||||
}
|
||||
|
||||
group("SyncStreamService - _handleEvents", () {
|
||||
test(
|
||||
"processes events and acks successfully when handlers succeed",
|
||||
() async {
|
||||
final events = [
|
||||
SyncStreamStub.userDeleteV1,
|
||||
SyncStreamStub.userV1Admin,
|
||||
SyncStreamStub.userV1User,
|
||||
SyncStreamStub.partnerDeleteV1,
|
||||
SyncStreamStub.partnerV1,
|
||||
];
|
||||
|
||||
await simulateEvents(events);
|
||||
|
||||
verifyInOrder([
|
||||
() => mockSyncStreamRepo.deleteUsersV1(any()),
|
||||
() => mockSyncApiRepo.ack(["2"]),
|
||||
() => mockSyncStreamRepo.updateUsersV1(any()),
|
||||
() => mockSyncApiRepo.ack(["5"]),
|
||||
() => mockSyncStreamRepo.deletePartnerV1(any()),
|
||||
() => mockSyncApiRepo.ack(["4"]),
|
||||
() => mockSyncStreamRepo.updatePartnerV1(any()),
|
||||
() => mockSyncApiRepo.ack(["3"]),
|
||||
]);
|
||||
verifyNever(() => mockAbortCallbackWrapper());
|
||||
},
|
||||
);
|
||||
|
||||
test("processes final batch correctly", () async {
|
||||
test("processes events and acks successfully when handlers succeed", () async {
|
||||
final events = [
|
||||
SyncStreamStub.userDeleteV1,
|
||||
SyncStreamStub.userV1Admin,
|
||||
SyncStreamStub.userV1User,
|
||||
SyncStreamStub.partnerDeleteV1,
|
||||
SyncStreamStub.partnerV1,
|
||||
];
|
||||
|
||||
await simulateEvents(events);
|
||||
|
||||
verifyInOrder([
|
||||
() => mockSyncStreamRepo.deleteUsersV1(any()),
|
||||
() => mockSyncApiRepo.ack(["2"]),
|
||||
() => mockSyncStreamRepo.updateUsersV1(any()),
|
||||
() => mockSyncApiRepo.ack(["5"]),
|
||||
() => mockSyncStreamRepo.deletePartnerV1(any()),
|
||||
() => mockSyncApiRepo.ack(["4"]),
|
||||
() => mockSyncStreamRepo.updatePartnerV1(any()),
|
||||
() => mockSyncApiRepo.ack(["3"]),
|
||||
]);
|
||||
verifyNever(() => mockAbortCallbackWrapper());
|
||||
});
|
||||
|
||||
test("processes final batch correctly", () async {
|
||||
final events = [SyncStreamStub.userDeleteV1, SyncStreamStub.userV1Admin];
|
||||
|
||||
await simulateEvents(events);
|
||||
|
||||
verifyInOrder([
|
||||
() => mockSyncStreamRepo.deleteUsersV1(any()),
|
||||
() => mockSyncApiRepo.ack(["2"]),
|
||||
@@ -174,11 +150,7 @@ void main() {
|
||||
);
|
||||
await sut.sync();
|
||||
|
||||
final events = [
|
||||
SyncStreamStub.userDeleteV1,
|
||||
SyncStreamStub.userV1Admin,
|
||||
SyncStreamStub.partnerDeleteV1,
|
||||
];
|
||||
final events = [SyncStreamStub.userDeleteV1, SyncStreamStub.userV1Admin, SyncStreamStub.partnerDeleteV1];
|
||||
|
||||
when(() => mockSyncStreamRepo.deleteUsersV1(any())).thenAnswer((_) async {
|
||||
when(() => cancellationChecker()).thenReturn(true);
|
||||
@@ -195,50 +167,43 @@ void main() {
|
||||
verify(() => mockSyncApiRepo.ack(["2"])).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
"aborts and stops processing if cancelled before processing batch",
|
||||
() async {
|
||||
final cancellationChecker = _MockCancellationWrapper();
|
||||
when(() => cancellationChecker()).thenReturn(false);
|
||||
test("aborts and stops processing if cancelled before processing batch", () async {
|
||||
final cancellationChecker = _MockCancellationWrapper();
|
||||
when(() => cancellationChecker()).thenReturn(false);
|
||||
|
||||
final processingCompleter = Completer<void>();
|
||||
bool handler1Started = false;
|
||||
when(() => mockSyncStreamRepo.deleteUsersV1(any())).thenAnswer((_) async {
|
||||
handler1Started = true;
|
||||
return processingCompleter.future;
|
||||
});
|
||||
final processingCompleter = Completer<void>();
|
||||
bool handler1Started = false;
|
||||
when(() => mockSyncStreamRepo.deleteUsersV1(any())).thenAnswer((_) async {
|
||||
handler1Started = true;
|
||||
return processingCompleter.future;
|
||||
});
|
||||
|
||||
sut = SyncStreamService(
|
||||
syncApiRepository: mockSyncApiRepo,
|
||||
syncStreamRepository: mockSyncStreamRepo,
|
||||
cancelChecker: cancellationChecker.call,
|
||||
);
|
||||
sut = SyncStreamService(
|
||||
syncApiRepository: mockSyncApiRepo,
|
||||
syncStreamRepository: mockSyncStreamRepo,
|
||||
cancelChecker: cancellationChecker.call,
|
||||
);
|
||||
|
||||
await sut.sync();
|
||||
await sut.sync();
|
||||
|
||||
final events = [
|
||||
SyncStreamStub.userDeleteV1,
|
||||
SyncStreamStub.userV1Admin,
|
||||
SyncStreamStub.partnerDeleteV1,
|
||||
];
|
||||
final events = [SyncStreamStub.userDeleteV1, SyncStreamStub.userV1Admin, SyncStreamStub.partnerDeleteV1];
|
||||
|
||||
final processingFuture = handleEventsCallback(events, mockAbortCallbackWrapper.call);
|
||||
await pumpEventQueue();
|
||||
final processingFuture = handleEventsCallback(events, mockAbortCallbackWrapper.call);
|
||||
await pumpEventQueue();
|
||||
|
||||
expect(handler1Started, isTrue);
|
||||
expect(handler1Started, isTrue);
|
||||
|
||||
// Signal cancellation while handler 1 is waiting
|
||||
when(() => cancellationChecker()).thenReturn(true);
|
||||
await pumpEventQueue();
|
||||
// Signal cancellation while handler 1 is waiting
|
||||
when(() => cancellationChecker()).thenReturn(true);
|
||||
await pumpEventQueue();
|
||||
|
||||
processingCompleter.complete();
|
||||
await processingFuture;
|
||||
processingCompleter.complete();
|
||||
await processingFuture;
|
||||
|
||||
verifyNever(() => mockSyncStreamRepo.updateUsersV1(any()));
|
||||
verifyNever(() => mockSyncStreamRepo.updateUsersV1(any()));
|
||||
|
||||
verify(() => mockSyncApiRepo.ack(["2"])).called(1);
|
||||
},
|
||||
);
|
||||
verify(() => mockSyncApiRepo.ack(["2"])).called(1);
|
||||
});
|
||||
|
||||
test("processes memory sync events successfully", () async {
|
||||
final events = [
|
||||
@@ -289,15 +254,9 @@ void main() {
|
||||
test("handles memory sync failure gracefully", () async {
|
||||
when(() => mockSyncStreamRepo.updateMemoriesV1(any())).thenThrow(Exception("Memory sync failed"));
|
||||
|
||||
final events = [
|
||||
SyncStreamStub.memoryV1,
|
||||
SyncStreamStub.userV1Admin,
|
||||
];
|
||||
final events = [SyncStreamStub.memoryV1, SyncStreamStub.userV1Admin];
|
||||
|
||||
expect(
|
||||
() async => await simulateEvents(events),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
expect(() async => await simulateEvents(events), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test("processes memory asset events with correct data types", () async {
|
||||
|
||||
@@ -89,9 +89,7 @@ void main() {
|
||||
when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => null);
|
||||
|
||||
final result = await sut.refreshMyUser();
|
||||
verifyNever(
|
||||
() => mockStoreService.put(StoreKey.currentUser, UserStub.admin),
|
||||
);
|
||||
verifyNever(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin));
|
||||
verifyNever(() => mockUserRepo.update(UserStub.admin));
|
||||
expect(result, isNull);
|
||||
});
|
||||
@@ -103,10 +101,7 @@ void main() {
|
||||
final updatedUser = UserStub.admin.copyWith(profileImagePath: profileImagePath);
|
||||
|
||||
when(
|
||||
() => mockUserApiRepo.createProfileImage(
|
||||
name: profileImagePath,
|
||||
data: Uint8List(0),
|
||||
),
|
||||
() => mockUserApiRepo.createProfileImage(name: profileImagePath, data: Uint8List(0)),
|
||||
).thenAnswer((_) async => profileImagePath);
|
||||
when(() => mockStoreService.put(StoreKey.currentUser, updatedUser)).thenAnswer((_) async => true);
|
||||
when(() => mockUserRepo.update(updatedUser)).thenAnswer((_) async => UserStub.admin);
|
||||
@@ -123,16 +118,11 @@ void main() {
|
||||
final updatedUser = UserStub.admin.copyWith(profileImagePath: profileImagePath);
|
||||
|
||||
when(
|
||||
() => mockUserApiRepo.createProfileImage(
|
||||
name: profileImagePath,
|
||||
data: Uint8List(0),
|
||||
),
|
||||
() => mockUserApiRepo.createProfileImage(name: profileImagePath, data: Uint8List(0)),
|
||||
).thenThrow(Exception('Failed to create profile image'));
|
||||
|
||||
final result = await sut.createProfileImage(profileImagePath, Uint8List(0));
|
||||
verifyNever(
|
||||
() => mockStoreService.put(StoreKey.currentUser, updatedUser),
|
||||
);
|
||||
verifyNever(() => mockStoreService.put(StoreKey.currentUser, updatedUser));
|
||||
verifyNever(() => mockUserRepo.update(updatedUser));
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
+2518
-1180
File diff suppressed because it is too large
Load Diff
+2518
-1180
File diff suppressed because it is too large
Load Diff
+2515
-1178
File diff suppressed because it is too large
Load Diff
+2720
-1282
File diff suppressed because it is too large
Load Diff
Vendored
+15
-14
@@ -42,20 +42,21 @@ final class AlbumStub {
|
||||
endDate: DateTime(2023),
|
||||
)..assets.addAll([AssetStub.image1]);
|
||||
|
||||
static final twoAsset = Album(
|
||||
name: "album-with-two-assets",
|
||||
localId: "album-with-two-assets-local",
|
||||
remoteId: "album-with-two-assets-remote",
|
||||
createdAt: DateTime(2001),
|
||||
modifiedAt: DateTime(2010),
|
||||
shared: false,
|
||||
activityEnabled: false,
|
||||
startDate: DateTime(2019),
|
||||
endDate: DateTime(2020),
|
||||
)
|
||||
..assets.addAll([AssetStub.image1, AssetStub.image2])
|
||||
..activityEnabled = true
|
||||
..owner.value = User.fromDto(UserStub.admin);
|
||||
static final twoAsset =
|
||||
Album(
|
||||
name: "album-with-two-assets",
|
||||
localId: "album-with-two-assets-local",
|
||||
remoteId: "album-with-two-assets-remote",
|
||||
createdAt: DateTime(2001),
|
||||
modifiedAt: DateTime(2010),
|
||||
shared: false,
|
||||
activityEnabled: false,
|
||||
startDate: DateTime(2019),
|
||||
endDate: DateTime(2020),
|
||||
)
|
||||
..assets.addAll([AssetStub.image1, AssetStub.image2])
|
||||
..activityEnabled = true
|
||||
..owner.value = User.fromDto(UserStub.admin);
|
||||
|
||||
static final create2020end2020Album = Album(
|
||||
name: "create2020update2020Album",
|
||||
|
||||
+5
-27
@@ -4,24 +4,12 @@ import 'package:openapi/api.dart';
|
||||
abstract final class SyncStreamStub {
|
||||
static final userV1Admin = SyncEvent(
|
||||
type: SyncEntityType.userV1,
|
||||
data: SyncUserV1(
|
||||
deletedAt: DateTime(2020),
|
||||
email: "admin@admin",
|
||||
id: "1",
|
||||
name: "Admin",
|
||||
avatarColor: null,
|
||||
),
|
||||
data: SyncUserV1(deletedAt: DateTime(2020), email: "admin@admin", id: "1", name: "Admin", avatarColor: null),
|
||||
ack: "1",
|
||||
);
|
||||
static final userV1User = SyncEvent(
|
||||
type: SyncEntityType.userV1,
|
||||
data: SyncUserV1(
|
||||
deletedAt: DateTime(2021),
|
||||
email: "user@user",
|
||||
id: "5",
|
||||
name: "User",
|
||||
avatarColor: null,
|
||||
),
|
||||
data: SyncUserV1(deletedAt: DateTime(2021), email: "user@user", id: "5", name: "User", avatarColor: null),
|
||||
ack: "5",
|
||||
);
|
||||
static final userDeleteV1 = SyncEvent(
|
||||
@@ -32,11 +20,7 @@ abstract final class SyncStreamStub {
|
||||
|
||||
static final partnerV1 = SyncEvent(
|
||||
type: SyncEntityType.partnerV1,
|
||||
data: SyncPartnerV1(
|
||||
inTimeline: true,
|
||||
sharedById: "1",
|
||||
sharedWithId: "2",
|
||||
),
|
||||
data: SyncPartnerV1(inTimeline: true, sharedById: "1", sharedWithId: "2"),
|
||||
ack: "3",
|
||||
);
|
||||
static final partnerDeleteV1 = SyncEvent(
|
||||
@@ -72,19 +56,13 @@ abstract final class SyncStreamStub {
|
||||
|
||||
static final memoryToAssetV1 = SyncEvent(
|
||||
type: SyncEntityType.memoryToAssetV1,
|
||||
data: SyncMemoryAssetV1(
|
||||
assetId: "asset-1",
|
||||
memoryId: "memory-1",
|
||||
),
|
||||
data: SyncMemoryAssetV1(assetId: "asset-1", memoryId: "memory-1"),
|
||||
ack: "7",
|
||||
);
|
||||
|
||||
static final memoryToAssetDeleteV1 = SyncEvent(
|
||||
type: SyncEntityType.memoryToAssetDeleteV1,
|
||||
data: SyncMemoryAssetDeleteV1(
|
||||
assetId: "asset-2",
|
||||
memoryId: "memory-1",
|
||||
),
|
||||
data: SyncMemoryAssetDeleteV1(assetId: "asset-2", memoryId: "memory-1"),
|
||||
ack: "8",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,48 +12,21 @@ void main() {
|
||||
late MediumFactory mediumFactory;
|
||||
|
||||
setUp(() {
|
||||
db = Drift(
|
||||
DatabaseConnection(
|
||||
NativeDatabase.memory(),
|
||||
closeStreamsSynchronously: true,
|
||||
),
|
||||
);
|
||||
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
mediumFactory = MediumFactory(db);
|
||||
});
|
||||
|
||||
group('getAll', () {
|
||||
test('sorts albums by backupSelection & isIosSharedAlbum', () async {
|
||||
final localAlbumRepo = mediumFactory.getRepository<DriftLocalAlbumRepository>();
|
||||
await localAlbumRepo.upsert(mediumFactory.localAlbum(id: '1', backupSelection: BackupSelection.none));
|
||||
await localAlbumRepo.upsert(mediumFactory.localAlbum(id: '2', backupSelection: BackupSelection.excluded));
|
||||
await localAlbumRepo.upsert(
|
||||
mediumFactory.localAlbum(
|
||||
id: '1',
|
||||
backupSelection: BackupSelection.none,
|
||||
),
|
||||
);
|
||||
await localAlbumRepo.upsert(
|
||||
mediumFactory.localAlbum(
|
||||
id: '2',
|
||||
backupSelection: BackupSelection.excluded,
|
||||
),
|
||||
);
|
||||
await localAlbumRepo.upsert(
|
||||
mediumFactory.localAlbum(
|
||||
id: '3',
|
||||
backupSelection: BackupSelection.selected,
|
||||
isIosSharedAlbum: true,
|
||||
),
|
||||
);
|
||||
await localAlbumRepo.upsert(
|
||||
mediumFactory.localAlbum(
|
||||
id: '4',
|
||||
backupSelection: BackupSelection.selected,
|
||||
),
|
||||
mediumFactory.localAlbum(id: '3', backupSelection: BackupSelection.selected, isIosSharedAlbum: true),
|
||||
);
|
||||
await localAlbumRepo.upsert(mediumFactory.localAlbum(id: '4', backupSelection: BackupSelection.selected));
|
||||
final albums = await localAlbumRepo.getAll(
|
||||
sortBy: {
|
||||
SortLocalAlbumsBy.backupSelection,
|
||||
SortLocalAlbumsBy.isIosSharedAlbum,
|
||||
},
|
||||
sortBy: {SortLocalAlbumsBy.backupSelection, SortLocalAlbumsBy.isIosSharedAlbum},
|
||||
);
|
||||
expect(albums.length, 4);
|
||||
expect(albums[0].id, '4'); // selected
|
||||
|
||||
@@ -24,16 +24,8 @@ Future<void> _addStrStoreValue(Isar db, StoreKey key, String? value) async {
|
||||
|
||||
Future<void> _populateStore(Isar db) async {
|
||||
await db.writeTxn(() async {
|
||||
await _addIntStoreValue(
|
||||
db,
|
||||
StoreKey.colorfulInterface,
|
||||
_kTestColorfulInterface ? 1 : 0,
|
||||
);
|
||||
await _addIntStoreValue(
|
||||
db,
|
||||
StoreKey.backupFailedSince,
|
||||
_kTestBackupFailed.millisecondsSinceEpoch,
|
||||
);
|
||||
await _addIntStoreValue(db, StoreKey.colorfulInterface, _kTestColorfulInterface ? 1 : 0);
|
||||
await _addIntStoreValue(db, StoreKey.backupFailedSince, _kTestBackupFailed.millisecondsSinceEpoch);
|
||||
await _addStrStoreValue(db, StoreKey.accessToken, _kTestAccessToken);
|
||||
await _addIntStoreValue(db, StoreKey.version, _kTestVersion);
|
||||
});
|
||||
@@ -143,21 +135,10 @@ void main() {
|
||||
stream,
|
||||
emitsInAnyOrder([
|
||||
emits(const StoreDto<Object>(StoreKey.version, _kTestVersion)),
|
||||
emits(
|
||||
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
|
||||
),
|
||||
emits(
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
),
|
||||
emits(
|
||||
const StoreDto<Object>(
|
||||
StoreKey.colorfulInterface,
|
||||
_kTestColorfulInterface,
|
||||
),
|
||||
),
|
||||
emits(
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
|
||||
),
|
||||
emits(StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed)),
|
||||
emits(const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken)),
|
||||
emits(const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface)),
|
||||
emits(const StoreDto<Object>(StoreKey.version, _kTestVersion + 10)),
|
||||
]),
|
||||
);
|
||||
await sut.update(StoreKey.version, _kTestVersion + 10);
|
||||
|
||||
@@ -63,14 +63,8 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
Future<void> streamChanges(
|
||||
Function(List<SyncEvent>, Function() abort) onDataCallback,
|
||||
) {
|
||||
return sut.streamChanges(
|
||||
onDataCallback,
|
||||
batchSize: testBatchSize,
|
||||
httpClient: mockHttpClient,
|
||||
);
|
||||
Future<void> streamChanges(Function(List<SyncEvent>, Function() abort) onDataCallback) {
|
||||
return sut.streamChanges(onDataCallback, batchSize: testBatchSize, httpClient: mockHttpClient);
|
||||
}
|
||||
|
||||
test('streamChanges stops processing stream when abort is called', () async {
|
||||
@@ -96,11 +90,7 @@ void main() {
|
||||
for (int i = 0; i < testBatchSize; i++) {
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(
|
||||
SyncEntityType.userDeleteV1.toString(),
|
||||
SyncUserDeleteV1(userId: "user$i").toJson(),
|
||||
'ack$i',
|
||||
),
|
||||
_createJsonLine(SyncEntityType.userDeleteV1.toString(), SyncUserDeleteV1(userId: "user$i").toJson(), 'ack$i'),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -108,11 +98,7 @@ void main() {
|
||||
for (int i = testBatchSize; i < testBatchSize * 2; i++) {
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(
|
||||
SyncEntityType.userDeleteV1.toString(),
|
||||
SyncUserDeleteV1(userId: "user$i").toJson(),
|
||||
'ack$i',
|
||||
),
|
||||
_createJsonLine(SyncEntityType.userDeleteV1.toString(), SyncUserDeleteV1(userId: "user$i").toJson(), 'ack$i'),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -126,113 +112,91 @@ void main() {
|
||||
verify(() => mockHttpClient.close()).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
'streamChanges does not process remaining lines in finally block if aborted',
|
||||
() async {
|
||||
int onDataCallCount = 0;
|
||||
bool abortWasCalledInCallback = false;
|
||||
test('streamChanges does not process remaining lines in finally block if aborted', () async {
|
||||
int onDataCallCount = 0;
|
||||
bool abortWasCalledInCallback = false;
|
||||
|
||||
onDataCallback(List<SyncEvent> events, Function() abort) {
|
||||
onDataCallCount++;
|
||||
if (onDataCallCount == 1) {
|
||||
abort();
|
||||
abortWasCalledInCallback = true;
|
||||
} else {
|
||||
fail("onData called more than once after abort was invoked");
|
||||
}
|
||||
onDataCallback(List<SyncEvent> events, Function() abort) {
|
||||
onDataCallCount++;
|
||||
if (onDataCallCount == 1) {
|
||||
abort();
|
||||
abortWasCalledInCallback = true;
|
||||
} else {
|
||||
fail("onData called more than once after abort was invoked");
|
||||
}
|
||||
}
|
||||
|
||||
final streamChangesFuture = streamChanges(onDataCallback);
|
||||
final streamChangesFuture = streamChanges(onDataCallback);
|
||||
|
||||
await pumpEventQueue();
|
||||
await pumpEventQueue();
|
||||
|
||||
for (int i = 0; i < testBatchSize; i++) {
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(
|
||||
SyncEntityType.userDeleteV1.toString(),
|
||||
SyncUserDeleteV1(userId: "user$i").toJson(),
|
||||
'ack$i',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// emit a single event to skip batching and trigger finally
|
||||
for (int i = 0; i < testBatchSize; i++) {
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(
|
||||
SyncEntityType.userDeleteV1.toString(),
|
||||
SyncUserDeleteV1(userId: "user100").toJson(),
|
||||
'ack100',
|
||||
),
|
||||
_createJsonLine(SyncEntityType.userDeleteV1.toString(), SyncUserDeleteV1(userId: "user$i").toJson(), 'ack$i'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await responseStreamController.close();
|
||||
await expectLater(streamChangesFuture, completes);
|
||||
// emit a single event to skip batching and trigger finally
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(SyncEntityType.userDeleteV1.toString(), SyncUserDeleteV1(userId: "user100").toJson(), 'ack100'),
|
||||
),
|
||||
);
|
||||
|
||||
expect(onDataCallCount, 1);
|
||||
expect(abortWasCalledInCallback, isTrue);
|
||||
verify(() => mockHttpClient.close()).called(1);
|
||||
},
|
||||
);
|
||||
await responseStreamController.close();
|
||||
await expectLater(streamChangesFuture, completes);
|
||||
|
||||
test(
|
||||
'streamChanges processes remaining lines in finally block if not aborted',
|
||||
() async {
|
||||
int onDataCallCount = 0;
|
||||
List<SyncEvent> receivedEventsBatch1 = [];
|
||||
List<SyncEvent> receivedEventsBatch2 = [];
|
||||
expect(onDataCallCount, 1);
|
||||
expect(abortWasCalledInCallback, isTrue);
|
||||
verify(() => mockHttpClient.close()).called(1);
|
||||
});
|
||||
|
||||
onDataCallback(List<SyncEvent> events, Function() _) {
|
||||
onDataCallCount++;
|
||||
if (onDataCallCount == 1) {
|
||||
receivedEventsBatch1 = events;
|
||||
} else if (onDataCallCount == 2) {
|
||||
receivedEventsBatch2 = events;
|
||||
} else {
|
||||
fail("onData called more than expected");
|
||||
}
|
||||
test('streamChanges processes remaining lines in finally block if not aborted', () async {
|
||||
int onDataCallCount = 0;
|
||||
List<SyncEvent> receivedEventsBatch1 = [];
|
||||
List<SyncEvent> receivedEventsBatch2 = [];
|
||||
|
||||
onDataCallback(List<SyncEvent> events, Function() _) {
|
||||
onDataCallCount++;
|
||||
if (onDataCallCount == 1) {
|
||||
receivedEventsBatch1 = events;
|
||||
} else if (onDataCallCount == 2) {
|
||||
receivedEventsBatch2 = events;
|
||||
} else {
|
||||
fail("onData called more than expected");
|
||||
}
|
||||
}
|
||||
|
||||
final streamChangesFuture = streamChanges(onDataCallback);
|
||||
final streamChangesFuture = streamChanges(onDataCallback);
|
||||
|
||||
await pumpEventQueue();
|
||||
await pumpEventQueue();
|
||||
|
||||
// Batch 1
|
||||
for (int i = 0; i < testBatchSize; i++) {
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(
|
||||
SyncEntityType.userDeleteV1.toString(),
|
||||
SyncUserDeleteV1(userId: "user$i").toJson(),
|
||||
'ack$i',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Partial Batch 2
|
||||
// Batch 1
|
||||
for (int i = 0; i < testBatchSize; i++) {
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(
|
||||
SyncEntityType.userDeleteV1.toString(),
|
||||
SyncUserDeleteV1(userId: "user100").toJson(),
|
||||
'ack100',
|
||||
),
|
||||
_createJsonLine(SyncEntityType.userDeleteV1.toString(), SyncUserDeleteV1(userId: "user$i").toJson(), 'ack$i'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await responseStreamController.close();
|
||||
await expectLater(streamChangesFuture, completes);
|
||||
// Partial Batch 2
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(SyncEntityType.userDeleteV1.toString(), SyncUserDeleteV1(userId: "user100").toJson(), 'ack100'),
|
||||
),
|
||||
);
|
||||
|
||||
expect(onDataCallCount, 2);
|
||||
expect(receivedEventsBatch1.length, testBatchSize);
|
||||
expect(receivedEventsBatch2.length, 1);
|
||||
verify(() => mockHttpClient.close()).called(1);
|
||||
},
|
||||
);
|
||||
await responseStreamController.close();
|
||||
await expectLater(streamChangesFuture, completes);
|
||||
|
||||
expect(onDataCallCount, 2);
|
||||
expect(receivedEventsBatch1.length, testBatchSize);
|
||||
expect(receivedEventsBatch2.length, 1);
|
||||
verify(() => mockHttpClient.close()).called(1);
|
||||
});
|
||||
|
||||
test('streamChanges handles stream error gracefully', () async {
|
||||
final streamError = Exception("Network Error");
|
||||
@@ -248,11 +212,7 @@ void main() {
|
||||
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(
|
||||
SyncEntityType.userDeleteV1.toString(),
|
||||
SyncUserDeleteV1(userId: "user1").toJson(),
|
||||
'ack1',
|
||||
),
|
||||
_createJsonLine(SyncEntityType.userDeleteV1.toString(), SyncUserDeleteV1(userId: "user1").toJson(), 'ack1'),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -40,12 +40,9 @@ class MockHttpOverrides extends HttpOverrides {
|
||||
|
||||
final cancelOnError = invocation.namedArguments[#cancelOnError] as bool;
|
||||
|
||||
return Stream<List<int>>.fromIterable([kTransparentImage.toList()]).listen(
|
||||
onData,
|
||||
onDone: onDone,
|
||||
onError: onError,
|
||||
cancelOnError: cancelOnError,
|
||||
);
|
||||
return Stream<List<int>>.fromIterable([
|
||||
kTransparentImage.toList(),
|
||||
]).listen(onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError);
|
||||
});
|
||||
|
||||
return client;
|
||||
|
||||
@@ -49,19 +49,8 @@ final _activities = [
|
||||
comment: 'Second Activity',
|
||||
user: UserStub.user1,
|
||||
),
|
||||
Activity(
|
||||
id: '3',
|
||||
createdAt: DateTime(300),
|
||||
type: ActivityType.like,
|
||||
assetId: 'asset-1',
|
||||
user: UserStub.user2,
|
||||
),
|
||||
Activity(
|
||||
id: '4',
|
||||
createdAt: DateTime(400),
|
||||
type: ActivityType.like,
|
||||
user: UserStub.user1,
|
||||
),
|
||||
Activity(id: '3', createdAt: DateTime(300), type: ActivityType.like, assetId: 'asset-1', user: UserStub.user2),
|
||||
Activity(id: '4', createdAt: DateTime(400), type: ActivityType.like, user: UserStub.user1),
|
||||
];
|
||||
|
||||
void main() {
|
||||
@@ -85,10 +74,7 @@ void main() {
|
||||
mockCurrentAssetProvider = MockCurrentAssetProvider(AssetStub.image1);
|
||||
activityMock = MockAlbumActivity(_activities);
|
||||
overrides = [
|
||||
albumActivityProvider(
|
||||
AlbumStub.twoAsset.remoteId!,
|
||||
AssetStub.image1.remoteId!,
|
||||
).overrideWith(() => activityMock),
|
||||
albumActivityProvider(AlbumStub.twoAsset.remoteId!, AssetStub.image1.remoteId!).overrideWith(() => activityMock),
|
||||
currentAlbumProvider.overrideWith(() => mockCurrentAlbumProvider),
|
||||
currentAssetProvider.overrideWith(() => mockCurrentAssetProvider),
|
||||
];
|
||||
@@ -108,147 +94,82 @@ void main() {
|
||||
});
|
||||
|
||||
group("App bar", () {
|
||||
testWidgets(
|
||||
"No title when currentAsset != null",
|
||||
(tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
const ActivitiesPage(),
|
||||
overrides: overrides,
|
||||
);
|
||||
testWidgets("No title when currentAsset != null", (tester) async {
|
||||
await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides);
|
||||
|
||||
final listTile = tester.widget<AppBar>(find.byType(AppBar));
|
||||
expect(listTile.title, isNull);
|
||||
},
|
||||
);
|
||||
final listTile = tester.widget<AppBar>(find.byType(AppBar));
|
||||
expect(listTile.title, isNull);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
"Album name as title when currentAsset == null",
|
||||
(tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
const ActivitiesPage(),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
testWidgets("Album name as title when currentAsset == null", (tester) async {
|
||||
await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
mockCurrentAssetProvider.state = null;
|
||||
await tester.pumpAndSettle();
|
||||
mockCurrentAssetProvider.state = null;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(AlbumStub.twoAsset.name), findsOneWidget);
|
||||
final listTile = tester.widget<AppBar>(find.byType(AppBar));
|
||||
expect(listTile.title, isNotNull);
|
||||
},
|
||||
);
|
||||
expect(find.text(AlbumStub.twoAsset.name), findsOneWidget);
|
||||
final listTile = tester.widget<AppBar>(find.byType(AppBar));
|
||||
expect(listTile.title, isNotNull);
|
||||
});
|
||||
});
|
||||
|
||||
group("Body", () {
|
||||
testWidgets(
|
||||
"Contains a stack with Activity List and Activity Input",
|
||||
(tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
const ActivitiesPage(),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
testWidgets("Contains a stack with Activity List and Activity Input", (tester) async {
|
||||
await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(Stack),
|
||||
matching: find.byType(ActivityTextField),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.descendant(of: find.byType(Stack), matching: find.byType(ActivityTextField)), findsOneWidget);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(Stack),
|
||||
matching: find.byType(ListView),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
},
|
||||
);
|
||||
expect(find.descendant(of: find.byType(Stack), matching: find.byType(ListView)), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
"List Contains all dismissible activities",
|
||||
(tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
const ActivitiesPage(),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
testWidgets("List Contains all dismissible activities", (tester) async {
|
||||
await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final listFinder = find.descendant(
|
||||
of: find.byType(Stack),
|
||||
matching: find.byType(ListView),
|
||||
);
|
||||
final listChildren = find.descendant(
|
||||
of: listFinder,
|
||||
matching: find.byType(DismissibleActivity),
|
||||
);
|
||||
expect(listChildren, findsNWidgets(_activities.length));
|
||||
},
|
||||
);
|
||||
final listFinder = find.descendant(of: find.byType(Stack), matching: find.byType(ListView));
|
||||
final listChildren = find.descendant(of: listFinder, matching: find.byType(DismissibleActivity));
|
||||
expect(listChildren, findsNWidgets(_activities.length));
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
"Submitting text input adds a comment with the text",
|
||||
(tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
const ActivitiesPage(),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
testWidgets("Submitting text input adds a comment with the text", (tester) async {
|
||||
await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
when(() => activityMock.addComment(any())).thenAnswer((_) => Future.value());
|
||||
when(() => activityMock.addComment(any())).thenAnswer((_) => Future.value());
|
||||
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, 'Test comment');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, 'Test comment');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
|
||||
verify(() => activityMock.addComment('Test comment'));
|
||||
},
|
||||
);
|
||||
verify(() => activityMock.addComment('Test comment'));
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
"Owner can remove all activities",
|
||||
(tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
const ActivitiesPage(),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
testWidgets("Owner can remove all activities", (tester) async {
|
||||
await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final deletableActivityFinder = find.byWidgetPredicate(
|
||||
(widget) => widget is DismissibleActivity && widget.onDismiss != null,
|
||||
);
|
||||
expect(deletableActivityFinder, findsNWidgets(_activities.length));
|
||||
},
|
||||
);
|
||||
final deletableActivityFinder = find.byWidgetPredicate(
|
||||
(widget) => widget is DismissibleActivity && widget.onDismiss != null,
|
||||
);
|
||||
expect(deletableActivityFinder, findsNWidgets(_activities.length));
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
"Non-Owner can remove only their activities",
|
||||
(tester) async {
|
||||
final mockCurrentUser = MockCurrentUserProvider();
|
||||
testWidgets("Non-Owner can remove only their activities", (tester) async {
|
||||
final mockCurrentUser = MockCurrentUserProvider();
|
||||
|
||||
await tester.pumpConsumerWidget(
|
||||
const ActivitiesPage(),
|
||||
overrides: [
|
||||
...overrides,
|
||||
currentUserProvider.overrideWith((ref) => mockCurrentUser),
|
||||
],
|
||||
);
|
||||
mockCurrentUser.state = UserStub.user1;
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpConsumerWidget(
|
||||
const ActivitiesPage(),
|
||||
overrides: [...overrides, currentUserProvider.overrideWith((ref) => mockCurrentUser)],
|
||||
);
|
||||
mockCurrentUser.state = UserStub.user1;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final deletableActivityFinder = find.byWidgetPredicate(
|
||||
(widget) => widget is DismissibleActivity && widget.onDismiss != null,
|
||||
);
|
||||
expect(
|
||||
deletableActivityFinder,
|
||||
findsNWidgets(
|
||||
_activities.where((a) => a.user == UserStub.user1).length,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
final deletableActivityFinder = find.byWidgetPredicate(
|
||||
(widget) => widget is DismissibleActivity && widget.onDismiss != null,
|
||||
);
|
||||
expect(deletableActivityFinder, findsNWidgets(_activities.where((a) => a.user == UserStub.user1).length));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,19 +26,8 @@ final _activities = [
|
||||
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,
|
||||
),
|
||||
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() {
|
||||
@@ -70,11 +59,7 @@ void main() {
|
||||
// Init and wait for providers future to complete
|
||||
provider = albumActivityProvider('test-album', 'test-asset');
|
||||
listener = ListenerMock();
|
||||
container.listen(
|
||||
provider,
|
||||
listener.call,
|
||||
fireImmediately: true,
|
||||
);
|
||||
container.listen(provider, listener.call, fireImmediately: true);
|
||||
|
||||
await container.read(provider.future);
|
||||
});
|
||||
@@ -83,18 +68,14 @@ void main() {
|
||||
verifyInOrder([
|
||||
() => listener.call(null, const AsyncLoading()),
|
||||
() => listener.call(
|
||||
const AsyncLoading(),
|
||||
any(
|
||||
that: allOf(
|
||||
[
|
||||
isA<AsyncData<List<Activity>>>(),
|
||||
predicate(
|
||||
(AsyncData<List<Activity>> ad) => ad.requireValue.every((e) => _activities.contains(e)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const AsyncLoading(),
|
||||
any(
|
||||
that: allOf([
|
||||
isA<AsyncData<List<Activity>>>(),
|
||||
predicate((AsyncData<List<Activity>> ad) => ad.requireValue.every((e) => _activities.contains(e))),
|
||||
]),
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
verifyNoMoreInteractions(listener);
|
||||
@@ -102,30 +83,15 @@ void main() {
|
||||
|
||||
group('addLike()', () {
|
||||
test('Like successfully added', () async {
|
||||
final like = Activity(
|
||||
id: '5',
|
||||
createdAt: DateTime(2023),
|
||||
type: ActivityType.like,
|
||||
user: UserStub.admin,
|
||||
);
|
||||
final like = Activity(id: '5', createdAt: DateTime(2023), type: ActivityType.like, user: UserStub.admin);
|
||||
|
||||
when(
|
||||
() => activityMock.addActivity(
|
||||
'test-album',
|
||||
ActivityType.like,
|
||||
assetId: 'test-asset',
|
||||
),
|
||||
() => 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',
|
||||
),
|
||||
);
|
||||
verify(() => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset'));
|
||||
|
||||
final activities = await container.read(provider.future);
|
||||
expect(activities, hasLength(5));
|
||||
@@ -136,31 +102,14 @@ void main() {
|
||||
});
|
||||
|
||||
test('Like failed', () async {
|
||||
final like = Activity(
|
||||
id: '5',
|
||||
createdAt: DateTime(2023),
|
||||
type: ActivityType.like,
|
||||
user: UserStub.admin,
|
||||
);
|
||||
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),
|
||||
);
|
||||
() => 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',
|
||||
),
|
||||
);
|
||||
verify(() => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset'));
|
||||
|
||||
final activities = await container.read(provider.future);
|
||||
expect(activities, hasLength(4));
|
||||
@@ -174,16 +123,11 @@ void main() {
|
||||
|
||||
await container.read(provider.notifier).removeActivity('3');
|
||||
|
||||
verify(
|
||||
() => activityMock.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'))),
|
||||
);
|
||||
expect(activities, isNot(anyElement(predicate((Activity a) => a.id == '3'))));
|
||||
|
||||
verifyNever(() => activityStatisticsMock.removeActivity());
|
||||
});
|
||||
@@ -195,10 +139,7 @@ void main() {
|
||||
|
||||
final activities = await container.read(provider.future);
|
||||
expect(activities, hasLength(4));
|
||||
expect(
|
||||
activities,
|
||||
anyElement(predicate((Activity a) => a.id == '3')),
|
||||
);
|
||||
expect(activities, anyElement(predicate((Activity a) => a.id == '3')));
|
||||
});
|
||||
|
||||
test('Comment successfully removed', () async {
|
||||
@@ -207,10 +148,7 @@ void main() {
|
||||
await container.read(provider.notifier).removeActivity('1');
|
||||
|
||||
final activities = await container.read(provider.future);
|
||||
expect(
|
||||
activities,
|
||||
isNot(anyElement(predicate((Activity a) => a.id == '1'))),
|
||||
);
|
||||
expect(activities, isNot(anyElement(predicate((Activity a) => a.id == '1'))));
|
||||
|
||||
verify(() => activityStatisticsMock.removeActivity());
|
||||
});
|
||||
@@ -281,11 +219,7 @@ void main() {
|
||||
);
|
||||
|
||||
when(
|
||||
() => activityMock.addActivity(
|
||||
'test-album',
|
||||
ActivityType.comment,
|
||||
comment: 'Test-Comment',
|
||||
),
|
||||
() => 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]);
|
||||
@@ -294,12 +228,7 @@ void main() {
|
||||
await container.read(albumProvider.notifier).addComment('Test-Comment');
|
||||
|
||||
verify(
|
||||
() => activityMock.addActivity(
|
||||
'test-album',
|
||||
ActivityType.comment,
|
||||
assetId: null,
|
||||
comment: 'Test-Comment',
|
||||
),
|
||||
() => activityMock.addActivity('test-album', ActivityType.comment, assetId: null, comment: 'Test-Comment'),
|
||||
);
|
||||
|
||||
final activities = await container.read(albumProvider.future);
|
||||
@@ -327,9 +256,7 @@ void main() {
|
||||
assetId: 'test-asset',
|
||||
comment: 'Test-Comment',
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => AsyncError(Exception('Error'), StackTrace.current),
|
||||
);
|
||||
).thenAnswer((_) async => AsyncError(Exception('Error'), StackTrace.current));
|
||||
|
||||
await container.read(provider.notifier).addComment('Test-Comment');
|
||||
|
||||
|
||||
@@ -15,11 +15,7 @@ void main() {
|
||||
|
||||
setUp(() async {
|
||||
activityMock = ActivityServiceMock();
|
||||
container = TestUtils.createContainer(
|
||||
overrides: [
|
||||
activityServiceProvider.overrideWith((ref) => activityMock),
|
||||
],
|
||||
);
|
||||
container = TestUtils.createContainer(overrides: [activityServiceProvider.overrideWith((ref) => activityMock)]);
|
||||
listener = ListenerMock();
|
||||
});
|
||||
|
||||
@@ -31,34 +27,21 @@ void main() {
|
||||
// Read here to make the getStatistics call
|
||||
container.read(activityStatisticsProvider('test-album', 'test-asset'));
|
||||
|
||||
container.listen(
|
||||
activityStatisticsProvider('test-album', 'test-asset'),
|
||||
listener.call,
|
||||
fireImmediately: true,
|
||||
);
|
||||
container.listen(activityStatisticsProvider('test-album', 'test-asset'), listener.call, fireImmediately: true);
|
||||
|
||||
// Sleep for the getStatistics future to resolve
|
||||
await Future.delayed(const Duration(milliseconds: 1));
|
||||
|
||||
verifyInOrder([
|
||||
() => listener.call(null, 0),
|
||||
() => listener.call(0, 5),
|
||||
]);
|
||||
verifyInOrder([() => listener.call(null, 0), () => listener.call(0, 5)]);
|
||||
|
||||
verifyNoMoreInteractions(listener);
|
||||
});
|
||||
|
||||
test('Adds activity', () async {
|
||||
when(
|
||||
() => activityMock.getStatistics('test-album'),
|
||||
).thenAnswer((_) async => const ActivityStats(comments: 10));
|
||||
when(() => activityMock.getStatistics('test-album')).thenAnswer((_) async => const ActivityStats(comments: 10));
|
||||
|
||||
final provider = activityStatisticsProvider('test-album');
|
||||
container.listen(
|
||||
provider,
|
||||
listener.call,
|
||||
fireImmediately: true,
|
||||
);
|
||||
container.listen(provider, listener.call, fireImmediately: true);
|
||||
|
||||
// Sleep for the getStatistics future to resolve
|
||||
await Future.delayed(const Duration(milliseconds: 1));
|
||||
@@ -75,11 +58,7 @@ void main() {
|
||||
).thenAnswer((_) async => const ActivityStats(comments: 10));
|
||||
|
||||
final provider = activityStatisticsProvider('new-album', 'test-asset');
|
||||
container.listen(
|
||||
provider,
|
||||
listener.call,
|
||||
fireImmediately: true,
|
||||
);
|
||||
container.listen(provider, listener.call, fireImmediately: true);
|
||||
|
||||
// Sleep for the getStatistics future to resolve
|
||||
await Future.delayed(const Duration(milliseconds: 1));
|
||||
|
||||
@@ -49,12 +49,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Returns an Input text field', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTextField(
|
||||
onSubmit: (_) {},
|
||||
),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides);
|
||||
|
||||
expect(find.byType(TextField), findsOneWidget);
|
||||
});
|
||||
@@ -63,76 +58,38 @@ void main() {
|
||||
final userProvider = MockCurrentUserProvider();
|
||||
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTextField(
|
||||
onSubmit: (_) {},
|
||||
),
|
||||
overrides: [
|
||||
currentUserProvider.overrideWith((ref) => userProvider),
|
||||
...overrides,
|
||||
],
|
||||
ActivityTextField(onSubmit: (_) {}),
|
||||
overrides: [currentUserProvider.overrideWith((ref) => userProvider), ...overrides],
|
||||
);
|
||||
|
||||
expect(find.byType(UserCircleAvatar), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('UserCircleAvatar displayed when user != null', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTextField(
|
||||
onSubmit: (_) {},
|
||||
),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides);
|
||||
|
||||
expect(find.byType(UserCircleAvatar), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Filled icon if likedId != null',
|
||||
(tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTextField(
|
||||
onSubmit: (_) {},
|
||||
likeId: '1',
|
||||
),
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.widgetWithIcon(IconButton, Icons.favorite_rounded),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.widgetWithIcon(IconButton, Icons.favorite_border_rounded),
|
||||
findsNothing,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('Bordered icon if likedId == null', (tester) async {
|
||||
testWidgets('Filled icon if likedId != null', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTextField(
|
||||
onSubmit: (_) {},
|
||||
),
|
||||
ActivityTextField(onSubmit: (_) {}, likeId: '1'),
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.widgetWithIcon(IconButton, Icons.favorite_border_rounded),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.widgetWithIcon(IconButton, Icons.favorite_rounded),
|
||||
findsNothing,
|
||||
);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_rounded), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_border_rounded), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Bordered icon if likedId == null', (tester) async {
|
||||
await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides);
|
||||
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_border_rounded), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_rounded), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Adds new like', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTextField(
|
||||
onSubmit: (_) {},
|
||||
),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides);
|
||||
|
||||
when(() => activityMock.addLike()).thenAnswer((_) => Future.value());
|
||||
|
||||
@@ -144,10 +101,7 @@ void main() {
|
||||
|
||||
testWidgets('Removes like if already liked', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTextField(
|
||||
onSubmit: (_) {},
|
||||
likeId: 'test-suffix',
|
||||
),
|
||||
ActivityTextField(onSubmit: (_) {}, likeId: 'test-suffix'),
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
@@ -163,10 +117,7 @@ void main() {
|
||||
String? receivedText;
|
||||
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTextField(
|
||||
onSubmit: (text) => receivedText = text,
|
||||
likeId: 'test-suffix',
|
||||
),
|
||||
ActivityTextField(onSubmit: (text) => receivedText = text, likeId: 'test-suffix'),
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
@@ -180,11 +131,7 @@ void main() {
|
||||
String? receviedText;
|
||||
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTextField(
|
||||
onSubmit: (text) => receviedText = text,
|
||||
isEnabled: false,
|
||||
likeId: 'test-suffix',
|
||||
),
|
||||
ActivityTextField(onSubmit: (text) => receviedText = text, isEnabled: false, likeId: 'test-suffix'),
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
|
||||
@@ -43,14 +43,7 @@ void main() {
|
||||
|
||||
testWidgets('Returns a ListTile', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(
|
||||
Activity(
|
||||
id: '1',
|
||||
createdAt: DateTime(100),
|
||||
type: ActivityType.like,
|
||||
user: UserStub.admin,
|
||||
),
|
||||
),
|
||||
ActivityTile(Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin)),
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
@@ -59,14 +52,7 @@ void main() {
|
||||
|
||||
testWidgets('No trailing widget when activity assetId == null', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(
|
||||
Activity(
|
||||
id: '1',
|
||||
createdAt: DateTime(100),
|
||||
type: ActivityType.like,
|
||||
user: UserStub.admin,
|
||||
),
|
||||
),
|
||||
ActivityTile(Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin)),
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
@@ -77,13 +63,7 @@ void main() {
|
||||
testWidgets('Asset Thumbanil as trailing widget when activity assetId != null', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(
|
||||
Activity(
|
||||
id: '1',
|
||||
createdAt: DateTime(100),
|
||||
type: ActivityType.like,
|
||||
user: UserStub.admin,
|
||||
assetId: '1',
|
||||
),
|
||||
Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin, assetId: '1'),
|
||||
),
|
||||
overrides: overrides,
|
||||
);
|
||||
@@ -96,13 +76,7 @@ void main() {
|
||||
testWidgets('No trailing widget when current asset != null', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(
|
||||
Activity(
|
||||
id: '1',
|
||||
createdAt: DateTime(100),
|
||||
type: ActivityType.like,
|
||||
user: UserStub.admin,
|
||||
assetId: '1',
|
||||
),
|
||||
Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin, assetId: '1'),
|
||||
),
|
||||
overrides: overrides,
|
||||
);
|
||||
@@ -115,37 +89,23 @@ void main() {
|
||||
});
|
||||
|
||||
group('Like Activity', () {
|
||||
final activity = Activity(
|
||||
id: '1',
|
||||
createdAt: DateTime(100),
|
||||
type: ActivityType.like,
|
||||
user: UserStub.admin,
|
||||
);
|
||||
final activity = Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin);
|
||||
|
||||
testWidgets('Like contains filled heart as leading', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(activity),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides);
|
||||
|
||||
// Leading widget should not be null
|
||||
final listTile = tester.widget<ListTile>(find.byType(ListTile));
|
||||
expect(listTile.leading, isNotNull);
|
||||
|
||||
// And should have a favorite icon
|
||||
final favoIconFinder = find.widgetWithIcon(
|
||||
listTile.leading!.runtimeType,
|
||||
Icons.favorite_rounded,
|
||||
);
|
||||
final favoIconFinder = find.widgetWithIcon(listTile.leading!.runtimeType, Icons.favorite_rounded);
|
||||
|
||||
expect(favoIconFinder, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Like title is center aligned', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(activity),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides);
|
||||
|
||||
final listTile = tester.widget<ListTile>(find.byType(ListTile));
|
||||
|
||||
@@ -153,10 +113,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('No subtitle for likes', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(activity),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides);
|
||||
|
||||
final listTile = tester.widget<ListTile>(find.byType(ListTile));
|
||||
|
||||
@@ -174,10 +131,7 @@ void main() {
|
||||
);
|
||||
|
||||
testWidgets('Comment contains User Circle Avatar as leading', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(activity),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides);
|
||||
|
||||
final userAvatarFinder = find.byType(UserCircleAvatar);
|
||||
expect(userAvatarFinder, findsOneWidget);
|
||||
@@ -192,10 +146,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Comment title is top aligned', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(activity),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides);
|
||||
|
||||
final listTile = tester.widget<ListTile>(find.byType(ListTile));
|
||||
|
||||
@@ -203,21 +154,12 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Contains comment text as subtitle', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
ActivityTile(activity),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides);
|
||||
|
||||
final listTile = tester.widget<ListTile>(find.byType(ListTile));
|
||||
|
||||
expect(listTile.subtitle, isNotNull);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(ListTile),
|
||||
matching: find.text(activity.comment!),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.descendant(of: find.byType(ListTile), matching: find.text(activity.comment!)), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,12 +15,7 @@ import '../../test_utils.dart';
|
||||
import '../../widget_tester_extensions.dart';
|
||||
import '../asset_viewer/asset_viewer_mocks.dart';
|
||||
|
||||
final activity = Activity(
|
||||
id: '1',
|
||||
createdAt: DateTime(100),
|
||||
type: ActivityType.like,
|
||||
user: UserStub.admin,
|
||||
);
|
||||
final activity = Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin);
|
||||
|
||||
void main() {
|
||||
late MockCurrentAssetProvider assetProvider;
|
||||
@@ -34,10 +29,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Returns a Dismissible', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
DismissibleActivity('1', ActivityTile(activity)),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(DismissibleActivity('1', ActivityTile(activity)), overrides: overrides);
|
||||
|
||||
expect(find.byType(Dismissible), findsOneWidget);
|
||||
});
|
||||
@@ -58,11 +50,7 @@ void main() {
|
||||
testWidgets('Ok action in ConfirmDialog should call onDismiss with activityId', (tester) async {
|
||||
String? receivedActivityId;
|
||||
await tester.pumpConsumerWidget(
|
||||
DismissibleActivity(
|
||||
'1',
|
||||
ActivityTile(activity),
|
||||
onDismiss: (id) => receivedActivityId = id,
|
||||
),
|
||||
DismissibleActivity('1', ActivityTile(activity), onDismiss: (id) => receivedActivityId = id),
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
@@ -91,10 +79,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('No delete dialog if onDismiss is not set', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
DismissibleActivity('1', ActivityTile(activity)),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(DismissibleActivity('1', ActivityTile(activity)), overrides: overrides);
|
||||
|
||||
final dismissible = find.byType(Dismissible);
|
||||
await tester.drag(dismissible, const Offset(500, 0));
|
||||
@@ -104,10 +89,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('No icon for background if onDismiss is not set', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
DismissibleActivity('1', ActivityTile(activity)),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(DismissibleActivity('1', ActivityTile(activity)), overrides: overrides);
|
||||
|
||||
final dismissible = find.byType(Dismissible);
|
||||
await tester.drag(dismissible, const Offset(-500, 0));
|
||||
|
||||
@@ -22,12 +22,7 @@ void main() {
|
||||
db = await TestUtils.initIsar();
|
||||
});
|
||||
|
||||
final albums = [
|
||||
AlbumStub.emptyAlbum,
|
||||
AlbumStub.sharedWithUser,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.twoAsset,
|
||||
];
|
||||
final albums = [AlbumStub.emptyAlbum, AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.twoAsset];
|
||||
|
||||
setUp(() {
|
||||
db.writeTxnSync(() {
|
||||
@@ -48,23 +43,13 @@ void main() {
|
||||
const created = AlbumSortMode.created;
|
||||
test("Created time - ASC", () {
|
||||
final sorted = created.sortFn(albums, false);
|
||||
final sortedList = [
|
||||
AlbumStub.emptyAlbum,
|
||||
AlbumStub.twoAsset,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.sharedWithUser,
|
||||
];
|
||||
final sortedList = [AlbumStub.emptyAlbum, AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
|
||||
test("Created time - DESC", () {
|
||||
final sorted = created.sortFn(albums, true);
|
||||
final sortedList = [
|
||||
AlbumStub.sharedWithUser,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.twoAsset,
|
||||
AlbumStub.emptyAlbum,
|
||||
];
|
||||
final sortedList = [AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.twoAsset, AlbumStub.emptyAlbum];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
});
|
||||
@@ -73,23 +58,13 @@ void main() {
|
||||
const assetCount = AlbumSortMode.assetCount;
|
||||
test("Asset Count - ASC", () {
|
||||
final sorted = assetCount.sortFn(albums, false);
|
||||
final sortedList = [
|
||||
AlbumStub.emptyAlbum,
|
||||
AlbumStub.sharedWithUser,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.twoAsset,
|
||||
];
|
||||
final sortedList = [AlbumStub.emptyAlbum, AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.twoAsset];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
|
||||
test("Asset Count - DESC", () {
|
||||
final sorted = assetCount.sortFn(albums, true);
|
||||
final sortedList = [
|
||||
AlbumStub.twoAsset,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.sharedWithUser,
|
||||
AlbumStub.emptyAlbum,
|
||||
];
|
||||
final sortedList = [AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser, AlbumStub.emptyAlbum];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
});
|
||||
@@ -98,23 +73,13 @@ void main() {
|
||||
const lastModified = AlbumSortMode.lastModified;
|
||||
test("Last modified - ASC", () {
|
||||
final sorted = lastModified.sortFn(albums, false);
|
||||
final sortedList = [
|
||||
AlbumStub.twoAsset,
|
||||
AlbumStub.emptyAlbum,
|
||||
AlbumStub.sharedWithUser,
|
||||
AlbumStub.oneAsset,
|
||||
];
|
||||
final sortedList = [AlbumStub.twoAsset, AlbumStub.emptyAlbum, AlbumStub.sharedWithUser, AlbumStub.oneAsset];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
|
||||
test("Last modified - DESC", () {
|
||||
final sorted = lastModified.sortFn(albums, true);
|
||||
final sortedList = [
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.sharedWithUser,
|
||||
AlbumStub.emptyAlbum,
|
||||
AlbumStub.twoAsset,
|
||||
];
|
||||
final sortedList = [AlbumStub.oneAsset, AlbumStub.sharedWithUser, AlbumStub.emptyAlbum, AlbumStub.twoAsset];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
});
|
||||
@@ -123,23 +88,13 @@ void main() {
|
||||
const created = AlbumSortMode.created;
|
||||
test("Created - ASC", () {
|
||||
final sorted = created.sortFn(albums, false);
|
||||
final sortedList = [
|
||||
AlbumStub.emptyAlbum,
|
||||
AlbumStub.twoAsset,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.sharedWithUser,
|
||||
];
|
||||
final sortedList = [AlbumStub.emptyAlbum, AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
|
||||
test("Created - DESC", () {
|
||||
final sorted = created.sortFn(albums, true);
|
||||
final sortedList = [
|
||||
AlbumStub.sharedWithUser,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.twoAsset,
|
||||
AlbumStub.emptyAlbum,
|
||||
];
|
||||
final sortedList = [AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.twoAsset, AlbumStub.emptyAlbum];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
});
|
||||
@@ -148,15 +103,12 @@ void main() {
|
||||
const mostRecent = AlbumSortMode.mostRecent;
|
||||
|
||||
test("Most Recent - DESC", () {
|
||||
final sorted = mostRecent.sortFn(
|
||||
[
|
||||
AlbumStub.create2020end2020Album,
|
||||
AlbumStub.create2020end2022Album,
|
||||
AlbumStub.create2020end2024Album,
|
||||
AlbumStub.create2020end2026Album,
|
||||
],
|
||||
false,
|
||||
);
|
||||
final sorted = mostRecent.sortFn([
|
||||
AlbumStub.create2020end2020Album,
|
||||
AlbumStub.create2020end2022Album,
|
||||
AlbumStub.create2020end2024Album,
|
||||
AlbumStub.create2020end2026Album,
|
||||
], false);
|
||||
final sortedList = [
|
||||
AlbumStub.create2020end2026Album,
|
||||
AlbumStub.create2020end2024Album,
|
||||
@@ -167,15 +119,12 @@ void main() {
|
||||
});
|
||||
|
||||
test("Most Recent - ASC", () {
|
||||
final sorted = mostRecent.sortFn(
|
||||
[
|
||||
AlbumStub.create2020end2020Album,
|
||||
AlbumStub.create2020end2022Album,
|
||||
AlbumStub.create2020end2024Album,
|
||||
AlbumStub.create2020end2026Album,
|
||||
],
|
||||
true,
|
||||
);
|
||||
final sorted = mostRecent.sortFn([
|
||||
AlbumStub.create2020end2020Album,
|
||||
AlbumStub.create2020end2022Album,
|
||||
AlbumStub.create2020end2024Album,
|
||||
AlbumStub.create2020end2026Album,
|
||||
], true);
|
||||
final sortedList = [
|
||||
AlbumStub.create2020end2020Album,
|
||||
AlbumStub.create2020end2022Album,
|
||||
@@ -191,23 +140,13 @@ void main() {
|
||||
|
||||
test("Most Oldest - ASC", () {
|
||||
final sorted = mostOldest.sortFn(albums, false);
|
||||
final sortedList = [
|
||||
AlbumStub.twoAsset,
|
||||
AlbumStub.emptyAlbum,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.sharedWithUser,
|
||||
];
|
||||
final sortedList = [AlbumStub.twoAsset, AlbumStub.emptyAlbum, AlbumStub.oneAsset, AlbumStub.sharedWithUser];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
|
||||
test("Most Oldest - DESC", () {
|
||||
final sorted = mostOldest.sortFn(albums, true);
|
||||
final sortedList = [
|
||||
AlbumStub.sharedWithUser,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.emptyAlbum,
|
||||
AlbumStub.twoAsset,
|
||||
];
|
||||
final sortedList = [AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.emptyAlbum, AlbumStub.twoAsset];
|
||||
expect(sorted, orderedEquals(sortedList));
|
||||
});
|
||||
});
|
||||
@@ -221,67 +160,43 @@ void main() {
|
||||
setUp(() async {
|
||||
settingsMock = MockAppSettingsService();
|
||||
container = TestUtils.createContainer(
|
||||
overrides: [
|
||||
appSettingsServiceProvider.overrideWith((ref) => settingsMock),
|
||||
],
|
||||
overrides: [appSettingsServiceProvider.overrideWith((ref) => settingsMock)],
|
||||
);
|
||||
when(
|
||||
() => settingsMock.setSetting<bool>(
|
||||
AppSettingsEnum.selectedAlbumSortReverse,
|
||||
any(),
|
||||
),
|
||||
() => settingsMock.setSetting<bool>(AppSettingsEnum.selectedAlbumSortReverse, any()),
|
||||
).thenAnswer((_) async => {});
|
||||
when(
|
||||
() => settingsMock.setSetting<int>(
|
||||
AppSettingsEnum.selectedAlbumSortOrder,
|
||||
any(),
|
||||
),
|
||||
() => settingsMock.setSetting<int>(AppSettingsEnum.selectedAlbumSortOrder, any()),
|
||||
).thenAnswer((_) async => {});
|
||||
});
|
||||
|
||||
test('Returns the default sort mode when none set', () {
|
||||
// Returns the default value when nothing is set
|
||||
when(
|
||||
() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortOrder),
|
||||
).thenReturn(0);
|
||||
when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortOrder)).thenReturn(0);
|
||||
|
||||
expect(container.read(albumSortByOptionsProvider), AlbumSortMode.created);
|
||||
});
|
||||
|
||||
test('Returns the correct sort mode with index from Store', () {
|
||||
// Returns the default value when nothing is set
|
||||
when(
|
||||
() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortOrder),
|
||||
).thenReturn(3);
|
||||
when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortOrder)).thenReturn(3);
|
||||
|
||||
expect(
|
||||
container.read(albumSortByOptionsProvider),
|
||||
AlbumSortMode.lastModified,
|
||||
);
|
||||
expect(container.read(albumSortByOptionsProvider), AlbumSortMode.lastModified);
|
||||
});
|
||||
|
||||
test('Properly saves the correct store index of sort mode', () {
|
||||
container.read(albumSortByOptionsProvider.notifier).changeSortMode(AlbumSortMode.mostOldest);
|
||||
|
||||
verify(
|
||||
() => settingsMock.setSetting(
|
||||
AppSettingsEnum.selectedAlbumSortOrder,
|
||||
AlbumSortMode.mostOldest.storeIndex,
|
||||
),
|
||||
() => settingsMock.setSetting(AppSettingsEnum.selectedAlbumSortOrder, AlbumSortMode.mostOldest.storeIndex),
|
||||
);
|
||||
});
|
||||
|
||||
test('Notifies listeners on state change', () {
|
||||
when(
|
||||
() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortOrder),
|
||||
).thenReturn(0);
|
||||
when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortOrder)).thenReturn(0);
|
||||
|
||||
final listener = ListenerMock<AlbumSortMode>();
|
||||
container.listen(
|
||||
albumSortByOptionsProvider,
|
||||
listener.call,
|
||||
fireImmediately: true,
|
||||
);
|
||||
container.listen(albumSortByOptionsProvider, listener.call, fireImmediately: true);
|
||||
|
||||
// Created -> Most Oldest
|
||||
container.read(albumSortByOptionsProvider.notifier).changeSortMode(AlbumSortMode.mostOldest);
|
||||
@@ -309,28 +224,18 @@ void main() {
|
||||
setUp(() async {
|
||||
settingsMock = MockAppSettingsService();
|
||||
container = TestUtils.createContainer(
|
||||
overrides: [
|
||||
appSettingsServiceProvider.overrideWith((ref) => settingsMock),
|
||||
],
|
||||
overrides: [appSettingsServiceProvider.overrideWith((ref) => settingsMock)],
|
||||
);
|
||||
when(
|
||||
() => settingsMock.setSetting<bool>(
|
||||
AppSettingsEnum.selectedAlbumSortReverse,
|
||||
any(),
|
||||
),
|
||||
() => settingsMock.setSetting<bool>(AppSettingsEnum.selectedAlbumSortReverse, any()),
|
||||
).thenAnswer((_) async => {});
|
||||
when(
|
||||
() => settingsMock.setSetting<int>(
|
||||
AppSettingsEnum.selectedAlbumSortOrder,
|
||||
any(),
|
||||
),
|
||||
() => settingsMock.setSetting<int>(AppSettingsEnum.selectedAlbumSortOrder, any()),
|
||||
).thenAnswer((_) async => {});
|
||||
});
|
||||
|
||||
test('Returns the default sort order when none set - false', () {
|
||||
when(
|
||||
() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortReverse),
|
||||
).thenReturn(false);
|
||||
when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortReverse)).thenReturn(false);
|
||||
|
||||
expect(container.read(albumSortOrderProvider), isFalse);
|
||||
});
|
||||
@@ -338,25 +243,14 @@ void main() {
|
||||
test('Properly saves the correct order', () {
|
||||
container.read(albumSortOrderProvider.notifier).changeSortDirection(true);
|
||||
|
||||
verify(
|
||||
() => settingsMock.setSetting(
|
||||
AppSettingsEnum.selectedAlbumSortReverse,
|
||||
true,
|
||||
),
|
||||
);
|
||||
verify(() => settingsMock.setSetting(AppSettingsEnum.selectedAlbumSortReverse, true));
|
||||
});
|
||||
|
||||
test('Notifies listeners on state change', () {
|
||||
when(
|
||||
() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortReverse),
|
||||
).thenReturn(false);
|
||||
when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortReverse)).thenReturn(false);
|
||||
|
||||
final listener = ListenerMock<bool>();
|
||||
container.listen(
|
||||
albumSortOrderProvider,
|
||||
listener.call,
|
||||
fireImmediately: true,
|
||||
);
|
||||
container.listen(albumSortOrderProvider, listener.call, fireImmediately: true);
|
||||
|
||||
// false -> true
|
||||
container.read(albumSortOrderProvider.notifier).changeSortDirection(true);
|
||||
|
||||
@@ -5,21 +5,11 @@ import 'package:immich_mobile/extensions/asset_extensions.dart';
|
||||
import 'package:timezone/data/latest.dart';
|
||||
import 'package:timezone/timezone.dart';
|
||||
|
||||
ExifInfo makeExif({
|
||||
DateTime? dateTimeOriginal,
|
||||
String? timeZone,
|
||||
}) {
|
||||
return ExifInfo(
|
||||
dateTimeOriginal: dateTimeOriginal,
|
||||
timeZone: timeZone,
|
||||
);
|
||||
ExifInfo makeExif({DateTime? dateTimeOriginal, String? timeZone}) {
|
||||
return ExifInfo(dateTimeOriginal: dateTimeOriginal, timeZone: timeZone);
|
||||
}
|
||||
|
||||
Asset makeAsset({
|
||||
required String id,
|
||||
required DateTime createdAt,
|
||||
ExifInfo? exifInfo,
|
||||
}) {
|
||||
Asset makeAsset({required String id, required DateTime createdAt, ExifInfo? exifInfo}) {
|
||||
return Asset(
|
||||
checksum: '',
|
||||
localId: id,
|
||||
@@ -79,10 +69,7 @@ void main() {
|
||||
test('Returns dateTimeOriginal in UTC from exifInfo with invalid timezone', () {
|
||||
final createdAt = DateTime.parse("2023-01-27T14:00:00-0500");
|
||||
final dateTimeOriginal = DateTime.parse("2022-01-27T14:00:00+0530");
|
||||
final e = makeExif(
|
||||
dateTimeOriginal: dateTimeOriginal,
|
||||
timeZone: "#_#",
|
||||
); // Invalid timezone
|
||||
final e = makeExif(dateTimeOriginal: dateTimeOriginal, timeZone: "#_#"); // Invalid timezone
|
||||
final a = makeAsset(id: '1', createdAt: createdAt, exifInfo: e);
|
||||
final (dt, tz) = a.getTZAdjustedTimeAndOffset();
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@ import 'package:immich_mobile/extensions/string_extensions.dart';
|
||||
void main() {
|
||||
group('Test toDuration', () {
|
||||
test('ok', () {
|
||||
expect(
|
||||
"1:02:33".toDuration(),
|
||||
const Duration(hours: 1, minutes: 2, seconds: 33),
|
||||
);
|
||||
expect("1:02:33".toDuration(), const Duration(hours: 1, minutes: 2, seconds: 33));
|
||||
});
|
||||
test('malformed', () {
|
||||
expect("".toDuration(), isNull);
|
||||
@@ -45,9 +42,7 @@ void main() {
|
||||
test('withKey', () {
|
||||
final a = ["a", "bb", "cc", "ddd"];
|
||||
expect(
|
||||
a.uniqueConsecutive(
|
||||
compare: (s1, s2) => s1.length.compareTo(s2.length),
|
||||
),
|
||||
a.uniqueConsecutive(compare: (s1, s2) => s1.length.compareTo(s2.length)),
|
||||
orderedEquals(["a", "bb", "ddd"]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -58,10 +58,7 @@ void main() {
|
||||
|
||||
group('Test grouped', () {
|
||||
test('test grouped check months', () async {
|
||||
final renderList = await RenderList.fromAssets(
|
||||
assets,
|
||||
GroupAssetsBy.day,
|
||||
);
|
||||
final renderList = await RenderList.fromAssets(assets, GroupAssetsBy.day);
|
||||
|
||||
// Oct
|
||||
// Day 1
|
||||
@@ -75,33 +72,18 @@ void main() {
|
||||
// Day 1
|
||||
// 5 Assets => 2 Rows
|
||||
expect(renderList.elements, hasLength(4));
|
||||
expect(
|
||||
renderList.elements[0].type,
|
||||
RenderAssetGridElementType.monthTitle,
|
||||
);
|
||||
expect(renderList.elements[0].type, RenderAssetGridElementType.monthTitle);
|
||||
expect(renderList.elements[0].date.month, 1);
|
||||
expect(
|
||||
renderList.elements[1].type,
|
||||
RenderAssetGridElementType.groupDividerTitle,
|
||||
);
|
||||
expect(renderList.elements[1].type, RenderAssetGridElementType.groupDividerTitle);
|
||||
expect(renderList.elements[1].date.month, 1);
|
||||
expect(
|
||||
renderList.elements[2].type,
|
||||
RenderAssetGridElementType.monthTitle,
|
||||
);
|
||||
expect(renderList.elements[2].type, RenderAssetGridElementType.monthTitle);
|
||||
expect(renderList.elements[2].date.month, 2);
|
||||
expect(
|
||||
renderList.elements[3].type,
|
||||
RenderAssetGridElementType.monthTitle,
|
||||
);
|
||||
expect(renderList.elements[3].type, RenderAssetGridElementType.monthTitle);
|
||||
expect(renderList.elements[3].date.month, 10);
|
||||
});
|
||||
|
||||
test('test grouped check types', () async {
|
||||
final renderList = await RenderList.fromAssets(
|
||||
assets,
|
||||
GroupAssetsBy.day,
|
||||
);
|
||||
final renderList = await RenderList.fromAssets(assets, GroupAssetsBy.day);
|
||||
|
||||
// Oct
|
||||
// Day 1
|
||||
|
||||
@@ -67,9 +67,7 @@ void main() {
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
mapStateNotifier.state = mapState.copyWith(
|
||||
darkStyleFetched: const AsyncError("Error", StackTrace.empty),
|
||||
);
|
||||
mapStateNotifier.state = mapState.copyWith(darkStyleFetched: const AsyncError("Error", StackTrace.empty));
|
||||
await tester.pumpAndSettle();
|
||||
expect(mapStyle?.hasError, isTrue);
|
||||
});
|
||||
@@ -86,10 +84,7 @@ void main() {
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
mapStateNotifier.state = mapState.copyWith(
|
||||
themeMode: ThemeMode.light,
|
||||
lightStyleFetched: const AsyncData("light"),
|
||||
);
|
||||
mapStateNotifier.state = mapState.copyWith(themeMode: ThemeMode.light, lightStyleFetched: const AsyncData("light"));
|
||||
await tester.pumpAndSettle();
|
||||
expect(mapStyle?.valueOrNull, "light");
|
||||
});
|
||||
|
||||
@@ -66,13 +66,7 @@ void main() {
|
||||
final MockPartnerRepository partnerRepository = MockPartnerRepository();
|
||||
final MockUserService userService = MockUserService();
|
||||
|
||||
final owner = UserDto(
|
||||
id: "1",
|
||||
updatedAt: DateTime.now(),
|
||||
email: "a@b.c",
|
||||
name: "first last",
|
||||
isAdmin: false,
|
||||
);
|
||||
final owner = UserDto(id: "1", updatedAt: DateTime.now(), email: "a@b.c", name: "first last", isAdmin: false);
|
||||
late SyncService s;
|
||||
setUpAll(() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -81,10 +75,7 @@ void main() {
|
||||
db.writeTxnSync(() => db.clearSync());
|
||||
await StoreService.init(storeRepository: IsarStoreRepository(db));
|
||||
await Store.put(StoreKey.currentUser, owner);
|
||||
await LogService.init(
|
||||
logRepository: IsarLogRepository(db),
|
||||
storeRepository: IsarStoreRepository(db),
|
||||
);
|
||||
await LogService.init(logRepository: IsarLogRepository(db), storeRepository: IsarStoreRepository(db));
|
||||
});
|
||||
final List<Asset> initialAssets = [
|
||||
makeAsset(checksum: "a", remoteId: "0-1"),
|
||||
@@ -119,22 +110,20 @@ void main() {
|
||||
when(() => userRepository.getAll(sortBy: SortUserBy.id)).thenAnswer((_) async => [owner]);
|
||||
when(() => userRepository.getAll()).thenAnswer((_) async => [owner]);
|
||||
when(
|
||||
() => assetRepository.getAll(
|
||||
ownerId: owner.id,
|
||||
sortBy: AssetSort.checksum,
|
||||
),
|
||||
() => assetRepository.getAll(ownerId: owner.id, sortBy: AssetSort.checksum),
|
||||
).thenAnswer((_) async => initialAssets);
|
||||
when(() => assetRepository.getAllByOwnerIdChecksum(any(), any()))
|
||||
.thenAnswer((_) async => [initialAssets[3], null, null]);
|
||||
when(
|
||||
() => assetRepository.getAllByOwnerIdChecksum(any(), any()),
|
||||
).thenAnswer((_) async => [initialAssets[3], null, null]);
|
||||
when(() => assetRepository.updateAll(any())).thenAnswer((_) async => []);
|
||||
when(() => assetRepository.deleteByIds(any())).thenAnswer((_) async {});
|
||||
when(() => exifInfoRepository.updateAll(any())).thenAnswer((_) async => []);
|
||||
when(() => assetRepository.transaction<void>(any())).thenAnswer(
|
||||
(call) => (call.positionalArguments.first as Function).call(),
|
||||
);
|
||||
when(() => assetRepository.transaction<Null>(any())).thenAnswer(
|
||||
(call) => (call.positionalArguments.first as Function).call(),
|
||||
);
|
||||
when(
|
||||
() => assetRepository.transaction<void>(any()),
|
||||
).thenAnswer((call) => (call.positionalArguments.first as Function).call());
|
||||
when(
|
||||
() => assetRepository.transaction<Null>(any()),
|
||||
).thenAnswer((call) => (call.positionalArguments.first as Function).call());
|
||||
when(() => userApiRepository.getAll()).thenAnswer((_) async => [owner]);
|
||||
registerFallbackValue(Direction.sharedByMe);
|
||||
when(() => partnerApiRepository.getAll(any())).thenAnswer((_) async => []);
|
||||
@@ -170,9 +159,7 @@ void main() {
|
||||
);
|
||||
expect(c1, isTrue);
|
||||
final updatedAsset = initialAssets[3].updatedCopy(remoteAssets[3]);
|
||||
verify(
|
||||
() => assetRepository.updateAll([remoteAssets[4], remoteAssets[5], updatedAsset]),
|
||||
);
|
||||
verify(() => assetRepository.updateAll([remoteAssets[4], remoteAssets[5], updatedAsset]));
|
||||
});
|
||||
|
||||
test('test syncing duplicate assets', () async {
|
||||
@@ -191,10 +178,7 @@ void main() {
|
||||
);
|
||||
expect(c1, isTrue);
|
||||
when(
|
||||
() => assetRepository.getAll(
|
||||
ownerId: owner.id,
|
||||
sortBy: AssetSort.checksum,
|
||||
),
|
||||
() => assetRepository.getAll(ownerId: owner.id, sortBy: AssetSort.checksum),
|
||||
).thenAnswer((_) async => remoteAssets);
|
||||
final bool c2 = await s.syncRemoteAssetsToDb(
|
||||
users: [owner],
|
||||
@@ -204,10 +188,7 @@ void main() {
|
||||
expect(c2, isFalse);
|
||||
final currentState = [...remoteAssets];
|
||||
when(
|
||||
() => assetRepository.getAll(
|
||||
ownerId: owner.id,
|
||||
sortBy: AssetSort.checksum,
|
||||
),
|
||||
() => assetRepository.getAll(ownerId: owner.id, sortBy: AssetSort.checksum),
|
||||
).thenAnswer((_) async => currentState);
|
||||
remoteAssets.removeAt(4);
|
||||
final bool c3 = await s.syncRemoteAssetsToDb(
|
||||
@@ -228,18 +209,19 @@ void main() {
|
||||
|
||||
test('test efficient sync', () async {
|
||||
when(
|
||||
() => assetRepository.deleteAllByRemoteId(
|
||||
[initialAssets[1].remoteId!, initialAssets[2].remoteId!],
|
||||
state: AssetState.remote,
|
||||
),
|
||||
() => assetRepository.deleteAllByRemoteId([
|
||||
initialAssets[1].remoteId!,
|
||||
initialAssets[2].remoteId!,
|
||||
], state: AssetState.remote),
|
||||
).thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
when(
|
||||
() => assetRepository.getAllByRemoteId(["2-1", "1-1"], state: AssetState.merged),
|
||||
).thenAnswer((_) async => [initialAssets[2]]);
|
||||
when(() => assetRepository.getAllByOwnerIdChecksum(any(), any()))
|
||||
.thenAnswer((_) async => [initialAssets[0], null, null]); //afg
|
||||
when(
|
||||
() => assetRepository.getAllByOwnerIdChecksum(any(), any()),
|
||||
).thenAnswer((_) async => [initialAssets[0], null, null]); //afg
|
||||
final List<Asset> toUpsert = [
|
||||
makeAsset(checksum: "a", remoteId: "0-1"), // changed
|
||||
makeAsset(checksum: "f", remoteId: "0-2"), // new
|
||||
@@ -262,18 +244,11 @@ void main() {
|
||||
test('test upsert with EXIF data', () async {
|
||||
final assets = [AssetStub.image1, AssetStub.image2];
|
||||
|
||||
expect(
|
||||
assets.map((a) => a.exifInfo?.assetId),
|
||||
List.filled(assets.length, null),
|
||||
);
|
||||
expect(assets.map((a) => a.exifInfo?.assetId), List.filled(assets.length, null));
|
||||
await s.upsertAssetsWithExif(assets);
|
||||
verify(
|
||||
() => exifInfoRepository.updateAll(
|
||||
any(
|
||||
that: containsAll(
|
||||
assets.map((a) => a.exifInfo!.copyWith(assetId: a.id)),
|
||||
),
|
||||
),
|
||||
any(that: containsAll(assets.map((a) => a.exifInfo!.copyWith(assetId: a.id)))),
|
||||
),
|
||||
);
|
||||
expect(assets.map((a) => a.exifInfo?.assetId), assets.map((a) => a.id));
|
||||
@@ -282,8 +257,4 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
Future<(List<Asset>?, List<String>?)> _failDiff(
|
||||
List<UserDto> user,
|
||||
DateTime time,
|
||||
) =>
|
||||
Future.value((null, null));
|
||||
Future<(List<Asset>?, List<String>?)> _failDiff(List<UserDto> user, DateTime time) => Future.value((null, null));
|
||||
|
||||
@@ -7,33 +7,13 @@ void main() {
|
||||
AsyncMutex lock = AsyncMutex();
|
||||
List<int> events = [];
|
||||
expect(0, lock.enqueued);
|
||||
lock.run(
|
||||
() => Future.delayed(
|
||||
const Duration(milliseconds: 10),
|
||||
() => events.add(1),
|
||||
),
|
||||
);
|
||||
lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(1)));
|
||||
expect(1, lock.enqueued);
|
||||
lock.run(
|
||||
() => Future.delayed(
|
||||
const Duration(milliseconds: 3),
|
||||
() => events.add(2),
|
||||
),
|
||||
);
|
||||
lock.run(() => Future.delayed(const Duration(milliseconds: 3), () => events.add(2)));
|
||||
expect(2, lock.enqueued);
|
||||
lock.run(
|
||||
() => Future.delayed(
|
||||
const Duration(milliseconds: 1),
|
||||
() => events.add(3),
|
||||
),
|
||||
);
|
||||
lock.run(() => Future.delayed(const Duration(milliseconds: 1), () => events.add(3)));
|
||||
expect(3, lock.enqueued);
|
||||
await lock.run(
|
||||
() => Future.delayed(
|
||||
const Duration(milliseconds: 10),
|
||||
() => events.add(4),
|
||||
),
|
||||
);
|
||||
await lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(4)));
|
||||
expect(0, lock.enqueued);
|
||||
expect(events, [1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
@@ -9,22 +9,12 @@ void main() {
|
||||
final dateTimeString = DateFormat.yMMMMd().format(dateTime);
|
||||
|
||||
test('returns description if it has one', () {
|
||||
final result = getAltText(
|
||||
const ExifInfo(description: 'description'),
|
||||
dateTime,
|
||||
AssetType.image,
|
||||
[],
|
||||
);
|
||||
final result = getAltText(const ExifInfo(description: 'description'), dateTime, AssetType.image, []);
|
||||
expect(result, 'description');
|
||||
});
|
||||
|
||||
test('returns image alt text with date if no location', () {
|
||||
final (template, args) = getAltTextTemplate(
|
||||
const ExifInfo(),
|
||||
dateTime,
|
||||
AssetType.image,
|
||||
[],
|
||||
);
|
||||
final (template, args) = getAltTextTemplate(const ExifInfo(), dateTime, AssetType.image, []);
|
||||
expect(template, "image_alt_text_date");
|
||||
expect(args["isVideo"], "false");
|
||||
expect(args["date"], dateTimeString);
|
||||
@@ -45,12 +35,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns image alt text with date and some people', () {
|
||||
final (template, args) = getAltTextTemplate(
|
||||
const ExifInfo(),
|
||||
dateTime,
|
||||
AssetType.image,
|
||||
["Alice", "Bob"],
|
||||
);
|
||||
final (template, args) = getAltTextTemplate(const ExifInfo(), dateTime, AssetType.image, ["Alice", "Bob"]);
|
||||
expect(template, "image_alt_text_date_2_people");
|
||||
expect(args["isVideo"], "false");
|
||||
expect(args["date"], dateTimeString);
|
||||
|
||||
@@ -6,10 +6,7 @@ void main() {
|
||||
String? result;
|
||||
|
||||
result = getVersionCompatibilityMessage(1, 0, 2, 0);
|
||||
expect(
|
||||
result,
|
||||
'Your app major version is not compatible with the server!',
|
||||
);
|
||||
expect(result, 'Your app major version is not compatible with the server!');
|
||||
|
||||
result = getVersionCompatibilityMessage(1, 106, 1, 105);
|
||||
expect(
|
||||
|
||||
@@ -45,18 +45,11 @@ void main() {
|
||||
final emptyTextSearch = isA<MetadataSearchDto>().having((s) => s.originalFileName, 'originalFileName', null);
|
||||
|
||||
testWidgets('contextual search with/without text', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
const SearchPage(),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(const SearchPage(), overrides: overrides);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byIcon(Icons.abc_rounded),
|
||||
findsOneWidget,
|
||||
reason: 'Should have contextual search icon',
|
||||
);
|
||||
expect(find.byIcon(Icons.abc_rounded), findsOneWidget, reason: 'Should have contextual search icon');
|
||||
|
||||
final searchField = find.byKey(const Key('search_text_field'));
|
||||
expect(searchField, findsOneWidget);
|
||||
@@ -64,14 +57,9 @@ void main() {
|
||||
await tester.enterText(searchField, 'test');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.search);
|
||||
|
||||
var captured = verify(
|
||||
() => mockSearchApi.searchSmart(captureAny()),
|
||||
).captured;
|
||||
var captured = verify(() => mockSearchApi.searchSmart(captureAny())).captured;
|
||||
|
||||
expect(
|
||||
captured.first,
|
||||
isA<SmartSearchDto>().having((s) => s.query, 'query', 'test'),
|
||||
);
|
||||
expect(captured.first, isA<SmartSearchDto>().having((s) => s.query, 'query', 'test'));
|
||||
|
||||
await tester.enterText(searchField, '');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.search);
|
||||
@@ -81,10 +69,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('not contextual search with/without text', (tester) async {
|
||||
await tester.pumpConsumerWidget(
|
||||
const SearchPage(),
|
||||
overrides: overrides,
|
||||
);
|
||||
await tester.pumpConsumerWidget(const SearchPage(), overrides: overrides);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@@ -92,11 +77,7 @@ void main() {
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byIcon(Icons.image_search_rounded),
|
||||
findsOneWidget,
|
||||
reason: 'Should not have contextual search icon',
|
||||
);
|
||||
expect(find.byIcon(Icons.image_search_rounded), findsOneWidget, reason: 'Should not have contextual search icon');
|
||||
|
||||
final searchField = find.byKey(const Key('search_text_field'));
|
||||
expect(searchField, findsOneWidget);
|
||||
@@ -104,14 +85,9 @@ void main() {
|
||||
await tester.enterText(searchField, 'test');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.search);
|
||||
|
||||
var captured = verify(
|
||||
() => mockSearchApi.searchAssets(captureAny()),
|
||||
).captured;
|
||||
var captured = verify(() => mockSearchApi.searchAssets(captureAny())).captured;
|
||||
|
||||
expect(
|
||||
captured.first,
|
||||
isA<MetadataSearchDto>().having((s) => s.originalFileName, 'originalFileName', 'test'),
|
||||
);
|
||||
expect(captured.first, isA<MetadataSearchDto>().having((s) => s.originalFileName, 'originalFileName', 'test'));
|
||||
|
||||
await tester.enterText(searchField, '');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.search);
|
||||
|
||||
@@ -33,12 +33,12 @@ void main() {
|
||||
|
||||
when(() => userService.getMyUser()).thenReturn(UserStub.user1);
|
||||
|
||||
when(() => albumRepository.transaction<void>(any())).thenAnswer(
|
||||
(call) => (call.positionalArguments.first as Function).call(),
|
||||
);
|
||||
when(() => assetRepository.transaction<Null>(any())).thenAnswer(
|
||||
(call) => (call.positionalArguments.first as Function).call(),
|
||||
);
|
||||
when(
|
||||
() => albumRepository.transaction<void>(any()),
|
||||
).thenAnswer((call) => (call.positionalArguments.first as Function).call());
|
||||
when(
|
||||
() => assetRepository.transaction<Null>(any()),
|
||||
).thenAnswer((call) => (call.positionalArguments.first as Function).call());
|
||||
|
||||
sut = AlbumService(
|
||||
syncService,
|
||||
@@ -66,15 +66,14 @@ void main() {
|
||||
|
||||
test('one selected albums, two on device', () async {
|
||||
when(() => backupRepository.getIdsBySelection(BackupSelection.exclude)).thenAnswer((_) async => []);
|
||||
when(() => backupRepository.getIdsBySelection(BackupSelection.select))
|
||||
.thenAnswer((_) async => [AlbumStub.oneAsset.localId!]);
|
||||
when(
|
||||
() => backupRepository.getIdsBySelection(BackupSelection.select),
|
||||
).thenAnswer((_) async => [AlbumStub.oneAsset.localId!]);
|
||||
when(() => albumMediaRepository.getAll()).thenAnswer((_) async => [AlbumStub.oneAsset, AlbumStub.twoAsset]);
|
||||
when(() => syncService.syncLocalAlbumAssetsToDb(any(), any())).thenAnswer((_) async => true);
|
||||
final result = await sut.refreshDeviceAlbums();
|
||||
expect(result, true);
|
||||
verify(
|
||||
() => syncService.syncLocalAlbumAssetsToDb([AlbumStub.oneAsset], null),
|
||||
).called(1);
|
||||
verify(() => syncService.syncLocalAlbumAssetsToDb([AlbumStub.oneAsset], null)).called(1);
|
||||
verifyNoMoreInteractions(syncService);
|
||||
});
|
||||
});
|
||||
@@ -85,15 +84,12 @@ void main() {
|
||||
when(() => syncService.syncUsersFromServer(any())).thenAnswer((_) async => true);
|
||||
when(() => albumApiRepository.getAll(shared: true)).thenAnswer((_) async => [AlbumStub.sharedWithUser]);
|
||||
|
||||
when(() => albumApiRepository.getAll(shared: null))
|
||||
.thenAnswer((_) async => [AlbumStub.oneAsset, AlbumStub.twoAsset]);
|
||||
when(
|
||||
() => albumApiRepository.getAll(shared: null),
|
||||
).thenAnswer((_) async => [AlbumStub.oneAsset, AlbumStub.twoAsset]);
|
||||
|
||||
when(
|
||||
() => syncService.syncRemoteAlbumsToDb([
|
||||
AlbumStub.twoAsset,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.sharedWithUser,
|
||||
]),
|
||||
() => syncService.syncRemoteAlbumsToDb([AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser]),
|
||||
).thenAnswer((_) async => true);
|
||||
final result = await sut.refreshRemoteAlbums();
|
||||
expect(result, true);
|
||||
@@ -102,13 +98,7 @@ void main() {
|
||||
verify(() => albumApiRepository.getAll(shared: true)).called(1);
|
||||
verify(() => albumApiRepository.getAll(shared: null)).called(1);
|
||||
verify(
|
||||
() => syncService.syncRemoteAlbumsToDb(
|
||||
[
|
||||
AlbumStub.twoAsset,
|
||||
AlbumStub.oneAsset,
|
||||
AlbumStub.sharedWithUser,
|
||||
],
|
||||
),
|
||||
() => syncService.syncRemoteAlbumsToDb([AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser]),
|
||||
).called(1);
|
||||
verifyNoMoreInteractions(userService);
|
||||
verifyNoMoreInteractions(albumApiRepository);
|
||||
@@ -130,9 +120,7 @@ void main() {
|
||||
() => entityService.fillAlbumWithDatabaseEntities(AlbumStub.oneAsset),
|
||||
).thenAnswer((_) async => AlbumStub.oneAsset);
|
||||
|
||||
when(
|
||||
() => albumRepository.create(AlbumStub.oneAsset),
|
||||
).thenAnswer((_) async => AlbumStub.twoAsset);
|
||||
when(() => albumRepository.create(AlbumStub.oneAsset)).thenAnswer((_) async => AlbumStub.twoAsset);
|
||||
|
||||
final result = await sut.createAlbum("name", [AssetStub.image1], [UserStub.user1]);
|
||||
expect(result, AlbumStub.twoAsset);
|
||||
@@ -143,9 +131,7 @@ void main() {
|
||||
sharedUserIds: [UserStub.user1.id],
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => entityService.fillAlbumWithDatabaseEntities(AlbumStub.oneAsset),
|
||||
).called(1);
|
||||
verify(() => entityService.fillAlbumWithDatabaseEntities(AlbumStub.oneAsset)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -153,29 +139,14 @@ void main() {
|
||||
test('one added, one duplicate', () async {
|
||||
when(
|
||||
() => albumApiRepository.addAssets(AlbumStub.oneAsset.remoteId!, any()),
|
||||
).thenAnswer(
|
||||
(_) async => (added: [AssetStub.image2.remoteId!], duplicates: [AssetStub.image1.remoteId!]),
|
||||
);
|
||||
when(
|
||||
() => albumRepository.get(AlbumStub.oneAsset.id),
|
||||
).thenAnswer((_) async => AlbumStub.oneAsset);
|
||||
when(
|
||||
() => albumRepository.addAssets(AlbumStub.oneAsset, [AssetStub.image2]),
|
||||
).thenAnswer((_) async {});
|
||||
when(
|
||||
() => albumRepository.removeAssets(AlbumStub.oneAsset, []),
|
||||
).thenAnswer((_) async {});
|
||||
when(
|
||||
() => albumRepository.recalculateMetadata(AlbumStub.oneAsset),
|
||||
).thenAnswer((_) async => AlbumStub.oneAsset);
|
||||
when(
|
||||
() => albumRepository.update(AlbumStub.oneAsset),
|
||||
).thenAnswer((_) async => AlbumStub.oneAsset);
|
||||
).thenAnswer((_) async => (added: [AssetStub.image2.remoteId!], duplicates: [AssetStub.image1.remoteId!]));
|
||||
when(() => albumRepository.get(AlbumStub.oneAsset.id)).thenAnswer((_) async => AlbumStub.oneAsset);
|
||||
when(() => albumRepository.addAssets(AlbumStub.oneAsset, [AssetStub.image2])).thenAnswer((_) async {});
|
||||
when(() => albumRepository.removeAssets(AlbumStub.oneAsset, [])).thenAnswer((_) async {});
|
||||
when(() => albumRepository.recalculateMetadata(AlbumStub.oneAsset)).thenAnswer((_) async => AlbumStub.oneAsset);
|
||||
when(() => albumRepository.update(AlbumStub.oneAsset)).thenAnswer((_) async => AlbumStub.oneAsset);
|
||||
|
||||
final result = await sut.addAssets(
|
||||
AlbumStub.oneAsset,
|
||||
[AssetStub.image1, AssetStub.image2],
|
||||
);
|
||||
final result = await sut.addAssets(AlbumStub.oneAsset, [AssetStub.image1, AssetStub.image2]);
|
||||
|
||||
expect(result != null, true);
|
||||
expect(result!.alreadyInAlbum, [AssetStub.image1.remoteId!]);
|
||||
@@ -187,9 +158,7 @@ void main() {
|
||||
test('one added', () async {
|
||||
when(
|
||||
() => albumApiRepository.addUsers(AlbumStub.emptyAlbum.remoteId!, any()),
|
||||
).thenAnswer(
|
||||
(_) async => AlbumStub.sharedWithUser,
|
||||
);
|
||||
).thenAnswer((_) async => AlbumStub.sharedWithUser);
|
||||
|
||||
when(
|
||||
() => albumRepository.addUsers(
|
||||
@@ -198,14 +167,9 @@ void main() {
|
||||
),
|
||||
).thenAnswer((_) async => AlbumStub.emptyAlbum);
|
||||
|
||||
when(
|
||||
() => albumRepository.update(AlbumStub.emptyAlbum),
|
||||
).thenAnswer((_) async => AlbumStub.emptyAlbum);
|
||||
when(() => albumRepository.update(AlbumStub.emptyAlbum)).thenAnswer((_) async => AlbumStub.emptyAlbum);
|
||||
|
||||
final result = await sut.addUsers(
|
||||
AlbumStub.emptyAlbum,
|
||||
[UserStub.user2.id],
|
||||
);
|
||||
final result = await sut.addUsers(AlbumStub.emptyAlbum, [UserStub.user2.id]);
|
||||
|
||||
expect(result, true);
|
||||
});
|
||||
|
||||
@@ -81,9 +81,7 @@ void main() {
|
||||
final upsertExifCallback = verify(() => syncService.upsertAssetsWithExif(captureAny()));
|
||||
upsertExifCallback.called(1);
|
||||
final receivedAssets = upsertExifCallback.captured.firstOrNull as List<Object>? ?? [];
|
||||
final receivedDatetime = receivedAssets.cast<Asset>().map(
|
||||
(a) => a.exifInfo?.dateTimeOriginal ?? DateTime(0),
|
||||
);
|
||||
final receivedDatetime = receivedAssets.cast<Asset>().map((a) => a.exifInfo?.dateTimeOriginal ?? DateTime(0));
|
||||
expect(receivedDatetime.every((d) => d == dateTime), isTrue);
|
||||
});
|
||||
|
||||
@@ -97,8 +95,8 @@ void main() {
|
||||
upsertExifCallback.called(1);
|
||||
final receivedAssets = upsertExifCallback.captured.firstOrNull as List<Object>? ?? [];
|
||||
final receivedCoords = receivedAssets.cast<Asset>().map(
|
||||
(a) => LatLng(a.exifInfo?.latitude ?? 0, a.exifInfo?.longitude ?? 0),
|
||||
);
|
||||
(a) => LatLng(a.exifInfo?.latitude ?? 0, a.exifInfo?.longitude ?? 0),
|
||||
);
|
||||
expect(receivedCoords.every((l) => l == latLng), isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,10 +95,7 @@ void main() {
|
||||
|
||||
when(() => apiService.resolveAndSetEndpoint(testUrl)).thenThrow(Exception('Invalid URL'));
|
||||
|
||||
expect(
|
||||
() async => await sut.validateServerUrl(testUrl),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
expect(() async => await sut.validateServerUrl(testUrl), throwsA(isA<Exception>()));
|
||||
|
||||
verify(() => apiService.resolveAndSetEndpoint(testUrl)).called(1);
|
||||
verifyNever(() => apiService.setDeviceInfoHeader());
|
||||
@@ -109,10 +106,7 @@ void main() {
|
||||
|
||||
when(() => apiService.resolveAndSetEndpoint(testUrl)).thenThrow(Exception('Server is not reachable'));
|
||||
|
||||
expect(
|
||||
() async => await sut.validateServerUrl(testUrl),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
expect(() async => await sut.validateServerUrl(testUrl), throwsA(isA<Exception>()));
|
||||
|
||||
verify(() => apiService.resolveAndSetEndpoint(testUrl)).called(1);
|
||||
verifyNever(() => apiService.setDeviceInfoHeader());
|
||||
@@ -126,10 +120,7 @@ void main() {
|
||||
when(() => authRepository.clearLocalData()).thenAnswer((_) => Future.value(null));
|
||||
when(() => uploadService.cancelBackup()).thenAnswer((_) => Future.value(1));
|
||||
when(
|
||||
() => appSettingsService.setSetting(
|
||||
AppSettingsEnum.enableBackup,
|
||||
false,
|
||||
),
|
||||
() => appSettingsService.setSetting(AppSettingsEnum.enableBackup, false),
|
||||
).thenAnswer((_) => Future.value(null));
|
||||
await sut.logout();
|
||||
|
||||
@@ -144,10 +135,7 @@ void main() {
|
||||
when(() => authRepository.clearLocalData()).thenAnswer((_) => Future.value(null));
|
||||
when(() => uploadService.cancelBackup()).thenAnswer((_) => Future.value(1));
|
||||
when(
|
||||
() => appSettingsService.setSetting(
|
||||
AppSettingsEnum.enableBackup,
|
||||
false,
|
||||
),
|
||||
() => appSettingsService.setSetting(AppSettingsEnum.enableBackup, false),
|
||||
).thenAnswer((_) => Future.value(null));
|
||||
await sut.logout();
|
||||
|
||||
@@ -176,8 +164,9 @@ void main() {
|
||||
when(() => authRepository.getEndpointSwitchingFeature()).thenReturn(true);
|
||||
when(() => authRepository.getPreferredWifiName()).thenReturn('TestWifi');
|
||||
when(() => authRepository.getLocalEndpoint()).thenReturn('http://local.endpoint');
|
||||
when(() => apiService.resolveAndSetEndpoint('http://local.endpoint'))
|
||||
.thenAnswer((_) async => 'http://local.endpoint');
|
||||
when(
|
||||
() => apiService.resolveAndSetEndpoint('http://local.endpoint'),
|
||||
).thenAnswer((_) async => 'http://local.endpoint');
|
||||
|
||||
final result = await sut.setOpenApiServiceEndpoint();
|
||||
|
||||
@@ -192,12 +181,9 @@ void main() {
|
||||
test('Should set external endpoint if wifi name not matching', () async {
|
||||
when(() => authRepository.getEndpointSwitchingFeature()).thenReturn(true);
|
||||
when(() => authRepository.getPreferredWifiName()).thenReturn('DifferentWifi');
|
||||
when(() => authRepository.getExternalEndpointList()).thenReturn([
|
||||
const AuxilaryEndpoint(
|
||||
url: 'https://external.endpoint',
|
||||
status: AuxCheckStatus.valid,
|
||||
),
|
||||
]);
|
||||
when(
|
||||
() => authRepository.getExternalEndpointList(),
|
||||
).thenReturn([const AuxilaryEndpoint(url: 'https://external.endpoint', status: AuxCheckStatus.valid)]);
|
||||
when(
|
||||
() => apiService.resolveAndSetEndpoint('https://external.endpoint'),
|
||||
).thenAnswer((_) async => 'https://external.endpoint/api');
|
||||
@@ -209,23 +195,15 @@ void main() {
|
||||
verify(() => networkService.getWifiName()).called(1);
|
||||
verify(() => authRepository.getPreferredWifiName()).called(1);
|
||||
verify(() => authRepository.getExternalEndpointList()).called(1);
|
||||
verify(
|
||||
() => apiService.resolveAndSetEndpoint('https://external.endpoint'),
|
||||
).called(1);
|
||||
verify(() => apiService.resolveAndSetEndpoint('https://external.endpoint')).called(1);
|
||||
});
|
||||
|
||||
test('Should set second external endpoint if the first throw any error', () async {
|
||||
when(() => authRepository.getEndpointSwitchingFeature()).thenReturn(true);
|
||||
when(() => authRepository.getPreferredWifiName()).thenReturn('DifferentWifi');
|
||||
when(() => authRepository.getExternalEndpointList()).thenReturn([
|
||||
const AuxilaryEndpoint(
|
||||
url: 'https://external.endpoint',
|
||||
status: AuxCheckStatus.valid,
|
||||
),
|
||||
const AuxilaryEndpoint(
|
||||
url: 'https://external.endpoint2',
|
||||
status: AuxCheckStatus.valid,
|
||||
),
|
||||
const AuxilaryEndpoint(url: 'https://external.endpoint', status: AuxCheckStatus.valid),
|
||||
const AuxilaryEndpoint(url: 'https://external.endpoint2', status: AuxCheckStatus.valid),
|
||||
]);
|
||||
|
||||
when(
|
||||
@@ -242,23 +220,15 @@ void main() {
|
||||
verify(() => networkService.getWifiName()).called(1);
|
||||
verify(() => authRepository.getPreferredWifiName()).called(1);
|
||||
verify(() => authRepository.getExternalEndpointList()).called(1);
|
||||
verify(
|
||||
() => apiService.resolveAndSetEndpoint('https://external.endpoint2'),
|
||||
).called(1);
|
||||
verify(() => apiService.resolveAndSetEndpoint('https://external.endpoint2')).called(1);
|
||||
});
|
||||
|
||||
test('Should set second external endpoint if the first throw ApiException', () async {
|
||||
when(() => authRepository.getEndpointSwitchingFeature()).thenReturn(true);
|
||||
when(() => authRepository.getPreferredWifiName()).thenReturn('DifferentWifi');
|
||||
when(() => authRepository.getExternalEndpointList()).thenReturn([
|
||||
const AuxilaryEndpoint(
|
||||
url: 'https://external.endpoint',
|
||||
status: AuxCheckStatus.valid,
|
||||
),
|
||||
const AuxilaryEndpoint(
|
||||
url: 'https://external.endpoint2',
|
||||
status: AuxCheckStatus.valid,
|
||||
),
|
||||
const AuxilaryEndpoint(url: 'https://external.endpoint', status: AuxCheckStatus.valid),
|
||||
const AuxilaryEndpoint(url: 'https://external.endpoint2', status: AuxCheckStatus.valid),
|
||||
]);
|
||||
|
||||
when(
|
||||
@@ -275,17 +245,16 @@ void main() {
|
||||
verify(() => networkService.getWifiName()).called(1);
|
||||
verify(() => authRepository.getPreferredWifiName()).called(1);
|
||||
verify(() => authRepository.getExternalEndpointList()).called(1);
|
||||
verify(
|
||||
() => apiService.resolveAndSetEndpoint('https://external.endpoint2'),
|
||||
).called(1);
|
||||
verify(() => apiService.resolveAndSetEndpoint('https://external.endpoint2')).called(1);
|
||||
});
|
||||
|
||||
test('Should handle error when setting local connection', () async {
|
||||
when(() => authRepository.getEndpointSwitchingFeature()).thenReturn(true);
|
||||
when(() => authRepository.getPreferredWifiName()).thenReturn('TestWifi');
|
||||
when(() => authRepository.getLocalEndpoint()).thenReturn('http://local.endpoint');
|
||||
when(() => apiService.resolveAndSetEndpoint('http://local.endpoint'))
|
||||
.thenThrow(Exception('Local endpoint error'));
|
||||
when(
|
||||
() => apiService.resolveAndSetEndpoint('http://local.endpoint'),
|
||||
).thenThrow(Exception('Local endpoint error'));
|
||||
|
||||
final result = await sut.setOpenApiServiceEndpoint();
|
||||
|
||||
@@ -300,12 +269,9 @@ void main() {
|
||||
test('Should handle error when setting external connection', () async {
|
||||
when(() => authRepository.getEndpointSwitchingFeature()).thenReturn(true);
|
||||
when(() => authRepository.getPreferredWifiName()).thenReturn('DifferentWifi');
|
||||
when(() => authRepository.getExternalEndpointList()).thenReturn([
|
||||
const AuxilaryEndpoint(
|
||||
url: 'https://external.endpoint',
|
||||
status: AuxCheckStatus.valid,
|
||||
),
|
||||
]);
|
||||
when(
|
||||
() => authRepository.getExternalEndpointList(),
|
||||
).thenReturn([const AuxilaryEndpoint(url: 'https://external.endpoint', status: AuxCheckStatus.valid)]);
|
||||
when(
|
||||
() => apiService.resolveAndSetEndpoint('https://external.endpoint'),
|
||||
).thenThrow(Exception('External endpoint error'));
|
||||
@@ -317,9 +283,7 @@ void main() {
|
||||
verify(() => networkService.getWifiName()).called(1);
|
||||
verify(() => authRepository.getPreferredWifiName()).called(1);
|
||||
verify(() => authRepository.getExternalEndpointList()).called(1);
|
||||
verify(
|
||||
() => apiService.resolveAndSetEndpoint('https://external.endpoint'),
|
||||
).called(1);
|
||||
verify(() => apiService.resolveAndSetEndpoint('https://external.endpoint')).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,23 +22,22 @@ void main() {
|
||||
|
||||
group('fillAlbumWithDatabaseEntities', () {
|
||||
test('remote album with owner, thumbnail, sharedUsers and assets', () async {
|
||||
final Album album = Album(
|
||||
name: "album-with-two-assets-and-two-users",
|
||||
localId: "album-with-two-assets-and-two-users-local",
|
||||
remoteId: "album-with-two-assets-and-two-users-remote",
|
||||
createdAt: DateTime(2001),
|
||||
modifiedAt: DateTime(2010),
|
||||
shared: true,
|
||||
activityEnabled: true,
|
||||
startDate: DateTime(2019),
|
||||
endDate: DateTime(2020),
|
||||
)
|
||||
..remoteThumbnailAssetId = AssetStub.image1.remoteId
|
||||
..assets.addAll([AssetStub.image1, AssetStub.image1])
|
||||
..owner.value = User.fromDto(UserStub.user1)
|
||||
..sharedUsers.addAll(
|
||||
[User.fromDto(UserStub.admin), User.fromDto(UserStub.admin)],
|
||||
);
|
||||
final Album album =
|
||||
Album(
|
||||
name: "album-with-two-assets-and-two-users",
|
||||
localId: "album-with-two-assets-and-two-users-local",
|
||||
remoteId: "album-with-two-assets-and-two-users-remote",
|
||||
createdAt: DateTime(2001),
|
||||
modifiedAt: DateTime(2010),
|
||||
shared: true,
|
||||
activityEnabled: true,
|
||||
startDate: DateTime(2019),
|
||||
endDate: DateTime(2020),
|
||||
)
|
||||
..remoteThumbnailAssetId = AssetStub.image1.remoteId
|
||||
..assets.addAll([AssetStub.image1, AssetStub.image1])
|
||||
..owner.value = User.fromDto(UserStub.user1)
|
||||
..sharedUsers.addAll([User.fromDto(UserStub.admin), User.fromDto(UserStub.admin)]);
|
||||
|
||||
when(() => userRepository.getByUserId(any())).thenAnswer((_) async => UserStub.admin);
|
||||
when(() => userRepository.getByUserId(any())).thenAnswer((_) async => UserStub.admin);
|
||||
@@ -52,23 +51,20 @@ void main() {
|
||||
await sut.fillAlbumWithDatabaseEntities(album);
|
||||
expect(album.owner.value?.toDto(), UserStub.admin);
|
||||
expect(album.thumbnail.value, AssetStub.image1);
|
||||
expect(
|
||||
album.remoteUsers.map((u) => u.toDto()).toSet(),
|
||||
{UserStub.user1, UserStub.user2},
|
||||
);
|
||||
expect(album.remoteUsers.map((u) => u.toDto()).toSet(), {UserStub.user1, UserStub.user2});
|
||||
expect(album.remoteAssets.toSet(), {AssetStub.image1, AssetStub.image2});
|
||||
});
|
||||
|
||||
test('remote album without any info', () async {
|
||||
makeEmptyAlbum() => Album(
|
||||
name: "album-without-info",
|
||||
localId: "album-without-info-local",
|
||||
remoteId: "album-without-info-remote",
|
||||
createdAt: DateTime(2001),
|
||||
modifiedAt: DateTime(2010),
|
||||
shared: false,
|
||||
activityEnabled: false,
|
||||
);
|
||||
name: "album-without-info",
|
||||
localId: "album-without-info-local",
|
||||
remoteId: "album-without-info-remote",
|
||||
createdAt: DateTime(2001),
|
||||
modifiedAt: DateTime(2010),
|
||||
shared: false,
|
||||
activityEnabled: false,
|
||||
);
|
||||
|
||||
final album = makeEmptyAlbum();
|
||||
await sut.fillAlbumWithDatabaseEntities(album);
|
||||
|
||||
@@ -31,15 +31,10 @@ void main() {
|
||||
mockBackgroundService = MockBackgroundService();
|
||||
mockDeviceAssetRepository = MockDeviceAssetRepository();
|
||||
|
||||
sut = HashService(
|
||||
deviceAssetRepository: mockDeviceAssetRepository,
|
||||
backgroundService: mockBackgroundService,
|
||||
);
|
||||
sut = HashService(deviceAssetRepository: mockDeviceAssetRepository, backgroundService: mockBackgroundService);
|
||||
|
||||
when(() => mockDeviceAssetRepository.transaction<Null>(any())).thenAnswer((_) async {
|
||||
final capturedCallback = verify(
|
||||
() => mockDeviceAssetRepository.transaction<Null>(captureAny()),
|
||||
).captured;
|
||||
final capturedCallback = verify(() => mockDeviceAssetRepository.transaction<Null>(captureAny())).captured;
|
||||
// Invoke the transaction callback
|
||||
await (capturedCallback.firstOrNull as Future<Null> Function()?)?.call();
|
||||
});
|
||||
@@ -53,17 +48,13 @@ void main() {
|
||||
|
||||
when(() => mockBackgroundService.digestFiles([file.path])).thenAnswer((_) async => [hash]);
|
||||
// No DB entries for this asset
|
||||
when(
|
||||
() => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!]),
|
||||
).thenAnswer((_) async => []);
|
||||
when(() => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!])).thenAnswer((_) async => []);
|
||||
|
||||
final result = await sut.hashAssets([mockAsset]);
|
||||
|
||||
// Verify we stored the new hash in DB
|
||||
when(() => mockDeviceAssetRepository.transaction<Null>(any())).thenAnswer((_) async {
|
||||
final capturedCallback = verify(
|
||||
() => mockDeviceAssetRepository.transaction<Null>(captureAny()),
|
||||
).captured;
|
||||
final capturedCallback = verify(() => mockDeviceAssetRepository.transaction<Null>(captureAny())).captured;
|
||||
// Invoke the transaction callback
|
||||
await (capturedCallback.firstOrNull as Future<Null> Function()?)?.call();
|
||||
verify(
|
||||
@@ -73,10 +64,7 @@ void main() {
|
||||
).called(1);
|
||||
verify(() => mockDeviceAssetRepository.deleteIds([])).called(1);
|
||||
});
|
||||
expect(
|
||||
result,
|
||||
[AssetStub.image1.copyWith(checksum: base64.encode(hash))],
|
||||
);
|
||||
expect(result, [AssetStub.image1.copyWith(checksum: base64.encode(hash))]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -84,15 +72,9 @@ void main() {
|
||||
test("when the asset is not modified", () async {
|
||||
final hash = utf8.encode("image1-hash");
|
||||
|
||||
when(
|
||||
() => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!]),
|
||||
).thenAnswer(
|
||||
when(() => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!])).thenAnswer(
|
||||
(_) async => [
|
||||
DeviceAsset(
|
||||
assetId: AssetStub.image1.localId!,
|
||||
hash: hash,
|
||||
modifiedTime: AssetStub.image1.fileModifiedAt,
|
||||
),
|
||||
DeviceAsset(assetId: AssetStub.image1.localId!, hash: hash, modifiedTime: AssetStub.image1.fileModifiedAt),
|
||||
],
|
||||
);
|
||||
final result = await sut.hashAssets([AssetStub.image1]);
|
||||
@@ -102,9 +84,7 @@ void main() {
|
||||
verifyNever(() => mockDeviceAssetRepository.updateAll(any()));
|
||||
verifyNever(() => mockDeviceAssetRepository.deleteIds(any()));
|
||||
|
||||
expect(result, [
|
||||
AssetStub.image1.copyWith(checksum: base64.encode(hash)),
|
||||
]);
|
||||
expect(result, [AssetStub.image1.copyWith(checksum: base64.encode(hash))]);
|
||||
});
|
||||
|
||||
test("hashed successful when asset is modified", () async {
|
||||
@@ -118,9 +98,7 @@ void main() {
|
||||
final result = await sut.hashAssets([mockAsset]);
|
||||
|
||||
when(() => mockDeviceAssetRepository.transaction<Null>(any())).thenAnswer((_) async {
|
||||
final capturedCallback = verify(
|
||||
() => mockDeviceAssetRepository.transaction<Null>(captureAny()),
|
||||
).captured;
|
||||
final capturedCallback = verify(() => mockDeviceAssetRepository.transaction<Null>(captureAny())).captured;
|
||||
// Invoke the transaction callback
|
||||
await (capturedCallback.firstOrNull as Future<Null> Function()?)?.call();
|
||||
verify(
|
||||
@@ -133,9 +111,7 @@ void main() {
|
||||
|
||||
verify(() => mockBackgroundService.digestFiles([file.path])).called(1);
|
||||
|
||||
expect(result, [
|
||||
AssetStub.image1.copyWith(checksum: base64.encode(hash)),
|
||||
]);
|
||||
expect(result, [AssetStub.image1.copyWith(checksum: base64.encode(hash))]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -161,18 +137,14 @@ void main() {
|
||||
verifyNever(() => mockBackgroundService.digestFiles(any()));
|
||||
verifyNever(() => mockBackgroundService.digestFile(any()));
|
||||
verifyNever(() => mockDeviceAssetRepository.updateAll(any()));
|
||||
verify(
|
||||
() => mockDeviceAssetRepository.deleteIds([AssetStub.image1.localId!]),
|
||||
).called(1);
|
||||
verify(() => mockDeviceAssetRepository.deleteIds([AssetStub.image1.localId!])).called(1);
|
||||
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
test("cleanups DeviceAsset when hashing failed", () async {
|
||||
when(() => mockDeviceAssetRepository.transaction<Null>(any())).thenAnswer((_) async {
|
||||
final capturedCallback = verify(
|
||||
() => mockDeviceAssetRepository.transaction<Null>(captureAny()),
|
||||
).captured;
|
||||
final capturedCallback = verify(() => mockDeviceAssetRepository.transaction<Null>(captureAny())).captured;
|
||||
// Invoke the transaction callback
|
||||
await (capturedCallback.firstOrNull as Future<Null> Function()?)?.call();
|
||||
|
||||
@@ -194,9 +166,7 @@ void main() {
|
||||
// To avoid this, we capture the callback and execute it within the transaction stub itself
|
||||
// and verify the results inside the transaction stub
|
||||
verify(() => mockDeviceAssetRepository.updateAll([])).called(1);
|
||||
verify(
|
||||
() => mockDeviceAssetRepository.deleteIds([AssetStub.image1.localId!]),
|
||||
).called(1);
|
||||
verify(() => mockDeviceAssetRepository.deleteIds([AssetStub.image1.localId!])).called(1);
|
||||
});
|
||||
|
||||
when(() => mockBackgroundService.digestFiles([file.path])).thenAnswer(
|
||||
@@ -243,14 +213,11 @@ void main() {
|
||||
verify(() => mockBackgroundService.digestFiles([file1.path, file2.path])).called(1);
|
||||
verify(() => mockBackgroundService.digestFiles([file3.path])).called(1);
|
||||
|
||||
expect(
|
||||
result,
|
||||
[
|
||||
AssetStub.image1.copyWith(checksum: base64.encode(hash1)),
|
||||
AssetStub.image2.copyWith(checksum: base64.encode(hash2)),
|
||||
AssetStub.image3.copyWith(checksum: base64.encode(hash3)),
|
||||
],
|
||||
);
|
||||
expect(result, [
|
||||
AssetStub.image1.copyWith(checksum: base64.encode(hash1)),
|
||||
AssetStub.image2.copyWith(checksum: base64.encode(hash2)),
|
||||
AssetStub.image3.copyWith(checksum: base64.encode(hash3)),
|
||||
]);
|
||||
});
|
||||
|
||||
test("processes assets in batches when file limit is reached", () async {
|
||||
@@ -283,14 +250,11 @@ void main() {
|
||||
verify(() => mockBackgroundService.digestFiles([file2.path])).called(1);
|
||||
verify(() => mockBackgroundService.digestFiles([file3.path])).called(1);
|
||||
|
||||
expect(
|
||||
result,
|
||||
[
|
||||
AssetStub.image1.copyWith(checksum: base64.encode(hash1)),
|
||||
AssetStub.image2.copyWith(checksum: base64.encode(hash2)),
|
||||
AssetStub.image3.copyWith(checksum: base64.encode(hash3)),
|
||||
],
|
||||
);
|
||||
expect(result, [
|
||||
AssetStub.image1.copyWith(checksum: base64.encode(hash1)),
|
||||
AssetStub.image2.copyWith(checksum: base64.encode(hash2)),
|
||||
AssetStub.image3.copyWith(checksum: base64.encode(hash3)),
|
||||
]);
|
||||
});
|
||||
|
||||
test("HashService: Sort & Process different states", () async {
|
||||
@@ -345,15 +309,10 @@ void main() {
|
||||
test("handles all file access failures", () async {
|
||||
// No DB entries
|
||||
when(
|
||||
() => mockDeviceAssetRepository.getByIds(
|
||||
[AssetStub.image1.localId!, AssetStub.image2.localId!],
|
||||
),
|
||||
() => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!, AssetStub.image2.localId!]),
|
||||
).thenAnswer((_) async => []);
|
||||
|
||||
final result = await sut.hashAssets([
|
||||
AssetStub.image1,
|
||||
AssetStub.image2,
|
||||
]);
|
||||
final result = await sut.hashAssets([AssetStub.image1, AssetStub.image2]);
|
||||
|
||||
verifyNever(() => mockBackgroundService.digestFiles(any()));
|
||||
verifyNever(() => mockDeviceAssetRepository.updateAll(any()));
|
||||
@@ -363,9 +322,7 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
Future<(Asset, File, DeviceAsset, Uint8List)> _createAssetMock(
|
||||
Asset asset,
|
||||
) async {
|
||||
Future<(Asset, File, DeviceAsset, Uint8List)> _createAssetMock(Asset asset) async {
|
||||
final random = Random();
|
||||
final hash = Uint8List.fromList(List.generate(20, (i) => random.nextInt(255)));
|
||||
final mockAsset = MockAsset();
|
||||
@@ -384,8 +341,9 @@ Future<(Asset, File, DeviceAsset, Uint8List)> _createAssetMock(
|
||||
when(() => mockAsset.fileName).thenReturn(asset.fileName);
|
||||
when(() => mockAsset.fileCreatedAt).thenReturn(asset.fileCreatedAt);
|
||||
when(() => mockAsset.fileModifiedAt).thenReturn(asset.fileModifiedAt);
|
||||
when(() => mockAsset.copyWith(checksum: any(named: "checksum")))
|
||||
.thenReturn(asset.copyWith(checksum: base64.encode(hash)));
|
||||
when(
|
||||
() => mockAsset.copyWith(checksum: any(named: "checksum")),
|
||||
).thenReturn(asset.copyWith(checksum: base64.encode(hash)));
|
||||
when(() => mockAsset.local).thenAnswer((_) => mockAssetEntity);
|
||||
when(() => mockAssetEntity.originFile).thenAnswer((_) async => file);
|
||||
|
||||
|
||||
+11
-16
@@ -73,11 +73,7 @@ abstract final class TestUtils {
|
||||
List<Override> overrides = const [],
|
||||
List<ProviderObserver>? observers,
|
||||
}) {
|
||||
final container = ProviderContainer(
|
||||
parent: parent,
|
||||
overrides: overrides,
|
||||
observers: observers,
|
||||
);
|
||||
final container = ProviderContainer(parent: parent, overrides: overrides, observers: observers);
|
||||
|
||||
// Dispose on test end
|
||||
addTearDown(container.dispose);
|
||||
@@ -94,23 +90,22 @@ abstract final class TestUtils {
|
||||
|
||||
// Workaround till the following issue is resolved
|
||||
// https://github.com/dart-lang/test/issues/2307
|
||||
static T fakeAsync<T>(
|
||||
Future<T> Function(FakeAsync _) callback, {
|
||||
DateTime? initialTime,
|
||||
}) {
|
||||
static T fakeAsync<T>(Future<T> Function(FakeAsync _) callback, {DateTime? initialTime}) {
|
||||
late final T result;
|
||||
Object? error;
|
||||
StackTrace? stack;
|
||||
FakeAsync(initialTime: initialTime).run((FakeAsync async) {
|
||||
bool shouldPump = true;
|
||||
unawaited(
|
||||
callback(async).then<void>(
|
||||
(value) => result = value,
|
||||
onError: (e, s) {
|
||||
error = e;
|
||||
stack = s;
|
||||
},
|
||||
).whenComplete(() => shouldPump = false),
|
||||
callback(async)
|
||||
.then<void>(
|
||||
(value) => result = value,
|
||||
onError: (e, s) {
|
||||
error = e;
|
||||
stack = s;
|
||||
},
|
||||
)
|
||||
.whenComplete(() => shouldPump = false),
|
||||
);
|
||||
|
||||
while (shouldPump) {
|
||||
|
||||
@@ -18,10 +18,7 @@ extension PumpConsumerWidget on WidgetTester {
|
||||
return pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: overrides,
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: Material(child: widget),
|
||||
),
|
||||
child: MaterialApp(debugShowCheckedModeBanner: false, home: Material(child: widget)),
|
||||
),
|
||||
duration: duration,
|
||||
phase: phase,
|
||||
|
||||
Reference in New Issue
Block a user