From b7f3ac545a980b75408903d229bba26b98b1ba75 Mon Sep 17 00:00:00 2001 From: sawden Date: Fri, 22 Dec 2023 09:15:53 +0100 Subject: [PATCH] Add tests for localization --- .../src/__tests__/localization.test.ts | 221 ++++++++++++++++++ .../frontend/src/__tests__/middleware.test.ts | 117 ++++++++++ 2 files changed, 338 insertions(+) create mode 100644 apps/frontend/src/__tests__/localization.test.ts create mode 100644 apps/frontend/src/__tests__/middleware.test.ts diff --git a/apps/frontend/src/__tests__/localization.test.ts b/apps/frontend/src/__tests__/localization.test.ts new file mode 100644 index 0000000..01291c2 --- /dev/null +++ b/apps/frontend/src/__tests__/localization.test.ts @@ -0,0 +1,221 @@ +import type { ApiLocale } from "@/lib/api/types"; +import { + addLocaleToPath, + getDefaultLocaleFormLocales, + isPathnameLocale, + isPathnameMissingLocale, + localizeHref, + removeLocaleFromPath, +} from "../lib/localization"; + +const LOCALES_MOCK = [ + { + id: 2, + name: "German (de)", + code: "de", + createdAt: "2023-12-15T20:24:52.563Z", + updatedAt: "2023-12-15T20:24:52.563Z", + isDefault: false, + }, + { + id: 1, + name: "English (en)", + code: "en", + createdAt: "2023-12-15T20:24:19.257Z", + updatedAt: "2023-12-15T20:24:19.257Z", + isDefault: true, + }, +]; + +describe("localizeHref()", () => { + describe("should localize when href is", () => { + it("root-relative root", () => { + const result = localizeHref("/", "en"); + expect(result).toEqual("/en"); + }); + + it("root-relative", () => { + const result = localizeHref("/pathname", "en"); + expect(result).toEqual("/en/pathname"); + }); + + it("nested root-relative", () => { + const result = localizeHref("/some/pathname", "en"); + expect(result).toEqual("/en/some/pathname"); + }); + }); + + describe("should not localize href", () => { + describe("when href is", () => { + it("absolute", () => { + const result = localizeHref("http://example.com/example", "en"); + expect(result).toEqual("http://example.com/example"); + }); + + it("relative", () => { + const result = localizeHref("pathname", "en"); + expect(result).toEqual("pathname"); + }); + + it("anchor", () => { + const result = localizeHref("#example", "en"); + expect(result).toEqual("#example"); + }); + + it("empty", () => { + const result = localizeHref("", "en"); + expect(result).toEqual(""); + }); + }); + it("when locale is empty", () => { + expect(localizeHref("/example", "")).toEqual("/example"); + }); + }); +}); + +describe("getDefaultLocaleFormLocales()", () => { + it("returns the default locale when one is present", () => { + const defaultLocale = getDefaultLocaleFormLocales(LOCALES_MOCK); + expect(defaultLocale).toEqual(LOCALES_MOCK[1]); + }); + + it("returns undefined when the locales list is empty", () => { + const locales: ApiLocale[] = []; + + const defaultLocale = getDefaultLocaleFormLocales(locales); + expect(defaultLocale).toBeUndefined(); + }); + + it("returns undefined when there is no default locale", () => { + const locales = LOCALES_MOCK.map((locale) => ({ + ...locale, + isDefault: false, + })); + + const defaultLocale = getDefaultLocaleFormLocales(locales); + expect(defaultLocale).toBeUndefined(); + }); +}); + +describe("removeLocaleFromPath()", () => { + it("removes locale from start of the path", () => { + const result = removeLocaleFromPath("/en/pathname", "en"); + expect(result).toEqual("/pathname"); + }); + + it("does not remove locale from middle of the path", () => { + const result = removeLocaleFromPath("/pathname/en/abc", "en"); + expect(result).toEqual("/pathname/en/abc"); + }); + + it("returns '/' when path is just the locale", () => { + const result = removeLocaleFromPath("/en", "en"); + expect(result).toEqual("/"); + }); + + it("returns original path when locale is not in the path", () => { + const result = removeLocaleFromPath("/pathname/abc", "en"); + expect(result).toEqual("/pathname/abc"); + }); + + it("returns original path when path is locale-like string", () => { + const result = removeLocaleFromPath("/enterprise", "en"); + expect(result).toEqual("/enterprise"); + }); +}); + +describe("addLocaleToPath()", () => { + describe("should add locale when", () => { + it("path is root", () => { + const result = addLocaleToPath("/", "en"); + expect(result).toEqual("/en"); + }); + + it("path is empty", () => { + const result = addLocaleToPath("", "en"); + expect(result).toEqual("/en"); + }); + + it("path is '/pathname'", () => { + const result = addLocaleToPath("/pathname", "en"); + expect(result).toEqual("/en/pathname"); + }); + + it("path is 'pathname'", () => { + const result = addLocaleToPath("pathname", "en"); + expect(result).toEqual("/en/pathname"); + }); + + it("path is nested", () => { + const result = addLocaleToPath("/some/pathname", "en"); + expect(result).toEqual("/en/some/pathname"); + }); + }); +}); + +describe("isPathnameMissingLocale()", () => { + describe("should return true when", () => { + it("pathname is missing locale", () => { + const result = isPathnameMissingLocale("/pathname", LOCALES_MOCK); + expect(result).toBe(true); + }); + + it("pathname starts with locale-like string", () => { + const result = isPathnameMissingLocale("/enterprise", LOCALES_MOCK); + expect(result).toBe(true); + }); + + it("locales list is empty", () => { + const result = isPathnameMissingLocale("/en/pathname", []); + expect(result).toBe(true); + }); + }); + + describe("should return false when", () => { + it("pathname starts with locale", () => { + const result = isPathnameMissingLocale("/en/pathname", LOCALES_MOCK); + expect(result).toBe(false); + }); + + it("pathname is exactly the locale", () => { + const result = isPathnameMissingLocale("/en", LOCALES_MOCK); + expect(result).toBe(false); + }); + }); +}); + +describe("isPathnameLocale()", () => { + describe("should return true", () => { + it("when pathname starts with given locale", () => { + const result = isPathnameLocale("/en/pathname", "en"); + expect(result).toBe(true); + }); + + it("when pathname is exactly the given locale", () => { + const result = isPathnameLocale("/en", "en"); + expect(result).toBe(true); + }); + }); + + describe("should return false", () => { + it("when pathname does not start with given locale", () => { + const result = isPathnameLocale("/pathname/en", "en"); + expect(result).toBe(false); + }); + + it("when pathname starts with locale-like string of given locale", () => { + const result = isPathnameLocale("/enterprise", "en"); + expect(result).toBe(false); + }); + + it("when given locale is undefined", () => { + const result = isPathnameLocale("/en/pathname", undefined); + expect(result).toBe(false); + }); + + it("when pathname is not the given locale", () => { + const result = isPathnameLocale("/de", "en"); + expect(result).toBe(false); + }); + }); +}); diff --git a/apps/frontend/src/__tests__/middleware.test.ts b/apps/frontend/src/__tests__/middleware.test.ts new file mode 100644 index 0000000..4cc74ac --- /dev/null +++ b/apps/frontend/src/__tests__/middleware.test.ts @@ -0,0 +1,117 @@ +/** + * @jest-environment node + */ + +import { NextRequest, NextResponse } from "next/server"; +import { fetchLocales } from "../lib/api"; +import { middleware } from "../middleware"; + +jest.mock("../lib/api"); + +const LOCALES_MOCK = [ + { + id: 2, + name: "German (de)", + code: "de", + createdAt: "2023-12-15T20:24:52.563Z", + updatedAt: "2023-12-15T20:24:52.563Z", + isDefault: false, + }, + { + id: 1, + name: "English (en)", + code: "en", + createdAt: "2023-12-15T20:24:19.257Z", + updatedAt: "2023-12-15T20:24:19.257Z", + isDefault: true, + }, +]; + +describe("middleware", () => { + const rewriteSpy = jest.spyOn(NextResponse, "rewrite"); + const redirectSpy = jest.spyOn(NextResponse, "redirect"); + const DOMAIN_MOCK = "https://example.com"; + + beforeEach(() => { + (fetchLocales as jest.Mock).mockResolvedValue(LOCALES_MOCK); + }); + + afterEach(() => { + rewriteSpy.mockClear(); + redirectSpy.mockClear(); + }); + + describe("should remove default locale from URL", () => { + it("when pathname starts with default locale", async () => { + const request = new NextRequest( + new Request(`${DOMAIN_MOCK}/en/pathname`), + ); + const response = await middleware(request); + + expect(redirectSpy).toHaveBeenCalledTimes(1); + expect(rewriteSpy).toHaveBeenCalledTimes(0); + expect(response).toBeInstanceOf(NextResponse); + expect(response?.status).toEqual(307); + expect(response?.headers.get("Location")).toEqual( + `${DOMAIN_MOCK}/pathname`, + ); + }); + + it("when pathname is exactly default locale", async () => { + const request = new NextRequest(new Request(`${DOMAIN_MOCK}/en`)); + const response = await middleware(request); + + expect(redirectSpy).toHaveBeenCalledTimes(1); + expect(rewriteSpy).toHaveBeenCalledTimes(0); + expect(response).toBeInstanceOf(NextResponse); + expect(response?.status).toEqual(307); + expect(response?.headers.get("Location")).toEqual(`${DOMAIN_MOCK}/`); + }); + }); + + describe("should not modify URL", () => { + it("when pathname starts with non-default locale", async () => { + const request = new NextRequest( + new Request(`${DOMAIN_MOCK}/de/pathname`), + ); + const response = await middleware(request); + + expect(redirectSpy).toHaveBeenCalledTimes(0); + expect(rewriteSpy).toHaveBeenCalledTimes(0); + expect(response).toBeUndefined(); + }); + + it("when pathname is exactly non-default locale", async () => { + const request = new NextRequest(new Request(`${DOMAIN_MOCK}/de`)); + const response = await middleware(request); + + expect(redirectSpy).toHaveBeenCalledTimes(0); + expect(rewriteSpy).toHaveBeenCalledTimes(0); + expect(response).toBeUndefined(); + }); + }); + + describe("should rewrite to default locale", () => { + it("when pathname is missing locale", async () => { + const request = new NextRequest(new Request(`${DOMAIN_MOCK}/pathname`)); + const response = await middleware(request); + + expect(redirectSpy).toHaveBeenCalledTimes(0); + expect(rewriteSpy).toHaveBeenCalledTimes(1); + expect(response).toBeInstanceOf(NextResponse); + expect(response?.status).toEqual(200); + expect(response?.headers.get("Location")).toEqual(null); + }); + + it("when pathname is '/'", async () => { + const request = new NextRequest(new Request(`${DOMAIN_MOCK}/`)); + const response = await middleware(request); + + expect(redirectSpy).toHaveBeenCalledTimes(0); + expect(rewriteSpy).toHaveBeenCalledTimes(1); + expect(response).toBeInstanceOf(NextResponse); + expect(response?.status).toEqual(200); + expect(response?.headers.get("Location")).toEqual(null); + }); + }); +});