Three new SPA list+editor surfaces, closing the Blazor-only gap found in the #91 phase-(b) recon: - FFmpeg Profiles (System group): list with resolution/video/audio summary + add/copy/delete; full General/Video/Audio editor. Per-hwaccel video-format and profile rules ported from FFmpegProfileEditViewModelValidator; video preset list ported from FFmpegLibraryHelper.PresetsForFFmpegProfile / AvailablePresets.ForAccelAndFormat (cited in code). Resolutions from /api/settings/resolutions, hwaccel kinds from the new lookup endpoint. Copy = client-side clone into the create form (no server copy endpoint). - Filler Presets (Media group): list + editor with the Blazor VM interlocks (Fallback/Tail force mode None; non-MidRoll clears expression; Fallback disables chapters; Playlist collection type forces Count mode; duration/ count/pad fields gated by mode). Collection pickers use the existing /api/library/browse client for all seven collection types incl. Playlist. - Watermarks (System group): list + editor with mode-driven field enabling, ChannelLogo vs Custom image source, upload via /api/artwork/uploads (target=watermark) and preview via /artwork/watermarks/{path}. - App.tsx: ScreenIds/routes (sub-paths /add + /{id}) and nav registration; api modules ffmpegProfiles/fillerPresets/watermarks with URL-assert tests; SettingsScreen Classic-UI help text updated (these are no longer Blazor-only) and the FFmpeg-profiles card now links to the new screen. - Regenerated web/src/api/generated/v1.d.ts from the #159 OpenAPI changes. web verification: typecheck clean; vitest 188/188 (14 files); vite build OK; eslint clean on touched files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
createFillerPreset,
|
|
deleteFillerPreset,
|
|
getFillerPreset,
|
|
updateFillerPreset,
|
|
type CreateFillerPresetRequest
|
|
} from './fillerPresets';
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
status
|
|
});
|
|
}
|
|
|
|
function noContent(): Response {
|
|
return new Response(null, { status: 204 });
|
|
}
|
|
|
|
const sampleRequest: CreateFillerPresetRequest = {
|
|
allowWatermarks: true,
|
|
collectionId: 5,
|
|
collectionType: 'Collection',
|
|
count: 3,
|
|
duration: null,
|
|
expression: null,
|
|
fillerKind: 'PreRoll',
|
|
fillerMode: 'Count',
|
|
mediaItemId: null,
|
|
multiCollectionId: null,
|
|
name: 'Intro',
|
|
padToNearestMinute: null,
|
|
playlistId: null,
|
|
smartCollectionId: null,
|
|
useChaptersAsMediaItems: false
|
|
};
|
|
|
|
describe('filler presets api client', () => {
|
|
beforeEach(() => {
|
|
window.localStorage.clear();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('getFillerPreset fetches a preset by id', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 4, name: 'Intro' }));
|
|
|
|
await expect(getFillerPreset(4)).resolves.toMatchObject({ id: 4 });
|
|
expect(fetchMock).toHaveBeenCalledWith('/api/filler-presets/4', expect.objectContaining({ method: 'GET' }));
|
|
});
|
|
|
|
it('createFillerPreset POSTs the request body', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 7, name: 'Intro' }, 201));
|
|
|
|
await expect(createFillerPreset(sampleRequest)).resolves.toMatchObject({ id: 7 });
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/filler-presets');
|
|
expect(init).toMatchObject({ method: 'POST' });
|
|
expect(JSON.parse(String(init?.body)).fillerKind).toBe('PreRoll');
|
|
});
|
|
|
|
it('updateFillerPreset PUTs to the id route', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 3, name: 'Intro' }));
|
|
|
|
await updateFillerPreset(3, sampleRequest);
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/filler-presets/3');
|
|
expect(init).toMatchObject({ method: 'PUT' });
|
|
});
|
|
|
|
it('deleteFillerPreset issues a DELETE and resolves on 204', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent());
|
|
|
|
await expect(deleteFillerPreset(9)).resolves.toBeUndefined();
|
|
expect(fetchMock).toHaveBeenCalledWith('/api/filler-presets/9', expect.objectContaining({ method: 'DELETE' }));
|
|
});
|
|
});
|