mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
fix: The 'Copy Link' function will prioritize copying any available custom URL, mirroring the behavior found on the web version
This commit is contained in:
parent
27bc8eba7b
commit
7af9d6d7b7
7 changed files with 502 additions and 3 deletions
|
|
@ -13,6 +13,7 @@ class SharedLink {
|
|||
final DateTime? expiresAt;
|
||||
final String key;
|
||||
final bool showMetadata;
|
||||
final String? slug;
|
||||
final SharedLinkSource type;
|
||||
|
||||
const SharedLink({
|
||||
|
|
@ -26,6 +27,7 @@ class SharedLink {
|
|||
required this.expiresAt,
|
||||
required this.key,
|
||||
required this.showMetadata,
|
||||
required this.slug,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
|
|
@ -40,6 +42,7 @@ class SharedLink {
|
|||
DateTime? expiresAt,
|
||||
String? key,
|
||||
bool? showMetadata,
|
||||
String? slug,
|
||||
SharedLinkSource? type,
|
||||
}) {
|
||||
return SharedLink(
|
||||
|
|
@ -53,6 +56,7 @@ class SharedLink {
|
|||
expiresAt: expiresAt ?? this.expiresAt,
|
||||
key: key ?? this.key,
|
||||
showMetadata: showMetadata ?? this.showMetadata,
|
||||
slug: slug ?? this.slug,
|
||||
type: type ?? this.type,
|
||||
);
|
||||
}
|
||||
|
|
@ -66,6 +70,7 @@ class SharedLink {
|
|||
expiresAt = dto.expiresAt,
|
||||
key = dto.key,
|
||||
showMetadata = dto.showMetadata,
|
||||
slug = dto.slug,
|
||||
type = dto.type == SharedLinkType.ALBUM ? SharedLinkSource.album : SharedLinkSource.individual,
|
||||
title = dto.type == SharedLinkType.ALBUM
|
||||
? dto.album?.albumName.toUpperCase() ?? "UNKNOWN SHARE"
|
||||
|
|
@ -78,7 +83,7 @@ class SharedLink {
|
|||
|
||||
@override
|
||||
String toString() =>
|
||||
'SharedLink(id=$id, title=$title, thumbAssetId=$thumbAssetId, allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, password=$password, expiresAt=$expiresAt, key=$key, showMetadata=$showMetadata, type=$type)';
|
||||
'SharedLink(id=$id, title=$title, thumbAssetId=$thumbAssetId, allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, password=$password, expiresAt=$expiresAt, key=$key, showMetadata=$showMetadata, slug=$slug, type=$type)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
|
|
@ -94,6 +99,7 @@ class SharedLink {
|
|||
other.expiresAt == expiresAt &&
|
||||
other.key == key &&
|
||||
other.showMetadata == showMetadata &&
|
||||
other.slug == slug &&
|
||||
other.type == type;
|
||||
|
||||
@override
|
||||
|
|
@ -108,5 +114,6 @@ class SharedLink {
|
|||
expiresAt.hashCode ^
|
||||
key.hashCode ^
|
||||
showMetadata.hashCode ^
|
||||
slug.hashCode ^
|
||||
type.hashCode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,7 +274,8 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
if (newLink != null && serverUrl != null) {
|
||||
newShareLink.value = "${serverUrl}share/${newLink.key}";
|
||||
final fullPath = getShareUrlPath(newLink);
|
||||
newShareLink.value = "$serverUrl$fullPath";
|
||||
copyLinkToClipboard();
|
||||
} else if (newLink == null) {
|
||||
ImmichToast.show(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
||||
import 'package:punycode/punycode.dart';
|
||||
|
||||
String sanitizeUrl(String url) {
|
||||
|
|
@ -90,3 +91,12 @@ String? punycodeDecodeUrl(String? serverUrl) {
|
|||
|
||||
return Uri.decodeFull(serverUri.replace(host: decodedHost).toString());
|
||||
}
|
||||
|
||||
/// Generates the appropriate share URL path for a given shared link.
|
||||
///
|
||||
/// Returns a path string based on the shared link's slug and key:
|
||||
/// - If slug is present: 's/${sharedLink.slug}'
|
||||
/// - Otherwise: 'share/${sharedLink.key}'
|
||||
String getShareUrlPath(SharedLink sharedLink) {
|
||||
return sharedLink.slug != null ? 's/${sharedLink.slug}' : 'share/${sharedLink.key}';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,9 @@ class SharedLinkItem extends ConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
Clipboard.setData(ClipboardData(text: "${serverUrl}share/${sharedLink.key}")).then((_) {
|
||||
final fullPath = getShareUrlPath(sharedLink);
|
||||
|
||||
Clipboard.setData(ClipboardData(text: "$serverUrl$fullPath")).then((_) {
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
|
|
|
|||
143
mobile/test/modules/shared_link/shared_link_edit_page_test.dart
Normal file
143
mobile/test/modules/shared_link/shared_link_edit_page_test.dart
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
@Tags(['widget'])
|
||||
library;
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:isar/isar.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/store.repository.dart';
|
||||
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
||||
import 'package:immich_mobile/pages/library/shared_link/shared_link_edit.page.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/services/shared_link.service.dart';
|
||||
|
||||
import '../../test_utils.dart';
|
||||
import 'shared_link_test_utils.dart';
|
||||
|
||||
late ClipboardCapturer clipboardCapturer;
|
||||
|
||||
void main() {
|
||||
late Isar db;
|
||||
late MockSharedLinkService mockSharedLinkService;
|
||||
late MockServerInfoNotifier mockServerInfoNotifier;
|
||||
late ProviderContainer container;
|
||||
|
||||
Future<void> createSharedLink(WidgetTester tester) async {
|
||||
await tester.enterText(find.byType(TextField).at(0), 'Test Description');
|
||||
await tester.pump();
|
||||
|
||||
final createButton = find.widgetWithText(ElevatedButton, 'create_link');
|
||||
await tester.ensureVisible(createButton);
|
||||
await tester.tap(createButton);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> pumpSharedLinkEditPage(
|
||||
WidgetTester tester,
|
||||
ProviderContainer container, {
|
||||
SharedLink? existingLink,
|
||||
List<String>? assetsList,
|
||||
String? albumId,
|
||||
}) async {
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: MaterialApp(
|
||||
home: SharedLinkEditPage(existingLink: existingLink, assetsList: assetsList, albumId: albumId),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
setUpAll(() async {
|
||||
TestUtils.init();
|
||||
db = await TestUtils.initIsar();
|
||||
setupTestViewport();
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
EasyLocalization.logger.enableBuildModes = [];
|
||||
|
||||
await StoreService.init(storeRepository: IsarStoreRepository(db));
|
||||
await Store.put(StoreKey.serverEndpoint, 'https://demo.immich.app');
|
||||
|
||||
mockSharedLinkService = MockSharedLinkService();
|
||||
mockServerInfoNotifier = MockServerInfoNotifier();
|
||||
container = ProviderContainer(
|
||||
overrides: [
|
||||
sharedLinkServiceProvider.overrideWith((ref) => mockSharedLinkService),
|
||||
serverInfoProvider.overrideWith((ref) => mockServerInfoNotifier),
|
||||
],
|
||||
);
|
||||
|
||||
clipboardCapturer = ClipboardCapturer();
|
||||
setupClipboardMock(clipboardCapturer);
|
||||
|
||||
setupDefaultMockResponses(mockSharedLinkService);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
container.dispose();
|
||||
cleanupClipboardMock();
|
||||
clipboardCapturer.clear();
|
||||
});
|
||||
|
||||
group('SharedLinkEditPage Tests', () {
|
||||
testWidgets('copies URL with slug to clipboard after link creation', (tester) async {
|
||||
await pumpSharedLinkEditPage(tester, container, albumId: 'album-1');
|
||||
await createSharedLink(tester);
|
||||
|
||||
final copyButton = find.byIcon(Icons.copy);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
expect(clipboardCapturer.text, 'https://demo.immich.app/s/new-slug');
|
||||
});
|
||||
|
||||
testWidgets('copies URL without slug to clipboard after link creation', (tester) async {
|
||||
setupMockResponseForLinkWithoutSlug(mockSharedLinkService);
|
||||
|
||||
await pumpSharedLinkEditPage(tester, container, albumId: 'album-1');
|
||||
await createSharedLink(tester);
|
||||
|
||||
final copyButton = find.byIcon(Icons.copy);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(clipboardCapturer.text, 'https://demo.immich.app/share/new-key');
|
||||
});
|
||||
|
||||
testWidgets('handles empty external domain by using server endpoint', (tester) async {
|
||||
setupEmptyExternalDomain(mockServerInfoNotifier);
|
||||
|
||||
await pumpSharedLinkEditPage(tester, container, albumId: 'album-1');
|
||||
await createSharedLink(tester);
|
||||
|
||||
final copyButton = find.byIcon(Icons.copy);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(clipboardCapturer.text, 'https://demo.immich.app/s/new-slug');
|
||||
});
|
||||
|
||||
testWidgets('uses custom domain when external domain is set', (tester) async {
|
||||
final mockServerInfoWithCustomDomain = createMockServerInfo(externalDomain: 'https://custom-immich.com');
|
||||
mockServerInfoNotifier.state = mockServerInfoWithCustomDomain;
|
||||
|
||||
await pumpSharedLinkEditPage(tester, container, albumId: 'album-1');
|
||||
await createSharedLink(tester);
|
||||
|
||||
final copyButton = find.byIcon(Icons.copy);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(clipboardCapturer.text, 'https://custom-immich.com/s/new-slug');
|
||||
});
|
||||
});
|
||||
}
|
||||
130
mobile/test/modules/shared_link/shared_link_item_test.dart
Normal file
130
mobile/test/modules/shared_link/shared_link_item_test.dart
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
@Tags(['widget'])
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mocktail/mocktail.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/store.repository.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/shared_link.provider.dart';
|
||||
import 'package:immich_mobile/widgets/shared_link/shared_link_item.dart';
|
||||
|
||||
import '../../fixtures/user.stub.dart';
|
||||
import '../../test_utils.dart';
|
||||
import '../../widget_tester_extensions.dart';
|
||||
import 'shared_link_test_utils.dart';
|
||||
|
||||
void main() {
|
||||
late MockServerInfoNotifier mockServerInfoNotifier;
|
||||
late MockSharedLinksNotifier mockSharedLinksNotifier;
|
||||
late List<Override> overrides;
|
||||
late ClipboardCapturer clipboardCapturer;
|
||||
late Isar db;
|
||||
|
||||
final sharedLinkWithSlug = createSharedLinkWithSlug();
|
||||
final sharedLinkWithoutSlug = createSharedLinkWithoutSlug();
|
||||
|
||||
setUpAll(() async {
|
||||
TestUtils.init();
|
||||
db = await TestUtils.initIsar();
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await StoreService.init(storeRepository: IsarStoreRepository(db));
|
||||
|
||||
await Store.put(StoreKey.currentUser, UserStub.admin);
|
||||
await Store.put(StoreKey.serverEndpoint, 'http://example.com');
|
||||
await Store.put(StoreKey.accessToken, 'test-token');
|
||||
await Store.put(StoreKey.serverUrl, 'http://example.com');
|
||||
|
||||
mockServerInfoNotifier = MockServerInfoNotifier();
|
||||
mockSharedLinksNotifier = MockSharedLinksNotifier();
|
||||
clipboardCapturer = ClipboardCapturer();
|
||||
|
||||
setupDefaultServerInfo(mockServerInfoNotifier);
|
||||
mockSharedLinksNotifier.state = const AsyncValue.data([]);
|
||||
|
||||
overrides = [
|
||||
serverInfoProvider.overrideWith((ref) => mockServerInfoNotifier),
|
||||
sharedLinksStateProvider.overrideWith((ref) => mockSharedLinksNotifier),
|
||||
];
|
||||
|
||||
setupClipboardMock(clipboardCapturer);
|
||||
|
||||
when(() => mockSharedLinksNotifier.deleteLink(any())).thenAnswer((_) async {});
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
reset(mockSharedLinksNotifier);
|
||||
cleanupClipboardMock();
|
||||
clipboardCapturer.clear();
|
||||
StoreService.I.dispose();
|
||||
});
|
||||
|
||||
group('SharedLinkItem Tests', () {
|
||||
testWidgets('copies URL with slug to clipboard', (tester) async {
|
||||
await tester.pumpConsumerWidget(Scaffold(body: SharedLinkItem(sharedLinkWithSlug)), overrides: overrides);
|
||||
|
||||
final copyButton = find.byIcon(Icons.copy_outlined);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(clipboardCapturer.text, 'https://example.com/s/test-slug');
|
||||
});
|
||||
|
||||
testWidgets('copies URL without slug to clipboard', (tester) async {
|
||||
await tester.pumpConsumerWidget(Scaffold(body: SharedLinkItem(sharedLinkWithoutSlug)), overrides: overrides);
|
||||
|
||||
final copyButton = find.byIcon(Icons.copy_outlined);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(clipboardCapturer.text, 'https://example.com/share/test-key-2');
|
||||
});
|
||||
|
||||
testWidgets('handles empty external domain by using server URL', (tester) async {
|
||||
setupEmptyExternalDomain(mockServerInfoNotifier);
|
||||
await Store.put(StoreKey.serverEndpoint, 'http://example.com');
|
||||
|
||||
await tester.pumpConsumerWidget(Scaffold(body: SharedLinkItem(sharedLinkWithSlug)), overrides: overrides);
|
||||
|
||||
final copyButton = find.byIcon(Icons.copy_outlined);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(clipboardCapturer.text, 'http://example.com/s/test-slug');
|
||||
});
|
||||
|
||||
testWidgets('shows snackbar on successful copy', (tester) async {
|
||||
await tester.pumpConsumerWidget(Scaffold(body: SharedLinkItem(sharedLinkWithSlug)), overrides: overrides);
|
||||
|
||||
final copyButton = find.byIcon(Icons.copy_outlined);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('copied'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('works with different shared links', (tester) async {
|
||||
await tester.pumpConsumerWidget(Scaffold(body: SharedLinkItem(sharedLinkWithSlug)), overrides: overrides);
|
||||
|
||||
final copyButton = find.byIcon(Icons.copy_outlined);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(clipboardCapturer.text, 'https://example.com/s/test-slug');
|
||||
|
||||
await tester.pumpConsumerWidget(Scaffold(body: SharedLinkItem(sharedLinkWithoutSlug)), overrides: overrides);
|
||||
await tester.tap(copyButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(clipboardCapturer.text, 'https://example.com/share/test-key-2');
|
||||
});
|
||||
});
|
||||
}
|
||||
206
mobile/test/modules/shared_link/shared_link_test_utils.dart
Normal file
206
mobile/test/modules/shared_link/shared_link_test_utils.dart
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_config.model.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_features.model.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/shared_link.provider.dart';
|
||||
import 'package:immich_mobile/services/server_info.service.dart';
|
||||
import 'package:immich_mobile/services/shared_link.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockSharedLinkService extends Mock implements SharedLinkService {}
|
||||
|
||||
class MockServerInfoService extends Mock implements ServerInfoService {}
|
||||
|
||||
class MockServerInfoNotifier extends ServerInfoNotifier {
|
||||
MockServerInfoNotifier({String externalDomain = 'https://demo.immich.app'}) : super(MockServerInfoService()) {
|
||||
state = createMockServerInfo(externalDomain: externalDomain);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> getServerConfig() async {}
|
||||
}
|
||||
|
||||
class MockSharedLinksNotifier extends StateNotifier<AsyncValue<List<SharedLink>>>
|
||||
with Mock
|
||||
implements SharedLinksNotifier {
|
||||
MockSharedLinksNotifier() : super(const AsyncValue.loading());
|
||||
}
|
||||
|
||||
final mockServerInfoNotifierProvider = Provider<ServerInfoNotifier>((ref) => MockServerInfoNotifier());
|
||||
|
||||
/// Creates a mock ServerInfo with customizable parameters for testing
|
||||
ServerInfo createMockServerInfo({
|
||||
String externalDomain = 'https://demo.immich.app',
|
||||
String serverEndpoint = 'https://demo.immich.app',
|
||||
}) {
|
||||
return ServerInfo(
|
||||
serverVersion: const ServerVersion(major: 1, minor: 0, patch: 0),
|
||||
latestVersion: const ServerVersion(major: 1, minor: 0, patch: 0),
|
||||
serverFeatures: const ServerFeatures(map: true, trash: true, oauthEnabled: false, passwordLogin: true),
|
||||
serverConfig: ServerConfig(
|
||||
trashDays: 30,
|
||||
oauthButtonText: '',
|
||||
externalDomain: externalDomain,
|
||||
mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json',
|
||||
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
|
||||
),
|
||||
serverDiskInfo: const ServerDiskInfo(diskAvailable: "0", diskSize: "0", diskUse: "0", diskUsagePercentage: 0),
|
||||
isVersionMismatch: false,
|
||||
isNewReleaseAvailable: false,
|
||||
versionMismatchErrorMessage: "",
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a mock album shared link with customizable parameters for testing
|
||||
SharedLink createAlbumSharedLink({
|
||||
String id = '1',
|
||||
String title = 'Test Album',
|
||||
String description = 'Test Description',
|
||||
String key = 'test-key',
|
||||
String? slug = 'test-slug',
|
||||
bool allowUpload = true,
|
||||
bool allowDownload = true,
|
||||
bool showMetadata = true,
|
||||
String? password,
|
||||
}) {
|
||||
return SharedLink(
|
||||
id: id,
|
||||
title: title,
|
||||
description: description,
|
||||
key: key,
|
||||
slug: slug,
|
||||
expiresAt: DateTime.now().add(const Duration(days: 1)),
|
||||
allowUpload: allowUpload,
|
||||
allowDownload: allowDownload,
|
||||
showMetadata: showMetadata,
|
||||
thumbAssetId: null,
|
||||
password: password,
|
||||
type: SharedLinkSource.album,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a shared link with slug for testing
|
||||
SharedLink createSharedLinkWithSlug() {
|
||||
return SharedLink(
|
||||
id: '1',
|
||||
title: 'Test Link',
|
||||
description: 'Test Description',
|
||||
key: 'test-key',
|
||||
slug: 'test-slug',
|
||||
expiresAt: DateTime.now().add(const Duration(days: 1)),
|
||||
allowUpload: true,
|
||||
allowDownload: true,
|
||||
showMetadata: true,
|
||||
thumbAssetId: null,
|
||||
password: null,
|
||||
type: SharedLinkSource.album,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a shared link without slug for testing
|
||||
SharedLink createSharedLinkWithoutSlug() {
|
||||
return SharedLink(
|
||||
id: '2',
|
||||
title: 'Test Link 2',
|
||||
description: 'Test Description 2',
|
||||
key: 'test-key-2',
|
||||
slug: null,
|
||||
expiresAt: DateTime.now().add(const Duration(days: 1)),
|
||||
allowUpload: false,
|
||||
allowDownload: false,
|
||||
showMetadata: false,
|
||||
thumbAssetId: null,
|
||||
password: null,
|
||||
type: SharedLinkSource.individual,
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets up default server info with external domain for testing
|
||||
void setupDefaultServerInfo(MockServerInfoNotifier mockServerInfoNotifier) {
|
||||
final mockServerInfo = createMockServerInfo(externalDomain: 'https://example.com');
|
||||
mockServerInfoNotifier.state = mockServerInfo;
|
||||
}
|
||||
|
||||
/// Sets up empty external domain for testing
|
||||
void setupEmptyExternalDomain(ServerInfoNotifier mockServerInfoNotifier) {
|
||||
final mockServerInfoWithEmptyDomain = createMockServerInfo(externalDomain: '');
|
||||
mockServerInfoNotifier.state = mockServerInfoWithEmptyDomain;
|
||||
}
|
||||
|
||||
/// Sets up default mock responses for shared link service
|
||||
void setupDefaultMockResponses(MockSharedLinkService mockSharedLinkService) {
|
||||
when(
|
||||
() => mockSharedLinkService.createSharedLink(
|
||||
albumId: any(named: 'albumId'),
|
||||
assetIds: any(named: 'assetIds'),
|
||||
showMeta: any(named: 'showMeta'),
|
||||
allowDownload: any(named: 'allowDownload'),
|
||||
allowUpload: any(named: 'allowUpload'),
|
||||
description: any(named: 'description'),
|
||||
password: any(named: 'password'),
|
||||
expiresAt: any(named: 'expiresAt'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => createAlbumSharedLink(id: 'new-link-id', title: 'New Link', key: 'new-key', slug: 'new-slug'),
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets up mock response for link without slug
|
||||
void setupMockResponseForLinkWithoutSlug(MockSharedLinkService mockSharedLinkService) {
|
||||
when(
|
||||
() => mockSharedLinkService.createSharedLink(
|
||||
albumId: any(named: 'albumId'),
|
||||
assetIds: any(named: 'assetIds'),
|
||||
showMeta: any(named: 'showMeta'),
|
||||
allowDownload: any(named: 'allowDownload'),
|
||||
allowUpload: any(named: 'allowUpload'),
|
||||
description: any(named: 'description'),
|
||||
password: any(named: 'password'),
|
||||
expiresAt: any(named: 'expiresAt'),
|
||||
),
|
||||
).thenAnswer((_) async => createAlbumSharedLink(id: 'new-link-id', title: 'New Link', key: 'new-key', slug: null));
|
||||
}
|
||||
|
||||
/// Test utility to capture clipboard operations for verification
|
||||
class ClipboardCapturer {
|
||||
String text = '';
|
||||
|
||||
void clear() {
|
||||
text = '';
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up a mock clipboard handler for tests that captures clipboard operations
|
||||
void setupClipboardMock(ClipboardCapturer capturer) {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (
|
||||
methodCall,
|
||||
) async {
|
||||
if (methodCall.method == 'Clipboard.setData') {
|
||||
final data = methodCall.arguments as Map<String, dynamic>;
|
||||
final text = data['text'] as String?;
|
||||
capturer.text = text ?? '';
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/// Cleans up the clipboard mock after tests to prevent interference
|
||||
void cleanupClipboardMock() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
SystemChannels.platform,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets up larger viewport for test button visibility
|
||||
void setupTestViewport() {
|
||||
TestWidgetsFlutterBinding.instance.platformDispatcher.views.first.physicalSize = const Size(1200, 2000);
|
||||
TestWidgetsFlutterBinding.instance.platformDispatcher.views.first.devicePixelRatio = 1.0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue