mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-18 00:55:50 +00:00
fix(frontend): update getShowVideoUrl tests to expect output_format and output_profile
This commit is contained in:
parent
98d7acd3c0
commit
c717df50f9
1 changed files with 361 additions and 33 deletions
|
|
@ -1,7 +1,14 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
removeRecording, getPosterUrl, getShowVideoUrl, runComSkip,
|
||||
deleteRecordingById, deleteSeriesAndRule, getRecordingUrl, getSeasonLabel, getSeriesInfo,
|
||||
removeRecording,
|
||||
getPosterUrl,
|
||||
getShowVideoUrl,
|
||||
runComSkip,
|
||||
deleteRecordingById,
|
||||
deleteSeriesAndRule,
|
||||
getRecordingUrl,
|
||||
getSeasonLabel,
|
||||
getSeriesInfo,
|
||||
} from '../RecordingCardUtils';
|
||||
import API from '../../../api';
|
||||
import useChannelsStore from '../../../store/channels';
|
||||
|
|
@ -10,50 +17,174 @@ vi.mock('../../../api');
|
|||
vi.mock('../../../store/channels');
|
||||
|
||||
describe('RecordingCardUtils', () => {
|
||||
beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); });
|
||||
|
||||
describe('getShowVideoUrl', () => {
|
||||
it('returns proxy URL with mpegts output format', () => {
|
||||
expect(getShowVideoUrl({ uuid: 'channel-123' }, 'production'))
|
||||
.toBe('/proxy/ts/stream/channel-123?output_format=mpegts');
|
||||
});
|
||||
it('includes output_profile when set in player prefs', () => {
|
||||
localStorage.setItem('dispatcharr-player-prefs', JSON.stringify({ webPlayerOutputProfileId: 5 }));
|
||||
expect(getShowVideoUrl({ uuid: 'channel-123' }, 'production'))
|
||||
.toBe('/proxy/ts/stream/channel-123?output_format=mpegts&output_profile=5');
|
||||
});
|
||||
it('prepends dev server URL in dev mode with output params', () => {
|
||||
expect(getShowVideoUrl({ uuid: 'channel-123' }, 'dev')).toMatch(/^https?:\/\/.*:5656\/proxy\/ts\/stream\/channel-123\?output_format=mpegts$/);
|
||||
});
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('removeRecording', () => {
|
||||
let mockRemoveRecording, mockFetchRecordings;
|
||||
let mockRemoveRecording;
|
||||
let mockFetchRecordings;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRemoveRecording = vi.fn(); mockFetchRecordings = vi.fn();
|
||||
useChannelsStore.getState = vi.fn(() => ({ removeRecording: mockRemoveRecording, fetchRecordings: mockFetchRecordings }));
|
||||
mockRemoveRecording = vi.fn();
|
||||
mockFetchRecordings = vi.fn();
|
||||
useChannelsStore.getState = vi.fn(() => ({
|
||||
removeRecording: mockRemoveRecording,
|
||||
fetchRecordings: mockFetchRecordings,
|
||||
}));
|
||||
});
|
||||
|
||||
it('optimistically removes recording from store', () => {
|
||||
API.deleteRecording.mockResolvedValue();
|
||||
|
||||
removeRecording('recording-1');
|
||||
|
||||
expect(mockRemoveRecording).toHaveBeenCalledWith('recording-1');
|
||||
});
|
||||
|
||||
it('calls API to delete recording', () => {
|
||||
API.deleteRecording.mockResolvedValue();
|
||||
|
||||
removeRecording('recording-1');
|
||||
|
||||
expect(API.deleteRecording).toHaveBeenCalledWith('recording-1');
|
||||
});
|
||||
|
||||
it('handles optimistic removal error', () => {
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation();
|
||||
mockRemoveRecording.mockImplementation(() => {
|
||||
throw new Error('Store error');
|
||||
});
|
||||
API.deleteRecording.mockResolvedValue();
|
||||
|
||||
removeRecording('recording-1');
|
||||
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
'Failed to optimistically remove recording',
|
||||
expect.any(Error)
|
||||
);
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
|
||||
it('refetches recordings when API delete fails', async () => {
|
||||
API.deleteRecording.mockRejectedValue(new Error('Delete failed'));
|
||||
|
||||
removeRecording('recording-1');
|
||||
await vi.waitFor(() => { expect(mockFetchRecordings).toHaveBeenCalled(); });
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockFetchRecordings).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles fetch error after failed delete', async () => {
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation();
|
||||
API.deleteRecording.mockRejectedValue(new Error('Delete failed'));
|
||||
mockFetchRecordings.mockImplementation(() => {
|
||||
throw new Error('Fetch error');
|
||||
});
|
||||
|
||||
removeRecording('recording-1');
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
'Failed to refresh recordings after delete',
|
||||
expect.any(Error)
|
||||
);
|
||||
});
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPosterUrl', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('returns logo URL when posterLogoId is provided', () => {
|
||||
vi.stubEnv('DEV', false);
|
||||
const result = getPosterUrl('logo-123', {}, '');
|
||||
|
||||
expect(result).toBe('/api/channels/logos/logo-123/cache/');
|
||||
});
|
||||
|
||||
it('returns custom poster_url when no posterLogoId', () => {
|
||||
vi.stubEnv('DEV', false);
|
||||
const customProps = { poster_url: '/custom/poster.jpg' };
|
||||
const result = getPosterUrl(null, customProps, '');
|
||||
|
||||
expect(result).toBe('/custom/poster.jpg');
|
||||
});
|
||||
|
||||
it('returns posterUrl when no posterLogoId or custom poster_url', () => {
|
||||
vi.stubEnv('DEV', false);
|
||||
const result = getPosterUrl(null, {}, '/fallback/poster.jpg');
|
||||
|
||||
expect(result).toBe('/fallback/poster.jpg');
|
||||
});
|
||||
|
||||
it('returns default logo when no parameters provided', () => {
|
||||
vi.stubEnv('DEV', false);
|
||||
const result = getPosterUrl(null, {}, '');
|
||||
|
||||
// Falls back to the imported default Dispatcharr logo asset
|
||||
expect(result).toBeTruthy();
|
||||
expect(result).toContain('logo');
|
||||
});
|
||||
|
||||
it('prepends dev server URL in dev mode for relative paths', () => {
|
||||
vi.stubEnv('DEV', true);
|
||||
const result = getPosterUrl(null, {}, '/poster.jpg');
|
||||
|
||||
expect(result).toMatch(/^https?:\/\/.*:5656\/poster\.jpg$/);
|
||||
});
|
||||
|
||||
it('does not prepend dev URL for absolute URLs', () => {
|
||||
vi.stubEnv('DEV', true);
|
||||
const result = getPosterUrl(null, {}, 'https://example.com/poster.jpg');
|
||||
|
||||
expect(result).toBe('https://example.com/poster.jpg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getShowVideoUrl', () => {
|
||||
it('returns proxy URL with mpegts output format for channel', () => {
|
||||
const channel = { uuid: 'channel-123' };
|
||||
const result = getShowVideoUrl(channel, 'production');
|
||||
|
||||
expect(result).toBe('/proxy/ts/stream/channel-123?output_format=mpegts');
|
||||
});
|
||||
|
||||
it('includes output_profile when set in player prefs', () => {
|
||||
localStorage.setItem(
|
||||
'dispatcharr-player-prefs',
|
||||
JSON.stringify({ webPlayerOutputProfileId: 5 })
|
||||
);
|
||||
const channel = { uuid: 'channel-123' };
|
||||
const result = getShowVideoUrl(channel, 'production');
|
||||
|
||||
expect(result).toBe(
|
||||
'/proxy/ts/stream/channel-123?output_format=mpegts&output_profile=5'
|
||||
);
|
||||
});
|
||||
|
||||
it('prepends dev server URL in dev mode with output params', () => {
|
||||
const channel = { uuid: 'channel-123' };
|
||||
const result = getShowVideoUrl(channel, 'dev');
|
||||
|
||||
expect(result).toMatch(
|
||||
/^https?:\/\/.*:5656\/proxy\/ts\/stream\/channel-123\?output_format=mpegts$/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('runComSkip', () => {
|
||||
it('calls API runComskip with recording id', async () => {
|
||||
API.runComskip.mockResolvedValue();
|
||||
await runComSkip({ id: 'recording-1' });
|
||||
const recording = { id: 'recording-1' };
|
||||
|
||||
await runComSkip(recording);
|
||||
|
||||
expect(API.runComskip).toHaveBeenCalledWith('recording-1');
|
||||
});
|
||||
});
|
||||
|
|
@ -61,34 +192,231 @@ describe('RecordingCardUtils', () => {
|
|||
describe('deleteRecordingById', () => {
|
||||
it('calls API deleteRecording with id', async () => {
|
||||
API.deleteRecording.mockResolvedValue();
|
||||
|
||||
await deleteRecordingById('recording-1');
|
||||
|
||||
expect(API.deleteRecording).toHaveBeenCalledWith('recording-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteSeriesAndRule', () => {
|
||||
it('removes series recordings and deletes series rule', async () => {
|
||||
API.bulkRemoveSeriesRecordings.mockResolvedValue();
|
||||
API.deleteSeriesRule.mockResolvedValue();
|
||||
const seriesInfo = { tvg_id: 'series-123', title: 'Test Series' };
|
||||
|
||||
await deleteSeriesAndRule(seriesInfo);
|
||||
|
||||
expect(API.bulkRemoveSeriesRecordings).toHaveBeenCalledWith({
|
||||
tvg_id: 'series-123',
|
||||
title: 'Test Series',
|
||||
scope: 'title',
|
||||
});
|
||||
expect(API.deleteSeriesRule).toHaveBeenCalledWith(
|
||||
'series-123',
|
||||
'Test Series'
|
||||
);
|
||||
});
|
||||
|
||||
it('works for title-only rules with no tvg_id', async () => {
|
||||
API.bulkRemoveSeriesRecordings.mockResolvedValue();
|
||||
API.deleteSeriesRule.mockResolvedValue();
|
||||
const seriesInfo = { tvg_id: undefined, title: 'Title-Only Show' };
|
||||
|
||||
await deleteSeriesAndRule(seriesInfo);
|
||||
|
||||
expect(API.bulkRemoveSeriesRecordings).toHaveBeenCalledWith({
|
||||
tvg_id: '',
|
||||
title: 'Title-Only Show',
|
||||
scope: 'title',
|
||||
});
|
||||
expect(API.deleteSeriesRule).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
'Title-Only Show'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles bulk remove error gracefully', async () => {
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation();
|
||||
API.bulkRemoveSeriesRecordings.mockRejectedValue(
|
||||
new Error('Bulk remove failed')
|
||||
);
|
||||
API.deleteSeriesRule.mockResolvedValue();
|
||||
const seriesInfo = { tvg_id: 'series-123', title: 'Test Series' };
|
||||
|
||||
await deleteSeriesAndRule(seriesInfo);
|
||||
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
'Failed to remove series recordings',
|
||||
expect.any(Error)
|
||||
);
|
||||
expect(API.deleteSeriesRule).toHaveBeenCalled();
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
|
||||
it('handles delete rule error gracefully', async () => {
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation();
|
||||
API.bulkRemoveSeriesRecordings.mockResolvedValue();
|
||||
API.deleteSeriesRule.mockRejectedValue(new Error('Delete rule failed'));
|
||||
const seriesInfo = { tvg_id: 'series-123', title: 'Test Series' };
|
||||
|
||||
await deleteSeriesAndRule(seriesInfo);
|
||||
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
'Failed to delete series rule',
|
||||
expect.any(Error)
|
||||
);
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRecordingUrl', () => {
|
||||
it('returns file_url when available', () => {
|
||||
expect(getRecordingUrl({ file_url: '/recordings/file.mp4' }, 'production')).toBe('/recordings/file.mp4');
|
||||
const customProps = { file_url: '/recordings/file.mp4' };
|
||||
const result = getRecordingUrl(customProps, 'production');
|
||||
|
||||
expect(result).toBe('/recordings/file.mp4');
|
||||
});
|
||||
it('returns undefined when no file URL available', () => {
|
||||
expect(getRecordingUrl({}, 'production')).toBeUndefined();
|
||||
|
||||
it('returns output_file_url when file_url is not available', () => {
|
||||
const customProps = { output_file_url: '/output/file.mp4' };
|
||||
const result = getRecordingUrl(customProps, 'production');
|
||||
|
||||
expect(result).toBe('/output/file.mp4');
|
||||
});
|
||||
|
||||
it('prefers file_url over output_file_url', () => {
|
||||
const customProps = {
|
||||
file_url: '/recordings/file.mp4',
|
||||
output_file_url: '/output/file.mp4',
|
||||
};
|
||||
const result = getRecordingUrl(customProps, 'production');
|
||||
|
||||
expect(result).toBe('/recordings/file.mp4');
|
||||
});
|
||||
|
||||
it('prepends dev server URL in dev mode for relative paths', () => {
|
||||
const customProps = { file_url: '/recordings/file.mp4' };
|
||||
const result = getRecordingUrl(customProps, 'dev');
|
||||
|
||||
expect(result).toMatch(/^https?:\/\/.*:5656\/recordings\/file\.mp4$/);
|
||||
});
|
||||
|
||||
it('does not prepend dev URL for absolute URLs', () => {
|
||||
const customProps = { file_url: 'https://example.com/file.mp4' };
|
||||
const result = getRecordingUrl(customProps, 'dev');
|
||||
|
||||
expect(result).toBe('https://example.com/file.mp4');
|
||||
});
|
||||
|
||||
it('returns undefined when no file URL is available', () => {
|
||||
const result = getRecordingUrl({}, 'production');
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles null customProps', () => {
|
||||
expect(getRecordingUrl(null, 'production')).toBeUndefined();
|
||||
const result = getRecordingUrl(null, 'production');
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSeasonLabel', () => {
|
||||
it('returns formatted label', () => { expect(getSeasonLabel(1, 5, null)).toBe('S01E05'); });
|
||||
it('returns onscreen when season missing', () => { expect(getSeasonLabel(null, 5, 'Episode 5')).toBe('Episode 5'); });
|
||||
it('returns null when nothing provided', () => { expect(getSeasonLabel(null, null, null)).toBeNull(); });
|
||||
it('returns formatted season and episode label', () => {
|
||||
const result = getSeasonLabel(1, 5, null);
|
||||
|
||||
expect(result).toBe('S01E05');
|
||||
});
|
||||
|
||||
it('pads single digit season and episode numbers', () => {
|
||||
const result = getSeasonLabel(2, 3, null);
|
||||
|
||||
expect(result).toBe('S02E03');
|
||||
});
|
||||
|
||||
it('handles multi-digit season and episode numbers', () => {
|
||||
const result = getSeasonLabel(12, 34, null);
|
||||
|
||||
expect(result).toBe('S12E34');
|
||||
});
|
||||
|
||||
it('returns onscreen value when season or episode is missing', () => {
|
||||
const result = getSeasonLabel(null, 5, 'Episode 5');
|
||||
|
||||
expect(result).toBe('Episode 5');
|
||||
});
|
||||
|
||||
it('returns onscreen value when only episode is missing', () => {
|
||||
const result = getSeasonLabel(1, null, 'Special');
|
||||
|
||||
expect(result).toBe('Special');
|
||||
});
|
||||
|
||||
it('returns null when no season, episode, or onscreen provided', () => {
|
||||
const result = getSeasonLabel(null, null, null);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('returns formatted label even when onscreen is provided', () => {
|
||||
const result = getSeasonLabel(1, 5, 'Episode 5');
|
||||
|
||||
expect(result).toBe('S01E05');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSeriesInfo', () => {
|
||||
it('extracts tvg_id and title', () => {
|
||||
expect(getSeriesInfo({ program: { tvg_id: 'series-123', title: 'Test Series' } }))
|
||||
.toEqual({ tvg_id: 'series-123', title: 'Test Series' });
|
||||
it('extracts tvg_id and title from program', () => {
|
||||
const customProps = {
|
||||
program: { tvg_id: 'series-123', title: 'Test Series' },
|
||||
};
|
||||
const result = getSeriesInfo(customProps);
|
||||
|
||||
expect(result).toEqual({
|
||||
tvg_id: 'series-123',
|
||||
title: 'Test Series',
|
||||
});
|
||||
});
|
||||
|
||||
it('handles missing program object', () => {
|
||||
const customProps = {};
|
||||
const result = getSeriesInfo(customProps);
|
||||
|
||||
expect(result).toEqual({
|
||||
tvg_id: undefined,
|
||||
title: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('handles null customProps', () => {
|
||||
const result = getSeriesInfo(null);
|
||||
|
||||
expect(result).toEqual({
|
||||
tvg_id: undefined,
|
||||
title: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('handles undefined customProps', () => {
|
||||
const result = getSeriesInfo(undefined);
|
||||
|
||||
expect(result).toEqual({
|
||||
tvg_id: undefined,
|
||||
title: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('handles partial program data', () => {
|
||||
const customProps = {
|
||||
program: { tvg_id: 'series-123' },
|
||||
};
|
||||
const result = getSeriesInfo(customProps);
|
||||
|
||||
expect(result).toEqual({
|
||||
tvg_id: 'series-123',
|
||||
title: undefined,
|
||||
});
|
||||
});
|
||||
it('handles null', () => { expect(getSeriesInfo(null)).toEqual({ tvg_id: undefined, title: undefined }); });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue