Compare commits

..

1 Commits

Author SHA1 Message Date
shenlong-tanwen 5a14204ed2 chore: cleanup partner action test 2026-06-24 09:36:37 +05:30
9 changed files with 23 additions and 83 deletions
@@ -5,32 +5,25 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/presentation/actions/partner.action.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:mocktail/mocktail.dart';
import '../../factories/user_factory.dart';
import '../../mocks.dart';
import '../../presentation_context.dart';
void main() {
late PresentationContext context;
late UserDto currentUser;
final mocks = ServiceMocks();
setUp(() async {
currentUser = UserFactory.createDto();
context = await PresentationContext.create();
when(mocks.user.tryGetMyUser).thenReturn(currentUser);
});
tearDown(() async {
mocks.resetAll();
await context.dispose();
tearDown(() {
context.dispose();
});
List<Override> overrides({List<User> candidates = const []}) => [
currentUserProvider.overrideWith((ref) => CurrentUserProvider(mocks.user.service)),
partnerServiceProvider.overrideWithValue(mocks.partner.service),
...context.overrides,
partnerServiceProvider.overrideWithValue(context.mocks.partner.service),
candidatesStateProvider.overrideWith((ref) => Stream<Iterable<User>>.value(candidates)),
];
@@ -43,7 +36,9 @@ void main() {
await tester.tap(find.text(candidate.name));
await tester.pumpAndSettle();
verify(() => mocks.partner.service.create(sharedById: currentUser.id, sharedWithId: candidate.id)).called(1);
verify(
() => context.mocks.partner.service.create(sharedById: context.currentUser.id, sharedWithId: candidate.id),
).called(1);
});
testWidgets('creates nothing when the selection dialog is dismissed', (tester) async {
@@ -51,7 +46,7 @@ void main() {
await tester.sendKeyEvent(LogicalKeyboardKey.escape); // dismiss without selecting
await tester.pumpAndSettle();
verifyNever(mocks.partner.create);
verifyNever(context.mocks.partner.create);
});
});
@@ -65,7 +60,9 @@ void main() {
await tester.tap(find.byType(TextButton).last); // confirm
await tester.pumpAndSettle();
verify(() => mocks.partner.service.delete(sharedById: currentUser.id, sharedWithId: partner.id)).called(1);
verify(
() => context.mocks.partner.service.delete(sharedById: context.currentUser.id, sharedWithId: partner.id),
).called(1);
});
testWidgets('deletes nothing when the confirmation is cancelled', (tester) async {
@@ -77,7 +74,7 @@ void main() {
await tester.tap(find.byType(TextButton).first); // cancel
await tester.pumpAndSettle();
verifyNever(mocks.partner.delete);
verifyNever(context.mocks.partner.delete);
});
});
}
@@ -13,7 +13,7 @@ void main() {
late PresentationContext context;
setUp(() async => context = await PresentationContext.create());
tearDown(() async => await context.dispose());
tearDown(() => context.dispose());
group('PartnerSharedByList', () {
testWidgets('shows the empty-state add button when there are no partners', (tester) async {
+9 -5
View File
@@ -23,7 +23,7 @@ import 'mocks.dart';
class PresentationContext {
PresentationContext._({required UserDto user}) : currentUser = user, mocks = ServiceMocks() {
when(mocks.user.tryGetMyUser).thenReturn(currentUser);
setup();
}
static const String serverEndpoint = 'http://localhost:3000';
@@ -46,10 +46,14 @@ class PresentationContext {
return PresentationContext._(user: UserFactory.createDto());
}
Future<void> dispose() async {
// TODO: Dispose the store and database after each test.
// This is currently not possible because the store is a singleton and is used across tests.
// Refactor the store to be created per test to allow proper disposal.
void setup() {
when(mocks.user.tryGetMyUser).thenReturn(currentUser);
}
void dispose() {
addTearDown(() {
mocks.resetAll();
});
}
}
-34
View File
@@ -291,40 +291,6 @@
"required": ["albumIds"]
}
},
{
"name": "assetDataWebhook",
"title": "Trigger Webhook",
"description": "POST/PUT event data to any URL",
"types": ["AssetV1"],
"hostFunctions": true,
"schema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"title": "URL",
"description": "Event data will be PUT/POSTed to this URL as a JSON object"
},
"headerName": {
"type": "string",
"title": "Header name",
"description": "The name of an additional header to include with the request (e.g. authentication)"
},
"headerValue": {
"type": "string",
"title": "Header value",
"description": "The value of the additional header"
},
"method": {
"type": "string",
"title": "Method",
"description": "The HTTP method to use in the request",
"enum": ["POST", "PUT"]
}
},
"required": ["url"]
}
},
{
"name": "noop1",
"title": "DEV: Nested properties",
-1
View File
@@ -24,5 +24,4 @@ declare module 'main' {
export function assetTimeline(): I32;
export function assetTrash(): I32;
export function assetAddToAlbums(): I32;
export function assetDataWebhook(): I32;
}
-20
View File
@@ -181,23 +181,3 @@ export const assetAddToAlbums = () => {
return {};
});
};
export const assetDataWebhook = () => {
return wrapper<WorkflowType.AssetV1, { url: string; headerName?: string; headerValue?: string; method?: string }>(
({ config, data }) => {
const headers = new Headers({ 'Content-Type': 'application/json' });
if (config.headerName && config.headerValue) {
headers.set(config.headerName, config.headerValue);
}
fetch(config.url, {
method: config.method ?? 'POST',
body: JSON.stringify(data.asset),
headers,
});
return {};
},
);
};
+1 -1
View File
@@ -4,7 +4,7 @@
"declaration": true,
"emitDeclarationOnly": true,
"esModuleInterop": true, // Enables compatibility with Babel-style module imports
"lib": ["es2020", "DOM"], // Specify a list of library files to be included in the compilation
"lib": ["es2020"], // Specify a list of library files to be included in the compilation
"module": "nodenext", // Specify module code generation
"moduleResolution": "nodenext",
"noEmit": true, // Do not emit outputs (no .js or .d.ts files)
@@ -33,11 +33,6 @@ type HostFunctionResult<T> =
type QueryParams<T extends (...args: any) => any> = Parameters<T>[0];
type AlbumSearchDto = QueryParams<typeof getAllAlbums>;
type HttpRequestOptions = {
method?: string;
headers?: Record<string, string>;
body?: string;
};
export const hostFunctions = (authToken: string) => {
const host = Host.getFunctions();
@@ -216,7 +216,6 @@ export class PluginRepository {
functions: {
'extism:host/user': functions ?? {},
},
allowedHosts: runInWorker ? ['*'] : [],
logger: {
trace: (message) => logger.verbose(message),
info: (message) => logger.log(message),