diff --git a/.gitignore b/.gitignore index 337e8a4..33c81de 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,9 @@ npm-cache/ # Per-article body ornaments (Figma Content page Template image fills) !public/content/blog/*-ornament-*.png +# Marketing book cover (raster; see docs/guides/static-assets.md) +!public/assets/marketing/community-rules-cover.png + # Visual regression snapshots (allow these) !tests/e2e/visual-regression.spec.ts-snapshots/ !tests/e2e/visual-regression.spec.ts-snapshots/*.png diff --git a/app/(marketing)/about/page.tsx b/app/(marketing)/about/page.tsx index a3695e1..bc3e882 100644 --- a/app/(marketing)/about/page.tsx +++ b/app/(marketing)/about/page.tsx @@ -1,5 +1,10 @@ import messages from "../../../messages/en/index"; -import { getAssetPath, governanceBookletPath } from "../../../lib/assetUtils"; +import { + ASSETS, + getAssetPath, + governanceBookletPath, + structureBeforeCrisisPath, +} from "../../../lib/assetUtils"; import { getTranslation } from "../../../lib/i18n/getTranslation"; import AboutHeader from "../../components/type/AboutHeader"; import type { AboutHeaderSegment } from "../../components/type/AboutHeader"; @@ -53,11 +58,20 @@ export default function AboutPage() { /> + - + - - - - - - - - - - - diff --git a/public/assets/marketing/governance-booklet.pdf b/public/assets/marketing/governance-booklet.pdf index ae19c1e..241adce 100644 Binary files a/public/assets/marketing/governance-booklet.pdf and b/public/assets/marketing/governance-booklet.pdf differ diff --git a/public/assets/marketing/structure-before-crisis.pdf b/public/assets/marketing/structure-before-crisis.pdf new file mode 100644 index 0000000..ae19c1e Binary files /dev/null and b/public/assets/marketing/structure-before-crisis.pdf differ diff --git a/stories/sections/Book.stories.js b/stories/sections/Book.stories.js index 5d94148..3af7ec3 100644 --- a/stories/sections/Book.stories.js +++ b/stories/sections/Book.stories.js @@ -1,5 +1,10 @@ import Book from "../../app/components/sections/Book"; -import { getAssetPath, governanceBookletPath } from "../../lib/assetUtils"; +import { + ASSETS, + getAssetPath, + governanceBookletPath, + structureBeforeCrisisPath, +} from "../../lib/assetUtils"; import messages from "../../messages/en/pages/about.json"; export default { @@ -11,12 +16,27 @@ export default { }, }; +const structureBeforeCrisis = messages.structureBeforeCrisis; +const communityRules = messages.communityRules; + export const Default = { args: { - title: messages.book.title, - description: messages.book.description, - buttonText: messages.book.buttonText, - buttonHref: getAssetPath(governanceBookletPath()), - imageAlt: messages.book.imageAlt, + title: structureBeforeCrisis.title, + description: structureBeforeCrisis.description, + buttonText: structureBeforeCrisis.buttonText, + buttonHref: getAssetPath(structureBeforeCrisisPath()), + imageSrc: getAssetPath(ASSETS.STRUCTURE_BEFORE_CRISIS_COVER), + imageAlt: structureBeforeCrisis.imageAlt, + }, +}; + +export const CommunityRules = { + args: { + title: communityRules.title, + description: communityRules.description, + buttonText: communityRules.buttonText, + buttonHref: getAssetPath(governanceBookletPath()), + imageSrc: getAssetPath(ASSETS.COMMUNITY_RULES_COVER), + imageAlt: communityRules.imageAlt, }, }; diff --git a/tests/pages/about.test.tsx b/tests/pages/about.test.tsx new file mode 100644 index 0000000..02cd578 --- /dev/null +++ b/tests/pages/about.test.tsx @@ -0,0 +1,66 @@ +import { afterEach, describe, expect, test } from "vitest"; +import { within } from "@testing-library/react"; +import { + cleanup, + renderWithProviders as render, + screen, +} from "../utils/test-utils"; +import AboutPage from "../../app/(marketing)/about/page"; +import { + ASSETS, + getAssetPath, + governanceBookletPath, + structureBeforeCrisisPath, +} from "../../lib/assetUtils"; +import messages from "../../messages/en/index"; + +afterEach(() => { + cleanup(); +}); + +function bookSection(title: string) { + const heading = screen.getByRole("heading", { name: title }); + const section = heading.closest("section"); + expect(section).not.toBeNull(); + return within(section as HTMLElement); +} + +describe("AboutPage", () => { + test("pairs each book cover, heading, alt, and download with the same title", () => { + render(); + const page = messages.pages.about; + + const structureBeforeCrisis = bookSection(page.structureBeforeCrisis.title); + expect( + structureBeforeCrisis.getByText(page.structureBeforeCrisis.description), + ).toBeInTheDocument(); + expect( + structureBeforeCrisis.getByRole("img", { + name: page.structureBeforeCrisis.imageAlt, + }), + ).toHaveAttribute( + "src", + getAssetPath(ASSETS.STRUCTURE_BEFORE_CRISIS_COVER), + ); + expect( + structureBeforeCrisis.getByRole("link", { + name: page.structureBeforeCrisis.buttonText, + }), + ).toHaveAttribute("href", getAssetPath(structureBeforeCrisisPath())); + + const communityRules = bookSection(page.communityRules.title); + expect( + communityRules.getByText(page.communityRules.description), + ).toBeInTheDocument(); + expect( + communityRules.getByRole("img", { + name: page.communityRules.imageAlt, + }), + ).toHaveAttribute("src", getAssetPath(ASSETS.COMMUNITY_RULES_COVER)); + expect( + communityRules.getByRole("link", { + name: page.communityRules.buttonText, + }), + ).toHaveAttribute("href", getAssetPath(governanceBookletPath())); + }); +}); diff --git a/tests/unit/lib/governanceBooklet.test.ts b/tests/unit/lib/governanceBooklet.test.ts index 185d4cc..319c9ea 100644 --- a/tests/unit/lib/governanceBooklet.test.ts +++ b/tests/unit/lib/governanceBooklet.test.ts @@ -1,18 +1,21 @@ -import { existsSync, readFileSync } from "node:fs"; +import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { getAssetPath, governanceBookletPath } from "../../../lib/assetUtils"; +import { + governanceBookletPath, + structureBeforeCrisisPath, +} from "../../../lib/assetUtils"; -describe("governance booklet", () => { - it("ships a PDF at the About Book download path", () => { - const relative = governanceBookletPath(); - expect(relative).toBe("assets/marketing/governance-booklet.pdf"); - expect(getAssetPath(relative)).toBe("/assets/marketing/governance-booklet.pdf"); +const publicRoot = join(process.cwd(), "public"); - const abs = join(process.cwd(), "public", relative); - expect(existsSync(abs)).toBe(true); +function assertPdf(relativePath: string) { + const bytes = readFileSync(join(publicRoot, relativePath)); + expect(bytes.subarray(0, 5).toString("ascii")).toBe("%PDF-"); +} - const header = readFileSync(abs).subarray(0, 5).toString("latin1"); - expect(header).toBe("%PDF-"); +describe("marketing book PDFs", () => { + it("ships both About book downloads as PDFs", () => { + assertPdf(governanceBookletPath()); + assertPdf(structureBeforeCrisisPath()); }); });