mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 12:53:32 +00:00
34ca715fb7
Two follow ups on multi event editing. Duration is now editable across a selection. The batch endpoint already applies every mutation to a cloned rundown and calls processRundown once on commit, so a batched duration cascades correctly through linked events with a single recalculation, and the server infers the duration lock for entries which were locked to their end. Start and end times remain excluded: they are absolute points in time, so giving several events the same value collapses every linked event to a zero duration. Only the duration is offered in a multi selection. The optimistic update is skipped when the patch carries a duration, since the resulting schedule cannot be resolved on the client. Conflicting values are now marked with a dedicated symbol rather than undefined, which was doing double duty for "the entries disagree" and "the field is absent". The distinction was already leaking into the custom field merge, which needed a key lookup to tell the two apart. The symbol also makes it a type error to assign a merged value into an entry patch, where it would previously have been dropped silently on serialisation. Components keep taking plain optional values: the conflict is resolved once at the editor boundary, so no indeterminate handling spreads into the tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PDETGBrhwgAqgTHjmrmJGJ
78 lines
3.9 KiB
TypeScript
78 lines
3.9 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
test('Editing multiple events', async ({ page }) => {
|
|
await page.goto('/editor');
|
|
await page.getByRole('button', { name: 'Edit' }).click();
|
|
|
|
// clear rundown
|
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
|
|
|
// create two events with distinct titles
|
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
|
await expect(page.getByTestId('rundown-event')).toHaveCount(2);
|
|
|
|
await page.getByTestId('entry-1').getByTestId('entry__title').fill('first');
|
|
await page.getByTestId('entry-1').getByTestId('entry__title').press('Enter');
|
|
await page.getByTestId('entry-2').getByTestId('entry__title').fill('second');
|
|
await page.getByTestId('entry-2').getByTestId('entry__title').press('Enter');
|
|
|
|
const editor = page.getByTestId('editor-container');
|
|
|
|
// a single selection shows the full schedule and the event id
|
|
await page.getByTestId('entry-1').getByTestId('rundown-event').click();
|
|
await expect(editor.getByTestId('time-input-timeStart')).toBeVisible();
|
|
await expect(editor.getByLabel('Title', { exact: true })).toHaveValue('first');
|
|
|
|
// selecting both events shows a merged view
|
|
await page
|
|
.getByTestId('entry-2')
|
|
.getByTestId('rundown-event')
|
|
.click({ modifiers: ['Shift'] });
|
|
await expect(editor.locator('#eventId')).toHaveValue('2 events selected');
|
|
await expect(editor.getByText('Automations are not available when editing multiple events')).toBeVisible();
|
|
|
|
// start and end times are unique to an event, only the duration can be batched
|
|
await expect(editor.getByTestId('time-input-timeStart')).toBeHidden();
|
|
await expect(editor.getByTestId('time-input-timeEnd')).toBeHidden();
|
|
await expect(editor.getByTestId('time-input-duration')).toBeVisible();
|
|
|
|
// fields which do not agree are shown as mixed
|
|
const title = editor.getByLabel('Title', { exact: true });
|
|
await expect(title).toHaveValue('');
|
|
await expect(title).toHaveAttribute('placeholder', 'Mixed');
|
|
|
|
// leaving a mixed field without editing it does not overwrite the events
|
|
await title.click();
|
|
await page.keyboard.press('Tab');
|
|
await expect(page.getByTestId('entry-1').getByTestId('entry__title')).toHaveValue('first');
|
|
await expect(page.getByTestId('entry-2').getByTestId('entry__title')).toHaveValue('second');
|
|
|
|
// editing a field applies it to the whole selection
|
|
await title.fill('shared title');
|
|
await title.press('Enter');
|
|
await expect(page.getByTestId('entry-1').getByTestId('entry__title')).toHaveValue('shared title');
|
|
await expect(page.getByTestId('entry-2').getByTestId('entry__title')).toHaveValue('shared title');
|
|
|
|
// the value is no longer mixed
|
|
await expect(editor.getByLabel('Title', { exact: true })).toHaveValue('shared title');
|
|
|
|
// a batched duration is applied to every event and the rundown is recalculated once
|
|
const duration = editor.getByTestId('time-input-duration');
|
|
await duration.click();
|
|
await duration.fill('5m');
|
|
await duration.press('Enter');
|
|
await expect(page.getByTestId('entry-1').getByTestId('time-input-duration')).toHaveValue('00:05:00');
|
|
await expect(page.getByTestId('entry-2').getByTestId('time-input-duration')).toHaveValue('00:05:00');
|
|
// the second event is linked, so its start follows the new end of the first
|
|
await expect(page.getByTestId('entry-2').getByTestId('time-input-timeStart')).toHaveValue('00:05:00');
|
|
await expect(page.getByTestId('entry-2').getByTestId('time-input-timeEnd')).toHaveValue('00:10:00');
|
|
|
|
// going back to a single selection restores the full editor
|
|
await page.getByTestId('entry-1').getByTestId('rundown-event').click();
|
|
await expect(editor.getByTestId('time-input-timeStart')).toBeVisible();
|
|
await expect(editor.locator('#eventId')).not.toHaveValue('2 events selected');
|
|
});
|