Part of the #180 library picker fixes (SPA side). Channel builder: TelevisionSeason is removed from the library grid fan-out, so a multi-season show renders as one tile instead of one tile per season. Show tiles gain a "Seasons" drill-in affordance (both grid and compact layouts) that opens a dialog listing that show's seasons (via the new GET /api/library/browse?parentId= &mediaType=TelevisionSeason), each with title, artwork and an Add button that drops the specific season into the lineup. Collections add-items dialog: the default search fan-out now excludes seasons, and a media-kind filter row (All / Movies / Shows / Seasons / Artists, default = All-without-seasons) keeps seasons reachable when explicitly selected. Client: getLibraryBrowseItems gains an optional parentId param. Tests cover the param mapping, the builder no longer requesting TelevisionSeason, and the collections dialog default-excluding vs explicitly-including seasons. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
import { cleanup, render, waitFor } from '@testing-library/react';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { ChannelBuilderScreen } from './ChannelBuilder';
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
status
|
|
});
|
|
}
|
|
|
|
// Mocks every endpoint the builder loads on mount so it can render its library
|
|
// browser, plus /api/library/browse which the browse hook fans out across.
|
|
function mockBuilderApi() {
|
|
return vi.spyOn(window, 'fetch').mockImplementation((input: RequestInfo | URL) => {
|
|
const url = input.toString();
|
|
|
|
if (url.startsWith('/api/library/browse')) {
|
|
return Promise.resolve(jsonResponse({ page: [], totalCount: 0 }));
|
|
}
|
|
|
|
if (url === '/api/channel-templates/default') {
|
|
// Builder treats a 404 here as "no default template".
|
|
return Promise.resolve(jsonResponse({ status: 404, title: 'Not Found' }, 404));
|
|
}
|
|
|
|
// channels, channel-templates, ffmpeg/profiles, filler-presets, watermarks, media-sources
|
|
return Promise.resolve(jsonResponse([]));
|
|
});
|
|
}
|
|
|
|
function browseMediaTypes(fetchMock: ReturnType<typeof mockBuilderApi>): string[] {
|
|
return fetchMock.mock.calls
|
|
.map(([u]) => u.toString())
|
|
.filter((u) => u.startsWith('/api/library/browse'))
|
|
.map((u) => new URL(u, 'http://localhost').searchParams.get('mediaType'))
|
|
.filter((mediaType): mediaType is string => mediaType != null && mediaType !== '');
|
|
}
|
|
|
|
describe('ChannelBuilder library browse', () => {
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
window.localStorage.clear();
|
|
});
|
|
|
|
it('fans out over movies/shows/artists and never requests TelevisionSeason', async () => {
|
|
const fetchMock = mockBuilderApi();
|
|
|
|
render(<ChannelBuilderScreen />);
|
|
|
|
// Wait until the initial library browse fan-out has fired.
|
|
await waitFor(() => {
|
|
expect(browseMediaTypes(fetchMock).length).toBeGreaterThan(0);
|
|
});
|
|
|
|
const types = new Set(browseMediaTypes(fetchMock));
|
|
expect(types).toEqual(new Set(['Movie', 'TelevisionShow', 'Artist']));
|
|
expect(types.has('TelevisionSeason')).toBe(false);
|
|
});
|
|
});
|