+
{children}
),
@@ -281,7 +345,9 @@ vi.mock('@mantine/core', () => ({
>
{(data || []).map((d) => (
-
+
))}
),
@@ -297,7 +363,9 @@ vi.mock('@mantine/core', () => ({
/>
),
Text: ({ children, style }) => (
-
{children}
+
+ {children}
+
),
TextInput: ({ value, placeholder, onChange, readOnly }) => (
({
),
Tooltip: ({ children, label }) =>
{children}
,
UnstyledButton: ({ children, onClick }) => (
-
+
),
useMantineTheme: vi.fn(() => ({
- tailwind: { yellow: { 3: '#fde047' }, red: { 6: '#dc2626' }, green: { 5: '#22c55e' } },
+ tailwind: {
+ yellow: { 3: '#fde047' },
+ red: { 6: '#dc2626' },
+ green: { 5: '#22c55e' },
+ },
palette: {
- custom: { greenMain: '#22c55e', indigoMain: '#6366f1', greyBorder: '#71717a' },
+ custom: {
+ greenMain: '#22c55e',
+ indigoMain: '#6366f1',
+ greyBorder: '#71717a',
+ },
},
})),
}));
@@ -353,7 +431,12 @@ import useOutputProfilesStore from '../../../store/outputProfiles';
import useWarningsStore from '../../../store/warnings';
import useLocalStorage from '../../../hooks/useLocalStorage';
import { useTable } from '../CustomTable';
-import { deleteChannel, deleteChannels, queryChannels, getAllChannelIds } from '../../../utils/tables/ChannelsTableUtils.js';
+import {
+ deleteChannel,
+ deleteChannels,
+ queryChannels,
+ getAllChannelIds,
+} from '../../../utils/tables/ChannelsTableUtils.js';
import { requeryChannels } from '../../../utils/forms/ChannelUtils.js';
import { copyToClipboard } from '../../../utils';
import { buildLiveStreamUrl } from '../../../utils/components/FloatingVideoUtils.js';
@@ -433,9 +516,7 @@ const setupMocks = ({
})
);
- vi.mocked(useAuthStore).mockImplementation((sel) =>
- sel({ user: authUser })
- );
+ vi.mocked(useAuthStore).mockImplementation((sel) => sel({ user: authUser }));
vi.mocked(useEPGsStore).mockImplementation((sel) =>
sel({ epgs, tvgsById: {}, tvgsLoaded: true })
@@ -471,8 +552,7 @@ const setupMocks = ({
const getActionsCol = () =>
capturedTableOptions.columns.find((c) => c.id === 'actions');
-const getCol = (id) =>
- capturedTableOptions.columns.find((c) => c.id === id);
+const getCol = (id) => capturedTableOptions.columns.find((c) => c.id === id);
// ══════════════════════════════════════════════════════════════════════════════
// Tests
@@ -516,10 +596,17 @@ describe('ChannelsTable', () => {
tableOverrides: { getRowModel: () => ({ rows: [] }) },
});
vi.mocked(useChannelsStore).mockImplementation((sel) =>
- sel({ channelIds: [], profiles: {}, selectedProfileId: '0', channelGroups: {} })
+ sel({
+ channelIds: [],
+ profiles: {},
+ selectedProfileId: '0',
+ channelGroups: {},
+ })
);
render(
);
- await waitFor(() => expect(screen.getByTestId('onboarding')).toBeInTheDocument());
+ await waitFor(() =>
+ expect(screen.getByTestId('onboarding')).toBeInTheDocument()
+ );
});
it('renders pagination controls when channels exist', () => {
@@ -633,7 +720,12 @@ describe('ChannelsTable', () => {
tableOverrides: { getRowModel: () => ({ rows: [] }) },
});
vi.mocked(useChannelsStore).mockImplementation((sel) =>
- sel({ channelIds: [], profiles: {}, selectedProfileId: '0', channelGroups: {} })
+ sel({
+ channelIds: [],
+ profiles: {},
+ selectedProfileId: '0',
+ channelGroups: {},
+ })
);
render(
);
const addBtn = await screen.findByTestId('onboarding-add');
@@ -665,7 +757,9 @@ describe('ChannelsTable', () => {
const { tableInstance } = setupMocks({ authUser: makeAdminUser() });
render(
);
const { getByTestId } = renderActionCell(channel, tableInstance);
- expect(getByTestId('icon-square-pen').closest('button')).not.toBeDisabled();
+ expect(
+ getByTestId('icon-square-pen').closest('button')
+ ).not.toBeDisabled();
});
it('clicking edit button opens channel form populated with the channel', () => {
@@ -702,7 +796,9 @@ describe('ChannelsTable', () => {
const { tableInstance } = setupMocks({ authUser: makeAdminUser() });
render(
);
const { getByTestId } = renderActionCell(channel, tableInstance);
- expect(getByTestId('icon-square-minus').closest('button')).not.toBeDisabled();
+ expect(
+ getByTestId('icon-square-minus').closest('button')
+ ).not.toBeDisabled();
});
it('opens confirmation dialog when delete is clicked and warning is not suppressed', () => {
@@ -820,9 +916,38 @@ describe('ChannelsTable', () => {
render(
);
fireEvent.click(screen.getByTestId('header-delete-channels'));
fireEvent.click(screen.getByTestId('confirm-ok'));
- await waitFor(() =>
- expect(deleteChannels).toHaveBeenCalledWith([1, 2])
- );
+ await waitFor(() => expect(deleteChannels).toHaveBeenCalledWith([1, 2]));
+ });
+
+ it('awaits requeryChannels before clearing bulk selection', async () => {
+ const channels = [makeChannel({ id: 1 }), makeChannel({ id: 2 })];
+ const setSelectedTableIds = vi.fn();
+ let resolveRequery;
+ const requeryPromise = new Promise((resolve) => {
+ resolveRequery = resolve;
+ });
+ vi.mocked(requeryChannels).mockReturnValue(requeryPromise);
+
+ setupMocks({
+ channels,
+ tableOverrides: {
+ getRowModel: vi.fn(() => ({ rows: [] })),
+ getHeaderGroups: vi.fn(() => []),
+ setSelectedTableIds,
+ selectedTableIds: [1, 2],
+ },
+ isWarningSuppressed: vi.fn(() => false),
+ });
+ render(
);
+ fireEvent.click(screen.getByTestId('header-delete-channels'));
+ fireEvent.click(screen.getByTestId('confirm-ok'));
+
+ await waitFor(() => expect(deleteChannels).toHaveBeenCalledWith([1, 2]));
+ expect(requeryChannels).toHaveBeenCalled();
+ expect(setSelectedTableIds).not.toHaveBeenCalled();
+
+ resolveRequery();
+ await waitFor(() => expect(setSelectedTableIds).toHaveBeenCalledWith([]));
});
it('skips dialog and calls deleteChannels directly when warning is suppressed', async () => {
@@ -839,9 +964,7 @@ describe('ChannelsTable', () => {
});
render(
);
fireEvent.click(screen.getByTestId('header-delete-channels'));
- await waitFor(() =>
- expect(deleteChannels).toHaveBeenCalledWith([1, 2])
- );
+ await waitFor(() => expect(deleteChannels).toHaveBeenCalledWith([1, 2]));
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument();
});
});
@@ -856,7 +979,9 @@ describe('ChannelsTable', () => {
vi.mocked(useVideoStore).mockImplementation((sel) =>
sel({ showVideo: showVideoMock })
);
- vi.mocked(buildLiveStreamUrl).mockReturnValue('/proxy/ts/stream/uuid-abc');
+ vi.mocked(buildLiveStreamUrl).mockReturnValue(
+ '/proxy/ts/stream/uuid-abc'
+ );
render(
);
const col = getActionsCol();
const { getByTestId } = render(
@@ -884,10 +1009,14 @@ describe('ChannelsTable', () => {
);
// Record menu item is the second menu-item (after Copy URL)
const menuItems = getAllByTestId('menu-item');
- const recordItem = menuItems.find((el) => el.textContent.includes('Record'));
+ const recordItem = menuItems.find((el) =>
+ el.textContent.includes('Record')
+ );
fireEvent.click(recordItem);
expect(screen.getByTestId('recording-form')).toBeInTheDocument();
- expect(screen.getByTestId('recording-form-channel')).toHaveTextContent('CNN');
+ expect(screen.getByTestId('recording-form-channel')).toHaveTextContent(
+ 'CNN'
+ );
});
it('closes recording form when onClose is called', () => {
@@ -899,7 +1028,9 @@ describe('ChannelsTable', () => {
col.cell({ row: { original: channel }, table: tableInstance })
);
const menuItems = getAllByTestId('menu-item');
- const recordItem = menuItems.find((el) => el.textContent.includes('Record'));
+ const recordItem = menuItems.find((el) =>
+ el.textContent.includes('Record')
+ );
fireEvent.click(recordItem);
fireEvent.click(screen.getByTestId('recording-form-close'));
expect(screen.queryByTestId('recording-form')).not.toBeInTheDocument();
@@ -918,7 +1049,9 @@ describe('ChannelsTable', () => {
col.cell({ row: { original: channel }, table: tableInstance })
);
const menuItems = getAllByTestId('unstyled-button');
- const copyBtn = menuItems.find((el) => el.textContent.includes('Copy URL'));
+ const copyBtn = menuItems.find((el) =>
+ el.textContent.includes('Copy URL')
+ );
fireEvent.click(copyBtn);
expect(copyToClipboard).toHaveBeenCalled();
});
@@ -949,7 +1082,9 @@ describe('ChannelsTable', () => {
const tableInstance = makeDefaultTableInstance({
getState: () => ({ selectedTableIds: [] }),
});
- return render(col.cell({ row: { original: channel }, table: tableInstance }));
+ return render(
+ col.cell({ row: { original: channel }, table: tableInstance })
+ );
};
it('renders a Switch for the enabled column', () => {
@@ -1002,7 +1137,10 @@ describe('ChannelsTable', () => {
setupMocks();
render(
);
const col = getCol('name');
- const result = col.accessorFn({ effective_name: 'Override Name', name: 'Original' });
+ const result = col.accessorFn({
+ effective_name: 'Override Name',
+ name: 'Original',
+ });
expect(result).toBe('Override Name');
});
@@ -1057,7 +1195,10 @@ describe('ChannelsTable', () => {
describe('expandedRowRenderer', () => {
it('renders ChannelTableStreams for the expanded row', () => {
- const channel = makeChannel({ id: 1, streams: [{ id: 10, is_stale: false }] });
+ const channel = makeChannel({
+ id: 1,
+ streams: [{ id: 10, is_stale: false }],
+ });
setupMocks({ channels: [channel] });
render(
);
const { expandedRowRenderer } = capturedTableOptions;
diff --git a/frontend/src/utils/forms/ChannelUtils.js b/frontend/src/utils/forms/ChannelUtils.js
index 40cf9a27..7eabab0a 100644
--- a/frontend/src/utils/forms/ChannelUtils.js
+++ b/frontend/src/utils/forms/ChannelUtils.js
@@ -40,9 +40,7 @@ export const updateChannel = (values) => {
export const addChannel = (channel) => {
return API.addChannel(channel);
};
-export const requeryChannels = () => {
- API.requeryChannels();
-};
+export const requeryChannels = () => API.requeryChannels();
// PATCH semantic: `override: null` deletes the override row; per-field
// nulls only clear the matching field.
diff --git a/frontend/src/utils/forms/__tests__/ChannelUtils.test.js b/frontend/src/utils/forms/__tests__/ChannelUtils.test.js
index 88002c09..53cd0d99 100644
--- a/frontend/src/utils/forms/__tests__/ChannelUtils.test.js
+++ b/frontend/src/utils/forms/__tests__/ChannelUtils.test.js
@@ -114,13 +114,20 @@ describe('ChannelUtils', () => {
describe('requeryChannels', () => {
it('calls API.requeryChannels', () => {
+ vi.mocked(API.requeryChannels).mockResolvedValue(undefined);
requeryChannels();
expect(API.requeryChannels).toHaveBeenCalledTimes(1);
});
- it('returns undefined', () => {
- const result = requeryChannels();
- expect(result).toBeUndefined();
+ it('returns the promise from API.requeryChannels', async () => {
+ const response = { results: [{ id: 1 }] };
+ vi.mocked(API.requeryChannels).mockResolvedValue(response);
+ await expect(requeryChannels()).resolves.toBe(response);
+ });
+
+ it('propagates rejection from API.requeryChannels', async () => {
+ vi.mocked(API.requeryChannels).mockRejectedValue(new Error('network'));
+ await expect(requeryChannels()).rejects.toThrow('network');
});
});