From 29cb481f787261ea0daccffdce1810ae6affb04b Mon Sep 17 00:00:00 2001
From: fezster <97789007+fezster@users.noreply.github.com>
Date: Tue, 24 Mar 2026 15:29:35 +0000
Subject: [PATCH 1/2] feat: When recording from EPG, use the explicit channel
number selected rather than relying on the findChannelByTvgId() function to
select the channel - of which there can be many and it would default to the
first channel in the list, which may not be the same stream you want to
record.
---
frontend/src/pages/Guide.jsx | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/frontend/src/pages/Guide.jsx b/frontend/src/pages/Guide.jsx
index 81171637..09f6239f 100644
--- a/frontend/src/pages/Guide.jsx
+++ b/frontend/src/pages/Guide.jsx
@@ -114,6 +114,7 @@ export default function TVChannelGuide({ startDate, endDate }) {
const [recordingForProgram, setRecordingForProgram] = useState(null);
const [recordChoiceOpen, setRecordChoiceOpen] = useState(false);
const [recordChoiceProgram, setRecordChoiceProgram] = useState(null);
+ const [recordChoiceChannel, setRecordChoiceChannel] = useState(null);
const [existingRuleMode, setExistingRuleMode] = useState(null);
const [rulesOpen, setRulesOpen] = useState(false);
const [rules, setRules] = useState([]);
@@ -708,8 +709,9 @@ export default function TVChannelGuide({ startDate, endDate }) {
);
const openRecordChoice = useCallback(
- async (program) => {
+ async (program, channel) => {
setRecordChoiceProgram(program);
+ setRecordChoiceChannel(channel);
setRecordChoiceOpen(true);
try {
const rules = await fetchRules();
@@ -725,8 +727,7 @@ export default function TVChannelGuide({ startDate, endDate }) {
);
const recordOne = useCallback(
- async (program) => {
- const channel = findChannelByTvgId(program.tvg_id);
+ async (program, channel) => {
if (!channel) {
showNotification({
title: 'Unable to schedule recording',
@@ -739,7 +740,7 @@ export default function TVChannelGuide({ startDate, endDate }) {
await createRecording(channel, program);
showNotification({ title: 'Recording scheduled' });
},
- [findChannelByTvgId]
+ []
);
const saveSeriesRule = useCallback(async (program, mode) => {
@@ -1436,7 +1437,7 @@ export default function TVChannelGuide({ startDate, endDate }) {
program={recordChoiceProgram}
recording={recordingForProgram}
existingRuleMode={existingRuleMode}
- onRecordOne={() => recordOne(recordChoiceProgram)}
+ onRecordOne={() => recordOne(recordChoiceProgram, recordChoiceChannel)}
onRecordSeriesAll={() =>
saveSeriesRule(recordChoiceProgram, 'all')
}
@@ -1473,7 +1474,7 @@ export default function TVChannelGuide({ startDate, endDate }) {
recording={recordingForProgram}
opened={!!selectedProgram}
onClose={handleCloseModal}
- onRecord={openRecordChoice}
+ onRecord={(program) => openRecordChoice(program, selectedChannel)}
/>
From fcbf79494722d4e1edc86ceaee8c09da544a3409 Mon Sep 17 00:00:00 2001
From: fezster <97789007+fezster@users.noreply.github.com>
Date: Wed, 25 Mar 2026 09:36:59 +0000
Subject: [PATCH 2/2] feat: Update Guide component tests to use selected
channel for recording and enhance program selection functionality
---
frontend/src/pages/__tests__/Guide.test.jsx | 120 ++++++++++++++++----
1 file changed, 97 insertions(+), 23 deletions(-)
diff --git a/frontend/src/pages/__tests__/Guide.test.jsx b/frontend/src/pages/__tests__/Guide.test.jsx
index 07ef1eeb..2145a2a0 100644
--- a/frontend/src/pages/__tests__/Guide.test.jsx
+++ b/frontend/src/pages/__tests__/Guide.test.jsx
@@ -153,7 +153,7 @@ vi.mock('react-window', () => ({
{children({
index: i,
style: {},
- data: itemData.filteredChannels[i],
+ data: itemData,
})}
))}
@@ -162,10 +162,42 @@ vi.mock('react-window', () => ({
}));
vi.mock('../../components/GuideRow', () => ({
- default: ({ data }) => (
-
GuideRow for {data?.name}
- ),
+ default: ({ index, data }) => {
+ const channel = data?.filteredChannels?.[index];
+ const channelPrograms = data?.programsByChannelId?.get(channel?.id) || [];
+ const program = channelPrograms[0] || {
+ id: 'prog-1',
+ tvg_id: 'tvg-1',
+ title: 'Test Program 1',
+ channel_id: channel?.id,
+ start_time: '2024-01-15T12:00:00Z',
+ end_time: '2024-01-15T13:00:00Z',
+ programStart: dayjs('2024-01-15T12:00:00Z'),
+ programEnd: dayjs('2024-01-15T13:00:00Z'),
+ startMs: dayjs('2024-01-15T12:00:00Z').valueOf(),
+ endMs: dayjs('2024-01-15T13:00:00Z').valueOf(),
+ };
+
+ return (
+
+ GuideRow for {channel?.name}
+
+
+ );
+ },
}));
+
vi.mock('../../components/HourTimeline', () => ({
default: ({ hourTimeline }) => (
@@ -554,43 +586,85 @@ describe('Guide', () => {
});
describe('Recording Functionality', () => {
- it('opens Series Rules modal when button is clicked', async () => {
+ it('opens Program Recording modal when Record One is clicked', async () => {
vi.useRealTimers();
const user = userEvent.setup();
render();
- const rulesButton = await screen.findByText('Series Rules');
- await user.click(rulesButton);
+ const selectButtons = await screen.findAllByTestId('guide-row-select');
+ await user.click(selectButtons[0]);
+
+ await waitFor(() =>
+ expect(screen.getByTestId('program-detail-modal')).toBeInTheDocument()
+ );
+
+ fireEvent.click(screen.getByText('Record'));
+
+ await waitFor(() =>
+ expect(screen.getByTestId('program-recording-modal')).toBeInTheDocument()
+ );
+
+ fireEvent.click(screen.getByText('Record One'));
await waitFor(() => {
- expect(
- screen.getByTestId('series-recording-modal')
- ).toBeInTheDocument();
+ expect(guideUtils.createRecording).toHaveBeenCalled();
});
-
- vi.useFakeTimers();
- vi.setSystemTime(new Date('2024-01-15T12:00:00Z'));
});
- it('fetches rules when opening Series Rules modal', async () => {
+ it('uses selected channel for recordOne instead of tvg_id fallback', async () => {
vi.useRealTimers();
- const mockRules = [{ id: 1, title: 'Test Rule' }];
- guideUtils.fetchRules.mockResolvedValue(mockRules);
+ API.getAllChannelIds.mockResolvedValue(['channel-1']);
+ API.getChannelsSummary.mockResolvedValue([mockChannelsState.channels['channel-1']]);
+ guideUtils.filterGuideChannels.mockImplementation((channels) =>
+ Array.isArray(channels) ? channels : Object.values(channels)
+ );
+
+ const program = {
+ id: 'prog-1',
+ tvg_id: 'tvg-1',
+ title: 'Test Program 1',
+ channel_id: 'channel-1',
+ start_time: now.toISOString(),
+ end_time: now.add(1, 'hour').toISOString(),
+ programStart: now,
+ programEnd: now.add(1, 'hour'),
+ startMs: now.valueOf(),
+ endMs: now.add(1, 'hour').valueOf(),
+ };
+
+ guideUtils.fetchPrograms.mockResolvedValue([program]);
- const user = userEvent.setup();
render();
- const rulesButton = await screen.findByText('Series Rules');
- await user.click(rulesButton);
+ await waitFor(() =>
+ expect(screen.getAllByTestId('guide-row').length).toBeGreaterThan(0)
+ );
+
+ // Use userEvent instead of fireEvent so microtasks (Suspense lazy resolution) are flushed
+ const user = userEvent.setup({ delay: null });
+
+ await user.click(screen.getByTestId('guide-row-select'));
+
+ await waitFor(() =>
+ expect(screen.getByTestId('program-detail-modal')).toBeInTheDocument()
+ );
+
+ await user.click(screen.getByText('Record'));
+
+ await waitFor(() =>
+ expect(screen.getByTestId('program-recording-modal')).toBeInTheDocument()
+ );
+
+ await user.click(screen.getByText('Record One'));
await waitFor(() => {
- expect(guideUtils.fetchRules).toHaveBeenCalled();
+ expect(guideUtils.createRecording).toHaveBeenCalledWith(
+ expect.objectContaining({ id: 'channel-1' }),
+ expect.objectContaining({ id: 'prog-1' })
+ );
});
-
- vi.useFakeTimers();
- vi.setSystemTime(new Date('2024-01-15T12:00:00Z'));
});
});