Files
2026-08-29 12:10:26 +02:00

285 lines
11 KiB
TypeScript

import { expect, test } from '@playwright/test';
import {
appendZipInput,
createZip,
dropFiles,
expectPdfDownload,
newZipButton,
replaceThroughDialog,
replaceZipInput,
settingsButton,
trackPageErrors,
waitForLoadedWorkspace,
workspaceStatus,
zipDropzone
} from './helpers';
test('beA reports an invalid ZIP and lets the user remove it', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{
name: 'defektes-archiv.zip',
mimeType: 'application/zip',
buffer: Buffer.from('kein gueltiges ZIP-Archiv')
}
]);
await expect(page.getByText('defektes-archiv.zip', { exact: true })).toBeVisible();
await expect(page.getByText('Konnte nicht geöffnet werden.')).toBeVisible();
await expect(page.getByRole('status')).toContainText('1 fehlgeschlagen');
await expect(page.getByRole('button', { name: 'Erneut versuchen' })).toBeVisible();
await page.getByRole('button', { name: 'Entfernen' }).click();
await expect(zipDropzone(page)).toBeVisible();
expect(pageErrors).toEqual([]);
});
test('cancelling the replacement confirmation keeps the current archive', async ({ page }) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a');
const zipB = await createZip('archiv-b');
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await replaceThroughDialog(page, zipB, false);
await expect(workspaceStatus(page, /1 ZIP · 1 Archiv geöffnet/)).toBeVisible();
await expect(page.getByText('2 ZIPs')).toHaveCount(0);
expect(pageErrors).toEqual([]);
});
test('confirming the replacement opens only the new ZIP and drops stale results', async ({
page
}) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a', 3);
const zipB = await createZip('archiv-b');
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
// Replace while the first ZIP may still be extracting; generation guarding
// must ensure no late result from archiv-a reappears.
await replaceThroughDialog(page, zipB, true);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await expect(page.getByText('1 lose Datei')).toHaveCount(1);
expect(pageErrors).toEqual([]);
});
test('cancelling the system file picker leaves the workspace unchanged', async ({ page }) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a');
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await newZipButton(page).click();
await replaceZipInput(page).setInputFiles([]);
await expect(page.getByText('Aktuelle Bearbeitung ersetzen?')).toHaveCount(0);
await expect(workspaceStatus(page, /1 ZIP · 1 Archiv geöffnet/)).toBeVisible();
expect(pageErrors).toEqual([]);
});
test('the replacement input accepts the same ZIP twice', async ({ page }) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a');
const zipB = await createZip('archiv-b');
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await replaceThroughDialog(page, zipB, true);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await replaceThroughDialog(page, zipB, true);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await expect(page.getByText('1 lose Datei')).toHaveCount(1);
expect(pageErrors).toEqual([]);
});
test('appending a ZIP keeps both archive editors visible', async ({ page }) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a');
const zipB = await createZip('archiv-b');
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await settingsButton(page).click();
await appendZipInput(page).setInputFiles([
{ name: 'archiv-b.zip', mimeType: 'application/zip', buffer: zipB }
]);
await waitForLoadedWorkspace(page, /2 ZIPs · 2 Archive geöffnet/);
await expect(page.getByText('1 lose Datei')).toHaveCount(2);
expect(pageErrors).toEqual([]);
});
test('bulk export only appears for two or more processed archives', async ({ page }) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a');
const zipB = await createZip('archiv-b');
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await settingsButton(page).click();
await expect(
page.getByRole('button', { name: 'Alle Archive als PDF herunterladen' })
).toHaveCount(0);
await appendZipInput(page).setInputFiles([
{ name: 'archiv-b.zip', mimeType: 'application/zip', buffer: zipB }
]);
await waitForLoadedWorkspace(page, /2 ZIPs · 2 Archive geöffnet/);
// The popover stays open, so the new bulk export action appears in place.
const exportButton = page.getByRole('button', { name: 'Alle Archive als PDF herunterladen' });
await expect(exportButton).toBeVisible();
const downloadPromise = page.waitForEvent('download');
await exportButton.click();
await expectPdfDownload(await downloadPromise, /\.pdf$/);
expect(pageErrors).toEqual([]);
});
test('clearing the workspace requires confirmation and returns to the dropzone', async ({
page
}) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a');
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await settingsButton(page).click();
await page.getByRole('button', { name: 'Aktuelle Bearbeitung leeren' }).click();
await expect(page.getByText('Aktuelle Bearbeitung leeren?')).toBeVisible();
await page.getByRole('button', { name: 'Abbrechen' }).click();
await expect(workspaceStatus(page, /1 ZIP · 1 Archiv geöffnet/)).toBeVisible();
await page.getByRole('button', { name: 'Aktuelle Bearbeitung leeren' }).click();
await page.getByRole('button', { name: 'Bearbeitung leeren', exact: true }).click();
await expect(zipDropzone(page)).toBeVisible();
await expect(newZipButton(page)).toHaveCount(0);
expect(pageErrors).toEqual([]);
});
test('the preview-size slider works with arrow keys and survives a reload', async ({ page }) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a');
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await expect(page.locator('li[style*="width: 200px"]').first()).toBeVisible();
await settingsButton(page).click();
const slider = page.getByRole('slider', { name: 'Vorschaugröße' });
await expect(page.getByText('200 px')).toBeVisible();
await slider.focus();
await page.keyboard.press('ArrowRight');
await expect(page.getByText('201 px')).toBeVisible();
await expect(page.locator('li[style*="width: 201px"]').first()).toBeVisible();
await page.reload();
await page.locator('[data-dropzone-ready="true"]').first().waitFor();
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await settingsButton(page).click();
await expect(page.getByText('201 px')).toBeVisible();
await expect(await page.evaluate(() => localStorage.getItem('thumbnailWidth'))).toBe('201');
expect(pageErrors).toEqual([]);
});
test('settings closes on Escape and outside click and returns focus to its trigger', async ({
page
}) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a');
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await expect(settingsButton(page)).toHaveAccessibleName('Einstellungen');
await settingsButton(page).click();
await expect(page.getByText('Vorschaugröße')).toBeVisible();
await page.keyboard.press('Escape');
await expect(page.getByText('Vorschaugröße')).toHaveCount(0);
await expect(settingsButton(page)).toBeFocused();
await settingsButton(page).click();
await expect(page.getByText('Vorschaugröße')).toBeVisible();
await page.getByRole('heading', { name: 'beA-Edit' }).click();
await expect(page.getByText('Vorschaugröße')).toHaveCount(0);
expect(pageErrors).toEqual([]);
});
test('mobile toolbar and settings popover stay inside the viewport', async ({ page }) => {
const pageErrors = trackPageErrors(page);
const zipA = await createZip('archiv-a');
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/tools/bea');
await dropFiles(page, zipDropzone(page), [
{ name: 'archiv-a.zip', mimeType: 'application/zip', buffer: zipA }
]);
await waitForLoadedWorkspace(page, /1 ZIP · 1 Archiv geöffnet/);
await settingsButton(page).click();
const popover = page.locator('[data-slot="popover-content"]');
await expect(popover).toBeVisible();
await expect(page.getByText('Weitere ZIP hinzufügen')).toBeVisible();
const popoverBox = await popover.boundingBox();
expect(popoverBox).not.toBeNull();
expect(popoverBox!.x).toBeGreaterThanOrEqual(0);
expect(popoverBox!.x + popoverBox!.width).toBeLessThanOrEqual(390);
expect(popoverBox!.y).toBeGreaterThanOrEqual(0);
expect(popoverBox!.y + popoverBox!.height).toBeLessThanOrEqual(844);
const popoverMetrics = await popover.evaluate((element) => ({
scrollWidth: element.scrollWidth,
clientWidth: element.clientWidth
}));
expect(popoverMetrics.scrollWidth).toBeLessThanOrEqual(popoverMetrics.clientWidth);
// The toolbar sits in normal flow, so it must not cover the archive header.
const toolbarBox = await workspaceStatus(page, /1 ZIP · 1 Archiv geöffnet/).boundingBox();
const archiveHeaderBox = await page.getByText('1 lose Datei').first().boundingBox();
expect(toolbarBox).not.toBeNull();
expect(archiveHeaderBox).not.toBeNull();
expect(toolbarBox!.y + toolbarBox!.height).toBeLessThanOrEqual(archiveHeaderBox!.y);
expect(pageErrors).toEqual([]);
});