test(web): cover extracted shell contracts

Refs #247

Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
2026-07-15 22:51:23 +02:00
co-authored by OpenAI Codex
parent 30565145cd
commit 44f4db5a6d
2 changed files with 65 additions and 4 deletions
+46
View File
@@ -0,0 +1,46 @@
import { afterEach, describe, expect, it } from 'vitest';
import {
routeById,
routeFromLocation
} from './routes';
describe('app route matching', () => {
afterEach(() => {
window.history.replaceState(null, '', '/app');
});
it('returns one stable route object across sibling allowSubPaths locations', () => {
const playoutsRoute = routeById.get('playouts');
window.history.replaceState(null, '', '/app/playouts/20/alternate-schedules');
const alternateSchedules = routeFromLocation();
window.history.replaceState(null, '', '/app/playouts/20/templates');
const templates = routeFromLocation();
expect(alternateSchedules).toBe(playoutsRoute);
expect(templates).toBe(playoutsRoute);
expect(templates).toBe(alternateSchedules);
});
it('ignores the query string while preserving the route object', () => {
const searchRoute = routeById.get('search');
window.history.replaceState(null, '', '/app/search?query=alpha');
const firstQuery = routeFromLocation();
window.history.replaceState(null, '', '/app/search?query=beta');
const secondQuery = routeFromLocation();
expect(firstQuery).toBe(searchRoute);
expect(secondQuery).toBe(searchRoute);
});
it('normalizes a trailing slash and rejects an unowned path', () => {
window.history.replaceState(null, '', '/app/channels/');
expect(routeFromLocation()).toBe(routeById.get('channels'));
window.history.replaceState(null, '', '/app/channels/42');
expect(routeFromLocation()).toBeNull();
});
});
+19 -4
View File
@@ -1,5 +1,6 @@
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { PrimaryActionProvider, usePrimaryActionHandler } from '../primaryAction';
import { SchedulesScreen } from './SchedulesScreen';
function jsonResponse(body: unknown, status = 200): Response {
@@ -157,6 +158,11 @@ async function renderReady(options: MockOptions = {}) {
return handle;
}
function SchedulesPrimaryActionButton() {
const handler = usePrimaryActionHandler('schedules');
return <button type="button" disabled={!handler} onClick={handler}>TopBar action</button>;
}
describe('SchedulesScreen — load', () => {
it('renders the lineup for the active schedule', async () => {
await renderReady();
@@ -438,10 +444,19 @@ describe('SchedulesScreen — schedule CRUD', () => {
await waitFor(() => expect(handle.requests.some((r) => r.url === '/api/v1/schedules/99/items')).toBe(true));
});
it('responds to the TopBar primary-action event by opening create', async () => {
await renderReady();
window.dispatchEvent(new CustomEvent('ctv:primary-action', { detail: 'schedules' }));
await waitFor(() => expect(screen.getByRole('dialog')).toBeTruthy());
it('registers the TopBar primary action to open create', async () => {
mockApi();
render(
<PrimaryActionProvider>
<SchedulesPrimaryActionButton />
<SchedulesScreen />
</PrimaryActionProvider>
);
await waitFor(() => expect(screen.getByLabelText('Schedule lineup')).toBeTruthy());
fireEvent.click(screen.getByRole('button', { name: 'TopBar action' }));
expect(screen.getByRole('dialog')).toBeTruthy();
expect(within(screen.getByRole('dialog')).getByLabelText('Name')).toBeTruthy();
});