✨ feat: add new contact form, hero, features, and richtext blocks; implement scroll-reveal action and update styles
- Introduced ContactFormBlock, FeaturesBlock, HeroBlock, and RichtextBlock components. - Implemented a scroll-reveal action for animations on element visibility. - Enhanced CSS styles for better theming and prose formatting. - Added localization support for new components and updated existing translations. - Created e2e tests for demo pages including contact form validation and navigation. - Added a video tour showcasing the demo pages and interactions.
This commit is contained in:
218
tests/e2e/demo.spec.ts
Normal file
218
tests/e2e/demo.spec.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import { test, expect, waitForSpaReady, navigateToRoute, clickSpaLink } from "./fixtures"
|
||||
|
||||
/**
|
||||
* Helper: Force all scroll-reveal elements to be visible.
|
||||
* The `.reveal` class starts with opacity:0 and only animates in
|
||||
* when the IntersectionObserver fires — which doesn't happen
|
||||
* reliably in headless Playwright screenshots/assertions.
|
||||
*/
|
||||
async function revealAll(page: import("@playwright/test").Page) {
|
||||
await page.evaluate(() => document.querySelectorAll(".reveal").forEach((e) => e.classList.add("revealed")))
|
||||
}
|
||||
|
||||
test.describe("Demo — Homepage", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/de/")
|
||||
await waitForSpaReady(page)
|
||||
await revealAll(page)
|
||||
})
|
||||
|
||||
test("should render hero section with headline and CTA", async ({ page }) => {
|
||||
const hero = page.locator("[data-block='hero']").first()
|
||||
await expect(hero).toBeVisible()
|
||||
|
||||
const h1 = hero.locator("h1")
|
||||
await expect(h1).toBeVisible()
|
||||
await expect(h1).not.toBeEmpty()
|
||||
|
||||
const cta = hero.locator("a[href*='#']")
|
||||
await expect(cta).toBeVisible()
|
||||
})
|
||||
|
||||
test("should render features section with cards", async ({ page }) => {
|
||||
const features = page.locator("[data-block='features']")
|
||||
await expect(features).toBeVisible()
|
||||
|
||||
const heading = features.locator("h2")
|
||||
await expect(heading).toBeVisible()
|
||||
|
||||
const cards = features.locator(".feature-card")
|
||||
await expect(cards).toHaveCount(6)
|
||||
})
|
||||
|
||||
test("should render richtext section with image", async ({ page }) => {
|
||||
const richtext = page.locator("[data-block='richtext']").first()
|
||||
await expect(richtext).toBeVisible()
|
||||
|
||||
const img = richtext.locator("img")
|
||||
await expect(img).toBeVisible()
|
||||
})
|
||||
|
||||
test("should render accordion with expandable items", async ({ page }) => {
|
||||
const accordion = page.locator("[data-block='accordion']")
|
||||
await expect(accordion).toBeVisible()
|
||||
|
||||
const buttons = accordion.locator("button")
|
||||
const count = await buttons.count()
|
||||
expect(count).toBeGreaterThanOrEqual(3)
|
||||
|
||||
// First item should be expanded by default
|
||||
const firstButton = buttons.first()
|
||||
await expect(firstButton).toHaveAttribute("aria-expanded", "true")
|
||||
|
||||
// Click a collapsed item to expand it
|
||||
const secondButton = buttons.nth(1)
|
||||
await expect(secondButton).toHaveAttribute("aria-expanded", "false")
|
||||
await secondButton.click()
|
||||
await expect(secondButton).toHaveAttribute("aria-expanded", "true")
|
||||
})
|
||||
|
||||
test("should have footer with navigation and language selector", async ({ page }) => {
|
||||
const footer = page.locator("footer")
|
||||
await expect(footer).toBeVisible()
|
||||
|
||||
const navLinks = footer.locator("nav a, ul a")
|
||||
const linkCount = await navLinks.count()
|
||||
expect(linkCount).toBeGreaterThanOrEqual(3)
|
||||
|
||||
// Language links in footer
|
||||
const deLangLink = footer.locator('a:has-text("Deutsch")')
|
||||
const enLangLink = footer.locator('a:has-text("English")')
|
||||
await expect(deLangLink).toBeVisible()
|
||||
await expect(enLangLink).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Demo — About Page", () => {
|
||||
test("should load about page with hero and content", async ({ page }) => {
|
||||
await page.goto("/en/about")
|
||||
await waitForSpaReady(page)
|
||||
await revealAll(page)
|
||||
|
||||
const hero = page.locator("[data-block='hero']").first()
|
||||
await expect(hero).toBeVisible()
|
||||
|
||||
const h1 = hero.locator("h1")
|
||||
await expect(h1).toContainText("About")
|
||||
|
||||
const richtextBlocks = page.locator("[data-block='richtext']")
|
||||
const count = await richtextBlocks.count()
|
||||
expect(count).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Demo — Contact Page", () => {
|
||||
test("should load contact page with form", async ({ page }) => {
|
||||
await page.goto("/en/contact")
|
||||
await waitForSpaReady(page)
|
||||
await revealAll(page)
|
||||
|
||||
const hero = page.locator("[data-block='hero']").first()
|
||||
await expect(hero).toBeVisible()
|
||||
|
||||
const form = page.locator("[data-block='contact-form']")
|
||||
await expect(form).toBeVisible()
|
||||
})
|
||||
|
||||
test("should have all form fields", async ({ page }) => {
|
||||
await page.goto("/en/contact")
|
||||
await waitForSpaReady(page)
|
||||
await revealAll(page)
|
||||
|
||||
await expect(page.getByLabel("Name")).toBeVisible()
|
||||
await expect(page.getByLabel("Email")).toBeVisible()
|
||||
await expect(page.locator("select, [role='combobox']").first()).toBeVisible()
|
||||
await expect(page.getByLabel("Message")).toBeVisible()
|
||||
await expect(page.locator("button[type='submit'], button:has-text('Send')")).toBeVisible()
|
||||
})
|
||||
|
||||
test("should validate required fields", async ({ page }) => {
|
||||
await page.goto("/en/contact")
|
||||
await waitForSpaReady(page)
|
||||
await revealAll(page)
|
||||
|
||||
// Click send without filling fields
|
||||
const submitBtn = page.locator("button[type='submit'], button:has-text('Send')").first()
|
||||
await submitBtn.click()
|
||||
|
||||
// Should show validation errors (form stays, no success toast)
|
||||
await expect(page.locator("[data-block='contact-form']")).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Demo — Navigation", () => {
|
||||
test("should navigate between pages via header links", async ({ page }) => {
|
||||
await page.goto("/en/")
|
||||
await waitForSpaReady(page)
|
||||
|
||||
// Navigate to About
|
||||
await page.locator('header nav a[href*="/about"]').click()
|
||||
await page.waitForLoadState("domcontentloaded")
|
||||
expect(page.url()).toContain("/about")
|
||||
await expect(page.locator("[data-block='hero'] h1")).toContainText("About")
|
||||
|
||||
// Navigate to Contact
|
||||
await page.locator('header nav a[href*="/contact"]').click()
|
||||
await page.waitForLoadState("domcontentloaded")
|
||||
expect(page.url()).toContain("/contact")
|
||||
await expect(page.locator("[data-block='hero'] h1")).toContainText("Contact")
|
||||
|
||||
// Navigate back to Home
|
||||
await page.locator('header a[href="/en"]').first().click()
|
||||
await page.waitForLoadState("domcontentloaded")
|
||||
expect(page.url()).toMatch(/\/en\/?$/)
|
||||
})
|
||||
|
||||
test("should switch language with route translation", async ({ page }) => {
|
||||
// Start on English about page
|
||||
await page.goto("/en/about")
|
||||
await waitForSpaReady(page)
|
||||
expect(page.url()).toContain("/en/about")
|
||||
|
||||
// Click German language link — should translate route to /de/ueber-uns
|
||||
const deLink = page.locator('a[href*="/de/ueber-uns"]').first()
|
||||
await expect(deLink).toBeVisible()
|
||||
await deLink.click()
|
||||
await page.waitForLoadState("domcontentloaded")
|
||||
expect(page.url()).toContain("/de/ueber-uns")
|
||||
|
||||
// Verify hero updated to German
|
||||
await expect(page.locator("[data-block='hero'] h1")).toBeVisible()
|
||||
})
|
||||
|
||||
test("should switch language on contact page with route translation", async ({ page }) => {
|
||||
await page.goto("/en/contact")
|
||||
await waitForSpaReady(page)
|
||||
|
||||
const deLink = page.locator('a[href*="/de/kontakt"]').first()
|
||||
await expect(deLink).toBeVisible()
|
||||
await deLink.click()
|
||||
await page.waitForLoadState("domcontentloaded")
|
||||
expect(page.url()).toContain("/de/kontakt")
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Demo — 404 Page", () => {
|
||||
test("should show 404 for unknown routes", async ({ page }) => {
|
||||
await page.goto("/en/nonexistent-page")
|
||||
await waitForSpaReady(page)
|
||||
|
||||
await expect(page.locator("text=404")).toBeVisible()
|
||||
await expect(page.locator("h1")).toContainText("Page not found")
|
||||
|
||||
// Use the specific "Back to Home" link in the 404 main content, not header/footer
|
||||
const homeLink = page.getByRole("link", { name: "Back to Home" })
|
||||
await expect(homeLink).toBeVisible()
|
||||
})
|
||||
|
||||
test("should navigate back from 404 to home", async ({ page }) => {
|
||||
await page.goto("/en/nonexistent-page")
|
||||
await waitForSpaReady(page)
|
||||
|
||||
const homeLink = page.getByRole("link", { name: "Back to Home" })
|
||||
await homeLink.click()
|
||||
await page.waitForLoadState("domcontentloaded")
|
||||
expect(page.url()).toMatch(/\/en\/?$/)
|
||||
await expect(page.locator("[data-block='hero']")).toBeVisible()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user