import { expect, test } from "@playwright/test"; // Default rich fixture: all fields populated (hero_image, hero_vector, intro). const pageUrl = process.env.TEST_PAGE_URL || "http://community-works-collaborative.test/sample-page/"; // No-intro fixture: `intro` ACF field intentionally empty. const noIntroUrl = process.env.TEST_PAGE_URL_NO_INTRO || "http://community-works-collaborative.test/inner-page-no-intro/"; // No-asset fixture: `hero_image` and `hero_vector` ACF fields intentionally empty. const noAssetUrl = process.env.TEST_PAGE_URL_NO_ASSET || "http://community-works-collaborative.test/inner-page-no-asset/"; const getBox = async (locator) => { const box = await locator.boundingBox(); expect(box).not.toBeNull(); return box; }; const expectNear = (actual, expected, tolerance = 3) => { expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance); }; test.describe("inner page layout", () => { test("desktop: hero, intro, body render in order with the new media layers", async ({ page, }) => { const pageErrors = []; page.on("pageerror", (err) => pageErrors.push(err.message)); await page.setViewportSize({ width: 1280, height: 900 }); await page.goto(pageUrl); const hero = page.locator(".page-hero").first(); const intro = page.locator(".page-intro").first(); const content = page.locator(".entry-content").first(); await expect(hero).toBeVisible(); await expect(intro).toBeVisible(); await expect(content).toBeVisible(); // Order check: hero, then intro, then content. const heroBox = await getBox(hero); const introBox = await getBox(intro); const contentBox = await getBox(content); expect(heroBox.y).toBeLessThan(introBox.y); expect(introBox.y).toBeLessThan(contentBox.y); // Heading is rendered inside the hero. await expect(hero.locator("h1").first()).toBeVisible(); // Intro is centered and capped at the max-w-3xl width. const introInner = intro.locator(".content-wrapper").first(); const introInnerBox = await getBox(introInner); expect(introInnerBox.width).toBeLessThanOrEqual(768 + 3); // Page renders without JS errors. expect(pageErrors).toEqual([]); }); test("mobile 402: hero, intro, body order and intro width", async ({ page, }) => { const pageErrors = []; page.on("pageerror", (err) => pageErrors.push(err.message)); await page.setViewportSize({ width: 402, height: 874 }); await page.goto(pageUrl); const hero = page.locator(".page-hero").first(); const intro = page.locator(".page-intro").first(); const content = page.locator(".entry-content").first(); await expect(hero).toBeVisible(); await expect(intro).toBeVisible(); await expect(content).toBeVisible(); const heroBox = await getBox(hero); const introBox = await getBox(intro); const contentBox = await getBox(content); expect(heroBox.y).toBeLessThan(introBox.y); expect(introBox.y).toBeLessThan(contentBox.y); // Intro fits within the mobile viewport. const introInner = intro.locator(".content-wrapper").first(); const introInnerBox = await getBox(introInner); expect(introInnerBox.x + introInnerBox.width).toBeLessThanOrEqual( 402 + 3, ); // No JS errors. expect(pageErrors).toEqual([]); }); test("mobile 320: nothing clips horizontally", async ({ page }) => { await page.setViewportSize({ width: 320, height: 800 }); await page.goto(pageUrl); const hero = page.locator(".page-hero").first(); const intro = page.locator(".page-intro").first(); await expect(hero).toBeVisible(); await expect(intro).toBeVisible(); const heroBox = await getBox(hero); const introBox = await getBox(intro); expect(heroBox.x + heroBox.width).toBeLessThanOrEqual(320 + 3); expect(introBox.x + introBox.width).toBeLessThanOrEqual(320 + 3); }); test("mobile 767: layout still holds before the desktop breakpoint", async ({ page, }) => { await page.setViewportSize({ width: 767, height: 900 }); await page.goto(pageUrl); const hero = page.locator(".page-hero").first(); const intro = page.locator(".page-intro").first(); await expect(hero).toBeVisible(); await expect(intro).toBeVisible(); const heroBox = await getBox(hero); const introBox = await getBox(intro); expect(heroBox.x + heroBox.width).toBeLessThanOrEqual(767 + 3); expect(introBox.x + introBox.width).toBeLessThanOrEqual(767 + 3); }); // Spec line 113: "A page with no `intro` field renders hero + body with no empty intro wrapper." test("no-intro: hero and body render with no .page-intro wrapper", async ({ page, }) => { await page.setViewportSize({ width: 1280, height: 900 }); await page.goto(noIntroUrl); const hero = page.locator(".page-hero").first(); const content = page.locator(".entry-content").first(); await expect(hero).toBeVisible(); await expect(content).toBeVisible(); // No empty intro wrapper rendered when the intro field is empty. expect(await page.locator(".page-intro").count()).toBe(0); // Hero and body still render in the expected order. const heroBox = await getBox(hero); const contentBox = await getBox(content); expect(heroBox.y).toBeLessThan(contentBox.y); // Heading is still inside the hero. await expect(hero.locator("h1").first()).toBeVisible(); }); // Spec line 114: "A page with no `hero_image` and no `hero_vector` renders the existing dark text-only hero unchanged." test("no-asset: dark text-only hero renders without image or vector", async ({ page, }) => { await page.setViewportSize({ width: 1280, height: 900 }); await page.goto(noAssetUrl); const hero = page.locator(".page-hero").first(); await expect(hero).toBeVisible(); // No media layers when both assets are empty. expect(await page.locator(".page-hero__media").count()).toBe(0); expect(await page.locator(".page-hero__vector").count()).toBe(0); // Heading still renders inside the hero. await expect(hero.locator("h1").first()).toBeVisible(); // Hero still exposes the dark text-only class hook. await expect(hero).toHaveClass(/page-hero/); }); });