diff --git a/frontend/src/components/__tests__/M3URefreshNotification.test.jsx b/frontend/src/components/__tests__/M3URefreshNotification.test.jsx index 2c5a5572..43375fa7 100644 --- a/frontend/src/components/__tests__/M3URefreshNotification.test.jsx +++ b/frontend/src/components/__tests__/M3URefreshNotification.test.jsx @@ -57,6 +57,7 @@ vi.mock('@mantine/core', async () => { // Mock lucide-react icons vi.mock('lucide-react', () => ({ + ListOrdered: () =>
, CircleCheck: () =>
, })); diff --git a/frontend/src/components/__tests__/NotificationCenter.test.jsx b/frontend/src/components/__tests__/NotificationCenter.test.jsx index 9dfb13f5..2231c951 100644 --- a/frontend/src/components/__tests__/NotificationCenter.test.jsx +++ b/frontend/src/components/__tests__/NotificationCenter.test.jsx @@ -33,8 +33,12 @@ vi.mock('@mantine/core', async () => { {children}
), - PopoverTarget: ({ children }) =>
{children}
, - PopoverDropdown: ({ children }) =>
{children}
, + PopoverTarget: ({ children }) => ( +
{children}
+ ), + PopoverDropdown: ({ children }) => ( +
{children}
+ ), Indicator: ({ children, label, disabled, processing }) => (
{
), ActionIcon: ({ children, onClick, 'aria-label': ariaLabel, ...props }) => ( - ), - ScrollAreaAutosize: ({ children }) =>
{children}
, - Badge: ({ children, ...props }) => {children}, - Card: ({ children, ...props }) =>
{children}
, - ThemeIcon: ({ children, ...props }) =>
{children}
, - Group: ({ children, ...props }) =>
{children}
, - Stack: ({ children, ...props }) =>
{children}
, - Box: ({ children, ...props }) =>
{children}
, - Text: ({ children, ...props }) => {children}, + ScrollAreaAutosize: ({ children }) => ( +
{children}
+ ), + Badge: ({ children, ...props }) => ( + + {children} + + ), + Card: ({ children, ...props }) => ( +
+ {children} +
+ ), + ThemeIcon: ({ children, ...props }) => ( +
+ {children} +
+ ), + Group: ({ children, ...props }) => ( +
+ {children} +
+ ), + Stack: ({ children, ...props }) => ( +
+ {children} +
+ ), + Box: ({ children, ...props }) => ( +
+ {children} +
+ ), + Text: ({ children, ...props }) => ( + + {children} + + ), Button: ({ children, onClick, ...props }) => ( +
{children}
); }, - Box: ({ children, ...props }) =>
{children}
, + Box: ({ children, ...props }) => ( +
+ {children} +
+ ), Button: ({ children, onClick, disabled, ...props }) => ( - ), - Flex: ({ children, ...props }) =>
{children}
, - Group: ({ children, ...props }) =>
{children}
, + Flex: ({ children, ...props }) => ( +
+ {children} +
+ ), + Group: ({ children, ...props }) => ( +
+ {children} +
+ ), Image: ({ src, alt, ...props }) => ( {alt} ), - Text: ({ children, ...props }) =>
{children}
, - Title: ({ children, order, ...props }) => ( -
{children}
+ Text: ({ children, ...props }) => ( +
+ {children} +
), - Select: ({ value, onChange, data, label, placeholder, disabled, ...props }) => ( + Title: ({ children, order, ...props }) => ( +
+ {children} +
+ ), + Select: ({ + value, + onChange, + data, + label, + placeholder, + disabled, + ...props + }) => (
), - Badge: ({ children, ...props }) => {children}, + Badge: ({ children, ...props }) => ( + + {children} + + ), Loader: (props) =>
, - Stack: ({ children, ...props }) =>
{children}
, + Stack: ({ children, ...props }) => ( +
+ {children} +
+ ), ActionIcon: ({ children, onClick, disabled, ...props }) => ( - ), Tabs: ({ children, value, onChange, ...props }) => (
-
{ - const tab = e.target.closest('[data-tab-value]'); - if (tab) onChange?.(tab.dataset.tabValue); - }}> +
{ + const tab = e.target.closest('[data-tab-value]'); + if (tab) onChange?.(tab.dataset.tabValue); + }} + > {children}
), TabsList: ({ children }) =>
{children}
, TabsTab: ({ children, value }) => ( - + ), TabsPanel: ({ children, value }) => ( -
{children}
+
+ {children} +
+ ), + Table: ({ children, ...props }) => ( + + {children} +
+ ), + TableThead: ({ children }) => ( + {children} + ), + TableTbody: ({ children }) => ( + {children} ), - Table: ({ children, ...props }) => {children}
, - TableThead: ({ children }) => {children}, - TableTbody: ({ children }) => {children}, TableTr: ({ children, onClick, ...props }) => ( - {children} + + {children} + + ), + TableTh: ({ children, ...props }) => ( + + {children} + + ), + TableTd: ({ children, ...props }) => ( + + {children} + ), - TableTh: ({ children, ...props }) => {children}, - TableTd: ({ children, ...props }) => {children}, Divider: (props) =>
, }; }); @@ -168,7 +239,7 @@ describe('SeriesModal', () => { m3u_account: { name: 'Provider 2' }, stream_name: 'Test Series 720p', quality_info: null, - } + }, ]; beforeEach(() => { @@ -187,9 +258,15 @@ describe('SeriesModal', () => { environment: { env_mode: 'prod' }, }; - useVODStore.mockImplementation((selector) => selector ? selector(mockVODStore) : mockVODStore); - useVideoStore.mockImplementation((selector) => selector ? selector(mockVideoStore) : mockVideoStore); - useSettingsStore.mockImplementation((selector) => selector ? selector(mockSettingsStore) : mockSettingsStore); + useVODStore.mockImplementation((selector) => + selector ? selector(mockVODStore) : mockVODStore + ); + useVideoStore.mockImplementation((selector) => + selector ? selector(mockVideoStore) : mockVideoStore + ); + useSettingsStore.mockImplementation((selector) => + selector ? selector(mockSettingsStore) : mockSettingsStore + ); copyToClipboard.mockResolvedValue(undefined); }); @@ -325,23 +402,37 @@ describe('SeriesModal', () => { it('should display IMDB link when imdb_id exists', async () => { render( - + ); await waitFor(() => { const link = screen.getByText(/IMDB/i).closest('a'); - expect(link).toHaveAttribute('href', 'https://www.imdb.com/title/tt1234567'); + expect(link).toHaveAttribute( + 'href', + 'https://www.imdb.com/title/tt1234567' + ); }); }); it('should display TMDB link when tmdb_id exists', async () => { render( - + ); await waitFor(() => { const link = screen.getByText(/TMDB/i).closest('a'); - expect(link).toHaveAttribute('href', 'https://www.themoviedb.org/tv/12345'); + expect(link).toHaveAttribute( + 'href', + 'https://www.themoviedb.org/tv/12345' + ); }); }); }); @@ -446,7 +537,12 @@ describe('SeriesModal', () => { }); it('should sort episodes by episode number', async () => { - const episode2 = { ...mockEpisode, id: 2, episode_number: 2, name: 'Second Episode' }; + const episode2 = { + ...mockEpisode, + id: 2, + episode_number: 2, + name: 'Second Episode', + }; mockVODStore.fetchSeriesInfo.mockResolvedValue({ ...mockDetailedSeries, episodesList: [episode2, mockEpisode], @@ -592,7 +688,12 @@ describe('SeriesModal', () => { describe('Season Tabs', () => { it('should create tabs for each season', async () => { - const season2Episode = { ...mockEpisode, id: 2, season_number: 2, episode_num: 1 }; + const season2Episode = { + ...mockEpisode, + id: 2, + season_number: 2, + episode_num: 1, + }; mockVODStore.fetchSeriesInfo.mockResolvedValue({ ...mockDetailedSeries, episodesList: [mockEpisode, season2Episode], @@ -670,7 +771,6 @@ describe('SeriesModal', () => { ); - await waitFor(() => { expect(screen.getByText(/Provider 1 - 1080p/)).toBeInTheDocument(); }); @@ -797,7 +897,7 @@ describe('SeriesModal', () => { ); await waitFor(() => { - expect(screen.getByText('Test Account')).toBeInTheDocument() + expect(screen.getByText('Test Account')).toBeInTheDocument(); }); }); }); diff --git a/frontend/src/components/cards/__tests__/RecordingCard.test.jsx b/frontend/src/components/cards/__tests__/RecordingCard.test.jsx index 9566f2b5..24e8dc46 100644 --- a/frontend/src/components/cards/__tests__/RecordingCard.test.jsx +++ b/frontend/src/components/cards/__tests__/RecordingCard.test.jsx @@ -109,13 +109,12 @@ vi.mock('@mantine/core', async () => ({ {children} ), - Tooltip: ({ children, label }) => ( -
{children}
- ), + Tooltip: ({ children, label }) =>
{children}
, })); // ── lucide-react ─────────────────────────────────────────────────────────────── vi.mock('lucide-react', () => ({ + ListOrdered: () => , AlertTriangle: () => , Plus: () => , Square: () => , @@ -140,8 +139,13 @@ vi.mock('../../../images/logo.png', () => ({ default: 'default-logo.png' })); import useChannelsStore from '../../../store/channels.jsx'; import useSettingsStore from '../../../store/settings.jsx'; import useVideoStore from '../../../store/useVideoStore.jsx'; -import { useDateTimeFormat, useTimeHelpers, format, isAfter, isBefore } - from '../../../utils/dateTimeUtils.js'; +import { + useDateTimeFormat, + useTimeHelpers, + format, + isAfter, + isBefore, +} from '../../../utils/dateTimeUtils.js'; import { notifications } from '@mantine/notifications'; import * as RecordingCardUtils from '../../../utils/cards/RecordingCardUtils.js'; import dayjs from 'dayjs'; @@ -187,7 +191,11 @@ const makeChannel = () => ({ }); /** Wire up all store/utility mocks with sensible defaults */ -const setupMocks = ({ now = NOW, recording = makeRecording(), channel = makeChannel() } = {}) => { +const setupMocks = ({ + now = NOW, + recording = makeRecording(), + channel = makeChannel(), +} = {}) => { const nowMoment = makeMoment(now); const startMoment = makeMoment(recording.start_time); const endMoment = makeMoment(recording.end_time); @@ -226,9 +234,13 @@ const setupMocks = ({ now = NOW, recording = makeRecording(), channel = makeChan vi.mocked(RecordingCardUtils.getPosterUrl).mockReturnValue('/poster.jpg'); vi.mocked(RecordingCardUtils.getChannelLogoUrl).mockReturnValue('/logo.png'); - vi.mocked(RecordingCardUtils.getRecordingUrl).mockReturnValue('/recordings/test.ts'); + vi.mocked(RecordingCardUtils.getRecordingUrl).mockReturnValue( + '/recordings/test.ts' + ); vi.mocked(RecordingCardUtils.getSeasonLabel).mockReturnValue(''); - vi.mocked(RecordingCardUtils.getSeriesInfo).mockReturnValue({ seriesId: 's1' }); + vi.mocked(RecordingCardUtils.getSeriesInfo).mockReturnValue({ + seriesId: 's1', + }); vi.mocked(RecordingCardUtils.getShowVideoUrl).mockReturnValue('/live/ch-1'); return { mockShowVideo, mockFetchRecordings }; @@ -237,10 +249,18 @@ const setupMocks = ({ now = NOW, recording = makeRecording(), channel = makeChan describe('RecordingCard', () => { beforeEach(() => { vi.clearAllMocks(); - vi.mocked(RecordingCardUtils.stopRecordingById).mockResolvedValue(undefined); - vi.mocked(RecordingCardUtils.deleteRecordingById).mockResolvedValue(undefined); - vi.mocked(RecordingCardUtils.deleteSeriesAndRule).mockResolvedValue(undefined); - vi.mocked(RecordingCardUtils.extendRecordingById).mockResolvedValue(undefined); + vi.mocked(RecordingCardUtils.stopRecordingById).mockResolvedValue( + undefined + ); + vi.mocked(RecordingCardUtils.deleteRecordingById).mockResolvedValue( + undefined + ); + vi.mocked(RecordingCardUtils.deleteSeriesAndRule).mockResolvedValue( + undefined + ); + vi.mocked(RecordingCardUtils.extendRecordingById).mockResolvedValue( + undefined + ); vi.mocked(RecordingCardUtils.runComSkip).mockResolvedValue(undefined); vi.mocked(RecordingCardUtils.removeRecording).mockReturnValue(undefined); }); @@ -250,17 +270,23 @@ describe('RecordingCard', () => { describe('rendering', () => { it('renders the recording title', () => { setupMocks(); - render(); + render( + + ); expect(screen.getByText('Test Show')).toBeInTheDocument(); }); it('renders "Custom Recording" when no program title', () => { setupMocks({ - recording: makeRecording({ custom_properties: { status: 'completed', program: {} } }), + recording: makeRecording({ + custom_properties: { status: 'completed', program: {} }, + }), }); render( ); @@ -269,7 +295,9 @@ describe('RecordingCard', () => { it('renders channel info', () => { setupMocks(); - render(); + render( + + ); expect(screen.getByText('501 • HBO')).toBeInTheDocument(); }); @@ -281,27 +309,35 @@ describe('RecordingCard', () => { it('shows description via RecordingSynopsis for non-series completed recording', () => { setupMocks(); - render(); + render( + + ); expect(screen.getByTestId('recording-synopsis')).toBeInTheDocument(); expect(screen.getByText('A test description')).toBeInTheDocument(); }); it('shows sub_title when present', () => { setupMocks(); - render(); + render( + + ); expect(screen.getByText('Pilot')).toBeInTheDocument(); }); it('shows season/episode label when getSeasonLabel returns a value', () => { setupMocks(); vi.mocked(RecordingCardUtils.getSeasonLabel).mockReturnValue('S01E02'); - render(); + render( + + ); expect(screen.getByText('S01E02')).toBeInTheDocument(); }); it('renders the poster image', () => { setupMocks(); - render(); + render( + + ); const img = screen.getByAltText('Test Show'); expect(img).toHaveAttribute('src', '/poster.jpg'); }); @@ -312,7 +348,9 @@ describe('RecordingCard', () => { describe('status badge', () => { it('shows "Completed" badge for a completed recording', () => { setupMocks(); - render(); + render( + + ); expect(screen.getByText('Completed')).toBeInTheDocument(); }); @@ -320,7 +358,10 @@ describe('RecordingCard', () => { const recording = makeRecording({ start_time: PAST, end_time: FUTURE, - custom_properties: { status: 'recording', program: { title: 'Live Show' } }, + custom_properties: { + status: 'recording', + program: { title: 'Live Show' }, + }, }); setupMocks({ recording }); render(); @@ -331,7 +372,10 @@ describe('RecordingCard', () => { const recording = makeRecording({ start_time: FUTURE, end_time: FUTURE, - custom_properties: { status: 'scheduled', program: { title: 'Future Show' } }, + custom_properties: { + status: 'scheduled', + program: { title: 'Future Show' }, + }, }); setupMocks({ recording }); render(); @@ -373,8 +417,7 @@ describe('RecordingCard', () => { // ── Series group ─────────────────────────────────────────────────────────── describe('series group', () => { - const makeSeriesRecording = () => - makeRecording({ _group_count: 3 }); + const makeSeriesRecording = () => makeRecording({ _group_count: 3 }); it('shows "Series" badge when _group_count > 1', () => { const recording = makeSeriesRecording(); @@ -394,7 +437,9 @@ describe('RecordingCard', () => { const recording = makeSeriesRecording(); setupMocks({ recording }); render(); - expect(screen.queryByTestId('recording-synopsis')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('recording-synopsis') + ).not.toBeInTheDocument(); }); }); @@ -500,7 +545,9 @@ describe('RecordingCard', () => { it('does not show "Watch Live" for a completed recording', () => { setupMocks(); - render(); + render( + + ); expect(screen.queryByText('Watch Live')).not.toBeInTheDocument(); }); @@ -523,7 +570,9 @@ describe('RecordingCard', () => { it('shows "Watch" button for a completed recording', () => { setupMocks(); - render(); + render( + + ); expect(screen.getByText('Watch')).toBeInTheDocument(); }); @@ -531,7 +580,10 @@ describe('RecordingCard', () => { const recording = makeRecording({ start_time: FUTURE, end_time: FUTURE, - custom_properties: { status: 'scheduled', program: { title: 'Future' } }, + custom_properties: { + status: 'scheduled', + program: { title: 'Future' }, + }, }); setupMocks({ recording }); render(); @@ -540,7 +592,9 @@ describe('RecordingCard', () => { it('calls showVideo with vod params when Watch is clicked', () => { const { mockShowVideo } = setupMocks(); - render(); + render( + + ); fireEvent.click(screen.getByText('Watch')); expect(mockShowVideo).toHaveBeenCalledWith( '/recordings/test.ts', @@ -552,7 +606,9 @@ describe('RecordingCard', () => { it('does not call showVideo when Watch is clicked but no file url', () => { const { mockShowVideo } = setupMocks(); vi.mocked(RecordingCardUtils.getRecordingUrl).mockReturnValue(null); - render(); + render( + + ); fireEvent.click(screen.getByText('Watch')); expect(mockShowVideo).not.toHaveBeenCalled(); }); @@ -575,7 +631,9 @@ describe('RecordingCard', () => { describe('"Remove commercials" button', () => { it('shows "Remove commercials" for a completed recording without comskip', () => { setupMocks(); - render(); + render( + + ); expect(screen.getByText('Remove commercials')).toBeInTheDocument(); }); @@ -597,7 +655,10 @@ describe('RecordingCard', () => { const recording = makeRecording({ start_time: FUTURE, end_time: FUTURE, - custom_properties: { status: 'scheduled', program: { title: 'Future' } }, + custom_properties: { + status: 'scheduled', + program: { title: 'Future' }, + }, }); setupMocks({ recording }); render(); @@ -606,20 +667,31 @@ describe('RecordingCard', () => { it('calls runComSkip and shows notification on success', async () => { setupMocks(); - render(); + render( + + ); fireEvent.click(screen.getByText('Remove commercials')); await waitFor(() => { - expect(RecordingCardUtils.runComSkip).toHaveBeenCalledWith(makeRecording()); + expect(RecordingCardUtils.runComSkip).toHaveBeenCalledWith( + makeRecording() + ); expect(notifications.show).toHaveBeenCalledWith( - expect.objectContaining({ title: 'Removing commercials', color: 'blue.5' }) + expect.objectContaining({ + title: 'Removing commercials', + color: 'blue.5', + }) ); }); }); it('does not show notification when runComSkip throws', async () => { - vi.mocked(RecordingCardUtils.runComSkip).mockRejectedValue(new Error('fail')); + vi.mocked(RecordingCardUtils.runComSkip).mockRejectedValue( + new Error('fail') + ); setupMocks(); - render(); + render( + + ); fireEvent.click(screen.getByText('Remove commercials')); await waitFor(() => { expect(notifications.show).not.toHaveBeenCalled(); @@ -634,7 +706,11 @@ describe('RecordingCard', () => { makeRecording({ start_time: PAST, end_time: FUTURE, - custom_properties: { status: 'recording', program: { title: 'Live Show' }, file_url: '/f.ts' }, + custom_properties: { + status: 'recording', + program: { title: 'Live Show' }, + file_url: '/f.ts', + }, }); it('shows extend menu for in-progress recording', () => { @@ -650,9 +726,15 @@ describe('RecordingCard', () => { render(); fireEvent.click(screen.getByText('+15 minutes')); await waitFor(() => { - expect(RecordingCardUtils.extendRecordingById).toHaveBeenCalledWith('rec-1', 15); + expect(RecordingCardUtils.extendRecordingById).toHaveBeenCalledWith( + 'rec-1', + 15 + ); expect(notifications.show).toHaveBeenCalledWith( - expect.objectContaining({ title: 'Recording extended', color: 'teal' }) + expect.objectContaining({ + title: 'Recording extended', + color: 'teal', + }) ); }); }); @@ -663,7 +745,10 @@ describe('RecordingCard', () => { render(); fireEvent.click(screen.getByText('+30 minutes')); await waitFor(() => { - expect(RecordingCardUtils.extendRecordingById).toHaveBeenCalledWith('rec-1', 30); + expect(RecordingCardUtils.extendRecordingById).toHaveBeenCalledWith( + 'rec-1', + 30 + ); }); }); @@ -673,12 +758,17 @@ describe('RecordingCard', () => { render(); fireEvent.click(screen.getByText('+1 hour')); await waitFor(() => { - expect(RecordingCardUtils.extendRecordingById).toHaveBeenCalledWith('rec-1', 60); + expect(RecordingCardUtils.extendRecordingById).toHaveBeenCalledWith( + 'rec-1', + 60 + ); }); }); it('shows error notification when extendRecordingById throws', async () => { - vi.mocked(RecordingCardUtils.extendRecordingById).mockRejectedValue(new Error('Network error')); + vi.mocked(RecordingCardUtils.extendRecordingById).mockRejectedValue( + new Error('Network error') + ); const recording = makeInProgress(); setupMocks({ recording }); render(); @@ -698,7 +788,11 @@ describe('RecordingCard', () => { makeRecording({ start_time: PAST, end_time: FUTURE, - custom_properties: { status: 'recording', program: { title: 'Live Show' }, file_url: '/f.ts' }, + custom_properties: { + status: 'recording', + program: { title: 'Live Show' }, + file_url: '/f.ts', + }, }); it('shows stop modal when stop button is clicked', () => { @@ -711,7 +805,9 @@ describe('RecordingCard', () => { fireEvent.click(stopButton); expect(screen.getByTestId('modal')).toBeInTheDocument(); - expect(screen.getByTestId('modal-title')).toHaveTextContent('Stop Recording'); + expect(screen.getByTestId('modal-title')).toHaveTextContent( + 'Stop Recording' + ); }); it('closes stop modal when Go Back is clicked', () => { @@ -736,7 +832,9 @@ describe('RecordingCard', () => { fireEvent.click(screen.getAllByText('Stop Recording')[1]); await waitFor(() => { - expect(RecordingCardUtils.stopRecordingById).toHaveBeenCalledWith('rec-1'); + expect(RecordingCardUtils.stopRecordingById).toHaveBeenCalledWith( + 'rec-1' + ); expect(mockFetchRecordings).toHaveBeenCalled(); }); }); @@ -761,48 +859,71 @@ describe('RecordingCard', () => { describe('delete recording', () => { it('shows delete modal for a completed non-series recording', () => { setupMocks(); - render(); + render( + + ); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); expect(screen.getByTestId('modal')).toBeInTheDocument(); - expect(screen.getByTestId('modal-title')).toHaveTextContent('Delete Recording'); + expect(screen.getByTestId('modal-title')).toHaveTextContent( + 'Delete Recording' + ); }); it('shows "Cancel Recording" title for upcoming recording delete', () => { const recording = makeRecording({ start_time: FUTURE, end_time: FUTURE, - custom_properties: { status: 'scheduled', program: { title: 'Future Show' } }, + custom_properties: { + status: 'scheduled', + program: { title: 'Future Show' }, + }, }); setupMocks({ recording }); render(); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); - expect(screen.getByTestId('modal-title')).toHaveTextContent('Cancel Recording'); + expect(screen.getByTestId('modal-title')).toHaveTextContent( + 'Cancel Recording' + ); }); it('calls removeRecording when delete is confirmed', async () => { setupMocks(); - render(); + render( + + ); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); fireEvent.click(screen.getByText('Delete')); await waitFor(() => { - expect(RecordingCardUtils.removeRecording).toHaveBeenCalledWith('rec-1'); + expect(RecordingCardUtils.removeRecording).toHaveBeenCalledWith( + 'rec-1' + ); }); }); it('closes delete modal after confirming', async () => { setupMocks(); - render(); + render( + + ); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); fireEvent.click(screen.getByText('Delete')); @@ -813,9 +934,13 @@ describe('RecordingCard', () => { it('closes delete modal on Go Back click', () => { setupMocks(); - render(); + render( + + ); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); fireEvent.click(screen.getByText('Go Back')); @@ -842,10 +967,14 @@ describe('RecordingCard', () => { setupMocks({ recording }); render(); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); - expect(screen.getByTestId('modal-title')).toHaveTextContent('Cancel Series'); + expect(screen.getByTestId('modal-title')).toHaveTextContent( + 'Cancel Series' + ); }); it('calls deleteRecordingById when "Only this upcoming" is clicked', async () => { @@ -853,12 +982,16 @@ describe('RecordingCard', () => { const { mockFetchRecordings } = setupMocks({ recording }); render(); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); fireEvent.click(screen.getByText('Only this upcoming')); await waitFor(() => { - expect(RecordingCardUtils.deleteRecordingById).toHaveBeenCalledWith('rec-1'); + expect(RecordingCardUtils.deleteRecordingById).toHaveBeenCalledWith( + 'rec-1' + ); expect(mockFetchRecordings).toHaveBeenCalled(); }); }); @@ -868,7 +1001,9 @@ describe('RecordingCard', () => { const { mockFetchRecordings } = setupMocks({ recording }); render(); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); fireEvent.click(screen.getByText('Entire series + rule')); @@ -883,7 +1018,9 @@ describe('RecordingCard', () => { setupMocks({ recording }); render(); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); fireEvent.click(screen.getByText('Only this upcoming')); @@ -918,13 +1055,18 @@ describe('RecordingCard', () => { _group_count: 3, start_time: FUTURE, end_time: FUTURE, - custom_properties: { status: 'scheduled', program: { title: 'Series' } }, + custom_properties: { + status: 'scheduled', + program: { title: 'Series' }, + }, }); const { mockFetchRecordings } = setupMocks({ recording }); mockFetchRecordings.mockRejectedValue(new Error('network')); render(); - const deleteButton = screen.getByTestId('icon-square-x').closest('button'); + const deleteButton = screen + .getByTestId('icon-square-x') + .closest('button'); fireEvent.click(deleteButton); await expect( @@ -932,4 +1074,4 @@ describe('RecordingCard', () => { ).resolves.not.toThrow(); }); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/components/cards/__tests__/SeriesCard.test.jsx b/frontend/src/components/cards/__tests__/SeriesCard.test.jsx index fb95c882..49b8524f 100644 --- a/frontend/src/components/cards/__tests__/SeriesCard.test.jsx +++ b/frontend/src/components/cards/__tests__/SeriesCard.test.jsx @@ -30,7 +30,13 @@ vi.mock('@mantine/core', async () => ({ ), Stack: ({ children, gap }) =>
{children}
, Text: ({ children, size, fw, c, lineClamp, style }) => ( - + {children} ), @@ -38,6 +44,7 @@ vi.mock('@mantine/core', async () => ({ // ── lucide-react ─────────────────────────────────────────────────────────────── vi.mock('lucide-react', () => ({ + ListOrdered: () => , Calendar: () => , Play: () => , Star: () => , @@ -78,7 +85,12 @@ describe('SeriesCard', () => { }); it('renders a fallback image when poster_url is missing', () => { - render(); + render( + + ); const img = screen.getByRole('img'); expect(img).toBeInTheDocument(); }); @@ -105,7 +117,12 @@ describe('SeriesCard', () => { it('renders play icon', () => { //this only renders when logo.url is missing, but we want to test that the icon itself renders correctly - render(); + render( + + ); expect(screen.getByTestId('icon-play')).toBeInTheDocument(); }); @@ -119,27 +136,52 @@ describe('SeriesCard', () => { describe('optional fields', () => { it('does not crash when year is missing', () => { - render(); + render( + + ); expect(screen.getByTestId('series-card')).toBeInTheDocument(); }); it('does not crash when rating is missing', () => { - render(); + render( + + ); expect(screen.getByTestId('series-card')).toBeInTheDocument(); }); it('does not crash when genre is missing', () => { - render(); + render( + + ); expect(screen.getByTestId('series-card')).toBeInTheDocument(); }); it('does not crash when description is missing', () => { - render(); + render( + + ); expect(screen.getByTestId('series-card')).toBeInTheDocument(); }); it('does not crash when seasons is missing', () => { - render(); + render( + + ); expect(screen.getByTestId('series-card')).toBeInTheDocument(); }); @@ -161,4 +203,4 @@ describe('SeriesCard', () => { expect(onClick).toHaveBeenCalledWith(series); }); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/components/cards/__tests__/StreamConnectionCard.test.jsx b/frontend/src/components/cards/__tests__/StreamConnectionCard.test.jsx index babcf1e6..2f006244 100644 --- a/frontend/src/components/cards/__tests__/StreamConnectionCard.test.jsx +++ b/frontend/src/components/cards/__tests__/StreamConnectionCard.test.jsx @@ -95,7 +95,12 @@ vi.mock('@mantine/core', () => ({ Center: ({ children }) =>
{children}
, Group: ({ children }) =>
{children}
, Progress: ({ value, size, color }) => ( -
+
), Select: ({ value, onChange, label, data, disabled, placeholder }) => (