Merge feature/web-3: lokale Webseiten-Vorschau (GET /api/preview/{**path} + /webseite/vorschau iframe-Vorschau mit Geraete-Umschalter) [god-QA validated]
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m51s
CI / Docker Build & Push (push) Has been cancelled
CI / Frontend Tests (Node/Vite) (push) Has been cancelled

This commit is contained in:
2026-06-06 14:22:31 +02:00
10 changed files with 398 additions and 8 deletions

View File

@@ -135,6 +135,29 @@ export async function installMockApi(page: Page): Promise<MockDb> {
if (method === 'PUT') return json(route, 204)
}
// ── WEB-3: lokale Vorschau (Renderer-Dateien; nur veröffentlichte Seiten) ──
if (path === '/render-site' && method === 'GET') {
const files = db.pages
.filter((p) => p.status === 'Published')
.map((p) => ({ path: p.slug === 'start' ? 'index.html' : `${p.slug}/index.html`, size: 1000 }))
return json(route, 200, [{ path: 'assets/site.css', size: 500 }, ...files])
}
const pv = path.match(/^\/preview(?:\/(.*))?$/)
if (pv && method === 'GET') {
const key = pv[1] ? pv[1].replace(/\/$/, '') : 'index.html'
if (key === 'assets/site.css') {
return route.fulfill({ status: 200, contentType: 'text/css', body: 'body{font-family:sans-serif}' })
}
const slug = key === 'index.html' ? 'start' : key.replace(/\/index\.html$/, '')
const p = db.pages.find((x) => x.slug === slug && x.status === 'Published')
if (!p) return json(route, 404, { title: 'Not Found' })
return route.fulfill({
status: 200,
contentType: 'text/html; charset=utf-8',
body: `<!doctype html><html lang="de"><head><meta charset="utf-8"><title>${p.title}</title></head><body><h1>${p.title}</h1><p>Vorschau-Mock</p></body></html>`,
})
}
// ── INBOX-1: Anfragen-Posteingang (vor den generischen Kollektionen) ──
if (path === '/requests/sync' && method === 'POST') {
return json(route, 200, db.mailConfigured ? { imported: 0, error: null } : { imported: 0, error: 'MailNotConfigured' })

View File

@@ -0,0 +1,66 @@
/**
* WEB-3: Lokale Vorschau der öffentlichen Webseite — iframe lädt die
* gerenderte Seite, Seiten-Wechsler (nur Veröffentlichte), Smartphone/
* Desktop-Umschalter, Einstiege von der Übersicht.
* Mock-gebunden (Seed-Seiten) → skipUnlessMock.
*/
import { de, expect, skipUnlessMock, test } from './fixtures'
const tw = de.pages.webseite
const tv = tw.vorschau
test.describe('Webseiten-Vorschau', () => {
test('Vorschau öffnet die gerenderte Startseite im iframe', async ({ page }) => {
skipUnlessMock()
await page.goto('/webseite')
await page.getByRole('link', { name: tv.button }).click()
await expect(page.getByRole('heading', { name: tv.title })).toBeVisible()
await expect(page.getByText(tv.intro)).toBeVisible()
// Inhalt der gerenderten Seite (Mock-HTML) ist im iframe sichtbar
const frame = page.frameLocator('.vorschau-iframe')
await expect(frame.getByRole('heading', { name: 'Startseite' })).toBeVisible()
})
test('Seiten-Wechsler listet nur Veröffentlichte und wechselt die Vorschau', async ({ page }) => {
skipUnlessMock()
await page.goto('/webseite/vorschau')
const select = page.getByLabel(tv.pageSelect)
// 2 veröffentlichte Seed-Seiten; Entwürfe (z. B. „Über die Zucht“) fehlen
await expect(select.locator('option')).toHaveCount(2)
await expect(select.locator('option', { hasText: 'Über die Zucht' })).toHaveCount(0)
await select.selectOption({ label: 'Abgabetiere' })
const frame = page.frameLocator('.vorschau-iframe')
await expect(frame.getByRole('heading', { name: 'Abgabetiere' })).toBeVisible()
})
test('Smartphone/Desktop-Umschalter ändert die Rahmenbreite', async ({ page }) => {
skipUnlessMock()
await page.goto('/webseite/vorschau')
// Standard: Smartphone-Rahmen
await expect(page.locator('.vorschau-frame--phone')).toBeVisible()
await page.getByRole('button', { name: tv.viewDesktop }).click()
await expect(page.locator('.vorschau-frame--phone')).toHaveCount(0)
await page.getByRole('button', { name: tv.viewPhone }).click()
await expect(page.locator('.vorschau-frame--phone')).toBeVisible()
})
test('Übersicht: „Ansehen“ nur bei Veröffentlichten, öffnet die richtige Seite', async ({ page }) => {
skipUnlessMock()
await page.goto('/webseite')
// Veröffentlichte Karte hat den Ansehen-Link, Entwurf nicht
const abgabeCard = page.locator('.webseite-card', { hasText: 'Abgabetiere' })
const draftCard = page.locator('.webseite-card', { hasText: 'Über die Zucht' })
await expect(abgabeCard.getByRole('link', { name: tv.openPage })).toBeVisible()
await expect(draftCard.getByRole('link', { name: tv.openPage })).toHaveCount(0)
await abgabeCard.getByRole('link', { name: tv.openPage }).click()
await expect(page).toHaveURL(/\/webseite\/vorschau\?seite=abgabetiere/)
const frame = page.frameLocator('.vorschau-iframe')
await expect(frame.getByRole('heading', { name: 'Abgabetiere' })).toBeVisible()
})
})