Files
immich-folio/app/[...path]/__tests__/path-depth.test.ts
2026-10-02 18:14:23 +02:00

89 lines
3.1 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { ReactElement } from 'react';
/**
* The catch-all answers one segment (subpage, content page, standalone album)
* and two (subpage/album). A third segment used to fall through to the
* one-segment branch, which only reads `path[0]`: `/travel/iceland/anything`
* rendered the Travel subpage and `/iceland/x/y` the Iceland album, each with
* a 200, under an endless supply of URLs.
*/
const NOT_FOUND = new Error('NEXT_NOT_FOUND');
vi.mock('next/navigation', () => ({
notFound: () => {
throw NOT_FOUND;
},
}));
vi.mock('next/headers', () => ({
cookies: async () => ({ get: () => undefined }),
}));
vi.mock('@/lib/admin/auth', () => ({ isAdminAuthenticated: async () => false }));
const immich = vi.hoisted(() => ({
isSubpageSlug: vi.fn((slug: string) => slug === 'travel'),
getSubpageAlbums: vi.fn(),
getAlbumBySlug: vi.fn(),
findAlbumBySlug: vi.fn(),
getStandaloneAlbums: vi.fn(async () => []),
getSubpages: vi.fn(async () => []),
getAssetInfo: vi.fn(async () => null),
}));
vi.mock('@/lib/immich', () => ({ immich }));
const ALBUM = {
id: 'album-1',
albumName: 'Iceland',
slug: 'iceland',
assets: [],
albumThumbnailAssetId: null,
};
import PathPage, { generateMetadata } from '../page';
import { getServerDictionary } from '@/lib/i18n/server';
const call = (path: string[]) => ({
params: Promise.resolve({ path }),
searchParams: Promise.resolve({}),
});
beforeEach(() => {
vi.clearAllMocks();
immich.getAlbumBySlug.mockResolvedValue(ALBUM);
immich.findAlbumBySlug.mockResolvedValue(ALBUM);
immich.getSubpageAlbums.mockResolvedValue({
subpage: { slug: 'travel', name: 'Travel', albumIds: ['album-1', 'album-2'] },
albums: [ALBUM, { ...ALBUM, id: 'album-2', slug: 'norway', albumName: 'Norway' }],
});
});
describe('catch-all path depth', () => {
it.each([[['iceland', 'x', 'y']], [['travel', 'iceland', 'extra']], [['travel', 'a', 'b', 'c']]])(
'answers %j with a 404',
async (path) => {
await expect(PathPage(call(path))).rejects.toBe(NOT_FOUND);
expect(immich.getAlbumBySlug).not.toHaveBeenCalled();
expect(immich.findAlbumBySlug).not.toHaveBeenCalled();
expect(immich.getSubpageAlbums).not.toHaveBeenCalled();
},
);
it('gives a three-segment path no album metadata, only the not-found title', async () => {
const meta = await generateMetadata(call(['iceland', 'x', 'y']));
expect(meta.title).toBe(getServerDictionary().error.notFoundTitle);
expect(immich.getAlbumBySlug).not.toHaveBeenCalled();
});
it('still renders one and two segments', async () => {
for (const path of [['iceland'], ['travel', 'iceland']]) {
// The page decides, then hands the rendering to the content inside its
// Suspense boundary; run that too.
const shell = (await PathPage(call(path))) as ReactElement<{ children: ReactElement }>;
const content = shell.props.children as ReactElement<object>;
const render = content.type as (props: object) => Promise<unknown>;
await expect(render(content.props)).resolves.toBeTruthy();
}
});
});