diff --git a/frontend/src/components/forms/settings/__tests__/DvrSettingsForm.test.jsx b/frontend/src/components/forms/settings/__tests__/DvrSettingsForm.test.jsx index ad86f1e0..f3293051 100644 --- a/frontend/src/components/forms/settings/__tests__/DvrSettingsForm.test.jsx +++ b/frontend/src/components/forms/settings/__tests__/DvrSettingsForm.test.jsx @@ -125,13 +125,17 @@ const mockFormValues = { const makeFormMock = (overrides = {}) => ({ getInputProps: vi.fn((field, opts) => { - if (opts?.type === 'checkbox') return { checked: mockFormValues[field] ?? false, onChange: vi.fn() }; + if (opts?.type === 'checkbox') + return { checked: mockFormValues[field] ?? false, onChange: vi.fn() }; return { value: mockFormValues[field] ?? '', onChange: vi.fn() }; }), setValues: vi.fn(), setFieldValue: vi.fn(), getValues: vi.fn(() => mockFormValues), - onSubmit: vi.fn((handler) => (e) => { e?.preventDefault?.(); return handler(); }), + onSubmit: vi.fn((handler) => (e) => { + e?.preventDefault?.(); + return handler(); + }), ...overrides, }); @@ -229,7 +233,9 @@ describe('DvrSettingsForm', () => { expect(screen.getByTestId('tv_template')).toBeInTheDocument(); expect(screen.getByTestId('tv_fallback_template')).toBeInTheDocument(); expect(screen.getByTestId('movie_template')).toBeInTheDocument(); - expect(screen.getByTestId('movie_fallback_template')).toBeInTheDocument(); + expect( + screen.getByTestId('movie_fallback_template') + ).toBeInTheDocument(); }); }); @@ -250,7 +256,9 @@ describe('DvrSettingsForm', () => { it('shows "No custom comskip.ini uploaded." when no config exists', async () => { render(); await waitFor(() => { - expect(screen.getByText('No custom comskip.ini uploaded.')).toBeInTheDocument(); + expect( + screen.getByText('No custom comskip.ini uploaded.') + ).toBeInTheDocument(); }); }); @@ -261,7 +269,9 @@ describe('DvrSettingsForm', () => { }); render(); await waitFor(() => { - expect(screen.getByText('Using /app/docker/comskip.ini')).toBeInTheDocument(); + expect( + screen.getByText('Using /app/docker/comskip.ini') + ).toBeInTheDocument(); }); }); }); @@ -306,7 +316,9 @@ describe('DvrSettingsForm', () => { it('handles getComskipConfig error gracefully', async () => { vi.mocked(getComskipConfig).mockRejectedValue(new Error('network error')); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); render(); await waitFor(() => { expect(consoleSpy).toHaveBeenCalledWith( @@ -357,8 +369,13 @@ describe('DvrSettingsForm', () => { fireEvent.submit(screen.getByText('Save').closest('form')); await waitFor(() => { - expect(getChangedSettings).toHaveBeenCalledWith(mockFormValues, makeSettings()); - expect(saveChangedSettings).toHaveBeenCalledWith(makeSettings(), { pre_offset_minutes: 5 }); + expect(getChangedSettings).toHaveBeenCalledWith( + mockFormValues, + makeSettings() + ); + expect(saveChangedSettings).toHaveBeenCalledWith(makeSettings(), { + pre_offset_minutes: 5, + }); }); }); @@ -373,8 +390,12 @@ describe('DvrSettingsForm', () => { }); it('does not show success alert when saveChangedSettings throws', async () => { - vi.mocked(saveChangedSettings).mockRejectedValue(new Error('save failed')); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + vi.mocked(saveChangedSettings).mockRejectedValue( + new Error('save failed') + ); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); render(); fireEvent.submit(screen.getByText('Save').closest('form')); @@ -400,11 +421,17 @@ describe('DvrSettingsForm', () => { }); it('calls uploadComskipIni with the selected file', async () => { - const mockFile = new File(['content'], 'comskip.ini', { type: 'text/plain' }); - vi.mocked(uploadComskipIni).mockResolvedValue({ path: '/uploaded/comskip.ini' }); + const mockFile = new File(['content'], 'comskip.ini', { + type: 'text/plain', + }); + vi.mocked(uploadComskipIni).mockResolvedValue({ + path: '/uploaded/comskip.ini', + }); render(); - fireEvent.change(screen.getByTestId('file-input'), { target: { files: [mockFile] } }); + fireEvent.change(screen.getByTestId('file-input'), { + target: { files: [mockFile] }, + }); await waitFor(() => { fireEvent.click(screen.getByText('Upload comskip.ini')); @@ -416,11 +443,17 @@ describe('DvrSettingsForm', () => { }); it('shows success notification after successful upload', async () => { - const mockFile = new File(['content'], 'comskip.ini', { type: 'text/plain' }); - vi.mocked(uploadComskipIni).mockResolvedValue({ path: '/uploaded/comskip.ini' }); + const mockFile = new File(['content'], 'comskip.ini', { + type: 'text/plain', + }); + vi.mocked(uploadComskipIni).mockResolvedValue({ + path: '/uploaded/comskip.ini', + }); render(); - fireEvent.change(screen.getByTestId('file-input'), { target: { files: [mockFile] } }); + fireEvent.change(screen.getByTestId('file-input'), { + target: { files: [mockFile] }, + }); await waitFor(() => { fireEvent.click(screen.getByText('Upload comskip.ini')); @@ -428,17 +461,26 @@ describe('DvrSettingsForm', () => { await waitFor(() => { expect(showNotification).toHaveBeenCalledWith( - expect.objectContaining({ title: 'comskip.ini uploaded', color: 'green' }) + expect.objectContaining({ + title: 'comskip.ini uploaded', + color: 'green', + }) ); }); }); it('sets comskip_custom_path form field after successful upload', async () => { - const mockFile = new File(['content'], 'comskip.ini', { type: 'text/plain' }); - vi.mocked(uploadComskipIni).mockResolvedValue({ path: '/uploaded/comskip.ini' }); + const mockFile = new File(['content'], 'comskip.ini', { + type: 'text/plain', + }); + vi.mocked(uploadComskipIni).mockResolvedValue({ + path: '/uploaded/comskip.ini', + }); render(); - fireEvent.change(screen.getByTestId('file-input'), { target: { files: [mockFile] } }); + fireEvent.change(screen.getByTestId('file-input'), { + target: { files: [mockFile] }, + }); await waitFor(() => { fireEvent.click(screen.getByText('Upload comskip.ini')); @@ -453,12 +495,18 @@ describe('DvrSettingsForm', () => { }); it('handles upload error gracefully', async () => { - const mockFile = new File(['content'], 'comskip.ini', { type: 'text/plain' }); + const mockFile = new File(['content'], 'comskip.ini', { + type: 'text/plain', + }); vi.mocked(uploadComskipIni).mockRejectedValue(new Error('upload failed')); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); render(); - fireEvent.change(screen.getByTestId('file-input'), { target: { files: [mockFile] } }); + fireEvent.change(screen.getByTestId('file-input'), { + target: { files: [mockFile] }, + }); await waitFor(() => { fireEvent.click(screen.getByText('Upload comskip.ini')); @@ -480,4 +528,4 @@ describe('DvrSettingsForm', () => { expect(uploadComskipIni).not.toHaveBeenCalled(); }); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/components/forms/settings/__tests__/NavOrderForm.test.jsx b/frontend/src/components/forms/settings/__tests__/NavOrderForm.test.jsx index 5cbf603e..3fb91a43 100644 --- a/frontend/src/components/forms/settings/__tests__/NavOrderForm.test.jsx +++ b/frontend/src/components/forms/settings/__tests__/NavOrderForm.test.jsx @@ -64,7 +64,7 @@ vi.mock('@dnd-kit/modifiers', () => ({ // Mock Mantine components vi.mock('@mantine/core', () => ({ Box: ({ children, ...props }) =>
{children}
, - Button: ({ children, onClick, disabled, ...props }) => ( + Button: ({ children, onClick, disabled }) => ( diff --git a/frontend/src/components/forms/settings/__tests__/NetworkAccessForm.test.jsx b/frontend/src/components/forms/settings/__tests__/NetworkAccessForm.test.jsx index 32271d0b..15e5e430 100644 --- a/frontend/src/components/forms/settings/__tests__/NetworkAccessForm.test.jsx +++ b/frontend/src/components/forms/settings/__tests__/NetworkAccessForm.test.jsx @@ -36,8 +36,12 @@ vi.mock('../../../ConfirmationDialog.jsx', () => ({
{title}
{message}
- - + +
) : null, })); @@ -85,7 +89,10 @@ vi.mock('@mantine/core', () => ({ // ────────────────────────────────────────────────────────────────────────────── import useSettingsStore from '../../../../store/settings.jsx'; import { useForm } from '@mantine/form'; -import { checkSetting, updateSetting } from '../../../../utils/pages/SettingsUtils.js'; +import { + checkSetting, + updateSetting, +} from '../../../../utils/pages/SettingsUtils.js'; import { getNetworkAccessFormInitialValues, getNetworkAccessFormValidation, @@ -139,10 +146,15 @@ const setupMocks = ({ settings = makeSettings(), formOverrides = {} } = {}) => { const formMock = makeFormMock(formOverrides); vi.mocked(useForm).mockReturnValue(formMock); - vi.mocked(getNetworkAccessFormInitialValues).mockReturnValue(mockInitialValues); + vi.mocked(getNetworkAccessFormInitialValues).mockReturnValue( + mockInitialValues + ); vi.mocked(getNetworkAccessFormValidation).mockReturnValue({}); vi.mocked(getNetworkAccessDefaults).mockReturnValue(mockInitialValues); - vi.mocked(checkSetting).mockResolvedValue({ client_ip: '192.168.1.1', UI: ['192.168.0.0/16'] }); + vi.mocked(checkSetting).mockResolvedValue({ + client_ip: '192.168.1.1', + UI: ['192.168.0.0/16'], + }); vi.mocked(updateSetting).mockResolvedValue(undefined); vi.mocked(useSettingsStore).mockImplementation((sel) => sel({ settings })); @@ -180,7 +192,9 @@ describe('NetworkAccessForm', () => { it('does not show confirmation dialog on initial render', () => { render(); - expect(screen.queryByTestId('confirmation-dialog')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('confirmation-dialog') + ).not.toBeInTheDocument(); }); it('does not show error alert on initial render', () => { @@ -238,7 +252,10 @@ describe('NetworkAccessForm', () => { vi.mocked(updateSetting).mockResolvedValue(undefined); const { rerender } = render(); - fireEvent.submit(screen.getByText('Save').closest('form') ?? screen.getByText('Save').closest('div')); + fireEvent.submit( + screen.getByText('Save').closest('form') ?? + screen.getByText('Save').closest('div') + ); await waitFor(() => { expect(screen.queryByTestId('confirmation-dialog')).not.toBeNull(); @@ -270,7 +287,9 @@ describe('NetworkAccessForm', () => { fireEvent.click(screen.getByTestId('confirm-cancel')); await waitFor(() => { - expect(screen.queryByTestId('confirmation-dialog')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('confirmation-dialog') + ).not.toBeInTheDocument(); }); }); @@ -295,7 +314,9 @@ describe('NetworkAccessForm', () => { await waitFor(() => { expect(screen.getByTestId('alert')).toBeInTheDocument(); - expect(screen.getByTestId('alert-title')).toHaveTextContent('Saved Successfully'); + expect(screen.getByTestId('alert-title')).toHaveTextContent( + 'Saved Successfully' + ); }); }); @@ -312,8 +333,9 @@ describe('NetworkAccessForm', () => { }); it('does not show success alert when updateSetting throws', async () => { - vi.mocked(updateSetting).mockRejectedValue( - { body: { value: { m3u_custom_cidrs: 'Invalid CIDR' } } }); + vi.mocked(updateSetting).mockRejectedValue({ + body: { value: { m3u_custom_cidrs: 'Invalid CIDR' } }, + }); render(); fireEvent.click(screen.getByText('Save')); @@ -344,7 +366,9 @@ describe('NetworkAccessForm', () => { vi.mocked(checkSetting).mockResolvedValue(null); render(); await waitFor(() => { - expect(screen.queryByText(/\d+\.\d+\.\d+\.\d+/)).not.toBeInTheDocument(); + expect( + screen.queryByText(/\d+\.\d+\.\d+\.\d+/) + ).not.toBeInTheDocument(); }); }); }); @@ -352,9 +376,11 @@ describe('NetworkAccessForm', () => { // ── Network access error ─────────────────────────────────────────────────── describe('network access error state', () => { it('clears error state after successful save', async () => { - vi.mocked(checkSetting).mockResolvedValue( - { error: true, message: 'Invalid CIDR', data: 'Error details' } - ); + vi.mocked(checkSetting).mockResolvedValue({ + error: true, + message: 'Invalid CIDR', + data: 'Error details', + }); render(); @@ -362,8 +388,10 @@ describe('NetworkAccessForm', () => { fireEvent.click(screen.getByText('Save')); await waitFor(() => screen.getByTestId('alert')); - vi.mocked(checkSetting).mockResolvedValue( - { client_ip: '192.168.1.1', UI: ['192.168.0.0/16'] }); + vi.mocked(checkSetting).mockResolvedValue({ + client_ip: '192.168.1.1', + UI: ['192.168.0.0/16'], + }); // Second save — succeeds fireEvent.click(screen.getByText('Save')); @@ -371,8 +399,10 @@ describe('NetworkAccessForm', () => { fireEvent.click(screen.getByTestId('confirm-ok')); await waitFor(() => { - expect(screen.getByTestId('alert-title')).toHaveTextContent('Saved Successfully'); + expect(screen.getByTestId('alert-title')).toHaveTextContent( + 'Saved Successfully' + ); }); }); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/components/forms/settings/__tests__/ProxySettingsForm.test.jsx b/frontend/src/components/forms/settings/__tests__/ProxySettingsForm.test.jsx index 193f528c..c874b3e6 100644 --- a/frontend/src/components/forms/settings/__tests__/ProxySettingsForm.test.jsx +++ b/frontend/src/components/forms/settings/__tests__/ProxySettingsForm.test.jsx @@ -5,8 +5,14 @@ import ProxySettingsForm from '../ProxySettingsForm'; // ── Constants mock ───────────────────────────────────────────────────────────── vi.mock('../../../../constants.js', () => ({ PROXY_SETTINGS_OPTIONS: { - buffering_timeout: { label: 'Buffering Timeout', description: 'Timeout in seconds' }, - buffering_speed: { label: 'Buffering Speed', description: 'Speed multiplier' }, + buffering_timeout: { + label: 'Buffering Timeout', + description: 'Timeout in seconds', + }, + buffering_speed: { + label: 'Buffering Speed', + description: 'Speed multiplier', + }, redis_url: { label: 'Redis URL', description: 'Redis connection URL' }, }, })); @@ -133,7 +139,9 @@ const setupMocks = ({ settings = makeSettings(), formOverrides = {} } = {}) => { const formMock = makeFormMock(formOverrides); vi.mocked(useForm).mockReturnValue(formMock); - vi.mocked(getProxySettingsFormInitialValues).mockReturnValue(mockInitialValues); + vi.mocked(getProxySettingsFormInitialValues).mockReturnValue( + mockInitialValues + ); vi.mocked(getProxySettingDefaults).mockReturnValue(mockDefaults); vi.mocked(updateSetting).mockResolvedValue({ success: true }); vi.mocked(useSettingsStore).mockImplementation((sel) => sel({ settings })); @@ -161,12 +169,16 @@ describe('ProxySettingsForm', () => { it('renders NumberInput for buffering_timeout', () => { render(); - expect(screen.getByTestId('number-input-Buffering Timeout')).toBeInTheDocument(); + expect( + screen.getByTestId('number-input-Buffering Timeout') + ).toBeInTheDocument(); }); it('renders NumberInput for buffering_speed', () => { render(); - expect(screen.getByTestId('number-input-Buffering Speed')).toBeInTheDocument(); + expect( + screen.getByTestId('number-input-Buffering Speed') + ).toBeInTheDocument(); }); it('renders TextInput for redis_url', () => { @@ -216,7 +228,9 @@ describe('ProxySettingsForm', () => { }); it('handles null settings gracefully', () => { - vi.mocked(useSettingsStore).mockImplementation((sel) => sel({ settings: null })); + vi.mocked(useSettingsStore).mockImplementation((sel) => + sel({ settings: null }) + ); expect(() => render()).not.toThrow(); }); }); @@ -261,7 +275,9 @@ describe('ProxySettingsForm', () => { await waitFor(() => { expect(screen.getByTestId('alert')).toBeInTheDocument(); - expect(screen.getByTestId('alert-title')).toHaveTextContent('Saved Successfully'); + expect(screen.getByTestId('alert-title')).toHaveTextContent( + 'Saved Successfully' + ); }); }); @@ -344,4 +360,4 @@ describe('ProxySettingsForm', () => { expect(formMock.getInputProps).toHaveBeenCalledWith('redis_url'); }); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/components/forms/settings/__tests__/StreamSettingsForm.test.jsx b/frontend/src/components/forms/settings/__tests__/StreamSettingsForm.test.jsx index c0742c8e..25ee46aa 100644 --- a/frontend/src/components/forms/settings/__tests__/StreamSettingsForm.test.jsx +++ b/frontend/src/components/forms/settings/__tests__/StreamSettingsForm.test.jsx @@ -35,16 +35,16 @@ vi.mock('@mantine/form', () => ({ useForm: vi.fn() })); // ── ConfirmationDialog mock ──────────────────────────────────────────────────── vi.mock('../../../ConfirmationDialog.jsx', () => ({ default: ({ - opened, - onClose, - onConfirm, - title, - message, - confirmLabel, - cancelLabel, - actionKey, - onSuppressChange, - }) => + opened, + onClose, + onConfirm, + title, + message, + confirmLabel, + cancelLabel, + actionKey, + onSuppressChange, + }) => opened ? (
{title}
@@ -98,7 +98,9 @@ vi.mock('@mantine/core', () => ({ aria-label={label} multiple onChange={(e) => { - const selected = Array.from(e.target.selectedOptions).map((o) => o.value); + const selected = Array.from(e.target.selectedOptions).map( + (o) => o.value + ); rest.onChange?.(selected); }} value={rest.value ?? []} @@ -212,12 +214,12 @@ const makeStreamProfiles = () => [ ]; const setupMocks = ({ - settings = makeSettings(), - formOverrides = {}, - warningSuppressed = false, - userAgents = makeUserAgents(), - streamProfiles = makeStreamProfiles(), - } = {}) => { + settings = makeSettings(), + formOverrides = {}, + warningSuppressed = false, + userAgents = makeUserAgents(), + streamProfiles = makeStreamProfiles(), +} = {}) => { const formMock = makeFormMock(formOverrides); vi.mocked(useForm).mockReturnValue(formMock); @@ -290,7 +292,9 @@ describe('StreamSettingsForm', () => { it('renders the Auto-Import Mapped Files switch', () => { render(); - expect(screen.getByTestId('auto_import_mapped_files')).toBeInTheDocument(); + expect( + screen.getByTestId('auto_import_mapped_files') + ).toBeInTheDocument(); }); it('renders the M3U Hash Key multiselect', () => { @@ -323,7 +327,9 @@ describe('StreamSettingsForm', () => { it('does not show confirmation dialog on initial render', () => { render(); - expect(screen.queryByTestId('confirmation-dialog')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('confirmation-dialog') + ).not.toBeInTheDocument(); }); }); @@ -429,13 +435,19 @@ describe('StreamSettingsForm', () => { fireEvent.submit(screen.getByText('Save').closest('form')); await waitFor(() => { expect(screen.getByTestId('alert')).toBeInTheDocument(); - expect(screen.getByTestId('alert-title')).toHaveTextContent('Saved Successfully'); + expect(screen.getByTestId('alert-title')).toHaveTextContent( + 'Saved Successfully' + ); }); }); it('does not show success alert when saveChangedSettings throws', async () => { - vi.mocked(saveChangedSettings).mockRejectedValue(new Error('save failed')); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + vi.mocked(saveChangedSettings).mockRejectedValue( + new Error('save failed') + ); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); render(); fireEvent.submit(screen.getByText('Save').closest('form')); @@ -449,12 +461,17 @@ describe('StreamSettingsForm', () => { it('logs error when saveChangedSettings throws', async () => { const error = new Error('save failed'); vi.mocked(saveChangedSettings).mockRejectedValue(error); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); render(); fireEvent.submit(screen.getByText('Save').closest('form')); await waitFor(() => { - expect(consoleSpy).toHaveBeenCalledWith('Error saving settings:', error); + expect(consoleSpy).toHaveBeenCalledWith( + 'Error saving settings:', + error + ); }); consoleSpy.mockRestore(); }); @@ -466,7 +483,9 @@ describe('StreamSettingsForm', () => { await waitFor(() => { expect(saveChangedSettings).toHaveBeenCalled(); }); - expect(screen.queryByTestId('confirmation-dialog')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('confirmation-dialog') + ).not.toBeInTheDocument(); }); }); @@ -501,7 +520,9 @@ describe('StreamSettingsForm', () => { fireEvent.submit(screen.getByText('Save').closest('form')); await waitFor(() => { - expect(screen.getByTestId('confirm-ok')).toHaveTextContent('Save and Rehash'); + expect(screen.getByTestId('confirm-ok')).toHaveTextContent( + 'Save and Rehash' + ); }); }); @@ -526,9 +547,7 @@ describe('StreamSettingsForm', () => { render(); fireEvent.submit(screen.getByText('Save').closest('form')); - await waitFor(() => - screen.getByTestId('confirmation-dialog') - ); + await waitFor(() => screen.getByTestId('confirmation-dialog')); fireEvent.click(screen.getByTestId('confirm-ok')); await waitFor(() => { @@ -546,7 +565,9 @@ describe('StreamSettingsForm', () => { fireEvent.click(screen.getByTestId('confirm-ok')); await waitFor(() => { - expect(screen.getByTestId('alert-title')).toHaveTextContent('Saved Successfully'); + expect(screen.getByTestId('alert-title')).toHaveTextContent( + 'Saved Successfully' + ); }); }); @@ -560,7 +581,9 @@ describe('StreamSettingsForm', () => { fireEvent.click(screen.getByTestId('confirm-ok')); await waitFor(() => { - expect(screen.queryByTestId('confirmation-dialog')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('confirmation-dialog') + ).not.toBeInTheDocument(); }); }); @@ -574,7 +597,9 @@ describe('StreamSettingsForm', () => { fireEvent.click(screen.getByTestId('confirm-cancel')); await waitFor(() => { - expect(screen.queryByTestId('confirmation-dialog')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('confirmation-dialog') + ).not.toBeInTheDocument(); }); expect(saveChangedSettings).not.toHaveBeenCalled(); }); @@ -590,7 +615,9 @@ describe('StreamSettingsForm', () => { await waitFor(() => { expect(saveChangedSettings).toHaveBeenCalled(); }); - expect(screen.queryByTestId('confirmation-dialog')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('confirmation-dialog') + ).not.toBeInTheDocument(); }); }); @@ -613,7 +640,9 @@ describe('StreamSettingsForm', () => { fireEvent.click(screen.getByText('Rehash Streams')); await waitFor(() => { - expect(screen.getByTestId('confirm-ok')).toHaveTextContent('Start Rehash'); + expect(screen.getByTestId('confirm-ok')).toHaveTextContent( + 'Start Rehash' + ); }); }); @@ -636,7 +665,9 @@ describe('StreamSettingsForm', () => { await waitFor(() => { expect(rehashStreams).toHaveBeenCalled(); }); - expect(screen.queryByTestId('confirmation-dialog')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('confirmation-dialog') + ).not.toBeInTheDocument(); }); it('shows rehash success alert after rehash completes', async () => { @@ -654,7 +685,9 @@ describe('StreamSettingsForm', () => { it('does not show rehash success alert when rehashStreams throws', async () => { setupMocks({ warningSuppressed: true }); vi.mocked(rehashStreams).mockRejectedValue(new Error('fail')); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); render(); fireEvent.click(screen.getByText('Rehash Streams')); @@ -669,12 +702,17 @@ describe('StreamSettingsForm', () => { setupMocks({ warningSuppressed: true }); const error = new Error('network error'); vi.mocked(rehashStreams).mockRejectedValue(error); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); render(); fireEvent.click(screen.getByText('Rehash Streams')); await waitFor(() => { - expect(consoleSpy).toHaveBeenCalledWith('Error rehashing streams:', error); + expect(consoleSpy).toHaveBeenCalledWith( + 'Error rehashing streams:', + error + ); }); consoleSpy.mockRestore(); }); @@ -686,7 +724,9 @@ describe('StreamSettingsForm', () => { fireEvent.click(screen.getByTestId('confirm-cancel')); await waitFor(() => { - expect(screen.queryByTestId('confirmation-dialog')).not.toBeInTheDocument(); + expect( + screen.queryByTestId('confirmation-dialog') + ).not.toBeInTheDocument(); }); expect(rehashStreams).not.toHaveBeenCalled(); }); @@ -710,7 +750,9 @@ describe('StreamSettingsForm', () => { it('Rehash Streams button is disabled while rehashing', async () => { let resolveRehash; vi.mocked(rehashStreams).mockReturnValue( - new Promise((res) => { resolveRehash = res; }) + new Promise((res) => { + resolveRehash = res; + }) ); setupMocks({ warningSuppressed: true }); render(); @@ -736,7 +778,9 @@ describe('StreamSettingsForm', () => { it('calls getInputProps for default_stream_profile', () => { render(); - expect(formMock.getInputProps).toHaveBeenCalledWith('default_stream_profile'); + expect(formMock.getInputProps).toHaveBeenCalledWith( + 'default_stream_profile' + ); }); it('calls getInputProps for preferred_region', () => { @@ -757,4 +801,4 @@ describe('StreamSettingsForm', () => { expect(formMock.getInputProps).toHaveBeenCalledWith('m3u_hash_key'); }); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/components/forms/settings/__tests__/SystemSettingsForm.test.jsx b/frontend/src/components/forms/settings/__tests__/SystemSettingsForm.test.jsx index b9baa711..a3b156d5 100644 --- a/frontend/src/components/forms/settings/__tests__/SystemSettingsForm.test.jsx +++ b/frontend/src/components/forms/settings/__tests__/SystemSettingsForm.test.jsx @@ -53,7 +53,11 @@ vi.mock('@mantine/core', () => ({ // Imports after mocks // ────────────────────────────────────────────────────────────────────────────── import useSettingsStore from '../../../../store/settings.jsx'; -import { getChangedSettings, parseSettings, saveChangedSettings } from '../../../../utils/pages/SettingsUtils.js'; +import { + getChangedSettings, + parseSettings, + saveChangedSettings, +} from '../../../../utils/pages/SettingsUtils.js'; import { getSystemSettingsFormInitialValues } from '../../../../utils/forms/settings/SystemSettingsFormUtils.js'; import { useForm } from '@mantine/form'; @@ -83,7 +87,9 @@ const setupMocks = ({ settings = makeSettings() } = {}) => { vi.mocked(getSystemSettingsFormInitialValues).mockReturnValue(formValues); vi.mocked(useSettingsStore).mockImplementation((sel) => sel({ settings })); vi.mocked(parseSettings).mockReturnValue(formValues); - vi.mocked(getChangedSettings).mockReturnValue({ max_system_events: settings?.max_system_events ?? 100 }); + vi.mocked(getChangedSettings).mockReturnValue({ + max_system_events: settings?.max_system_events ?? 100, + }); vi.mocked(saveChangedSettings).mockResolvedValue(undefined); return { formMock }; @@ -122,7 +128,9 @@ describe('SystemSettingsForm', () => { setupMocks(); render(); expect( - screen.getByText('Number of events to retain (minimum: 10, maximum: 1000)') + screen.getByText( + 'Number of events to retain (minimum: 10, maximum: 1000)' + ) ).toBeInTheDocument(); }); @@ -184,7 +192,9 @@ describe('SystemSettingsForm', () => { const settings = makeSettings(); const { formMock } = setupMocks({ settings }); render(); - expect(formMock.setValues).toHaveBeenCalledWith({ max_system_events: 100 }); + expect(formMock.setValues).toHaveBeenCalledWith({ + max_system_events: 100, + }); }); it('does not call parseSettings when settings is null', () => { @@ -197,8 +207,12 @@ describe('SystemSettingsForm', () => { submitting: false, }; vi.mocked(useForm).mockReturnValue(formMock); - vi.mocked(getSystemSettingsFormInitialValues).mockReturnValue({ max_system_events: 100 }); - vi.mocked(useSettingsStore).mockImplementation((sel) => sel({ settings: null })); + vi.mocked(getSystemSettingsFormInitialValues).mockReturnValue({ + max_system_events: 100, + }); + vi.mocked(useSettingsStore).mockImplementation((sel) => + sel({ settings: null }) + ); vi.mocked(parseSettings).mockReturnValue({}); vi.mocked(saveChangedSettings).mockResolvedValue(undefined); @@ -213,8 +227,13 @@ describe('SystemSettingsForm', () => { it('calls form.setFieldValue when NumberInput changes', () => { const { formMock } = setupMocks(); render(); - fireEvent.change(screen.getByTestId('number-input'), { target: { value: '200' } }); - expect(formMock.setFieldValue).toHaveBeenCalledWith('max_system_events', 200); + fireEvent.change(screen.getByTestId('number-input'), { + target: { value: '200' }, + }); + expect(formMock.setFieldValue).toHaveBeenCalledWith( + 'max_system_events', + 200 + ); }); }); @@ -229,7 +248,10 @@ describe('SystemSettingsForm', () => { fireEvent.click(screen.getByText('Save')); await waitFor(() => { - expect(getChangedSettings).toHaveBeenCalledWith(formMock.getValues(), settings); + expect(getChangedSettings).toHaveBeenCalledWith( + formMock.getValues(), + settings + ); expect(saveChangedSettings).toHaveBeenCalled(); }); }); @@ -247,9 +269,13 @@ describe('SystemSettingsForm', () => { }); it('does not show success alert when saveChangedSettings throws', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); setupMocks(); - vi.mocked(saveChangedSettings).mockRejectedValue(new Error('save failed')); + vi.mocked(saveChangedSettings).mockRejectedValue( + new Error('save failed') + ); render(); fireEvent.click(screen.getByText('Save')); @@ -263,7 +289,9 @@ describe('SystemSettingsForm', () => { it('logs error when saveChangedSettings throws', async () => { const error = new Error('save failed'); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); setupMocks(); vi.mocked(saveChangedSettings).mockRejectedValue(error); @@ -271,7 +299,10 @@ describe('SystemSettingsForm', () => { fireEvent.click(screen.getByText('Save')); await waitFor(() => { - expect(consoleSpy).toHaveBeenCalledWith('Error saving settings:', error); + expect(consoleSpy).toHaveBeenCalledWith( + 'Error saving settings:', + error + ); }); consoleSpy.mockRestore(); }); @@ -308,4 +339,4 @@ describe('SystemSettingsForm', () => { expect(screen.getByTestId('alert')).toBeInTheDocument(); }); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/components/forms/settings/__tests__/UiSettingsForm.test.jsx b/frontend/src/components/forms/settings/__tests__/UiSettingsForm.test.jsx index b8c0b5ea..101f32a4 100644 --- a/frontend/src/components/forms/settings/__tests__/UiSettingsForm.test.jsx +++ b/frontend/src/components/forms/settings/__tests__/UiSettingsForm.test.jsx @@ -7,7 +7,9 @@ vi.mock('../../../../store/settings.jsx', () => ({ default: vi.fn() })); // ── Hook mocks ───────────────────────────────────────────────────────────────── vi.mock('../../../../hooks/useLocalStorage.jsx', () => ({ default: vi.fn() })); -vi.mock('../../../../hooks/useTablePreferences.jsx', () => ({ default: vi.fn() })); +vi.mock('../../../../hooks/useTablePreferences.jsx', () => ({ + default: vi.fn(), +})); // ── Utility mocks ────────────────────────────────────────────────────────────── vi.mock('../../../../utils/dateTimeUtils.js', () => ({ @@ -62,7 +64,10 @@ vi.mock('@mantine/core', () => ({ import useSettingsStore from '../../../../store/settings.jsx'; import useLocalStorage from '../../../../hooks/useLocalStorage.jsx'; import useTablePreferences from '../../../../hooks/useTablePreferences.jsx'; -import { buildTimeZoneOptions, getDefaultTimeZone } from '../../../../utils/dateTimeUtils.js'; +import { + buildTimeZoneOptions, + getDefaultTimeZone, +} from '../../../../utils/dateTimeUtils.js'; import { showNotification } from '../../../../utils/notificationUtils.js'; import { saveTimeZoneSetting } from '../../../../utils/forms/settings/UiSettingsFormUtils.js'; @@ -83,13 +88,13 @@ const TZ_OPTIONS = [ ]; const setupMocks = ({ - settings = makeSettings(), - timeFormat = '12h', - dateFormat = 'mdy', - timeZone = DEFAULT_TZ, - headerPinned = false, - tableSize = 'default', - } = {}) => { + settings = makeSettings(), + timeFormat = '12h', + dateFormat = 'mdy', + timeZone = DEFAULT_TZ, + headerPinned = false, + tableSize = 'default', +} = {}) => { const setTimeFormat = vi.fn(); const setDateFormat = vi.fn(); const setTimeZone = vi.fn(); @@ -102,13 +107,12 @@ const setupMocks = ({ vi.mocked(useSettingsStore).mockImplementation((sel) => sel({ settings })); - vi.mocked(useLocalStorage) - .mockImplementation((key, defaultVal) => { - if (key === 'time-format') return [timeFormat, setTimeFormat]; - if (key === 'date-format') return [dateFormat, setDateFormat]; - if (key === 'time-zone') return [timeZone, setTimeZone]; - return [defaultVal, vi.fn()]; - }); + vi.mocked(useLocalStorage).mockImplementation((key, defaultVal) => { + if (key === 'time-format') return [timeFormat, setTimeFormat]; + if (key === 'date-format') return [dateFormat, setDateFormat]; + if (key === 'time-zone') return [timeZone, setTimeZone]; + return [defaultVal, vi.fn()]; + }); vi.mocked(useTablePreferences).mockReturnValue({ headerPinned, @@ -117,7 +121,13 @@ const setupMocks = ({ setTableSize, }); - return { setTimeFormat, setDateFormat, setTimeZone, setHeaderPinned, setTableSize }; + return { + setTimeFormat, + setDateFormat, + setTimeZone, + setHeaderPinned, + setTableSize, + }; }; // ────────────────────────────────────────────────────────────────────────────── @@ -164,7 +174,9 @@ describe('UiSettingsForm', () => { it('renders the switch description', () => { setupMocks(); render(); - expect(screen.getByText('Keep table headers visible when scrolling')).toBeInTheDocument(); + expect( + screen.getByText('Keep table headers visible when scrolling') + ).toBeInTheDocument(); }); it('renders Table Size select with initial value', () => { @@ -331,7 +343,10 @@ describe('UiSettingsForm', () => { }); render(); await waitFor(() => { - expect(saveTimeZoneSetting).toHaveBeenCalledWith(DEFAULT_TZ, makeSettings({ timeZone: null })); + expect(saveTimeZoneSetting).toHaveBeenCalledWith( + DEFAULT_TZ, + makeSettings({ timeZone: null }) + ); }); }); @@ -359,9 +374,13 @@ describe('UiSettingsForm', () => { describe('error handling', () => { it('shows error notification when saveTimeZoneSetting throws on tz change', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); setupMocks({ settings: makeSettings({ timeZone: null }), timeZone: '' }); - vi.mocked(saveTimeZoneSetting).mockRejectedValue(new Error('network error')); + vi.mocked(saveTimeZoneSetting).mockRejectedValue( + new Error('network error') + ); render(); fireEvent.change(screen.getByTestId('select-time-zone'), { @@ -370,7 +389,10 @@ describe('UiSettingsForm', () => { await waitFor(() => { expect(showNotification).toHaveBeenCalledWith( - expect.objectContaining({ color: 'red', title: 'Failed to update time zone' }) + expect.objectContaining({ + color: 'red', + title: 'Failed to update time zone', + }) ); }); consoleSpy.mockRestore(); @@ -378,7 +400,9 @@ describe('UiSettingsForm', () => { it('logs error when saveTimeZoneSetting throws on tz change', async () => { const error = new Error('network error'); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); setupMocks({ settings: makeSettings({ timeZone: null }), timeZone: '' }); vi.mocked(saveTimeZoneSetting).mockRejectedValue(error); @@ -397,15 +421,25 @@ describe('UiSettingsForm', () => { }); it('shows error notification when saveTimeZoneSetting throws during initial sync', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - setupMocks({ settings: makeSettings({ timeZone: null }), timeZone: DEFAULT_TZ }); - vi.mocked(saveTimeZoneSetting).mockRejectedValue(new Error('sync failed')); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + setupMocks({ + settings: makeSettings({ timeZone: null }), + timeZone: DEFAULT_TZ, + }); + vi.mocked(saveTimeZoneSetting).mockRejectedValue( + new Error('sync failed') + ); render(); await waitFor(() => { expect(showNotification).toHaveBeenCalledWith( - expect.objectContaining({ color: 'red', title: 'Failed to update time zone' }) + expect.objectContaining({ + color: 'red', + title: 'Failed to update time zone', + }) ); }); consoleSpy.mockRestore(); @@ -421,4 +455,4 @@ describe('UiSettingsForm', () => { expect(buildTimeZoneOptions).toHaveBeenCalledWith('America/Chicago'); }); }); -}); \ No newline at end of file +});