mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 02:35:14 +00:00
commit df18a89d0562edc8fd8fb5bc4cac702aefb5272c
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sat Jan 10 19:18:23 2026 -0800
Updated tests
commit 90240344b89717fbad0e16fe209dbf00c567b1a8
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sun Jan 4 03:18:41 2026 -0800
Updated tests
commit 525b7cb32bc8d235613706d6795795a0177ea24b
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sun Jan 4 03:18:31 2026 -0800
Extracted component and util logic
commit e54ea2c3173c0ce3cfb0a2d70d76fdd0a66accc8
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Wed Dec 31 11:55:40 2025 -0800
Updated tests
commit 5cbe164cb9818d8eab607af037da5faee2c1556f
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Wed Dec 31 11:55:14 2025 -0800
Minor changes
Exporting UiSettingsForm as default
Reverted admin level type check
commit f9ab0d2a06091a2eed3ee6f34268c81bfd746f1e
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Tue Dec 30 23:31:29 2025 -0800
Extracted component and util logic
commit a705a4db4a32d0851d087a984111837a0a83f722
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sun Dec 28 00:47:29 2025 -0800
Updated tests
commit a72c6720a3980d0f279edf050b6b51eaae11cdbd
Merge: e8dcab6f 43525ca3
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sun Dec 28 00:04:24 2025 -0800
Merge branch 'enhancement/component-cleanup' into test/component-cleanup
commit e8dcab6f832570cb986f114cfa574db4994b3aab
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sat Dec 27 22:35:59 2025 -0800
Updated tests
commit 0fd230503844fba0c418ab0a03c46dc878697a55
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sat Dec 27 22:35:53 2025 -0800
Added plugins store
commit d987f2de72272f24e26b1ed5bc04bb5c83033868
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sat Dec 27 22:35:43 2025 -0800
Extracted component and util logic
commit 5a3138370a468a99c9f1ed0a36709a173656d809
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Wed Dec 24 23:13:07 2025 -0800
Lazy-loading button modals
commit ac6945b5b55e0e16d050d4412a20c82f19250c4b
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Wed Dec 24 22:41:51 2025 -0800
Extracted notification util
commit befe159fc06b67ee415f7498b5400fee0dc82528
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Wed Dec 24 22:28:12 2025 -0800
Extracted component and util logic
commit ec10a3a4200a0c94cae29691a9fe06e5c4317bb7
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Wed Dec 24 22:22:09 2025 -0800
Updated tests
commit c1c7214c8589c0ce7645ea24418d9dd978ac8c1f
Merge: eba6dce7 9c9cbab9
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Tue Dec 23 12:41:25 2025 -0800
Merge branch 'enhancement/component-cleanup' into test/component-cleanup
commit eba6dce786495e352d4696030500db41d028036e
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sun Dec 21 10:12:19 2025 -0800
Updated style props
commit 2024b0b267b849a5f100e5543b9188e8ad6dd3d9
Merge: b3700956 1029eb5b
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Sun Dec 21 09:27:21 2025 -0800
Merge branch 'enhancement/component-cleanup' into test/component-cleanup
commit b3700956a4c2f473f1e977826f9537d27ea018ae
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Thu Dec 18 07:45:36 2025 -0800
Reverted Channels change
commit 137cbb02473b7f2f41488601e3b64e5ff45ac656
Merge: 644ed001 2a0df81c
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Wed Dec 17 13:36:05 2025 -0800
Merge branch 'enhancement/component-cleanup' into test/component-cleanup
commit 644ed00196c41eaa44df1b98236b7e5cc3124d82
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Wed Dec 17 13:29:13 2025 -0800
Updated tests
commit c62d1bd0534aa19be99b8f87232ba872420111a0
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Tue Dec 16 14:12:31 2025 -0800
Updated tests
commit 0cc0ee31d5ad84c59d8eba9fc4424f118f5e0ee2
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Tue Dec 16 13:44:55 2025 -0800
Extracted component and util logic
commit 25d1b112af250b5ccebb1006511bff8e4387fc76
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Tue Dec 16 13:44:11 2025 -0800
Added correct import for Text component
commit d8a04c6c09edf158220d3073939c9fb60069745c
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Tue Dec 16 13:43:55 2025 -0800
Fixed component syntax
commit 59e35d3a4d0da8ed8476560cedacadf76162ea43
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Tue Dec 16 13:43:39 2025 -0800
Fixed cache_url fallback
commit d2a170d2efd3d2b0e6078c9eebeb8dcea237be3b
Merge: b8f7e435 6c1b0f9a
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Tue Dec 16 12:00:45 2025 -0800
Merge branch 'enhancement/component-cleanup' into test/component-cleanup
commit b8f7e4358a23f2e3a902929b57ab7a7d115241c5
Merge: 5b12c68a d97f0c90
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Mon Dec 15 07:42:06 2025 -0800
Merge branch 'enhancement/component-cleanup' into test/component-cleanup
commit 5b12c68ab8ce429adc8d1355632aa411007d365b
Merge: eff58126 c63cb75b
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Mon Dec 8 16:56:14 2025 -0800
Merge branch 'enhancement/unit-tests' into stage
commit eff58126fb6aba4ebe9a0c67eee65773bffb8ae9
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Mon Dec 8 16:49:43 2025 -0800
Update .gitignore
commit c63cb75b8cad204d48a392a28d8a5bdf8c270496
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Mon Dec 8 16:28:03 2025 -0800
Added unit tests for pages
commit 75306a6181ddeb2eaeb306387ba2b44c7fcfd5e3
Author: Nick Sandstrom <32273437+nick4810@users.noreply.github.com>
Date: Mon Dec 8 16:27:19 2025 -0800
Added Actions workflow
541 lines
No EOL
16 KiB
JavaScript
541 lines
No EOL
16 KiB
JavaScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
import DVRPage from '../DVR';
|
|
import dayjs from 'dayjs';
|
|
import useChannelsStore from '../../store/channels';
|
|
import useSettingsStore from '../../store/settings';
|
|
import useVideoStore from '../../store/useVideoStore';
|
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
|
import {
|
|
isAfter,
|
|
isBefore,
|
|
useTimeHelpers,
|
|
} from '../../utils/dateTimeUtils.js';
|
|
import { categorizeRecordings } from '../../utils/pages/DVRUtils.js';
|
|
import { getPosterUrl, getRecordingUrl, getShowVideoUrl } from '../../utils/cards/RecordingCardUtils.js';
|
|
|
|
vi.mock('../../store/channels');
|
|
vi.mock('../../store/settings');
|
|
vi.mock('../../store/useVideoStore');
|
|
vi.mock('../../hooks/useLocalStorage');
|
|
|
|
// Mock Mantine components
|
|
vi.mock('@mantine/core', () => ({
|
|
Box: ({ children }) => <div data-testid="box">{children}</div>,
|
|
Container: ({ children }) => <div data-testid="container">{children}</div>,
|
|
Title: ({ children, order }) => <h1 data-order={order}>{children}</h1>,
|
|
Text: ({ children }) => <p>{children}</p>,
|
|
Button: ({ children, onClick, leftSection, loading, ...props }) => (
|
|
<button onClick={onClick} disabled={loading} {...props}>
|
|
{leftSection}
|
|
{children}
|
|
</button>
|
|
),
|
|
Badge: ({ children }) => <span>{children}</span>,
|
|
SimpleGrid: ({ children }) => <div data-testid="simple-grid">{children}</div>,
|
|
Group: ({ children }) => <div data-testid="group">{children}</div>,
|
|
Stack: ({ children }) => <div data-testid="stack">{children}</div>,
|
|
Divider: () => <hr data-testid="divider" />,
|
|
useMantineTheme: () => ({
|
|
tailwind: {
|
|
green: { 5: '#22c55e' },
|
|
red: { 6: '#dc2626' },
|
|
yellow: { 6: '#ca8a04' },
|
|
gray: { 6: '#52525b' },
|
|
},
|
|
}),
|
|
}));
|
|
|
|
// Mock components
|
|
vi.mock('../../components/cards/RecordingCard', () => ({
|
|
default: ({ recording, onOpenDetails, onOpenRecurring }) => (
|
|
<div data-testid={`recording-card-${recording.id}`}>
|
|
<span>{recording.custom_properties?.Title || 'Recording'}</span>
|
|
<button onClick={() => onOpenDetails(recording)}>Open Details</button>
|
|
{recording.custom_properties?.rule && (
|
|
<button onClick={() => onOpenRecurring(recording)}>Open Recurring</button>
|
|
)}
|
|
</div>
|
|
),
|
|
}));
|
|
|
|
vi.mock('../../components/forms/RecordingDetailsModal', () => ({
|
|
default: ({ opened, onClose, recording, onEdit, onWatchLive, onWatchRecording }) =>
|
|
opened ? (
|
|
<div data-testid="details-modal">
|
|
<div data-testid="modal-title">{recording?.custom_properties?.Title}</div>
|
|
<button onClick={onClose}>Close Modal</button>
|
|
<button onClick={onEdit}>Edit</button>
|
|
<button onClick={onWatchLive}>Watch Live</button>
|
|
<button onClick={onWatchRecording}>Watch Recording</button>
|
|
</div>
|
|
) : null,
|
|
}));
|
|
|
|
vi.mock('../../components/forms/RecurringRuleModal', () => ({
|
|
default: ({ opened, onClose, ruleId }) =>
|
|
opened ? (
|
|
<div data-testid="recurring-modal">
|
|
<div>Rule ID: {ruleId}</div>
|
|
<button onClick={onClose}>Close Recurring</button>
|
|
</div>
|
|
) : null,
|
|
}));
|
|
|
|
vi.mock('../../components/forms/Recording', () => ({
|
|
default: ({ isOpen, onClose, recording }) =>
|
|
isOpen ? (
|
|
<div data-testid="recording-form">
|
|
<div>Recording ID: {recording?.id || 'new'}</div>
|
|
<button onClick={onClose}>Close Form</button>
|
|
</div>
|
|
) : null,
|
|
}));
|
|
|
|
vi.mock('../../components/ErrorBoundary', () => ({
|
|
default: ({ children }) => <div data-testid="error-boundary">{children}</div>,
|
|
}));
|
|
|
|
vi.mock('../../utils/dateTimeUtils.js', async (importActual) => {
|
|
const actual = await importActual();
|
|
return {
|
|
...actual,
|
|
isBefore: vi.fn(),
|
|
isAfter: vi.fn(),
|
|
useTimeHelpers: vi.fn(),
|
|
};
|
|
});
|
|
vi.mock('../../utils/cards/RecordingCardUtils.js', () => ({
|
|
getPosterUrl: vi.fn(),
|
|
getRecordingUrl: vi.fn(),
|
|
getShowVideoUrl: vi.fn(),
|
|
}));
|
|
vi.mock('../../utils/pages/DVRUtils.js', async (importActual) => {
|
|
const actual = await importActual();
|
|
return {
|
|
...actual,
|
|
categorizeRecordings: vi.fn(),
|
|
};
|
|
});
|
|
|
|
describe('DVRPage', () => {
|
|
const mockShowVideo = vi.fn();
|
|
const mockFetchRecordings = vi.fn();
|
|
const mockFetchChannels = vi.fn();
|
|
const mockFetchRecurringRules = vi.fn();
|
|
const mockRemoveRecording = vi.fn();
|
|
|
|
const defaultChannelsState = {
|
|
recordings: [],
|
|
channels: {},
|
|
recurringRules: [],
|
|
fetchRecordings: mockFetchRecordings,
|
|
fetchChannels: mockFetchChannels,
|
|
fetchRecurringRules: mockFetchRecurringRules,
|
|
removeRecording: mockRemoveRecording,
|
|
};
|
|
|
|
const defaultSettingsState = {
|
|
settings: {
|
|
'system-time-zone': { value: 'America/New_York' },
|
|
},
|
|
environment: {
|
|
env_mode: 'production',
|
|
},
|
|
};
|
|
|
|
const defaultVideoState = {
|
|
showVideo: mockShowVideo,
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.useFakeTimers();
|
|
const now = new Date('2024-01-15T12:00:00Z');
|
|
vi.setSystemTime(now);
|
|
|
|
isAfter.mockImplementation((a, b) => new Date(a) > new Date(b));
|
|
isBefore.mockImplementation((a, b) => new Date(a) < new Date(b));
|
|
useTimeHelpers.mockReturnValue({
|
|
toUserTime: (dt) => dayjs(dt).tz('America/New_York').toDate(),
|
|
userNow: () => dayjs().tz('America/New_York').toDate(),
|
|
});
|
|
|
|
categorizeRecordings.mockImplementation((recordings, toUserTime, now) => {
|
|
const inProgress = [];
|
|
const upcoming = [];
|
|
const completed = [];
|
|
recordings.forEach((rec) => {
|
|
const start = toUserTime(rec.start_time);
|
|
const end = toUserTime(rec.end_time);
|
|
if (now >= start && now <= end) inProgress.push(rec);
|
|
else if (now < start) upcoming.push(rec);
|
|
else completed.push(rec);
|
|
});
|
|
return { inProgress, upcoming, completed };
|
|
});
|
|
|
|
getPosterUrl.mockImplementation((recording) =>
|
|
recording?.id ? `http://poster.url/${recording.id}` : null
|
|
);
|
|
getRecordingUrl.mockImplementation((custom_properties) =>
|
|
custom_properties?.recording_url
|
|
);
|
|
getShowVideoUrl.mockImplementation((channel) =>
|
|
channel?.stream_url
|
|
);
|
|
|
|
useChannelsStore.mockImplementation((selector) => {
|
|
return selector ? selector(defaultChannelsState) : defaultChannelsState;
|
|
});
|
|
useChannelsStore.getState = () => defaultChannelsState;
|
|
|
|
useSettingsStore.mockImplementation((selector) => {
|
|
return selector ? selector(defaultSettingsState) : defaultSettingsState;
|
|
});
|
|
useSettingsStore.getState = () => defaultSettingsState;
|
|
|
|
useVideoStore.mockImplementation((selector) => {
|
|
return selector ? selector(defaultVideoState) : defaultVideoState;
|
|
});
|
|
useVideoStore.getState = () => defaultVideoState;
|
|
|
|
useLocalStorage.mockReturnValue(['America/New_York', vi.fn()]);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.clearAllTimers(); // Clear pending timers
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe('Initial Render', () => {
|
|
it('renders new recording buttons', () => {
|
|
render(<DVRPage />);
|
|
|
|
expect(screen.getByText('New Recording')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders empty state when no recordings', () => {
|
|
render(<DVRPage />);
|
|
|
|
expect(screen.getByText('No upcoming recordings.')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Recording Display', () => {
|
|
it('displays recordings grouped by date', () => {
|
|
const now = dayjs('2024-01-15T12:00:00Z');
|
|
const recordings = [
|
|
{
|
|
id: 1,
|
|
channel: 1,
|
|
start_time: now.toISOString(),
|
|
end_time: now.add(1, 'hour').toISOString(),
|
|
custom_properties: { Title: 'Show 1' },
|
|
},
|
|
{
|
|
id: 2,
|
|
channel: 1,
|
|
start_time: now.add(1, 'day').toISOString(),
|
|
end_time: now.add(1, 'day').add(1, 'hour').toISOString(),
|
|
custom_properties: { Title: 'Show 2' },
|
|
},
|
|
];
|
|
|
|
useChannelsStore.mockImplementation((selector) => {
|
|
const state = { ...defaultChannelsState, recordings };
|
|
return selector ? selector(state) : state;
|
|
});
|
|
|
|
render(<DVRPage />);
|
|
|
|
expect(screen.getByTestId('recording-card-1')).toBeInTheDocument();
|
|
expect(screen.getByTestId('recording-card-2')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('New Recording', () => {
|
|
it('opens recording form when new recording button is clicked', async () => {
|
|
render(<DVRPage />);
|
|
|
|
const newButton = screen.getByText('New Recording');
|
|
fireEvent.click(newButton);
|
|
|
|
expect(screen.getByTestId('recording-form')).toBeInTheDocument();
|
|
});
|
|
|
|
it('closes recording form when close is clicked', async () => {
|
|
render(<DVRPage />);
|
|
|
|
const newButton = screen.getByText('New Recording');
|
|
fireEvent.click(newButton);
|
|
|
|
expect(screen.getByTestId('recording-form')).toBeInTheDocument();
|
|
|
|
const closeButton = screen.getByText('Close Form');
|
|
fireEvent.click(closeButton);
|
|
|
|
expect(screen.queryByTestId('recording-form')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Recording Details Modal', () => {
|
|
const setupRecording = () => {
|
|
const now = dayjs('2024-01-15T12:00:00Z');
|
|
const recording = {
|
|
id: 1,
|
|
channel: 1,
|
|
start_time: now.toISOString(),
|
|
end_time: now.add(1, 'hour').toISOString(),
|
|
custom_properties: { Title: 'Test Show' },
|
|
};
|
|
|
|
useChannelsStore.mockImplementation((selector) => {
|
|
const state = {
|
|
...defaultChannelsState,
|
|
recordings: [recording],
|
|
channels: { 1: { id: 1, name: 'Channel 1', stream_url: 'http://stream.url' } },
|
|
};
|
|
return selector ? selector(state) : state;
|
|
});
|
|
|
|
return recording;
|
|
};
|
|
|
|
it('opens details modal when recording card is clicked', async () => {
|
|
vi.useRealTimers();
|
|
|
|
setupRecording();
|
|
render(<DVRPage />);
|
|
|
|
const detailsButton = screen.getByText('Open Details');
|
|
fireEvent.click(detailsButton);
|
|
|
|
await screen.findByTestId('details-modal');
|
|
expect(screen.getByTestId('modal-title')).toHaveTextContent('Test Show');
|
|
});
|
|
|
|
it('closes details modal when close is clicked', async () => {
|
|
vi.useRealTimers();
|
|
|
|
setupRecording();
|
|
render(<DVRPage />);
|
|
|
|
const detailsButton = screen.getByText('Open Details');
|
|
fireEvent.click(detailsButton);
|
|
|
|
await screen.findByTestId('details-modal');
|
|
|
|
const closeButton = screen.getByText('Close Modal');
|
|
fireEvent.click(closeButton);
|
|
|
|
expect(screen.queryByTestId('details-modal')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('opens edit form from details modal', async () => {
|
|
vi.useRealTimers();
|
|
|
|
setupRecording();
|
|
render(<DVRPage />);
|
|
|
|
const detailsButton = screen.getByText('Open Details');
|
|
fireEvent.click(detailsButton);
|
|
|
|
await screen.findByTestId('details-modal');
|
|
|
|
const editButton = screen.getByText('Edit');
|
|
fireEvent.click(editButton);
|
|
|
|
expect(screen.queryByTestId('details-modal')).not.toBeInTheDocument();
|
|
expect(screen.getByTestId('recording-form')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Recurring Rule Modal', () => {
|
|
it('opens recurring rule modal when recording has rule', async () => {
|
|
const now = dayjs('2024-01-15T12:00:00Z');
|
|
const recording = {
|
|
id: 1,
|
|
channel: 1,
|
|
start_time: now.toISOString(),
|
|
end_time: now.add(1, 'hour').toISOString(),
|
|
custom_properties: {
|
|
Title: 'Recurring Show',
|
|
rule: { id: 100 }
|
|
},
|
|
};
|
|
|
|
useChannelsStore.mockImplementation((selector) => {
|
|
const state = {
|
|
...defaultChannelsState,
|
|
recordings: [recording],
|
|
channels: { 1: { id: 1, name: 'Channel 1' } },
|
|
};
|
|
return selector ? selector(state) : state;
|
|
});
|
|
|
|
render(<DVRPage />);
|
|
|
|
const recurringButton = screen.getByText('Open Recurring');
|
|
fireEvent.click(recurringButton);
|
|
|
|
expect(screen.getByTestId('recurring-modal')).toBeInTheDocument();
|
|
expect(screen.getByText('Rule ID: 100')).toBeInTheDocument();
|
|
});
|
|
|
|
it('closes recurring modal when close is clicked', async () => {
|
|
const now = dayjs('2024-01-15T12:00:00Z');
|
|
const recording = {
|
|
id: 1,
|
|
channel: 1,
|
|
start_time: now.toISOString(),
|
|
end_time: now.add(1, 'hour').toISOString(),
|
|
custom_properties: {
|
|
Title: 'Recurring Show',
|
|
rule: { id: 100 },
|
|
},
|
|
};
|
|
|
|
useChannelsStore.mockImplementation((selector) => {
|
|
const state = {
|
|
...defaultChannelsState,
|
|
recordings: [recording],
|
|
channels: { 1: { id: 1, name: 'Channel 1' } },
|
|
};
|
|
return selector ? selector(state) : state;
|
|
});
|
|
|
|
render(<DVRPage />);
|
|
|
|
const recurringButton = screen.getByText('Open Recurring');
|
|
fireEvent.click(recurringButton);
|
|
|
|
expect(screen.getByTestId('recurring-modal')).toBeInTheDocument();
|
|
|
|
const closeButton = screen.getByText('Close Recurring');
|
|
fireEvent.click(closeButton);
|
|
|
|
expect(screen.queryByTestId('recurring-modal')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Watch Functionality', () => {
|
|
it('calls showVideo for watch live on in-progress recording', async () => {
|
|
vi.useRealTimers();
|
|
|
|
const now = dayjs();
|
|
const recording = {
|
|
id: 1,
|
|
channel: 1,
|
|
start_time: now.subtract(30, 'minutes').toISOString(),
|
|
end_time: now.add(30, 'minutes').toISOString(),
|
|
custom_properties: { Title: 'Live Show' },
|
|
};
|
|
|
|
useChannelsStore.mockImplementation((selector) => {
|
|
const state = {
|
|
...defaultChannelsState,
|
|
recordings: [recording],
|
|
channels: {
|
|
1: { id: 1, name: 'Channel 1', stream_url: 'http://stream.url' },
|
|
},
|
|
};
|
|
return selector ? selector(state) : state;
|
|
});
|
|
|
|
render(<DVRPage />);
|
|
|
|
const detailsButton = screen.getByText('Open Details');
|
|
fireEvent.click(detailsButton);
|
|
|
|
await screen.findByTestId('details-modal');
|
|
|
|
const watchLiveButton = screen.getByText('Watch Live');
|
|
fireEvent.click(watchLiveButton);
|
|
|
|
expect(mockShowVideo).toHaveBeenCalledWith(
|
|
expect.stringContaining('stream.url'),
|
|
'live'
|
|
);
|
|
});
|
|
|
|
it('calls showVideo for watch recording on completed recording', async () => {
|
|
vi.useRealTimers();
|
|
|
|
const now = dayjs('2024-01-15T12:00:00Z');
|
|
const recording = {
|
|
id: 1,
|
|
channel: 1,
|
|
start_time: now.subtract(2, 'hours').toISOString(),
|
|
end_time: now.subtract(1, 'hour').toISOString(),
|
|
custom_properties: {
|
|
Title: 'Recorded Show',
|
|
recording_url: 'http://recording.url/video.mp4',
|
|
},
|
|
};
|
|
|
|
useChannelsStore.mockImplementation((selector) => {
|
|
const state = {
|
|
...defaultChannelsState,
|
|
recordings: [recording],
|
|
channels: { 1: { id: 1, name: 'Channel 1' } },
|
|
};
|
|
return selector ? selector(state) : state;
|
|
});
|
|
|
|
render(<DVRPage />);
|
|
|
|
const detailsButton = screen.getByText('Open Details');
|
|
fireEvent.click(detailsButton);
|
|
|
|
await screen.findByTestId('details-modal');
|
|
|
|
const watchButton = screen.getByText('Watch Recording');
|
|
fireEvent.click(watchButton);
|
|
|
|
expect(mockShowVideo).toHaveBeenCalledWith(
|
|
expect.stringContaining('http://recording.url/video.mp4'),
|
|
'vod',
|
|
expect.objectContaining({
|
|
name: 'Recording',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('does not call showVideo when recording URL is missing', async () => {
|
|
vi.useRealTimers();
|
|
|
|
const now = dayjs('2024-01-15T12:00:00Z');
|
|
const recording = {
|
|
id: 1,
|
|
channel: 1,
|
|
start_time: now.subtract(2, 'hours').toISOString(),
|
|
end_time: now.subtract(1, 'hour').toISOString(),
|
|
custom_properties: { Title: 'No URL Show' },
|
|
};
|
|
|
|
useChannelsStore.mockImplementation((selector) => {
|
|
const state = {
|
|
...defaultChannelsState,
|
|
recordings: [recording],
|
|
channels: { 1: { id: 1, name: 'Channel 1' } },
|
|
};
|
|
return selector ? selector(state) : state;
|
|
});
|
|
|
|
render(<DVRPage />);
|
|
|
|
const detailsButton = await screen.findByText('Open Details');
|
|
fireEvent.click(detailsButton);
|
|
|
|
const modal = await screen.findByTestId('details-modal');
|
|
expect(modal).toBeInTheDocument();
|
|
|
|
const watchButton = screen.getByText('Watch Recording');
|
|
fireEvent.click(watchButton);
|
|
|
|
expect(mockShowVideo).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
}); |