#!/usr/bin/env node /* * copy-gate.test.mjs — COPY1..COPY6, the UI-SPEC copy gate (mirrors bot CS1-CS3). * -------------------------------------------------------------------------------- * COPY1 locale key-set parity · COPY2/COPY3 verbatim-contract content checks · * COPY4 pickLocale fallback rule · COPY5 no inline copy in shell modules · * COPY6 localeFromInitData over the raw initData string. */ import { strict as assert } from "node:assert"; import { readFileSync, readdirSync, existsSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { test } from "./harness.mjs"; const { strings, pickLocale, localeFromInitData } = await import("../src/i18n/strings.ts"); const UI_DIR = path.join(path.dirname(fileURLToPath(new URL(import.meta.url))), "..", "src", "ui"); /* ————— COPY1 — en/ms key-set identity (Phase 2 D-05 precedent) ————— */ test("COPY1 en and ms carry identical key-sets (parity gate, both locales)", () => { const enKeys = Object.keys(strings.en).sort(); const msKeys = Object.keys(strings.ms).sort(); assert.deepEqual(msKeys, enKeys, "ms missing/duplicated keys vs en (D-05 locale parity)"); assert.ok(enKeys.length >= 25, `expected the full ShellStrings surface (25 keys), found ${enKeys.length}`); assert.ok(enKeys.includes("deniedRegister"), "deniedRegister is a keys — cards.ts renders GEO //"); assert.ok(enKeys.includes("lostRegister"), "lostRegister is a key — cards.ts renders SYS //"); }); /* ————— COPY2 — denied copy names location ————— */ test("COPY2 the denied copy names location in both locales", () => { assert.ok(/location/i.test(strings.en.deniedHeadline), "en denied headline names location"); assert.ok(/location/i.test(strings.en.deniedBody), "en denied body names location"); assert.ok(/lokasi/i.test(strings.ms.deniedHeadline), "ms denied headline names lokasi"); assert.ok(/lokasi/i.test(strings.ms.deniedBody), "ms denied body names lokasi"); }); /* ————— COPY3 — the never-persisted fact is on the boot card ————— */ test("COPY3 the boot-2 body states the never-persisted fact", () => { assert.ok(/never written to storage/i.test(strings.en.bootAcquiringBody), "en boot-2 body carries the never-fact"); assert.ok(/tidak pernah disimpan/i.test(strings.ms.bootAcquiringBody), "ms boot-2 body carries the never-fact"); }); /* ————— COPY4 — pickLocale fallback rule ————— */ test("COPY4 pickLocale: ms and ms-MY select ms; en, en-GB, id, null, and empty string select en", () => { assert.equal(pickLocale("ms"), "ms"); assert.equal(pickLocale("ms-MY"), "ms"); assert.equal(pickLocale("en"), "en"); assert.equal(pickLocale("en-GB"), "en"); assert.equal(pickLocale("id"), "en", "unknown/neighbor locale falls back to en"); assert.equal(pickLocale(null), "en"); assert.equal(pickLocale(""), "en"); }); /* ————— COPY5 — no inline copy in the shell ————— */ test("COPY5 no shell module inlines copy — source-introspection grep over web/src/ui/*.ts and web/src/main.ts", () => { const banned = /Validating session|Acquiring location|TRY AGAIN|ASK VIA CHAT|Location is switched off|Signal lost|MENYEMAK|MENDAPAT|LANGSUNG|GEO \/\/ LOCATION|SYS \/\/ POSITION|BOOT-01 \/\/|BOOT-02 \/\/|CUBA LAGI|MINTA VIA CHAT|ISYARAT HILANG|LOKASI MATI|AWAITING PIN|OpenStreetMap contributors|Recenter map|Tetapkan semula peta/; const files = existsSync(UI_DIR) ? readdirSync(UI_DIR) .filter((f) => f.endsWith(".ts")) .map((f) => path.join(UI_DIR, f)) : []; files.push(path.join(UI_DIR, "..", "main.ts")); // the composition root — always scanned assert.ok(files.length >= 1, `scanned a real shell tree (found ${files.length} files)`); for (const file of files) { const src = readFileSync(file, "utf8"); // The gate scans code AND prose-in-comments alike? No — comments may cite // the register labels in their own names (e.g. a header example). Strip // block and line comments first: prose is not a call site (GEO3 pattern). const code = src .replace(/\/\*[\s\S]*?\*\//g, " ") .replace(/(^|[^:\\])\/\/[^\n]*/g, "$1"); const hit = banned.exec(code); assert.ok(!hit, `${path.relative(process.cwd(), file)} inlines copy: ${hit && hit[0]} — copy lives only in web/src/i18n/strings.ts`); } }); /* ————— COPY6 — localeFromInitData over the raw initData string ————— */ test("COPY6 localeFromInitData derives the locale from the raw initData string", () => { assert.equal( localeFromInitData('user=%7B%22language_code%22%3A%22ms%22%7D&auth_date=1&hash=x'), "ms", "ms language_code inside the signed user field selects ms", ); assert.equal( localeFromInitData('user=%7B%22language_code%22%3A%22en-GB%22%7D&auth_date=1&hash=x'), "en", "en-GB falls back to en", ); assert.equal(localeFromInitData(""), "en", "empty initData falls back to en"); assert.equal(localeFromInitData("not-initdata"), "en", "no user= component falls back to en"); assert.equal(localeFromInitData("user=%7Bbad-json"), "en", "decode/JSON failure must not throw"); });