Release 0.5.1
This commit is contained in:
1 parent
5a9d7735d4
commit
fb24d171ae
36 files changed
+5099
-263
No files matched your search
+466
-12
@@ -1,4 +1,5 @@
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
import Database from 'better-sqlite3';
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
@@ -61,6 +62,124 @@ function latestMailLink(subject: string): string {
|
||||
return match[0];
|
||||
}
|
||||
|
||||
function addSentHistoryFixture(pieceId: string) {
|
||||
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'));
|
||||
database.pragma('foreign_keys = ON');
|
||||
const insertShow = database.prepare(
|
||||
`INSERT INTO shows (id, title, starts_at_utc, twitch_title, announcement_text, status,
|
||||
revision, created_at_utc, updated_at_utc) VALUES (?, ?, ?, '', '', 'completed', 1, ?, ?)`
|
||||
);
|
||||
const insertSection = database.prepare(
|
||||
`INSERT INTO show_sections (id, show_id, name, category_id, position,
|
||||
explicit_duration_seconds, is_reserve_bank) VALUES (?, ?, 'Ablauf', NULL, 0, NULL, 0)`
|
||||
);
|
||||
const insertUsage = database.prepare(
|
||||
`INSERT INTO piece_usages (id, show_section_id, original_piece_id, position, status,
|
||||
duration_seconds, title_snapshot, intro_snapshot, research_snapshot, moderation_snapshot,
|
||||
transition_snapshot, presentation_snapshot, own_notes, is_reserve)
|
||||
VALUES (?, ?, ?, 0, 'done', 300, 'Historische Verwendung', '', '', '', '', '', '', 0)`
|
||||
);
|
||||
database.transaction(() => {
|
||||
for (const [suffix, title, date] of [
|
||||
['older', 'Rückblick im September', '2026-09-10T18:00:00.000Z'],
|
||||
['latest', 'Rückblick im Oktober', '2026-10-10T18:00:00.000Z']
|
||||
] as const) {
|
||||
const showId = `e2e-history-${suffix}`;
|
||||
const sectionId = `e2e-history-section-${suffix}`;
|
||||
insertShow.run(showId, title, date, date, date);
|
||||
insertSection.run(sectionId, showId);
|
||||
insertUsage.run(`e2e-history-usage-${suffix}`, sectionId, pieceId);
|
||||
}
|
||||
})();
|
||||
database.close();
|
||||
}
|
||||
|
||||
function removeSentHistoryFixture() {
|
||||
const dataDirectory = process.env.SENDEWERK_E2E_DATA_DIR;
|
||||
if (!dataDirectory) return;
|
||||
const database = new Database(path.join(dataDirectory, 'e2e.sqlite'));
|
||||
database.pragma('foreign_keys = ON');
|
||||
database.prepare("DELETE FROM shows WHERE id LIKE 'e2e-history-%'").run();
|
||||
database.close();
|
||||
}
|
||||
|
||||
function addPresentationFixture() {
|
||||
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'));
|
||||
database.pragma('foreign_keys = ON');
|
||||
const now = '2026-10-22T19:00:00.000Z';
|
||||
database.transaction(() => {
|
||||
database
|
||||
.prepare(
|
||||
`INSERT INTO shows (id, title, starts_at_utc, twitch_title, announcement_text, status,
|
||||
revision, created_at_utc, updated_at_utc)
|
||||
VALUES ('e2e-presentation', 'E2E Präsentation', ?, '', '', 'ready', 1, ?, ?)`
|
||||
)
|
||||
.run(now, now, now);
|
||||
database
|
||||
.prepare(
|
||||
`INSERT INTO show_sections (id, show_id, name, category_id, position,
|
||||
explicit_duration_seconds, is_reserve_bank)
|
||||
VALUES ('e2e-presentation-section', 'e2e-presentation', 'Player', NULL, 0, NULL, 0),
|
||||
('e2e-presentation-reserve', 'e2e-presentation', 'Reservebank', NULL, 1, NULL, 1)`
|
||||
)
|
||||
.run();
|
||||
const insert = database.prepare(
|
||||
`INSERT INTO piece_usages (id, show_section_id, original_piece_id, position, status,
|
||||
duration_seconds, title_snapshot, intro_snapshot, research_snapshot, moderation_snapshot,
|
||||
transition_snapshot, presentation_snapshot, own_notes, is_reserve,
|
||||
presentation_mode_snapshot, presentation_source_label_snapshot,
|
||||
presentation_source_url_snapshot, presentation_source_timecode_snapshot,
|
||||
presentation_title_meta_snapshot, presentation_description_snapshot,
|
||||
presentation_image_url_snapshot, presentation_site_name_snapshot,
|
||||
presentation_provider_snapshot, presentation_provider_id_snapshot)
|
||||
VALUES (?, 'e2e-presentation-section', NULL, ?, 'open', 300, ?, '', ?, ?, '', '', '', 0,
|
||||
'video', ?, ?, ?, ?, '', '', ?, ?, ?)`
|
||||
);
|
||||
insert.run(
|
||||
'e2e-youtube',
|
||||
0,
|
||||
'YouTube Kurzlink',
|
||||
'PRIVATE_RESEARCH_YOUTUBE',
|
||||
'PRIVATE_MODERATION_YOUTUBE',
|
||||
'YouTube',
|
||||
'https://youtu.be/dQw4w9WgXcQ',
|
||||
75,
|
||||
'YouTube Vorschau',
|
||||
'YouTube',
|
||||
'youtube',
|
||||
'dQw4w9WgXcQ'
|
||||
);
|
||||
insert.run(
|
||||
'e2e-tiktok',
|
||||
1,
|
||||
'TikTok Hochformat',
|
||||
'PRIVATE_RESEARCH_TIKTOK',
|
||||
'PRIVATE_MODERATION_TIKTOK',
|
||||
'TikTok',
|
||||
'https://www.tiktok.com/@scout2015/video/6718335390845095173',
|
||||
12,
|
||||
'TikTok Vorschau',
|
||||
'TikTok',
|
||||
'tiktok',
|
||||
'6718335390845095173'
|
||||
);
|
||||
})();
|
||||
database.close();
|
||||
}
|
||||
|
||||
function removePresentationFixture() {
|
||||
const dataDirectory = process.env.SENDEWERK_E2E_DATA_DIR;
|
||||
if (!dataDirectory) return;
|
||||
const database = new Database(path.join(dataDirectory, 'e2e.sqlite'));
|
||||
database.pragma('foreign_keys = ON');
|
||||
database.prepare("DELETE FROM shows WHERE id = 'e2e-presentation'").run();
|
||||
database.close();
|
||||
}
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
const page = await browser.newPage();
|
||||
await page.goto('/');
|
||||
@@ -176,6 +295,8 @@ test('desktop fundus is a compact, filterable list with persistent themes', asyn
|
||||
await expect(page.locator('.sidebar')).toBeVisible();
|
||||
await expect(page.locator('.mobile-navigation')).toBeHidden();
|
||||
await expect(page.locator('.utility-navigation a')).toHaveText('Verwaltung');
|
||||
await expect(page.locator('.piece-card')).toHaveCount(1);
|
||||
await page.getByLabel('Auch eingeplante Inhalte zeigen').check();
|
||||
await expect(page.locator('.piece-card')).toHaveCount(4);
|
||||
|
||||
const firstRow = await page.locator('.piece-card').nth(0).boundingBox();
|
||||
@@ -193,8 +314,8 @@ test('desktop fundus is a compact, filterable list with persistent themes', asyn
|
||||
await page.getByLabel('Status').selectOption('preparing');
|
||||
await page.getByRole('button', { name: 'Anwenden' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Kein passendes Piece' })).toBeVisible();
|
||||
await page.getByRole('link', { name: 'Filter zurücksetzen' }).click();
|
||||
await expect(page.locator('.piece-card')).toHaveCount(4);
|
||||
await page.getByRole('button', { name: 'Zurücksetzen' }).click();
|
||||
await expect(page.locator('.piece-card')).toHaveCount(1);
|
||||
|
||||
await page.getByRole('button', { name: 'Darstellung: System' }).click();
|
||||
await expect(page.locator('html')).toHaveAttribute('data-theme-preference', 'light');
|
||||
@@ -205,6 +326,118 @@ test('desktop fundus is a compact, filterable list with persistent themes', asyn
|
||||
await page.screenshot({ path: testInfo.outputPath('fundus-desktop-light.png'), fullPage: true });
|
||||
});
|
||||
|
||||
test('sent and planned source filters stay independent and removing the last usage offers the piece again', async ({
|
||||
page
|
||||
}, testInfo) => {
|
||||
addSentHistoryFixture('piece-effects');
|
||||
try {
|
||||
await page.goto('/fundus');
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Warum praktische Effekte wieder auffallen' })
|
||||
).toHaveCount(0);
|
||||
await page.evaluate(() => (document.body.dataset.filterDocument = 'stable'));
|
||||
await page.getByLabel('Auch verwendete Inhalte zeigen').check();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Warum praktische Effekte wieder auffallen' })
|
||||
).toHaveCount(0);
|
||||
await page.getByLabel('Auch eingeplante Inhalte zeigen').check();
|
||||
const usedPiece = page
|
||||
.locator('.piece-card')
|
||||
.filter({ hasText: 'Warum praktische Effekte wieder auffallen' });
|
||||
await expect(usedPiece).toBeVisible();
|
||||
await expect(usedPiece).toContainText('Gesendet am 10.10.2026');
|
||||
await expect(usedPiece).toContainText('+ 1 weitere');
|
||||
await expect(usedPiece.getByRole('link', { name: 'Rückblick im Oktober' })).toHaveAttribute(
|
||||
'href',
|
||||
'/sendungen/e2e-history-latest'
|
||||
);
|
||||
await usedPiece.getByText('Vollständige Verwendungshistorie').click();
|
||||
await expect(usedPiece.locator('.piece-usage-history li')).toHaveCount(2);
|
||||
await expect(page.locator('body')).toHaveAttribute('data-filter-document', 'stable');
|
||||
await page.getByRole('searchbox', { name: 'Fundus durchsuchen' }).fill('praktische Effekte');
|
||||
await page.getByRole('button', { name: 'Anwenden' }).click();
|
||||
await expect(page.getByLabel('Auch verwendete Inhalte zeigen')).toBeChecked();
|
||||
await expect(usedPiece).toBeVisible();
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('fundus-verwendungshistorie-desktop.png'),
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
await page.goto('/sendungen');
|
||||
await page.getByLabel('Titel').fill('E2E Filterprüfung');
|
||||
await page.getByLabel('Ausgangspunkt').selectOption('template-default');
|
||||
await page.getByRole('button', { name: 'Sendung anlegen' }).click();
|
||||
await page.getByPlaceholder('Fundus durchsuchen').fill('kleine Konsole');
|
||||
await page.getByRole('button', { name: 'Filtern' }).click();
|
||||
const consolePiece = page
|
||||
.locator('.builder-source-card')
|
||||
.filter({ hasText: 'Die kleine Konsole mit dem großen Dreh' });
|
||||
await consolePiece.getByRole('button', { name: 'Hinzufügen' }).click();
|
||||
await expect(consolePiece).toHaveCount(0);
|
||||
|
||||
await page.getByPlaceholder('Fundus durchsuchen').fill('');
|
||||
await page.getByRole('button', { name: 'Filtern' }).click();
|
||||
await page.getByLabel('Auch eingeplante Inhalte zeigen').check();
|
||||
await expect(
|
||||
page
|
||||
.locator('.builder-source-card')
|
||||
.filter({ hasText: 'Die kleine Konsole mit dem großen Dreh' })
|
||||
).toContainText('Eingeplant');
|
||||
await expect(
|
||||
page
|
||||
.locator('.builder-source-card')
|
||||
.filter({ hasText: 'Warum praktische Effekte wieder auffallen' })
|
||||
).toHaveCount(0);
|
||||
await page.getByPlaceholder('Fundus durchsuchen').fill('kleine Konsole');
|
||||
await page.getByRole('button', { name: 'Filtern' }).click();
|
||||
await expect(page.getByLabel('Auch eingeplante Inhalte zeigen')).toBeChecked();
|
||||
await expect(
|
||||
page
|
||||
.locator('.builder-source-card')
|
||||
.filter({ hasText: 'Die kleine Konsole mit dem großen Dreh' })
|
||||
).toContainText('Eingeplant');
|
||||
await page.getByPlaceholder('Fundus durchsuchen').fill('');
|
||||
await page.getByRole('button', { name: 'Filtern' }).click();
|
||||
|
||||
await page.getByLabel('Auch eingeplante Inhalte zeigen').uncheck();
|
||||
await page.getByLabel('Auch verwendete Inhalte zeigen').check();
|
||||
await expect(
|
||||
page
|
||||
.locator('.builder-source-card')
|
||||
.filter({ hasText: 'Die kleine Konsole mit dem großen Dreh' })
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
page
|
||||
.locator('.builder-source-card')
|
||||
.filter({ hasText: 'Warum praktische Effekte wieder auffallen' })
|
||||
).toContainText('Rückblick im Oktober');
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('sendungsfilter-desktop.png'),
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
await page
|
||||
.locator('.reserve-usage')
|
||||
.getByRole('button', { name: /aus Reserve entfernen/ })
|
||||
.click();
|
||||
await page.getByLabel('Auch verwendete Inhalte zeigen').uncheck();
|
||||
await expect(
|
||||
page
|
||||
.locator('.builder-source-card')
|
||||
.filter({ hasText: 'Die kleine Konsole mit dem großen Dreh' })
|
||||
).toBeVisible();
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.getByRole('button', { name: 'Fundus', exact: true }).click();
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('sendungsfilter-mobile.png'),
|
||||
fullPage: true
|
||||
});
|
||||
} finally {
|
||||
removeSentHistoryFixture();
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
@@ -322,10 +555,12 @@ test('a show can be planned from a template with independent usages and reserve'
|
||||
await expect(source).toHaveClass(/is-dragging/);
|
||||
await expect(source).toHaveCSS('opacity', '0.45');
|
||||
await emptyReactionTarget.dispatchEvent('drop', { dataTransfer });
|
||||
await sourceDragHandle.dispatchEvent('dragend', { dataTransfer });
|
||||
await dataTransfer.dispose();
|
||||
await expect(reactionSection.locator('.editable-usage')).toHaveCount(1);
|
||||
await expect(page.locator('body')).toHaveAttribute('data-builder-document', 'stable');
|
||||
await expect(source).toHaveCount(0);
|
||||
await page.getByLabel('Auch eingeplante Inhalte zeigen').check();
|
||||
await expect(source).toContainText('Eingeplant');
|
||||
await sourceDragHandle.dragTo(reactionSection.locator('.editable-usage'));
|
||||
|
||||
let usages = page
|
||||
@@ -333,7 +568,41 @@ test('a show can be planned from a template with independent usages and reserve'
|
||||
.filter({ has: page.getByRole('heading', { name: 'JC / Reaction' }) })
|
||||
.locator('.editable-usage');
|
||||
await expect(usages).toHaveCount(2);
|
||||
await usages.nth(0).locator('summary').click();
|
||||
await usages
|
||||
.nth(0)
|
||||
.getByRole('button', {
|
||||
name: 'Die kleine Konsole mit dem großen Dreh bearbeiten und verschieben'
|
||||
})
|
||||
.click();
|
||||
await expect(page.locator('dialog[open]')).toContainText('Sendungsspezifische Kopie');
|
||||
const desktopEditorItems = await page
|
||||
.locator('dialog[open] .usage-dialog-editor')
|
||||
.evaluate((element) =>
|
||||
Array.from(element.children)
|
||||
.filter((child) => child.matches('section, button'))
|
||||
.map((child) => child.getBoundingClientRect().top)
|
||||
);
|
||||
expect(Math.abs(desktopEditorItems[0] - desktopEditorItems[1])).toBeLessThan(2);
|
||||
expect(desktopEditorItems[2]).toBeGreaterThan(desktopEditorItems[1]);
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('verwendungseditor-desktop.png'),
|
||||
fullPage: true
|
||||
});
|
||||
await page
|
||||
.locator('dialog[open]')
|
||||
.getByRole('button', { name: 'Aus Fundus aktualisieren' })
|
||||
.click();
|
||||
await expect(page.locator('dialog[open]')).toHaveCount(0);
|
||||
usages = page
|
||||
.locator('.run-section')
|
||||
.filter({ has: page.getByRole('heading', { name: 'JC / Reaction' }) })
|
||||
.locator('.editable-usage');
|
||||
await usages
|
||||
.nth(0)
|
||||
.getByRole('button', {
|
||||
name: 'Die kleine Konsole mit dem großen Dreh bearbeiten und verschieben'
|
||||
})
|
||||
.click();
|
||||
await usages.nth(0).getByLabel('Titel').fill('Konsole als Auftakt');
|
||||
await usages.nth(0).getByLabel('Dauer in Minuten').fill('5');
|
||||
await usages.nth(0).getByLabel('Eigene Notizen').fill('Kurbel zuerst zeigen');
|
||||
@@ -344,20 +613,51 @@ test('a show can be planned from a template with independent usages and reserve'
|
||||
.locator('.run-section')
|
||||
.filter({ has: page.getByRole('heading', { name: 'JC / Reaction' }) })
|
||||
.locator('.editable-usage');
|
||||
await usages.nth(1).locator('summary').click();
|
||||
await usages
|
||||
.nth(1)
|
||||
.getByRole('button', {
|
||||
name: 'Die kleine Konsole mit dem großen Dreh bearbeiten und verschieben'
|
||||
})
|
||||
.click();
|
||||
await usages.nth(1).getByLabel('Titel').fill('Konsole als Rückblick');
|
||||
await usages.nth(1).getByLabel('Dauer in Minuten').fill('15');
|
||||
await usages.nth(1).getByLabel('Eigene Notizen').fill('Andere Perspektive');
|
||||
await usages.nth(1).getByRole('button', { name: 'Verwendung speichern' }).click();
|
||||
await expect(page.locator('.show-summary')).toContainText('2 Std. 25 Min.');
|
||||
|
||||
usages = page
|
||||
.locator('.run-section')
|
||||
.filter({ has: page.getByRole('heading', { name: 'JC / Reaction' }) })
|
||||
.locator('.editable-usage');
|
||||
await usages
|
||||
.nth(1)
|
||||
.getByRole('button', { name: 'Konsole als Rückblick bearbeiten und verschieben' })
|
||||
.click();
|
||||
await usages.nth(1).getByRole('button', { name: 'Duplizieren' }).click();
|
||||
await expect(reactionSection.locator('.editable-usage')).toHaveCount(3);
|
||||
await expect(reactionSection.locator('.editable-usage').nth(2)).toContainText(
|
||||
'Konsole als Rückblick'
|
||||
);
|
||||
await expect(page.locator('.show-summary')).toContainText('2 Std. 40 Min.');
|
||||
|
||||
const planTimeBeforeReserve = await page.locator('.show-summary').textContent();
|
||||
await source.getByLabel(/Ziel für/).selectOption('');
|
||||
await source.getByRole('button', { name: 'Hinzufügen' }).click();
|
||||
await expect(page.locator('.reserve-usage')).toHaveCount(1);
|
||||
expect(await page.locator('.show-summary').textContent()).toBe(planTimeBeforeReserve);
|
||||
await page
|
||||
.locator('.reserve-usage')
|
||||
.getByRole('button', {
|
||||
name: 'Die kleine Konsole mit dem großen Dreh bearbeiten und verschieben'
|
||||
})
|
||||
.click();
|
||||
await expect(page.locator('dialog[open]')).toContainText('Sendungsspezifische Kopie');
|
||||
await expect(
|
||||
page.locator('dialog[open]').getByRole('button', { name: 'Duplizieren' })
|
||||
).toBeVisible();
|
||||
await page.locator('dialog[open]').getByRole('button', { name: 'Editor schließen' }).click();
|
||||
|
||||
const moveUp = page.getByRole('button', { name: 'Konsole als Rückblick nach oben' });
|
||||
const moveUp = page.getByRole('button', { name: 'Konsole als Rückblick nach oben' }).first();
|
||||
await moveUp.focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(
|
||||
@@ -384,12 +684,60 @@ test('a show can be planned from a template with independent usages and reserve'
|
||||
await expect(page.locator('.reserve-usage')).toHaveCount(0);
|
||||
await expect(page.locator('body')).toHaveAttribute('data-builder-document', 'stable');
|
||||
await page.reload();
|
||||
await expect(page.locator('.editable-usage')).toHaveCount(3);
|
||||
await expect(page.locator('.editable-usage')).toHaveCount(4);
|
||||
const savedUsage = page.locator('.editable-usage').filter({ hasText: 'Konsole als Auftakt' });
|
||||
await savedUsage.locator('summary').click();
|
||||
await savedUsage
|
||||
.getByRole('button', { name: 'Konsole als Auftakt bearbeiten und verschieben' })
|
||||
.click();
|
||||
await expect(savedUsage.getByLabel('Eigene Notizen')).toHaveValue('Kurbel zuerst zeigen');
|
||||
await page.locator('dialog[open]').getByRole('button', { name: 'Editor schließen' }).click();
|
||||
const duplicatedUsage = page
|
||||
.locator('.editable-usage')
|
||||
.filter({ hasText: 'Konsole als Rückblick' });
|
||||
await expect(duplicatedUsage).toHaveCount(2);
|
||||
await duplicatedUsage
|
||||
.nth(1)
|
||||
.getByRole('button', { name: 'Konsole als Rückblick bearbeiten und verschieben' })
|
||||
.click();
|
||||
await expect(duplicatedUsage.nth(1).getByLabel('Dauer in Minuten')).toHaveValue('15');
|
||||
await expect(duplicatedUsage.nth(1).getByLabel('Eigene Notizen')).toHaveValue(
|
||||
'Andere Perspektive'
|
||||
);
|
||||
await page.locator('dialog[open]').getByRole('button', { name: 'Editor schließen' }).click();
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await savedUsage
|
||||
.getByRole('button', { name: 'Konsole als Auftakt bearbeiten und verschieben' })
|
||||
.click();
|
||||
const mobileEditorItems = await page
|
||||
.locator('dialog[open] .usage-dialog-editor')
|
||||
.evaluate((element) =>
|
||||
Array.from(element.children)
|
||||
.filter((child) => child.matches('section, button'))
|
||||
.map((child) => child.getBoundingClientRect().top)
|
||||
);
|
||||
expect(mobileEditorItems[1]).toBeGreaterThan(mobileEditorItems[0]);
|
||||
expect(mobileEditorItems[2]).toBeGreaterThan(mobileEditorItems[1]);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('verwendungseditor-mobile.png'),
|
||||
fullPage: true
|
||||
});
|
||||
await page
|
||||
.locator('dialog[open]')
|
||||
.getByRole('button', { name: 'Entfernen' })
|
||||
.scrollIntoViewIfNeeded();
|
||||
await expect(
|
||||
page.locator('dialog[open]').getByRole('button', { name: 'Entfernen' })
|
||||
).toBeVisible();
|
||||
await page
|
||||
.locator('dialog[open] form[action="?/updateUsage"] input[name="revision"]')
|
||||
.evaluate((element) => ((element as HTMLInputElement).value = '1'));
|
||||
await page.locator('dialog[open]').getByRole('button', { name: 'Verwendung speichern' }).click();
|
||||
await expect(page.locator('dialog[open]').getByRole('alert')).toContainText(
|
||||
'inzwischen geändert'
|
||||
);
|
||||
await page.locator('dialog[open]').getByRole('button', { name: 'Editor schließen' }).click();
|
||||
await page.getByRole('button', { name: 'Ablauf', exact: true }).click();
|
||||
await page.getByPlaceholder('Neuer Abschnitt').fill('Finales Spiel');
|
||||
await page.getByRole('button', { name: 'Abschnitt hinzufügen' }).click();
|
||||
@@ -397,7 +745,7 @@ test('a show can be planned from a template with independent usages and reserve'
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({ path: testInfo.outputPath('sendungsbau-mobile.png'), fullPage: true });
|
||||
|
||||
await page.goto('/fundus?q=kleine+Konsole');
|
||||
await page.goto('/fundus?q=kleine+Konsole&includePlanned=1');
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Die kleine Konsole mit dem großen Dreh' })
|
||||
).toBeVisible();
|
||||
@@ -430,7 +778,7 @@ test('all work areas fit at required widths in light and dark mode', async ({ pa
|
||||
test('long titles and longer fundus lists reflow without overflow', async ({ page }, testInfo) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto('/fundus');
|
||||
await expect(page.locator('.piece-card')).toHaveCount(4);
|
||||
await expect(page.locator('.piece-card')).toHaveCount(1);
|
||||
await page.evaluate(() => {
|
||||
const list = document.querySelector('.piece-grid');
|
||||
const rows = Array.from(document.querySelectorAll('.piece-card'));
|
||||
@@ -444,7 +792,7 @@ test('long titles and longer fundus lists reflow without overflow', async ({ pag
|
||||
}
|
||||
});
|
||||
|
||||
await expect(page.locator('.piece-card')).toHaveCount(16);
|
||||
await expect(page.locator('.piece-card')).toHaveCount(4);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await expectNoHorizontalOverflow(page);
|
||||
@@ -486,12 +834,71 @@ test('inbox, archive and administration stay concise', async ({ page }) => {
|
||||
expect(boxes[1]?.y).toBe(boxes[2]?.y);
|
||||
});
|
||||
|
||||
test('validated YouTube and TikTok players switch without exposing private fields', async ({
|
||||
page
|
||||
}, testInfo) => {
|
||||
addPresentationFixture();
|
||||
try {
|
||||
await page.context().route('https://www.youtube-nocookie.com/**', (route) =>
|
||||
route.fulfill({
|
||||
contentType: 'text/html',
|
||||
body: '<!doctype html><title>YouTube Test</title>'
|
||||
})
|
||||
);
|
||||
await page.context().route('https://www.tiktok.com/player/**', (route) =>
|
||||
route.fulfill({
|
||||
contentType: 'text/html',
|
||||
body: '<!doctype html><title>TikTok Test</title>'
|
||||
})
|
||||
);
|
||||
await page.goto('/live?show=e2e-presentation');
|
||||
const popupPromise = page.waitForEvent('popup');
|
||||
await page.getByRole('button', { name: 'Präsentationsfenster öffnen' }).click();
|
||||
const output = await popupPromise;
|
||||
await output.waitForLoadState('networkidle');
|
||||
await expect(output.getByText('Bereit für den nächsten Inhalt')).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'YouTube Kurzlink präsentieren' }).click();
|
||||
const youtube = output.locator('iframe[title="YouTube Vorschau"]');
|
||||
await expect(youtube).toHaveAttribute(
|
||||
'src',
|
||||
'https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ?start=75'
|
||||
);
|
||||
await expect(output.locator('.output-connection')).toHaveClass(/sr-only/);
|
||||
const publicState = await output.evaluate(async () =>
|
||||
JSON.stringify(await fetch(`${location.pathname}/state`).then((response) => response.json()))
|
||||
);
|
||||
expect(publicState).not.toContain('PRIVATE_RESEARCH');
|
||||
expect(publicState).not.toContain('PRIVATE_MODERATION');
|
||||
|
||||
await page.getByRole('button', { name: 'TikTok Hochformat präsentieren' }).click();
|
||||
await expect(youtube).toHaveCount(0);
|
||||
await expect(output.locator('iframe[title="TikTok Vorschau"]')).toHaveAttribute(
|
||||
'src',
|
||||
'https://www.tiktok.com/player/v1/6718335390845095173?autoplay=0&controls=1'
|
||||
);
|
||||
await expect(output.getByRole('link', { name: 'Original öffnen' })).toHaveAttribute(
|
||||
'href',
|
||||
'https://www.tiktok.com/@scout2015/video/6718335390845095173'
|
||||
);
|
||||
await output.setViewportSize({ width: 390, height: 844 });
|
||||
await expectNoHorizontalOverflow(output);
|
||||
await output.screenshot({
|
||||
path: testInfo.outputPath('heinzlive-video-mobile.png'),
|
||||
fullPage: true
|
||||
});
|
||||
await output.close();
|
||||
} finally {
|
||||
removePresentationFixture();
|
||||
}
|
||||
});
|
||||
|
||||
test('live control persists state and exposes only explicit presentation data', async ({
|
||||
page,
|
||||
browser
|
||||
}, testInfo) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto('/fundus?q=kleine+Konsole');
|
||||
await page.goto('/fundus?q=kleine+Konsole&includePlanned=1');
|
||||
await page.getByRole('heading', { name: 'Die kleine Konsole mit dem großen Dreh' }).click();
|
||||
const png = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
||||
@@ -701,3 +1108,50 @@ test('pieces autosave safely, detect conflicts and protect uploaded images', asy
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
|
||||
test('link preview uses one existing source, persists manual fields and isolates its test view', async ({
|
||||
page
|
||||
}, testInfo) => {
|
||||
await page.goto('/fundus/piece-console');
|
||||
await page.getByLabel('Linkvorschau').check();
|
||||
await page.getByRole('combobox', { name: 'Quelle', exact: true }).selectOption('0');
|
||||
await page.getByLabel('Titel', { exact: true }).last().fill('Manuell korrigierte Linkvorschau');
|
||||
await page.getByLabel('Anreißer').fill('Ein kurzer manueller Anreißer ohne Vorschaubild.');
|
||||
await expect(page.locator('.save-indicator')).toContainText('Gespeichert', { timeout: 10_000 });
|
||||
await page.reload();
|
||||
await expect(page.getByLabel('Linkvorschau')).toBeChecked();
|
||||
await expect(page.getByLabel('Titel', { exact: true }).last()).toHaveValue(
|
||||
'Manuell korrigierte Linkvorschau'
|
||||
);
|
||||
|
||||
const previewPromise = page.waitForEvent('popup');
|
||||
await page.getByRole('link', { name: 'Präsentation testen' }).click();
|
||||
const preview = await previewPromise;
|
||||
await preview.waitForLoadState('networkidle');
|
||||
await expect(preview.getByText('Isolierte Testansicht')).toBeVisible();
|
||||
await expect(
|
||||
preview.getByRole('heading', { name: 'Manuell korrigierte Linkvorschau' })
|
||||
).toBeVisible();
|
||||
await expect(preview.getByText('Ein kurzer manueller Anreißer ohne Vorschaubild.')).toBeVisible();
|
||||
await expect(preview.locator('.output-link-image')).toHaveCount(0);
|
||||
await preview.screenshot({
|
||||
path: testInfo.outputPath('linkvorschau-desktop.png'),
|
||||
fullPage: true
|
||||
});
|
||||
await preview.setViewportSize({ width: 390, height: 844 });
|
||||
await expectNoHorizontalOverflow(preview);
|
||||
await preview.screenshot({
|
||||
path: testInfo.outputPath('linkvorschau-mobile.png'),
|
||||
fullPage: true
|
||||
});
|
||||
await preview.close();
|
||||
|
||||
await page.locator('.source-row').first().getByLabel('URL').fill('http://127.0.0.1/private');
|
||||
await expect(page.locator('.save-indicator')).toContainText('Gespeichert', { timeout: 10_000 });
|
||||
const blockedPreview = page.waitForResponse((response) =>
|
||||
response.url().includes('?/loadPreview')
|
||||
);
|
||||
await page.getByRole('button', { name: 'Vorschau laden' }).click();
|
||||
expect((await blockedPreview).status()).toBe(400);
|
||||
await expect(page.getByRole('alert')).toContainText('Interne oder private Netzwerkziele');
|
||||
});
|
||||
Reference in new issue
Block a user