chore: prepare local.3 release with query regression coverage

This commit is contained in:
Codex
2026-10-16 20:58:44 +03:00
parent ae7cb3192e
commit 4649313c29
3 changed files with 153 additions and 27 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none' publish_to: 'none'
version: 3.3.3-local.2+3030401 version: 3.3.3-local.3+3030402
environment: environment:
sdk: '>=3.12.0 <4.0.0' sdk: '>=3.12.0 <4.0.0'
+21 -26
View File
@@ -824,7 +824,7 @@ importers:
version: 5.15.1(@photo-sphere-viewer/core@5.15.1) version: 5.15.1(@photo-sphere-viewer/core@5.15.1)
'@tanstack/svelte-query': '@tanstack/svelte-query':
specifier: ^6.3.2 specifier: ^6.3.2
version: 6.3.2(svelte@5.57.2(@typescript-eslint/types@8.71.2)) version: 6.3.2(svelte@5.57.1(@typescript-eslint/types@8.70.1))
'@types/geojson': '@types/geojson':
specifier: ^7946.0.16 specifier: ^7946.0.16
version: 7946.0.16 version: 7946.0.16
@@ -862,8 +862,8 @@ importers:
specifier: ^1.6.16 specifier: ^1.6.16
version: 1.7.3 version: 1.7.3
intl-messageformat: intl-messageformat:
specifier: ^11.0.0 specifier: ^12.0.0
version: 11.2.15 version: 12.1.3
justified-layout: justified-layout:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0 version: 4.1.0
@@ -2734,18 +2734,15 @@ packages:
'@formatjs/icu-messageformat-parser@2.11.4': '@formatjs/icu-messageformat-parser@2.11.4':
resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==} resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==}
'@formatjs/icu-messageformat-parser@3.5.18':
resolution: {integrity: sha512-wX1efcL8d7K5QapOTRsP/OLTA/kpwe4LwqWyNxDNg5iRs8i9jJbCyxB5P8jc4KOW5oiLzz2DJr+BRY5xMV9+bA==}
'@formatjs/icu-messageformat-parser@3.5.20': '@formatjs/icu-messageformat-parser@3.5.20':
resolution: {integrity: sha512-DUzCb2iskGDKvLU7P+nz/rjpmN59luoTRbWHFFNYNnZTxWCKvwPI94rNll99TGZaI6XoTbDF0QGegk21GFrQZQ==} resolution: {integrity: sha512-DUzCb2iskGDKvLU7P+nz/rjpmN59luoTRbWHFFNYNnZTxWCKvwPI94rNll99TGZaI6XoTbDF0QGegk21GFrQZQ==}
'@formatjs/icu-messageformat-parser@3.5.21':
resolution: {integrity: sha512-Vnl7IOSyMRaP4Pjv48etkT2nlcIwzh4QWIa3hewzGlZZPBbft+TbMir4MijpDViOLN/1hvkGC10oYMuilsG+fw==}
'@formatjs/icu-skeleton-parser@1.8.16': '@formatjs/icu-skeleton-parser@1.8.16':
resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==} resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==}
'@formatjs/icu-skeleton-parser@2.1.11':
resolution: {integrity: sha512-j8cUmOJzVgkHuS0QiQ6ga76UIoLOFSAMWhs7aZJztH3aAdCOAE6vpC8KVvFB4cU10ON0y2/5oOVmPJ43s2lTwA==}
'@formatjs/icu-skeleton-parser@2.1.12': '@formatjs/icu-skeleton-parser@2.1.12':
resolution: {integrity: sha512-ca7tZMLpfHX3sUD7hP6gE/qr6Iq89EDd3ZLeRhbsB7r0UD55DGJq+tmFLzrGrnx5StahUevQmQQVtbDRHsfDHA==} resolution: {integrity: sha512-ca7tZMLpfHX3sUD7hP6gE/qr6Iq89EDd3ZLeRhbsB7r0UD55DGJq+tmFLzrGrnx5StahUevQmQQVtbDRHsfDHA==}
@@ -8502,8 +8499,8 @@ packages:
intl-messageformat@10.7.18: intl-messageformat@10.7.18:
resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==}
intl-messageformat@11.2.15: intl-messageformat@12.1.3:
resolution: {integrity: sha512-W4VNytsXyWgW5lvVchmsb9xgA5rSKqoflu3/IQ9UCtB0z4R+Bx9MoEkwFT/+30RCl/KzUoH0QGGe9lppF5SuvQ==} resolution: {integrity: sha512-JXi5wk8R/oSECKyxSIZ00VS8KG6MZkHM0qU4NS7RrvIWWXkiv3wVqGlCvEvyXiDvKlt2yqwZMiZGIv+I8rrnZQ==}
invariant@2.2.4: invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
@@ -15743,21 +15740,19 @@ snapshots:
'@formatjs/icu-skeleton-parser': 1.8.16 '@formatjs/icu-skeleton-parser': 1.8.16
tslib: 2.8.1 tslib: 2.8.1
'@formatjs/icu-messageformat-parser@3.5.18':
dependencies:
'@formatjs/icu-skeleton-parser': 2.1.11
'@formatjs/icu-messageformat-parser@3.5.20': '@formatjs/icu-messageformat-parser@3.5.20':
dependencies: dependencies:
'@formatjs/icu-skeleton-parser': 2.1.12 '@formatjs/icu-skeleton-parser': 2.1.12
'@formatjs/icu-messageformat-parser@3.5.21':
dependencies:
'@formatjs/icu-skeleton-parser': 2.1.12
'@formatjs/icu-skeleton-parser@1.8.16': '@formatjs/icu-skeleton-parser@1.8.16':
dependencies: dependencies:
'@formatjs/ecma402-abstract': 2.3.6 '@formatjs/ecma402-abstract': 2.3.6
tslib: 2.8.1 tslib: 2.8.1
'@formatjs/icu-skeleton-parser@2.1.11': {}
'@formatjs/icu-skeleton-parser@2.1.12': {} '@formatjs/icu-skeleton-parser@2.1.12': {}
'@formatjs/intl-localematcher@0.6.2': '@formatjs/intl-localematcher@0.6.2':
@@ -17636,10 +17631,10 @@ snapshots:
'@tanstack/query-core@5.104.2': {} '@tanstack/query-core@5.104.2': {}
'@tanstack/svelte-query@6.3.2(svelte@5.57.2(@typescript-eslint/types@8.71.2))': '@tanstack/svelte-query@6.3.2(svelte@5.57.1(@typescript-eslint/types@8.70.1))':
dependencies: dependencies:
'@tanstack/query-core': 5.104.2 '@tanstack/query-core': 5.104.2
svelte: 5.57.2(@typescript-eslint/types@8.71.2) svelte: 5.57.1(@typescript-eslint/types@8.70.1)
'@testing-library/dom@10.4.1': '@testing-library/dom@10.4.1':
dependencies: dependencies:
@@ -18251,7 +18246,7 @@ snapshots:
'@types/through@0.0.33': '@types/through@0.0.33':
dependencies: dependencies:
'@types/node': 24.19.0 '@types/node': 24.19.2
'@types/trusted-types@2.0.7': '@types/trusted-types@2.0.7':
optional: true optional: true
@@ -18268,7 +18263,7 @@ snapshots:
'@types/ws@8.18.1': '@types/ws@8.18.1':
dependencies: dependencies:
'@types/node': 24.19.0 '@types/node': 24.19.2
'@types/yargs-parser@21.0.3': {} '@types/yargs-parser@21.0.3': {}
@@ -19249,7 +19244,7 @@ snapshots:
buffer-image-size@0.6.4: buffer-image-size@0.6.4:
dependencies: dependencies:
'@types/node': 24.19.0 '@types/node': 24.19.2
buffer@5.7.1: buffer@5.7.1:
dependencies: dependencies:
@@ -21566,7 +21561,7 @@ snapshots:
happy-dom@20.14.5: happy-dom@20.14.5:
dependencies: dependencies:
'@types/node': 24.19.0 '@types/node': 24.19.2
'@types/whatwg-mimetype': 3.0.2 '@types/whatwg-mimetype': 3.0.2
'@types/ws': 8.18.1 '@types/ws': 8.18.1
buffer-image-size: 0.6.4 buffer-image-size: 0.6.4
@@ -22069,10 +22064,10 @@ snapshots:
'@formatjs/icu-messageformat-parser': 2.11.4 '@formatjs/icu-messageformat-parser': 2.11.4
tslib: 2.8.1 tslib: 2.8.1
intl-messageformat@11.2.15: intl-messageformat@12.1.3:
dependencies: dependencies:
'@formatjs/fast-memoize': 3.1.7 '@formatjs/fast-memoize': 3.1.7
'@formatjs/icu-messageformat-parser': 3.5.18 '@formatjs/icu-messageformat-parser': 3.5.21
invariant@2.2.4: invariant@2.2.4:
dependencies: dependencies:
@@ -22382,7 +22377,7 @@ snapshots:
jest-worker@27.5.1: jest-worker@27.5.1:
dependencies: dependencies:
'@types/node': 24.19.0 '@types/node': 24.19.2
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1
@@ -0,0 +1,131 @@
import {
deleteLibrary,
getConfig,
getStorage,
updateConfig,
updateLibrary,
type AdminConfigDto,
type LibraryResponseDto,
} from '@immich/sdk';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { executeMutation, queryClient } from '$lib/queries/client';
import {
deleteLibraryMutation,
getAllLibrariesQuery,
getLibraryStatisticsQuery,
updateLibraryMutation,
} from '$lib/queries/library.query';
import { getStorageQuery } from '$lib/queries/server.query';
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
import { handleError } from '$lib/utils/handle-error';
vi.mock('@immich/ui', () => ({ toastManager: { primary: vi.fn(), info: vi.fn() } }));
vi.mock('$lib/utils/handle-error', () => ({ handleError: vi.fn() }));
vi.mock('$lib/utils/i18n', () => ({ getFormatter: async () => (key: string) => key }));
vi.mock('@immich/sdk', () => ({
createLibrary: vi.fn(),
deleteLibrary: vi.fn(),
getAllLibraries: vi.fn(),
getLibraryStatistics: vi.fn(),
scanLibrary: vi.fn(),
updateLibrary: vi.fn(),
getAboutInfo: vi.fn(),
getVersionHistory: vi.fn(),
getStorage: vi.fn(),
getServerStatistics: vi.fn(),
getConfig: vi.fn(),
getConfigDefaults: vi.fn(),
updateConfig: vi.fn(),
}));
const first = { id: 'first', name: 'Photos' } as LibraryResponseDto;
const second = { id: 'second', name: 'Archive' } as LibraryResponseDto;
describe('shared queries after the upstream migration', () => {
beforeEach(() => {
queryClient.clear();
vi.resetAllMocks();
});
afterEach(() => queryClient.clear());
it('clears account data on logout', () => {
queryClient.setQueryData(getAllLibrariesQuery().queryKey, [first]);
queryClient.setQueryData(getConfigQuery().queryKey, {} as AdminConfigDto);
eventManager.emit('AuthLogout');
expect(queryClient.getQueryCache().getAll()).toHaveLength(0);
});
it('shares a storage request between consumers', async () => {
const storage = { diskSizeRaw: 100, diskUseRaw: 20 } as Awaited<ReturnType<typeof getStorage>>;
vi.mocked(getStorage).mockResolvedValue(storage);
const values = await Promise.all([queryClient.query(getStorageQuery()), queryClient.query(getStorageQuery())]);
expect(values).toEqual([storage, storage]);
expect(getStorage).toHaveBeenCalledTimes(1);
});
it('preserves libraries and statistics when deletion fails', async () => {
queryClient.setQueryData(getAllLibrariesQuery().queryKey, [first, second]);
queryClient.setQueryData(getLibraryStatisticsQuery(first.id).queryKey, {
photos: 5,
videos: 0,
usage: 100,
total: 5,
});
vi.mocked(deleteLibrary).mockRejectedValue(new Error('offline'));
await executeMutation(deleteLibraryMutation, [{ id: first.id }]);
expect(queryClient.getQueryData(getAllLibrariesQuery().queryKey)).toEqual([first, second]);
expect(queryClient.getQueryData(getLibraryStatisticsQuery(first.id).queryKey)?.photos).toBe(5);
expect(handleError).toHaveBeenCalledOnce();
});
it('removes only the deleted library from the cache after success', async () => {
queryClient.setQueryData(getAllLibrariesQuery().queryKey, [first, second]);
queryClient.setQueryData(getLibraryStatisticsQuery(first.id).queryKey, {
photos: 5,
videos: 0,
usage: 100,
total: 5,
});
vi.mocked(deleteLibrary).mockResolvedValue(undefined as never);
await executeMutation(deleteLibraryMutation, [{ id: first.id }]);
expect(queryClient.getQueryData(getAllLibrariesQuery().queryKey)).toEqual([second]);
expect(queryClient.getQueryData(getLibraryStatisticsQuery(first.id).queryKey)).toBeUndefined();
});
it('uses the saved library response and refreshes its statistics', async () => {
queryClient.setQueryData(getAllLibrariesQuery().queryKey, [first, second]);
queryClient.setQueryData(getLibraryStatisticsQuery(first.id).queryKey, {
photos: 5,
videos: 0,
usage: 100,
total: 5,
});
const saved = { ...first, name: 'Renamed' };
vi.mocked(updateLibrary).mockResolvedValue(saved);
await executeMutation(updateLibraryMutation, [{ id: first.id, updateLibraryDto: { name: saved.name } }]);
expect(queryClient.getQueryData(getAllLibrariesQuery().queryKey)).toEqual([saved, second]);
expect(queryClient.getQueryState(getLibraryStatisticsQuery(first.id).queryKey)?.isInvalidated).toBe(true);
});
it('merges a settings edit with the latest server configuration', async () => {
const current = { server: { externalDomain: 'https://example.com' }, trash: { enabled: true, days: 30 } };
const update = { trash: { enabled: true, days: 60 } };
const saved = { ...current, ...update } as AdminConfigDto;
vi.mocked(getConfig).mockResolvedValue(current as AdminConfigDto);
vi.mocked(updateConfig).mockResolvedValue(saved);
await executeMutation(updateConfigMutation, update);
expect(updateConfig).toHaveBeenCalledWith({ adminConfigDto: saved });
expect(queryClient.getQueryData(getConfigQuery().queryKey)).toEqual(saved);
});
it('keeps the previous settings if saving fails', async () => {
const current = { trash: { enabled: true, days: 30 } } as AdminConfigDto;
queryClient.setQueryData(getConfigQuery().queryKey, current);
vi.mocked(getConfig).mockResolvedValue(current);
vi.mocked(updateConfig).mockRejectedValue(new Error('offline'));
await executeMutation(updateConfigMutation, { trash: { enabled: true, days: 60 } });
expect(queryClient.getQueryData(getConfigQuery().queryKey)).toEqual(current);
expect(handleError).toHaveBeenCalledOnce();
});
});