release: v0.19.0
Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of [CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md) for the full list. Highlights: client proofing links (#683), custom content pages (#738), asset picker search (#740), concurrent-edit detection (#742), env-locked settings (#739), an accessibility baseline (#741), three security fixes with advisories (#737, #755, #759), and a pre-release quality pass (#780–#797). --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ralksta <ralksta@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ralf <rohlenbo79@gmail.com> Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com> Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com> Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
This commit is contained in:
@@ -13,8 +13,15 @@ vi.mock('@/lib/immich', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const config = vi.hoisted(() => ({
|
||||
value: {
|
||||
standaloneAlbums: ['album-1'] as string[],
|
||||
subpages: [] as Array<{ slug: string; albumIds: string[]; enabled: boolean; hidden?: boolean }>,
|
||||
cacheTtl: 60_000,
|
||||
},
|
||||
}));
|
||||
vi.mock('@/lib/config', () => ({
|
||||
getConfig: () => ({ subpages: [], cacheTtl: 60_000 }),
|
||||
getConfig: () => config.value,
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/cache', () => ({
|
||||
@@ -22,6 +29,7 @@ vi.mock('@/lib/cache', () => ({
|
||||
}));
|
||||
|
||||
import { getMapData } from '@/lib/mapService';
|
||||
import { listedAlbumIds } from '@/lib/config/schema';
|
||||
|
||||
const ALBUM = { id: 'album-1', albumName: 'Trip', slug: 'trip' };
|
||||
|
||||
@@ -30,6 +38,8 @@ function asset(id: string, exif: Record<string, unknown>) {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
config.value.standaloneAlbums = ['album-1'];
|
||||
config.value.subpages = [];
|
||||
getAlbums.mockReset();
|
||||
getAlbum.mockReset();
|
||||
getAlbums.mockResolvedValue([ALBUM]);
|
||||
@@ -83,3 +93,101 @@ describe('getMapData coordinate filtering (#635)', () => {
|
||||
expect(locations).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* `enabled: false` takes a subpage offline. Its albums stay on the allowlist
|
||||
* (the configuration is kept), so the map has to leave them out itself: a
|
||||
* marker names the album, links it, counts it and shows one of its photos.
|
||||
*/
|
||||
describe('getMapData and offline subpages', () => {
|
||||
const berlin = { latitude: 52.5, longitude: 13.4, city: 'Berlin', country: 'Germany' };
|
||||
|
||||
beforeEach(() => {
|
||||
getAlbum.mockResolvedValue({ id: ALBUM.id, assets: [asset('a', berlin)] });
|
||||
});
|
||||
|
||||
it('leaves out an album whose only subpage is offline', async () => {
|
||||
config.value.standaloneAlbums = [];
|
||||
config.value.subpages = [{ slug: 'old-series', albumIds: [ALBUM.id], enabled: false }];
|
||||
|
||||
expect(await getMapData()).toEqual([]);
|
||||
});
|
||||
|
||||
it('keeps it when an enabled subpage lists it too, and links it there', async () => {
|
||||
config.value.standaloneAlbums = [];
|
||||
config.value.subpages = [
|
||||
{ slug: 'old-series', albumIds: [ALBUM.id], enabled: false },
|
||||
{ slug: 'travel', albumIds: [ALBUM.id], enabled: true },
|
||||
];
|
||||
|
||||
const locations = await getMapData();
|
||||
|
||||
expect(locations).toHaveLength(1);
|
||||
expect(locations[0].albums[0].subpageSlug).toBe('travel');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* `hidden: true` keeps a subpage reachable by direct link only. The map is a
|
||||
* listing: a marker names the album, links it under the subpage's address and
|
||||
* shows one of its photos, so an album only a hidden subpage carries must not
|
||||
* appear on it. Its photos stay reachable through the link (publishedAssets
|
||||
* still asks onlineAlbumIds, which is not narrowed).
|
||||
*/
|
||||
describe('getMapData and hidden subpages', () => {
|
||||
const berlin = { latitude: 52.5, longitude: 13.4, city: 'Berlin', country: 'Germany' };
|
||||
|
||||
beforeEach(() => {
|
||||
getAlbum.mockResolvedValue({ id: ALBUM.id, assets: [asset('a', berlin)] });
|
||||
});
|
||||
|
||||
it('leaves out an album whose only subpage is hidden', async () => {
|
||||
config.value.standaloneAlbums = [];
|
||||
config.value.subpages = [
|
||||
{ slug: 'private', albumIds: [ALBUM.id], enabled: true, hidden: true },
|
||||
];
|
||||
|
||||
expect(await getMapData()).toEqual([]);
|
||||
expect(getAlbum).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps it when a listed subpage carries it too, and links it there', async () => {
|
||||
config.value.standaloneAlbums = [];
|
||||
config.value.subpages = [
|
||||
{ slug: 'private', albumIds: [ALBUM.id], enabled: true, hidden: true },
|
||||
{ slug: 'travel', albumIds: [ALBUM.id], enabled: true },
|
||||
];
|
||||
|
||||
const locations = await getMapData();
|
||||
|
||||
expect(locations).toHaveLength(1);
|
||||
expect(locations[0].albums[0].subpageSlug).toBe('travel');
|
||||
});
|
||||
|
||||
it('keeps a standalone album that a hidden subpage also carries, linked standalone', async () => {
|
||||
config.value.standaloneAlbums = [ALBUM.id];
|
||||
config.value.subpages = [
|
||||
{ slug: 'private', albumIds: [ALBUM.id], enabled: true, hidden: true },
|
||||
];
|
||||
|
||||
const locations = await getMapData();
|
||||
|
||||
expect(locations).toHaveLength(1);
|
||||
expect(locations[0].albums[0].subpageSlug).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('listedAlbumIds', () => {
|
||||
it('drops albums of offline and hidden subpages, keeps standalone and listed ones', () => {
|
||||
const ids = listedAlbumIds({
|
||||
standaloneAlbums: ['s'],
|
||||
subpages: [
|
||||
{ albumIds: ['v', 'shared'], enabled: true },
|
||||
{ albumIds: ['off'], enabled: false },
|
||||
{ albumIds: ['h', 'shared'], enabled: true, hidden: true },
|
||||
],
|
||||
});
|
||||
|
||||
expect([...ids].sort()).toEqual(['s', 'shared', 'v']);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user