diff --git a/e2e/blog.spec.ts b/e2e/blog.spec.ts index b9fdb75..ee46c97 100644 --- a/e2e/blog.spec.ts +++ b/e2e/blog.spec.ts @@ -4,6 +4,9 @@ import { visiblePosts } from "../src/content/blog/.generated/posts"; import { blogArticlePath } from "../src/content/blog/model"; import { blogLabels } from "../src/content/blog/labels"; const first = visiblePosts[0]; +const architectureSlug = + "building-mobile-and-web-clients-around-one-appwrite-backend"; +const introSlug = "why-i-built-car-brain"; const articlePaths = visiblePosts.flatMap((p) => (["en", "pl"] as const).map((locale) => ({ path: blogArticlePath(p.slug, locale), @@ -76,11 +79,11 @@ for (const skin of ["standard", "terminal"] as const) const paths = [ "/blog/", "/blog/pl/", - ...(first - ? (["en", "pl"] as const).map((locale) => - blogArticlePath(first.slug, locale) - ) - : []), + ...visiblePosts.flatMap((post) => + (["en", "pl"] as const).map((locale) => + blogArticlePath(post.slug, locale) + ) + ), ]; const errors: string[] = []; page.on("pageerror", (error) => errors.push(error.message)); @@ -94,7 +97,20 @@ for (const skin of ["standard", "terminal"] as const) expect((await new AxeBuilder({ page }).analyze()).violations).toEqual( [] ); - if (path.includes(first?.slug ?? "__none__")) { + if (path.includes(introSlug)) { + await expect(page.locator(".product-screens img")).toHaveCount(3); + await expect(page.locator(".product-screens-row")).toHaveCSS( + "overflow-x", + "auto" + ); + const stack = page.locator(".mobile-stack"); + await expect(stack.getByRole("img")).toBeVisible(); + await expect(stack.getByRole("region")).toHaveCSS( + "overflow-x", + "auto" + ); + } + if (path.includes(architectureSlug)) { // Diagram geometry must survive missing component CSS. for (const selector of [".client-architecture", ".auth-sequence"]) { const diagram = page.locator(selector); @@ -144,8 +160,14 @@ test("blog discovery and keyboard navigation reach the index and both editions", })), ]; for (const { query, path } of destinations) { - await page.getByRole("button", { name: "Open command palette" }).click(); - await page.getByRole("combobox", { name: "Search commands" }).fill(query); + await page + .getByRole("button", { + name: /Open command palette|Otwórz paletę poleceń/, + }) + .click(); + await page + .getByRole("combobox", { name: /Search commands|Szukaj poleceń/ }) + .fill(query); await page.keyboard.press("Enter"); await expect(page).toHaveURL(new RegExp(path + "$")); } @@ -173,7 +195,7 @@ for (const [skin, theme, width] of [ await page.setViewportSize({ width, height: 1000 }); await page.emulateMedia({ reducedMotion: "reduce" }); await page.goto( - `${blogArticlePath(first.slug, width < 800 ? "pl" : "en")}?skin=${skin}&theme=${theme}` + `${blogArticlePath(architectureSlug, width < 800 ? "pl" : "en")}?skin=${skin}&theme=${theme}` ); await page.screenshot({ path: `test-results/review-${skin}-${theme}-${width}-header.png`, @@ -190,7 +212,7 @@ test("article diagrams keep their geometry without external stylesheets", async }) => { test.skip(!first, "No article in this build"); await page.setViewportSize({ width: 390, height: 900 }); - await page.goto(blogArticlePath(first.slug, "pl")); + await page.goto(blogArticlePath(architectureSlug, "pl")); await page.evaluate(() => { for (const sheet of Array.from(document.styleSheets)) sheet.disabled = true; }); diff --git a/public/blog/why-i-built-car-brain/refuels.jpg b/public/blog/why-i-built-car-brain/refuels.jpg new file mode 100644 index 0000000..ad01ad4 Binary files /dev/null and b/public/blog/why-i-built-car-brain/refuels.jpg differ diff --git a/public/blog/why-i-built-car-brain/services.jpg b/public/blog/why-i-built-car-brain/services.jpg new file mode 100644 index 0000000..35d3d13 Binary files /dev/null and b/public/blog/why-i-built-car-brain/services.jpg differ diff --git a/public/blog/why-i-built-car-brain/settings.jpg b/public/blog/why-i-built-car-brain/settings.jpg new file mode 100644 index 0000000..169dcd6 Binary files /dev/null and b/public/blog/why-i-built-car-brain/settings.jpg differ diff --git a/src/components/Blog/Blog.test.tsx b/src/components/Blog/Blog.test.tsx index 3e9cb0e..8390706 100644 --- a/src/components/Blog/Blog.test.tsx +++ b/src/components/Blog/Blog.test.tsx @@ -2,6 +2,8 @@ import React from "react"; import { render, screen } from "@testing-library/react"; import { expect, test } from "vitest"; import { ClientArchitecture } from "./ClientArchitecture"; +import { MobileStack } from "./MobileStack"; +import { ProductScreens } from "./ProductScreens"; import { BlogList, BlogLanguages, LatestWriting } from "./BlogList"; import { BlogIndex, blogIndexMetadata } from "./BlogIndex"; import { visiblePosts } from "@/content/blog/.generated/posts"; @@ -17,6 +19,15 @@ test("language switch keeps the same article", () => { expect.stringMatching(/^\/blog\/en\/example\/?$/) ); }); +test("the title link does not wrap the language switch", () => { + const post = visiblePosts[0]; + const { container } = render(); + const item = container.querySelector(".blog-list-item"); + expect(item).toHaveClass("card"); + const title = screen.getByRole("link", { name: post.editions.en.title }); + expect(title.closest(".blog-languages")).toBeNull(); + expect(item?.querySelector(".blog-languages a")).toBeTruthy(); +}); test("empty article list has no placeholder cards", () => { const { container } = render(); expect(container.querySelectorAll("li")).toHaveLength(0); @@ -54,6 +65,18 @@ test("article metadata preserves independent canonicals and prevents draft index expect(m.robots).toMatchObject({ index: false, follow: false }); }); +test("mobile stack and sample screens stay in the article", () => { + const { container } = render( + <> + + + + ); + expect( + screen.getByRole("img", { name: /Car Brain mobile stack/ }) + ).toBeVisible(); + expect(container.querySelectorAll(".product-screens img")).toHaveLength(3); +}); for (const locale of ["en", "pl"] as const) { test(`architecture exposes its boundaries and planned data path (${locale})`, () => { const { container } = render(); diff --git a/src/components/Blog/BlogList.tsx b/src/components/Blog/BlogList.tsx index 8f8a0de..5003f6c 100644 --- a/src/components/Blog/BlogList.tsx +++ b/src/components/Blog/BlogList.tsx @@ -12,15 +12,18 @@ import { SectionBand } from "@/components/SectionBand/SectionBand"; export function BlogLanguages({ locale, slug, + label, }: { locale: BlogLocale; slug?: string; + label?: string; }) { return (