Integrate Prisma for content; enhance SEO, i18n, and deployment workflows

Co-authored-by: dennis <dennis@konkol.net>
This commit is contained in:
Cursor Agent
2026-01-12 15:27:35 +00:00
parent f1cc398248
commit 423a2af938
38 changed files with 757 additions and 629 deletions

27
e2e/consent.spec.ts Normal file
View File

@@ -0,0 +1,27 @@
import { test, expect } from "@playwright/test";
test.describe("Consent banner", () => {
test("banner shows and can be accepted", async ({ page, context }) => {
// Start clean
await context.clearCookies();
await page.goto("/en", { waitUntil: "domcontentloaded" });
// Banner should appear on public pages when no consent is set yet
const bannerTitle = page.getByText(/Privacy settings|Datenschutz-Einstellungen/i);
await expect(bannerTitle).toBeVisible({ timeout: 10000 });
// Accept all
const acceptAll = page.getByRole("button", { name: /Accept all|Alles akzeptieren/i });
await acceptAll.click();
// Banner disappears
await expect(bannerTitle).toBeHidden({ timeout: 10000 });
// Cookie is written
const cookies = await context.cookies();
const consentCookie = cookies.find((c) => c.name === "dk0_consent_v1");
expect(consentCookie).toBeTruthy();
});
});

View File

@@ -6,7 +6,7 @@ import { test, expect } from '@playwright/test';
*/
test.describe('Critical Paths', () => {
test('Home page loads and displays correctly', async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle' });
await page.goto('/en', { waitUntil: 'networkidle' });
// Wait for page to be fully loaded
await page.waitForLoadState('domcontentloaded');
@@ -25,7 +25,7 @@ test.describe('Critical Paths', () => {
});
test('Projects page loads and displays projects', async ({ page }) => {
await page.goto('/projects', { waitUntil: 'networkidle' });
await page.goto('/en/projects', { waitUntil: 'networkidle' });
// Wait for projects to load
await page.waitForLoadState('domcontentloaded');
@@ -45,7 +45,7 @@ test.describe('Critical Paths', () => {
test('Individual project page loads', async ({ page }) => {
// First, get a project slug from the projects page
await page.goto('/projects', { waitUntil: 'networkidle' });
await page.goto('/en/projects', { waitUntil: 'networkidle' });
await page.waitForLoadState('domcontentloaded');
// Try to find a project link

View File

@@ -20,7 +20,7 @@ test.describe('Hydration Tests', () => {
});
// Navigate to home page
await page.goto('/', { waitUntil: 'networkidle' });
await page.goto('/en', { waitUntil: 'networkidle' });
await page.waitForLoadState('domcontentloaded');
// Check for hydration errors
@@ -51,7 +51,7 @@ test.describe('Hydration Tests', () => {
}
});
await page.goto('/');
await page.goto('/en');
await page.waitForLoadState('networkidle');
// Check for duplicate key warnings
@@ -71,11 +71,11 @@ test.describe('Hydration Tests', () => {
}
});
await page.goto('/', { waitUntil: 'networkidle' });
await page.goto('/en', { waitUntil: 'networkidle' });
await page.waitForLoadState('domcontentloaded');
// Navigate to projects page via link
const projectsLink = page.locator('a[href="/projects"], a[href*="projects"]').first();
const projectsLink = page.locator('a[href*="/projects"]').first();
if (await projectsLink.count() > 0) {
await projectsLink.click();
await page.waitForLoadState('domcontentloaded');
@@ -90,7 +90,7 @@ test.describe('Hydration Tests', () => {
});
test('Server and client HTML match', async ({ page }) => {
await page.goto('/');
await page.goto('/en');
// Get initial HTML
const initialHTML = await page.content();
@@ -108,7 +108,7 @@ test.describe('Hydration Tests', () => {
});
test('Interactive elements work after hydration', async ({ page }) => {
await page.goto('/');
await page.goto('/en');
await page.waitForLoadState('networkidle');
// Try to find and click interactive elements

17
e2e/i18n.spec.ts Normal file
View File

@@ -0,0 +1,17 @@
import { test, expect } from "@playwright/test";
test.describe("i18n routing", () => {
test("language switcher navigates between locales", async ({ page }) => {
await page.goto("/en", { waitUntil: "domcontentloaded" });
// Buttons are "EN"/"DE" in the header
const deButton = page.getByRole("button", { name: "DE" });
if (await deButton.count()) {
await deButton.click();
await expect(page).toHaveURL(/\/de(\/|$)/);
} else {
test.skip();
}
});
});

22
e2e/seo.spec.ts Normal file
View File

@@ -0,0 +1,22 @@
import { test, expect } from "@playwright/test";
test.describe("SEO endpoints", () => {
test("robots.txt is served and contains sitemap", async ({ request }) => {
const res = await request.get("/robots.txt");
expect(res.ok()).toBeTruthy();
const txt = await res.text();
expect(txt).toContain("User-agent:");
expect(txt).toContain("Sitemap:");
});
test("sitemap.xml is served and contains locale routes", async ({ request }) => {
const res = await request.get("/sitemap.xml");
expect(res.ok()).toBeTruthy();
const xml = await res.text();
expect(xml).toContain('<urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9">');
// At least the localized home routes should exist
expect(xml).toMatch(/\/en<\/loc>/);
expect(xml).toMatch(/\/de<\/loc>/);
});
});