// @vitest-environment node import { existsSync, readFileSync } from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import { describe, expect, it } from "vitest"; import { hexOf, oklchOf, tokenSet } from "@/styles/css-tokens"; import { config as proxyConfig } from "@/proxy"; import manifest from "./manifest"; // Spec 12.2/12.3 and 13.1: the web manifest, its files and what serves them. const ROOT = process.cwd(); const PUBLIC_DIR = path.join(ROOT, "public"); const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); interface Icon { src: string; sizes?: string; type?: string; purpose?: string; } /** Width and height from the IHDR chunk of a PNG. */ function pngSize(png: Buffer): { width: number; height: number } { expect(png.subarray(0, 8).equals(PNG_SIGNATURE)).toBe(true); expect(png.toString("latin1", 12, 16)).toBe("IHDR"); return { width: png.readUInt32BE(16), height: png.readUInt32BE(20) }; } function publicFile(src: string): string { return path.join(PUBLIC_DIR, ...src.split("/").filter(Boolean)); } const data = manifest(); const icons: Icon[] = data.icons ?? []; const shortcutIcons: Icon[] = (data.shortcuts ?? []).flatMap((shortcut) => shortcut.icons ?? []); describe("web manifest (spec 12.3)", () => { it("every manifest icon exists under public with the declared size", () => { const all = [...icons, ...shortcutIcons]; expect(all.length).toBeGreaterThan(0); for (const icon of all) { const file = publicFile(icon.src); expect(existsSync(file), icon.src).toBe(true); if (icon.sizes === "any") { expect(icon.type).toBe("image/svg+xml"); expect(readFileSync(file, "utf8")).toContain(" { const sizesOf = (purpose: string) => icons.filter((icon) => (icon.purpose ?? "any") === purpose).map((icon) => icon.sizes); expect(sizesOf("any")).toEqual(expect.arrayContaining(["192x192", "512x512", "any"])); expect(sizesOf("maskable")).toEqual(["192x192", "512x512"]); expect(sizesOf("monochrome")).toEqual(["512x512"]); }); it("background_color and theme_color equal the light --background", () => { const css = readFileSync(path.join(ROOT, "src/app/globals.css"), "utf8"); const lightBackground = hexOf(oklchOf(tokenSet(css, "light").background)); expect(lightBackground).toBe("#f9f4ea"); expect(data.background_color).toBe(lightBackground); expect(data.theme_color).toBe(lightBackground); }); it("is installable from the root with German metadata", () => { expect(data).toMatchObject({ id: "/", name: "ApiCore – Imkereiverwaltung", short_name: "ApiCore", lang: "de", dir: "ltr", start_url: "/", scope: "/", display: "standalone", categories: ["productivity", "business"], }); }); it("offers exactly the three spec shortcuts with their 96 px icons", () => { expect( (data.shortcuts ?? []).map((shortcut) => ({ name: shortcut.name, url: shortcut.url, icons: shortcut.icons?.map((icon) => `${icon.src} ${icon.sizes}`), })), ).toEqual([ { name: "Durchsicht erfassen", url: "/inspections/new", icons: ["/icons/shortcut-inspection-96.png 96x96"] }, { name: "Varroa messen", url: "/varroa/new", icons: ["/icons/shortcut-varroa-96.png 96x96"] }, { name: "Heute", url: "/dashboard", icons: ["/icons/shortcut-today-96.png 96x96"] }, ]); }); it("every shortcut url is in SHELL_ROUTES", () => { const require = createRequire(import.meta.url); const { SHELL_ROUTES } = require(path.join(PUBLIC_DIR, "sw-routes.js")) as { SHELL_ROUTES: readonly string[]; }; for (const shortcut of data.shortcuts ?? []) { expect(SHELL_ROUTES, shortcut.url).toContain(shortcut.url); } }); }); describe("app icons (spec 12.2)", () => { it("favicon.ico holds 16, 32 and 48 px entries", () => { const ico = readFileSync(path.join(ROOT, "src/app/favicon.ico")); expect(ico.readUInt16LE(0)).toBe(0); expect(ico.readUInt16LE(2)).toBe(1); const count = ico.readUInt16LE(4); const entries = Array.from({ length: count }, (_, index) => { const at = 6 + index * 16; const width = ico.readUInt8(at); const length = ico.readUInt32LE(at + 8); const offset = ico.readUInt32LE(at + 12); const image = ico.subarray(offset, offset + length); expect(image.length).toBe(length); expect(pngSize(image)).toEqual({ width, height: ico.readUInt8(at + 1) }); return width; }); expect(entries).toEqual([16, 32, 48]); }); it("apple-icon.png is a 180 px PNG and replaces the generated apple-icon route", () => { expect(pngSize(readFileSync(path.join(ROOT, "src/app/apple-icon.png")))).toEqual({ width: 180, height: 180 }); expect(existsSync(path.join(ROOT, "src/app/apple-icon.tsx"))).toBe(false); }); }); describe("proxy matcher", () => { const [matcher] = proxyConfig.matcher; const runsProxy = (pathname: string) => new RegExp(`^${matcher}$`).test(pathname); it("the proxy matcher excludes icons and apple-icon.png", () => { for (const pathname of [ "/icons/icon-192.png", "/icons/maskable-512.png", "/icons/shortcut-today-96.png", "/apple-icon.png", "/icon.svg", "/favicon.ico", "/sw.js", "/manifest.webmanifest", ]) { expect(runsProxy(pathname), pathname).toBe(false); } }); it("keeps the security headers on pages that only resemble an excluded file", () => { for (const pathname of [ "/", "/dashboard", "/inspections/new", "/icons", "/icons/x/y", "/icons/missing", "/iconsx/page", "/apple-iconxpng", "/iconxsvg", "/faviconxico", "/swxjs", ]) { expect(runsProxy(pathname), pathname).toBe(true); } }); });