mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Merge remote-tracking branch 'origin/main' into feature/new-activity-timeline
This commit is contained in:
@@ -53,7 +53,7 @@ void main() {
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
sut.dispose();
|
||||
unawaited(sut.dispose());
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
@@ -129,7 +129,7 @@ void main() {
|
||||
final stream = sut.watch(StoreKey.accessToken);
|
||||
final events = <String?>[_kAccessToken, _kAccessToken.toUpperCase(), null, _kAccessToken.toLowerCase()];
|
||||
|
||||
expectLater(stream, emitsInOrder(events));
|
||||
unawaited(expectLater(stream, emitsInOrder(events)));
|
||||
|
||||
for (final event in events) {
|
||||
valueController.add(event);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
@@ -99,7 +101,7 @@ void main() {
|
||||
final count = await db.storeValues.count();
|
||||
expect(count, isNot(isZero));
|
||||
await sut.deleteAll();
|
||||
expectLater(await db.storeValues.count(), isZero);
|
||||
unawaited(expectLater(await db.storeValues.count(), isZero));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,29 +126,31 @@ void main() {
|
||||
|
||||
test('watch()', () async {
|
||||
final stream = sut.watch(StoreKey.version);
|
||||
expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10]));
|
||||
unawaited(expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10])));
|
||||
await pumpEventQueue();
|
||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||
});
|
||||
|
||||
test('watchAll()', () async {
|
||||
final stream = sut.watchAll();
|
||||
expectLater(
|
||||
stream,
|
||||
emitsInOrder([
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion),
|
||||
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
|
||||
],
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
|
||||
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
|
||||
],
|
||||
]),
|
||||
unawaited(
|
||||
expectLater(
|
||||
stream,
|
||||
emitsInOrder([
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion),
|
||||
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
|
||||
],
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
|
||||
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
|
||||
],
|
||||
]),
|
||||
),
|
||||
);
|
||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||
});
|
||||
|
||||
@@ -80,6 +80,7 @@ void main() {
|
||||
int onDataCallCount = 0;
|
||||
bool abortWasCalledInCallback = false;
|
||||
List<SyncEvent> receivedEventsBatch1 = [];
|
||||
final Completer<void> firstBatchReceived = Completer<void>();
|
||||
|
||||
Future<void> onDataCallback(List<SyncEvent> events, Function() abort, Function() _) async {
|
||||
onDataCallCount++;
|
||||
@@ -87,6 +88,7 @@ void main() {
|
||||
receivedEventsBatch1 = events;
|
||||
abort();
|
||||
abortWasCalledInCallback = true;
|
||||
firstBatchReceived.complete();
|
||||
} else {
|
||||
fail("onData called more than once after abort was invoked");
|
||||
}
|
||||
@@ -94,7 +96,8 @@ void main() {
|
||||
|
||||
final streamChangesFuture = streamChanges(onDataCallback);
|
||||
|
||||
await pumpEventQueue();
|
||||
// Give the stream subscription time to start (longer delay to account for mock delay)
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
|
||||
for (int i = 0; i < testBatchSize; i++) {
|
||||
responseStreamController.add(
|
||||
@@ -104,6 +107,11 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
await firstBatchReceived.future.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => fail('First batch was not processed within timeout'),
|
||||
);
|
||||
|
||||
for (int i = testBatchSize; i < testBatchSize * 2; i++) {
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
@@ -124,12 +132,14 @@ void main() {
|
||||
test('streamChanges does not process remaining lines in finally block if aborted', () async {
|
||||
int onDataCallCount = 0;
|
||||
bool abortWasCalledInCallback = false;
|
||||
final Completer<void> firstBatchReceived = Completer<void>();
|
||||
|
||||
Future<void> onDataCallback(List<SyncEvent> events, Function() abort, Function() _) async {
|
||||
onDataCallCount++;
|
||||
if (onDataCallCount == 1) {
|
||||
abort();
|
||||
abortWasCalledInCallback = true;
|
||||
firstBatchReceived.complete();
|
||||
} else {
|
||||
fail("onData called more than once after abort was invoked");
|
||||
}
|
||||
@@ -137,7 +147,7 @@ void main() {
|
||||
|
||||
final streamChangesFuture = streamChanges(onDataCallback);
|
||||
|
||||
await pumpEventQueue();
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
|
||||
for (int i = 0; i < testBatchSize; i++) {
|
||||
responseStreamController.add(
|
||||
@@ -147,6 +157,11 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
await firstBatchReceived.future.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => fail('First batch was not processed within timeout'),
|
||||
);
|
||||
|
||||
// emit a single event to skip batching and trigger finally
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
@@ -166,13 +181,17 @@ void main() {
|
||||
int onDataCallCount = 0;
|
||||
List<SyncEvent> receivedEventsBatch1 = [];
|
||||
List<SyncEvent> receivedEventsBatch2 = [];
|
||||
final Completer<void> firstBatchReceived = Completer<void>();
|
||||
final Completer<void> secondBatchReceived = Completer<void>();
|
||||
|
||||
Future<void> onDataCallback(List<SyncEvent> events, Function() _, Function() __) async {
|
||||
onDataCallCount++;
|
||||
if (onDataCallCount == 1) {
|
||||
receivedEventsBatch1 = events;
|
||||
firstBatchReceived.complete();
|
||||
} else if (onDataCallCount == 2) {
|
||||
receivedEventsBatch2 = events;
|
||||
secondBatchReceived.complete();
|
||||
} else {
|
||||
fail("onData called more than expected");
|
||||
}
|
||||
@@ -180,7 +199,7 @@ void main() {
|
||||
|
||||
final streamChangesFuture = streamChanges(onDataCallback);
|
||||
|
||||
await pumpEventQueue();
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
|
||||
// Batch 1
|
||||
for (int i = 0; i < testBatchSize; i++) {
|
||||
@@ -191,7 +210,11 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
// Partial Batch 2
|
||||
await firstBatchReceived.future.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => fail('First batch was not processed within timeout'),
|
||||
);
|
||||
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
_createJsonLine(SyncEntityType.userDeleteV1.toString(), SyncUserDeleteV1(userId: "user100").toJson(), 'ack100'),
|
||||
@@ -199,6 +222,12 @@ void main() {
|
||||
);
|
||||
|
||||
await responseStreamController.close();
|
||||
|
||||
await secondBatchReceived.future.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => fail('Second batch was not processed within timeout'),
|
||||
);
|
||||
|
||||
await expectLater(streamChangesFuture, completes);
|
||||
|
||||
expect(onDataCallCount, 2);
|
||||
@@ -217,7 +246,7 @@ void main() {
|
||||
|
||||
final streamChangesFuture = streamChanges(onDataCallback);
|
||||
|
||||
await pumpEventQueue();
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
|
||||
responseStreamController.add(
|
||||
utf8.encode(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/device_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
@@ -10,6 +11,7 @@ import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository
|
||||
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockStoreRepository extends Mock implements IsarStoreRepository {}
|
||||
@@ -30,8 +32,14 @@ class MockRemoteAlbumRepository extends Mock implements DriftRemoteAlbumReposito
|
||||
|
||||
class MockLocalAssetRepository extends Mock implements DriftLocalAssetRepository {}
|
||||
|
||||
class MockDriftLocalAssetRepository extends Mock implements DriftLocalAssetRepository {}
|
||||
|
||||
class MockStorageRepository extends Mock implements StorageRepository {}
|
||||
|
||||
class MockDriftBackupRepository extends Mock implements DriftBackupRepository {}
|
||||
|
||||
class MockUploadRepository extends Mock implements UploadRepository {}
|
||||
|
||||
// API Repos
|
||||
class MockUserApiRepository extends Mock implements UserApiRepository {}
|
||||
|
||||
|
||||
@@ -64,9 +64,9 @@ void main() {
|
||||
TestUtils.init();
|
||||
db = await TestUtils.initIsar();
|
||||
await StoreService.init(storeRepository: IsarStoreRepository(db));
|
||||
Store.put(StoreKey.currentUser, UserStub.admin);
|
||||
Store.put(StoreKey.serverEndpoint, '');
|
||||
Store.put(StoreKey.accessToken, '');
|
||||
await Store.put(StoreKey.currentUser, UserStub.admin);
|
||||
await Store.put(StoreKey.serverEndpoint, '');
|
||||
await Store.put(StoreKey.accessToken, '');
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
|
||||
@@ -35,8 +35,8 @@ void main() {
|
||||
TestUtils.init();
|
||||
db = await TestUtils.initIsar();
|
||||
await StoreService.init(storeRepository: IsarStoreRepository(db));
|
||||
Store.put(StoreKey.currentUser, UserStub.admin);
|
||||
Store.put(StoreKey.serverEndpoint, '');
|
||||
await Store.put(StoreKey.currentUser, UserStub.admin);
|
||||
await Store.put(StoreKey.serverEndpoint, '');
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
|
||||
@@ -31,9 +31,9 @@ void main() {
|
||||
db = await TestUtils.initIsar();
|
||||
// For UserCircleAvatar
|
||||
await StoreService.init(storeRepository: IsarStoreRepository(db));
|
||||
Store.put(StoreKey.currentUser, UserStub.admin);
|
||||
Store.put(StoreKey.serverEndpoint, '');
|
||||
Store.put(StoreKey.accessToken, '');
|
||||
await Store.put(StoreKey.currentUser, UserStub.admin);
|
||||
await Store.put(StoreKey.serverEndpoint, '');
|
||||
await Store.put(StoreKey.accessToken, '');
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||
|
||||
@@ -7,11 +9,11 @@ void main() {
|
||||
AsyncMutex lock = AsyncMutex();
|
||||
List<int> events = [];
|
||||
expect(0, lock.enqueued);
|
||||
lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(1)));
|
||||
unawaited(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)));
|
||||
unawaited(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)));
|
||||
unawaited(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)));
|
||||
expect(0, lock.enqueued);
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart' hide isNull, isNotNull;
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/services/upload.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
import '../domain/service.mock.dart';
|
||||
import '../fixtures/asset.stub.dart';
|
||||
import '../infrastructure/repository.mock.dart';
|
||||
import '../repository.mocks.dart';
|
||||
|
||||
class MockAssetEntity extends Mock implements AssetEntity {}
|
||||
|
||||
void main() {
|
||||
late UploadService sut;
|
||||
late MockUploadRepository mockUploadRepository;
|
||||
late MockDriftBackupRepository mockBackupRepository;
|
||||
late MockStorageRepository mockStorageRepository;
|
||||
late MockDriftLocalAssetRepository mockLocalAssetRepository;
|
||||
late MockAppSettingsService mockAppSettingsService;
|
||||
late MockAssetMediaRepository mockAssetMediaRepository;
|
||||
late Drift db;
|
||||
|
||||
setUpAll(() async {
|
||||
registerFallbackValue(AppSettingsEnum.useCellularForUploadPhotos);
|
||||
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel('plugins.flutter.io/path_provider'),
|
||||
(MethodCall methodCall) async => 'test',
|
||||
);
|
||||
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
||||
|
||||
await Store.put(StoreKey.serverEndpoint, 'http://test-server.com');
|
||||
await Store.put(StoreKey.deviceId, 'test-device-id');
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
mockUploadRepository = MockUploadRepository();
|
||||
mockBackupRepository = MockDriftBackupRepository();
|
||||
mockStorageRepository = MockStorageRepository();
|
||||
mockLocalAssetRepository = MockDriftLocalAssetRepository();
|
||||
mockAppSettingsService = MockAppSettingsService();
|
||||
mockAssetMediaRepository = MockAssetMediaRepository();
|
||||
|
||||
when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)).thenReturn(false);
|
||||
when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)).thenReturn(false);
|
||||
|
||||
sut = UploadService(
|
||||
mockUploadRepository,
|
||||
mockBackupRepository,
|
||||
mockStorageRepository,
|
||||
mockLocalAssetRepository,
|
||||
mockAppSettingsService,
|
||||
mockAssetMediaRepository,
|
||||
);
|
||||
|
||||
mockUploadRepository.onUploadStatus = (_) {};
|
||||
mockUploadRepository.onTaskProgress = (_) {};
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
sut.dispose();
|
||||
});
|
||||
|
||||
group('getUploadTask', () {
|
||||
test('should call getOriginalFilename from AssetMediaRepository for regular photo', () async {
|
||||
final asset = LocalAssetStub.image1;
|
||||
final mockEntity = MockAssetEntity();
|
||||
final mockFile = File('/path/to/file.jpg');
|
||||
|
||||
when(() => mockEntity.isLivePhoto).thenReturn(false);
|
||||
when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity);
|
||||
when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile);
|
||||
when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'OriginalPhoto.jpg');
|
||||
|
||||
final task = await sut.getUploadTask(asset);
|
||||
|
||||
expect(task, isNotNull);
|
||||
expect(task!.fields['filename'], equals('OriginalPhoto.jpg'));
|
||||
verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1);
|
||||
});
|
||||
|
||||
test('should call getOriginalFilename when original filename is null', () async {
|
||||
final asset = LocalAssetStub.image2;
|
||||
final mockEntity = MockAssetEntity();
|
||||
final mockFile = File('/path/to/file.jpg');
|
||||
|
||||
when(() => mockEntity.isLivePhoto).thenReturn(false);
|
||||
when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity);
|
||||
when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile);
|
||||
when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => null);
|
||||
|
||||
final task = await sut.getUploadTask(asset);
|
||||
|
||||
expect(task, isNotNull);
|
||||
expect(task!.fields['filename'], equals(asset.name));
|
||||
verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1);
|
||||
});
|
||||
|
||||
test('should call getOriginalFilename for live photo', () async {
|
||||
final asset = LocalAssetStub.image1;
|
||||
final mockEntity = MockAssetEntity();
|
||||
final mockFile = File('/path/to/file.mov');
|
||||
|
||||
when(() => mockEntity.isLivePhoto).thenReturn(true);
|
||||
when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity);
|
||||
when(() => mockStorageRepository.getMotionFileForAsset(asset)).thenAnswer((_) async => mockFile);
|
||||
when(
|
||||
() => mockAssetMediaRepository.getOriginalFilename(asset.id),
|
||||
).thenAnswer((_) async => 'OriginalLivePhoto.HEIC');
|
||||
|
||||
final task = await sut.getUploadTask(asset);
|
||||
expect(task, isNotNull);
|
||||
// For live photos, extension should be changed to match the video file
|
||||
expect(task!.fields['filename'], equals('OriginalLivePhoto.mov'));
|
||||
verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('getLivePhotoUploadTask', () {
|
||||
test('should call getOriginalFilename for live photo upload task', () async {
|
||||
final asset = LocalAssetStub.image1;
|
||||
final mockEntity = MockAssetEntity();
|
||||
final mockFile = File('/path/to/livephoto.heic');
|
||||
|
||||
when(() => mockEntity.isLivePhoto).thenReturn(true);
|
||||
when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity);
|
||||
when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile);
|
||||
when(
|
||||
() => mockAssetMediaRepository.getOriginalFilename(asset.id),
|
||||
).thenAnswer((_) async => 'OriginalLivePhoto.HEIC');
|
||||
|
||||
final task = await sut.getLivePhotoUploadTask(asset, 'video-id-123');
|
||||
|
||||
expect(task, isNotNull);
|
||||
expect(task!.fields['filename'], equals('OriginalLivePhoto.HEIC'));
|
||||
expect(task.fields['livePhotoVideoId'], equals('video-id-123'));
|
||||
verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1);
|
||||
});
|
||||
|
||||
test('should call getOriginalFilename when original filename is null', () async {
|
||||
final asset = LocalAssetStub.image2;
|
||||
final mockEntity = MockAssetEntity();
|
||||
final mockFile = File('/path/to/fallback.heic');
|
||||
|
||||
when(() => mockEntity.isLivePhoto).thenReturn(true);
|
||||
when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity);
|
||||
when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile);
|
||||
when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => null);
|
||||
|
||||
final task = await sut.getLivePhotoUploadTask(asset, 'video-id-456');
|
||||
expect(task, isNotNull);
|
||||
// Should fall back to asset.name when original filename is null
|
||||
expect(task!.fields['filename'], equals(asset.name));
|
||||
verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -383,6 +383,42 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('similar photos button', () {
|
||||
test('should show when not locked and has remote', () {
|
||||
final remoteAsset = createRemoteAsset();
|
||||
final context = ActionButtonContext(
|
||||
asset: remoteAsset,
|
||||
isOwner: true,
|
||||
isArchived: false,
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
isStacked: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
expect(ActionButtonType.similarPhotos.shouldShow(context), isTrue);
|
||||
});
|
||||
|
||||
test('should not show when in locked view', () {
|
||||
final remoteAsset = createRemoteAsset();
|
||||
final context = ActionButtonContext(
|
||||
asset: remoteAsset,
|
||||
isOwner: true,
|
||||
isArchived: false,
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: true,
|
||||
currentAlbum: null,
|
||||
isStacked: false,
|
||||
advancedTroubleshooting: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
|
||||
expect(ActionButtonType.similarPhotos.shouldShow(context), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('trash button', () {
|
||||
test('should show when owner, not locked, has remote, and trash enabled', () {
|
||||
final remoteAsset = createRemoteAsset();
|
||||
@@ -777,6 +813,8 @@ void main() {
|
||||
|
||||
test('should build correct widget for each button type', () {
|
||||
for (final buttonType in ActionButtonType.values) {
|
||||
var buttonContext = context;
|
||||
|
||||
if (buttonType == ActionButtonType.removeFromAlbum) {
|
||||
final album = createRemoteAlbum();
|
||||
final contextWithAlbum = ActionButtonContext(
|
||||
@@ -792,6 +830,20 @@ void main() {
|
||||
);
|
||||
final widget = buttonType.buildButton(contextWithAlbum);
|
||||
expect(widget, isA<Widget>());
|
||||
} else if (buttonType == ActionButtonType.similarPhotos) {
|
||||
final contextWithAlbum = ActionButtonContext(
|
||||
asset: createRemoteAsset(),
|
||||
isOwner: true,
|
||||
isArchived: false,
|
||||
isTrashEnabled: true,
|
||||
isInLockedView: false,
|
||||
currentAlbum: null,
|
||||
advancedTroubleshooting: false,
|
||||
isStacked: false,
|
||||
source: ActionSource.timeline,
|
||||
);
|
||||
final widget = buttonType.buildButton(contextWithAlbum);
|
||||
expect(widget, isA<Widget>());
|
||||
} else if (buttonType == ActionButtonType.unstack) {
|
||||
final album = createRemoteAlbum();
|
||||
final contextWithAlbum = ActionButtonContext(
|
||||
@@ -808,7 +860,7 @@ void main() {
|
||||
final widget = buttonType.buildButton(contextWithAlbum);
|
||||
expect(widget, isA<Widget>());
|
||||
} else {
|
||||
final widget = buttonType.buildButton(context);
|
||||
final widget = buttonType.buildButton(buttonContext);
|
||||
expect(widget, isA<Widget>());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user