Refine live control and show overview
This commit is contained in:
1 parent
ec596d442e
commit
b1474bc33d
16 files changed
+1580
-405
No files matched your search
+188
-19
@@ -196,6 +196,38 @@ function removePresentationFixture() {
|
||||
database.close();
|
||||
}
|
||||
|
||||
function addCalendarFixture() {
|
||||
const dataDirectory = process.env.SENDEWERK_E2E_DATA_DIR;
|
||||
if (!dataDirectory) throw new Error('E2E-Datenverzeichnis ist nicht konfiguriert.');
|
||||
const database = new Database(path.join(dataDirectory, 'e2e.sqlite'));
|
||||
const insert = database.prepare(
|
||||
`INSERT INTO shows (id, title, starts_at_utc, twitch_title, announcement_text, status,
|
||||
revision, created_at_utc, updated_at_utc) VALUES (?, ?, ?, '', '', 'draft', 1, ?, ?)`
|
||||
);
|
||||
const now = '2026-10-24T10:00:00.000Z';
|
||||
database.transaction(() => {
|
||||
insert.run('e2e-calendar-late', 'Kalender Spättermin', '2026-10-24T18:00:00.000Z', now, now);
|
||||
insert.run('e2e-calendar-early', 'Kalender Frühtermin', '2026-10-24T08:00:00.000Z', now, now);
|
||||
insert.run(
|
||||
'e2e-calendar-next',
|
||||
'Kalender Novembertermin',
|
||||
'2026-11-03T17:00:00.000Z',
|
||||
now,
|
||||
now
|
||||
);
|
||||
insert.run('e2e-calendar-undated', 'Kalender ohne Termin', null, now, now);
|
||||
})();
|
||||
database.close();
|
||||
}
|
||||
|
||||
function removeCalendarFixture() {
|
||||
const dataDirectory = process.env.SENDEWERK_E2E_DATA_DIR;
|
||||
if (!dataDirectory) return;
|
||||
const database = new Database(path.join(dataDirectory, 'e2e.sqlite'));
|
||||
database.prepare("DELETE FROM shows WHERE id LIKE 'e2e-calendar-%'").run();
|
||||
database.close();
|
||||
}
|
||||
|
||||
function setSectionDuration(sectionId: string, seconds: number | null) {
|
||||
const dataDirectory = process.env.SENDEWERK_E2E_DATA_DIR;
|
||||
if (!dataDirectory) throw new Error('E2E-Datenverzeichnis ist nicht konfiguriert.');
|
||||
@@ -495,6 +527,56 @@ test('sent and planned source filters stay independent and removing the last usa
|
||||
}
|
||||
});
|
||||
|
||||
test('show overview switches persistently between list, calendar and mobile agenda', async ({
|
||||
page
|
||||
}, testInfo) => {
|
||||
addCalendarFixture();
|
||||
try {
|
||||
await page.setViewportSize({ width: 1440, height: 1000 });
|
||||
await page.goto('/sendungen?month=2026-10');
|
||||
await page.getByRole('button', { name: 'Kalender' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Oktober 2026' })).toBeVisible();
|
||||
const octoberDay = page
|
||||
.locator('.show-calendar-day')
|
||||
.filter({ hasText: 'Kalender Frühtermin' });
|
||||
await expect(octoberDay).toHaveClass(/today/);
|
||||
await expect(octoberDay).toContainText('Kalender Spättermin');
|
||||
await expect(octoberDay.locator('.show-calendar-event')).toHaveCount(2);
|
||||
await expect(octoberDay.locator('.show-calendar-event').first()).toContainText('10:00');
|
||||
await expect(page.locator('.show-undated')).toContainText('Kalender ohne Termin');
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('sendungen-kalender-desktop.png'),
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
await page.getByRole('link', { name: 'Nächster Monat' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'November 2026' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Kalender Novembertermin' })).toBeVisible();
|
||||
await page.reload();
|
||||
await expect(page.getByRole('button', { name: 'Kalender' })).toHaveAttribute(
|
||||
'aria-pressed',
|
||||
'true'
|
||||
);
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.getByRole('link', { name: 'Vorheriger Monat' }).click();
|
||||
await expect(page.locator('.show-calendar')).toBeHidden();
|
||||
await expect(page.locator('.show-agenda')).toContainText('Kalender Frühtermin');
|
||||
await expect(page.locator('.show-agenda')).toContainText('Kalender Spättermin');
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('sendungen-kalender-mobile.png'),
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: 'Liste' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Geplante Sendungen' })).toBeVisible();
|
||||
} finally {
|
||||
removeCalendarFixture();
|
||||
}
|
||||
});
|
||||
|
||||
test('desktop builder keeps the fundus beside the wider run sheet', async ({ page }, testInfo) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto('/fundus');
|
||||
@@ -1083,10 +1165,25 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
await expect(page.locator('.mobile-navigation')).toHaveCount(0);
|
||||
await page.getByRole('button', { name: 'Sendung starten' }).click();
|
||||
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
||||
await expect(page.getByRole('button', { name: 'Zurück', exact: true })).toBeDisabled();
|
||||
await expect(page.locator('.section-countdown')).toContainText(/0[45]:\d{2}/);
|
||||
await expect(
|
||||
page.locator('.live-menu button', { hasText: 'Live-Stand wiederaufnehmen' })
|
||||
).toHaveCount(0);
|
||||
const liveMenuTrigger = page.getByRole('button', { name: 'Menü', exact: true });
|
||||
await liveMenuTrigger.click();
|
||||
const liveMenu = page.getByRole('dialog', { name: 'Regiemenü' });
|
||||
await expect(liveMenu).toBeVisible();
|
||||
await expect(liveMenu).toContainText('Herbstsendung: Kleine Welten');
|
||||
await expect(liveMenu.getByRole('button', { name: 'Live-Stand wiederaufnehmen' })).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: 'Schrift verkleinern' })).toBeFocused();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(liveMenu).toBeHidden();
|
||||
await expect(liveMenuTrigger).toBeFocused();
|
||||
await liveMenuTrigger.press('Enter');
|
||||
await expect(liveMenu).toBeVisible();
|
||||
await page.locator('.live-current').click({ position: { x: 8, y: 8 } });
|
||||
await expect(liveMenu).toBeHidden();
|
||||
await expect(liveMenuTrigger).toBeFocused();
|
||||
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
||||
await expect(page.locator('.live-output-state')).toContainText('Leer');
|
||||
await page.getByText('Quellen · 1', { exact: true }).click();
|
||||
await expect(page.locator('.live-materials')).toContainText('Herstellerseite');
|
||||
await expect(
|
||||
@@ -1152,7 +1249,7 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
|
||||
await page.getByText('Reserve', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Als Nächstes' }).click();
|
||||
await expect(page.locator('.live-next-summary')).toContainText('Pixelart');
|
||||
await expect(page.locator('.live-next-inline')).toContainText('Pixelart');
|
||||
await page.getByRole('button', { name: 'Erledigen & weiter' }).click();
|
||||
await expect(page.locator('.live-current')).toContainText('Pixelart');
|
||||
await page.getByRole('button', { name: 'Überspringen' }).click();
|
||||
@@ -1181,9 +1278,28 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
await page.locator('.live-current-scroll').evaluate((element) => element.scrollTop)
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
const countdownBeforeBack = await page.locator('.section-countdown strong').textContent();
|
||||
await page.getByRole('button', { name: 'Zurück', exact: true }).click();
|
||||
await expect(page.locator('#current-title')).toHaveText(
|
||||
'Pixelart, die nicht nostalgisch sein will'
|
||||
);
|
||||
await expect(
|
||||
page
|
||||
.locator('.live-run-item.current')
|
||||
.filter({ hasText: 'Warum praktische Effekte wieder auffallen' })
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Überspringen' })).toBeDisabled();
|
||||
await expect(page.getByRole('button', { name: /Erledigen/ })).toBeDisabled();
|
||||
await expect(
|
||||
output.getByRole('heading', { name: 'Die kleine Konsole mit dem großen Dreh' })
|
||||
).toBeVisible();
|
||||
const countdownAfterBack = await page.locator('.section-countdown strong').textContent();
|
||||
expect(countdownAfterBack).not.toBe('05:00');
|
||||
expect(countdownAfterBack?.slice(0, 3)).toBe(countdownBeforeBack?.slice(0, 3));
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Die kleine Konsole mit dem großen Dreh als aktuelles Piece auswählen'
|
||||
name: 'Die kleine Konsole mit dem großen Dreh in der Regie auswählen'
|
||||
})
|
||||
.click();
|
||||
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
||||
@@ -1192,13 +1308,13 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
.toBe(0);
|
||||
await expect(
|
||||
page.locator('.live-run-item').filter({ hasText: 'Warum praktische Effekte wieder auffallen' })
|
||||
).toContainText('Kommend');
|
||||
).toContainText('Aktuell');
|
||||
await expect(
|
||||
output.getByRole('heading', { name: 'Die kleine Konsole mit dem großen Dreh' })
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Warum praktische Effekte wieder auffallen als aktuelles Piece auswählen'
|
||||
name: 'Warum praktische Effekte wieder auffallen in der Regie auswählen'
|
||||
})
|
||||
.click();
|
||||
await expect(page.locator('#current-title')).toHaveText(
|
||||
@@ -1267,6 +1383,53 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
});
|
||||
await page.screenshot({ path: testInfo.outputPath('live-controller-compact-desktop.png') });
|
||||
|
||||
await page.setViewportSize({ width: 670, height: 450 });
|
||||
await page.evaluate(() => {
|
||||
const section = document.querySelector('.live-context-title.section span');
|
||||
const next = document.querySelector('.live-context-title.next span');
|
||||
if (section)
|
||||
section.textContent =
|
||||
'Aktuelles Segment mit einem außergewöhnlich langen Namen für die kompakte Regie';
|
||||
if (next)
|
||||
next.textContent =
|
||||
'Danach folgt ein Piece mit einem ebenfalls außergewöhnlich langen und vollständigen Titel';
|
||||
});
|
||||
const smallHeader = page.locator('.live-command-header');
|
||||
const smallHeaderBox = await smallHeader.boundingBox();
|
||||
expect(smallHeaderBox?.height ?? 999).toBeLessThan(105);
|
||||
await expect(page.locator('.live-context-title.section span')).toHaveCSS('white-space', 'nowrap');
|
||||
await expect(page.locator('.live-context-title.next span')).toHaveCSS('white-space', 'nowrap');
|
||||
await page.locator('.live-context-title.section').click();
|
||||
await expect(page.locator('.live-context-title.section')).toHaveClass(/expanded/);
|
||||
await expect(page.locator('.live-context-title.section span')).toHaveCSS('white-space', 'normal');
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.locator('.live-context-title.section')).not.toHaveClass(/expanded/);
|
||||
await page.locator('.live-context-title.next').focus();
|
||||
await page.locator('.live-context-title.next').press('Enter');
|
||||
await expect(page.locator('.live-context-title.next')).toHaveClass(/expanded/);
|
||||
await page.keyboard.press('Escape');
|
||||
await liveMenuTrigger.click();
|
||||
await expect(liveMenu).toBeVisible();
|
||||
const compactMenuBox = await liveMenu.boundingBox();
|
||||
expect(compactMenuBox?.x ?? -1).toBeGreaterThanOrEqual(0);
|
||||
expect(compactMenuBox?.y ?? -1).toBeGreaterThanOrEqual(0);
|
||||
expect(
|
||||
Math.ceil((compactMenuBox?.x ?? 999) + (compactMenuBox?.width ?? 999))
|
||||
).toBeLessThanOrEqual(670);
|
||||
expect(
|
||||
Math.ceil((compactMenuBox?.y ?? 999) + (compactMenuBox?.height ?? 999))
|
||||
).toBeLessThanOrEqual(450);
|
||||
await liveMenu.getByRole('link', { name: /Zur Planung/ }).scrollIntoViewIfNeeded();
|
||||
await expect(liveMenu.getByRole('link', { name: /Zur Planung/ })).toBeVisible();
|
||||
await liveMenu.getByRole('button', { name: 'Zeiten ausblenden' }).click();
|
||||
await expect(page.locator('.live-piece-time')).toHaveCount(0);
|
||||
await liveMenu.getByRole('button', { name: 'Zeiten einblenden' }).click();
|
||||
await expect(page.locator('.live-piece-time')).toBeVisible();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(liveMenuTrigger).toBeFocused();
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({ path: testInfo.outputPath('live-controller-670x450.png') });
|
||||
|
||||
const countdownBeforeReload = await page.locator('.section-countdown strong').textContent();
|
||||
await page.reload();
|
||||
await expect(page.locator('#current-title')).toHaveText(
|
||||
@@ -1287,7 +1450,17 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
await page.evaluate(() => document.documentElement.scrollHeight <= window.innerHeight)
|
||||
).toBe(true);
|
||||
await page.screenshot({ path: testInfo.outputPath('live-controller-mobile.png') });
|
||||
await page.getByRole('button', { name: /Ablauf · Piece/ }).click();
|
||||
await liveMenuTrigger.click();
|
||||
const mobileMenuBox = await liveMenu.boundingBox();
|
||||
expect(Math.ceil((mobileMenuBox?.x ?? 999) + (mobileMenuBox?.width ?? 999))).toBeLessThanOrEqual(
|
||||
390
|
||||
);
|
||||
expect(Math.ceil((mobileMenuBox?.y ?? 999) + (mobileMenuBox?.height ?? 999))).toBeLessThanOrEqual(
|
||||
700
|
||||
);
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(liveMenuTrigger).toBeFocused();
|
||||
await page.getByRole('button', { name: /Ablauf ·/ }).click();
|
||||
await expect(liveRail).toHaveClass(/open/);
|
||||
await expect(railClose).toBeFocused();
|
||||
await expect
|
||||
@@ -1302,26 +1475,26 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
await page.screenshot({ path: testInfo.outputPath('live-controller-mobile-run.png') });
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(liveRail).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: /Ablauf · Piece/ })).toBeFocused();
|
||||
await page.getByRole('button', { name: /Ablauf · Piece/ }).click();
|
||||
await expect(page.getByRole('button', { name: /Ablauf ·/ })).toBeFocused();
|
||||
await page.getByRole('button', { name: /Ablauf ·/ }).click();
|
||||
await page.setViewportSize({ width: 900, height: 450 });
|
||||
await expect(liveRail).not.toHaveAttribute('role', 'dialog');
|
||||
await expect(page.locator('.live-command-header')).not.toHaveAttribute('inert', '');
|
||||
await page.setViewportSize({ width: 390, height: 700 });
|
||||
await expect(liveRail).toBeHidden();
|
||||
|
||||
await page.getByRole('button', { name: /Ablauf · Piece/ }).click();
|
||||
await page.getByRole('button', { name: /Ablauf ·/ }).click();
|
||||
await liveRail
|
||||
.getByRole('button', {
|
||||
name: 'Die kleine Konsole mit dem großen Dreh als aktuelles Piece auswählen'
|
||||
name: 'Die kleine Konsole mit dem großen Dreh in der Regie auswählen'
|
||||
})
|
||||
.click();
|
||||
await expect(liveRail).toBeHidden();
|
||||
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
||||
await page.getByRole('button', { name: /Ablauf · Piece/ }).click();
|
||||
await page.getByRole('button', { name: /Ablauf ·/ }).click();
|
||||
await liveRail
|
||||
.getByRole('button', {
|
||||
name: 'Warum praktische Effekte wieder auffallen als aktuelles Piece auswählen'
|
||||
name: 'Warum praktische Effekte wieder auffallen in der Regie auswählen'
|
||||
})
|
||||
.click();
|
||||
await expect(liveRail).toBeHidden();
|
||||
@@ -1329,15 +1502,11 @@ test('live control persists state and exposes only explicit presentation data',
|
||||
'Warum praktische Effekte wieder auffallen'
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Erledigen' }).click();
|
||||
await expect(page.getByRole('button', { name: 'Live-Stand wiederaufnehmen' })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Live-Stand wiederaufnehmen' }).click();
|
||||
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.getByRole('button', { name: 'Erledigen & Sendungsende' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Alle Pieces bearbeitet' })).toBeVisible();
|
||||
await expect(page.locator('.live-finished-label')).toHaveText('Ablauf abgeschlossen');
|
||||
await expect(page.getByRole('button', { name: 'Erledigen & weiter' })).toHaveCount(0);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
|
||||
await output.route('**/state', (route) => route.abort());
|
||||
await expect(output.getByText('Verbindung unterbrochen')).toBeVisible({ timeout: 5_000 });
|
||||
|
||||
Reference in new issue
Block a user