mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Astro's getRelativeLocaleUrl lowercases the locale segment by default, so landing links and hreflang for zh-CN, zh-TW, and pt-BR were emitted lowercase and 404 on case-sensitive Cloudflare Pages. Pin the casing at the localizeHref chokepoint with normalizeLocale: false, add an e2e hreflang casing guard, and add a deploy-time check that blocks the build if any lowercased locale path leaks into the output. Closes #554
77 lines
3.4 KiB
TypeScript
77 lines
3.4 KiB
TypeScript
// tests/e2e-landing/i18n-seo-rtl.spec.ts
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
test.describe("landing hreflang, RTL, and banner", () => {
|
|
test("emits reciprocal hreflang alternates including x-default", async ({ page }) => {
|
|
await page.goto("/faq");
|
|
const en = await page.locator('link[rel="alternate"][hreflang="en"]').getAttribute("href");
|
|
const de = await page.locator('link[rel="alternate"][hreflang="de"]').getAttribute("href");
|
|
const xd = await page
|
|
.locator('link[rel="alternate"][hreflang="x-default"]')
|
|
.getAttribute("href");
|
|
// Astro's `format: "directory"` emits trailing-slash URLs, so the alternates
|
|
// carry a trailing slash. What matters is reciprocity and that x-default == en.
|
|
expect(en).toBe("https://snapotter.com/faq/");
|
|
expect(de).toBe("https://snapotter.com/de/faq/");
|
|
expect(xd).toBe("https://snapotter.com/faq/");
|
|
expect(xd).toBe(en);
|
|
|
|
// Reciprocity: the German page points back to the same set.
|
|
await page.goto("/de/faq");
|
|
const enFromDe = await page
|
|
.locator('link[rel="alternate"][hreflang="en"]')
|
|
.getAttribute("href");
|
|
expect(enFromDe).toBe("https://snapotter.com/faq/");
|
|
});
|
|
|
|
// Regression guard for #554. Astro's getRelativeLocaleUrl lowercases the locale
|
|
// segment by default (normalizeTheLocale: "zh-CN" -> "zh-cn"), but the built
|
|
// directories and the sitemap keep canonical mixed case. The lowercased links
|
|
// resolve on case-insensitive local hosting but 404 on Cloudflare Pages. This
|
|
// reads the emitted href string, so it catches the mismatch regardless of the
|
|
// local filesystem's case sensitivity (which is why the specs above missed it).
|
|
test("mixed-case locales keep their exact casing in hreflang links", async ({ page }) => {
|
|
await page.goto("/faq");
|
|
for (const code of ["zh-CN", "zh-TW", "pt-BR"]) {
|
|
const href = await page
|
|
.locator(`link[rel="alternate"][hreflang="${code}"]`)
|
|
.getAttribute("href");
|
|
expect(href, `hreflang ${code} must preserve casing`).toBe(
|
|
`https://snapotter.com/${code}/faq/`,
|
|
);
|
|
// The lowercased path (the bug) must not appear.
|
|
expect(href).not.toContain(code.toLowerCase());
|
|
}
|
|
});
|
|
|
|
test("Arabic renders dir=rtl on <html>", async ({ page }) => {
|
|
await page.goto("/ar/faq");
|
|
await expect(page.locator("html")).toHaveAttribute("dir", "rtl");
|
|
await expect(page.locator("html")).toHaveAttribute("lang", "ar");
|
|
});
|
|
|
|
test("English page sets og:locale to en_US", async ({ page }) => {
|
|
await page.goto("/faq");
|
|
await expect(page.locator('meta[property="og:locale"]')).toHaveAttribute("content", "en");
|
|
});
|
|
|
|
test("machine-translation banner appears on de and dismisses persistently", async ({ page }) => {
|
|
await page.goto("/de/faq");
|
|
const banner = page.locator("[data-mt-banner]");
|
|
await expect(banner).toBeVisible();
|
|
await banner.locator("[data-mt-dismiss]").click();
|
|
await expect(banner).toBeHidden();
|
|
const stored = await page.evaluate(() => localStorage.getItem("snapotter:mt-banner"));
|
|
expect(stored).toBe("dismissed");
|
|
|
|
// Reload: banner stays hidden.
|
|
await page.reload();
|
|
await expect(page.locator("[data-mt-banner]")).toBeHidden();
|
|
});
|
|
|
|
test("English page shows no machine-translation banner", async ({ page }) => {
|
|
await page.goto("/faq");
|
|
await expect(page.locator("[data-mt-banner]")).toHaveCount(0);
|
|
});
|
|
});
|