| 1 | import { afterEach, describe, expect, it, vi } from "vitest"; |
| 2 | import { localeDirection, locales } from "./i18n/config"; |
| 3 | import * as dictionaries from "./i18n/dictionaries"; |
| 4 | |
| 5 | vi.mock("next/font/local", () => ({ default: () => ({ variable: "font" }) })); |
| 6 | |
| 7 | const { default: LocaleLayout, generateMetadata } = await import("@/app/[locale]/layout"); |
| 8 | const { default: RootNotFound } = await import("@/app/not-found"); |
| 9 | |
| 10 | function render(locale: string) { |
| 11 | return LocaleLayout({ children: null, params: Promise.resolve({ locale }) }); |
| 12 | } |
| 13 | |
| 14 | afterEach(() => vi.restoreAllMocks()); |
| 15 | |
| 16 | describe("locale layout", () => { |
| 17 | // Middleware leaves dotted files alone; unknown files reach `[locale]` |
| 18 | // just like unknown directory segments, including their child routes. |
| 19 | const invalidLocales = ["foo.txt", "llms-full.txt", "robots.json", "wp-login.php", "xx", ""]; |
| 20 | it.each(invalidLocales)("rejects %j before reading chrome dictionaries", async (locale) => { |
| 21 | const chrome = vi.spyOn(dictionaries, "getChrome"); |
| 22 | await expect(render(locale)).rejects.toMatchObject({ digest: "NEXT_HTTP_ERROR_FALLBACK;404" }); |
| 23 | expect(chrome).not.toHaveBeenCalled(); |
| 24 | }); |
| 25 | |
| 26 | // The layout cannot catch its own notFound(), so the root boundary answers. |
| 27 | // Without app/not-found.tsx that was the framework's bare page, with no |
| 28 | // document shell, nav, or footer. |
| 29 | it("answers that not-found inside the default locale's shell", async () => { |
| 30 | const element = RootNotFound(); |
| 31 | expect(element.type).toBe(LocaleLayout); |
| 32 | const document = await LocaleLayout(element.props); |
| 33 | expect(document.type).toBe("html"); |
| 34 | expect(document.props.lang).toBe("en"); |
| 35 | }); |
| 36 | |
| 37 | // Otherwise the home page's title, canonical, and hreflang stream into it. |
| 38 | it.each(invalidLocales)("gives %j noindex metadata without a home dictionary", async (locale) => { |
| 39 | const home = vi.spyOn(dictionaries, "getHome"); |
| 40 | const metadata = await generateMetadata({ params: Promise.resolve({ locale }) }); |
| 41 | expect(metadata.title).toBe("Not found · Codewhale"); |
| 42 | expect(metadata.robots).toEqual({ index: false, follow: true }); |
| 43 | expect(metadata.alternates).toEqual({}); |
| 44 | expect(home).not.toHaveBeenCalled(); |
| 45 | }); |
| 46 | |
| 47 | it.each(locales)("renders the registered locale %s", async (locale) => { |
| 48 | const element = await render(locale); |
| 49 | expect(element.props.lang).toBe(locale); |
| 50 | expect(element.props.dir).toBe(localeDirection(locale)); |
| 51 | }); |
| 52 | }); |
| 53 |