fix(web): review fixes — EPG switch gating, edit route key, stronger PUT assertion (#146)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m41s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 12:32:29 +02:00
co-authored by Claude Fable 5
parent c21827667a
commit 1f3385a2ff
3 changed files with 44 additions and 7 deletions
+1 -1
View File
@@ -2859,7 +2859,7 @@ function ScreenContent({
}
if (route.id === 'editChannel') {
return <ChannelEditScreen />;
return <ChannelEditScreen key={window.location.pathname} />;
}
if (route.id === 'guide') {
+32 -4
View File
@@ -34,6 +34,14 @@ const channel = {
showInEpg: true
};
// The PUT body is the GET response minus the read-only id / playoutCount fields.
function expectedPutBody(overrides: Record<string, unknown> = {}): Record<string, unknown> {
const { id, playoutCount, ...rest } = channel;
void id;
void playoutCount;
return { ...rest, ...overrides };
}
function json(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
headers: { 'Content-Type': 'application/json' },
@@ -115,14 +123,34 @@ describe('ChannelEditScreen', () => {
fireEvent.click(saveButton);
await waitFor(() => expect(puts).toHaveLength(1));
expect(puts[0]).toMatchObject({ name: 'Renamed', number: '5', playoutSource: 'Generated' });
// Read-only fields are not part of the PUT contract.
expect(puts[0]).not.toHaveProperty('id');
expect(puts[0]).not.toHaveProperty('playoutCount');
expect(puts[0]).toEqual(expectedPutBody({ name: 'Renamed' }));
expect(await screen.findByText('Channel saved')).toBeInTheDocument();
});
it('forces showInEpg off in the PUT body when Enabled is turned off', async () => {
const puts: unknown[] = [];
mockApi({ onPut: (body) => puts.push(body) });
render(<ChannelEditScreen />);
await screen.findByDisplayValue('Cartoons');
// The General pane renders exactly two switches, in document order: Enabled, then Show in EPG.
const [enabledSwitch, showInEpgSwitch] = screen.getAllByRole('switch');
fireEvent.click(enabledSwitch);
expect(showInEpgSwitch).toHaveAttribute('aria-checked', 'false');
expect(showInEpgSwitch).toHaveAttribute('aria-disabled', 'true');
const saveButton = await screen.findByRole('button', { name: 'Save changes' });
fireEvent.click(saveButton);
await waitFor(() => expect(puts).toHaveLength(1));
expect(puts[0]).toEqual(expectedPutBody({ isEnabled: false, showInEpg: false }));
});
it('shows an error state when the channel is not found', async () => {
mockApi({ channelStatus: 404 });
render(<ChannelEditScreen />);
+11 -2
View File
@@ -252,10 +252,19 @@ function GeneralPane({
/>
</Row>
<Row help="Whether this channel is served to clients." label="Enabled">
<Switch checked={draft.isEnabled} onChange={(next) => set({ isEnabled: next })} size="sm" />
<Switch
checked={draft.isEnabled}
onChange={(next) => set(next ? { isEnabled: next } : { isEnabled: next, showInEpg: false })}
size="sm"
/>
</Row>
<Row help="Include this channel in the XMLTV guide." label="Show in EPG">
<Switch checked={draft.showInEpg} onChange={(next) => set({ showInEpg: next })} size="sm" />
<Switch
checked={draft.showInEpg}
disabled={!draft.isEnabled}
onChange={(next) => set({ showInEpg: next })}
size="sm"
/>
</Row>
</Card>
</Pane>