This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
||||
// The brand is the only source of colour (decision AT-D11), and every string a page shows is a
|
||||
// message in both locales (spec §10.4).
|
||||
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { appCatalogues } from "../apps/web/src/ui/messages"
|
||||
import { appBrand, BRAND_TOKEN_NAMES, brandTokenStyle } from "../config/src/brand.js"
|
||||
|
||||
const walk = (dir: string, suffix: string): string[] =>
|
||||
readdirSync(dir).flatMap((name) => {
|
||||
// Installed packages are not this app's pages, and a linked workspace package would pull its
|
||||
// own sources (stories, fixtures) into the scan.
|
||||
if (name === "node_modules" || name === ".next") return []
|
||||
const path = join(dir, name)
|
||||
if (statSync(path).isDirectory()) return walk(path, suffix)
|
||||
return path.endsWith(suffix) ? [path] : []
|
||||
})
|
||||
|
||||
const appDir = new URL("..", import.meta.url).pathname
|
||||
|
||||
describe("the app brand", () => {
|
||||
it("reads its tokens from @conusai-kit/brand, so the app defines none", () => {
|
||||
expect(BRAND_TOKEN_NAMES.length).toBeGreaterThan(0)
|
||||
expect(appBrand.tokens).toEqual({})
|
||||
expect(brandTokenStyle({ ...appBrand, tokens: { primary: "#123456" } })).toEqual({ "--brand-primary": "#123456" })
|
||||
})
|
||||
|
||||
it("ships no colour literal of its own in the stylesheet", () => {
|
||||
const css = readFileSync(join(appDir, "apps/web/app/globals.css"), "utf8")
|
||||
const literals = css.match(/#[0-9a-fA-F]{3,8}\b|\brgb\(|\bhsl\(|\boklch\(/g) ?? []
|
||||
expect(literals).toEqual([])
|
||||
})
|
||||
|
||||
it("loads no external stylesheet, font or script", () => {
|
||||
for (const file of [...walk(join(appDir, "apps/web/app"), ".tsx"), join(appDir, "apps/web/app/globals.css")]) {
|
||||
const text = readFileSync(file, "utf8")
|
||||
expect(text, `${file} reaches outside this app`).not.toMatch(/https?:\/\/(?!127\.0\.0\.1|localhost)/)
|
||||
}
|
||||
})
|
||||
|
||||
it("has no attribution turned on by default: a customer sells their own product", () => {
|
||||
expect(appBrand.attribution).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("the message catalogues", () => {
|
||||
const en = appCatalogues.en ?? {}
|
||||
const lt = appCatalogues.lt ?? {}
|
||||
|
||||
it("hold the same keys in English and in Lithuanian", () => {
|
||||
for (const [module, messages] of Object.entries(en)) {
|
||||
expect(Object.keys(lt[module] ?? {}).sort(), `lt is missing keys of ${module}`).toEqual(
|
||||
Object.keys(messages).sort(),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("select the Lithuanian plural categories, which English does not have", () => {
|
||||
expect(lt.app?.memberCount).toContain("few {")
|
||||
expect(en.app?.memberCount).not.toContain("few {")
|
||||
})
|
||||
|
||||
it("hold the messages a page picks by a value rather than by name: roles, sign-in errors, invitation states", () => {
|
||||
const keys = [
|
||||
...["viewer", "member", "admin", "owner"].map((role) => `roles.${role}`),
|
||||
...["sessionExpired", "noTransaction", "denied", "provider", "failed"].map((code) => `signin.error.${code}`),
|
||||
...["invalid", "used", "expired", "member", "failed"].map((state) => `invite.error.${state}`),
|
||||
]
|
||||
for (const key of keys) {
|
||||
const [module, ...rest] = key.split(".")
|
||||
const message = rest.join(".")
|
||||
expect(en[module as string]?.[message], `en is missing ${key}`).toBeTruthy()
|
||||
expect(lt[module as string]?.[message], `lt is missing ${key}`).toBeTruthy()
|
||||
expect(lt[module as string]?.[message], `${key} is not translated`).not.toBe(en[module as string]?.[message])
|
||||
}
|
||||
})
|
||||
|
||||
it("select the Lithuanian plural categories of the counts the workspace pages show", () => {
|
||||
expect(lt.workspace?.projectCount).toContain("few {")
|
||||
expect(en.workspace?.projectCount).not.toContain("few {")
|
||||
})
|
||||
|
||||
it("hold every message the pages ask for, in both locales", () => {
|
||||
// Every label a page shows is `i18n.message("<key>")`, so the keys the pages use and the keys
|
||||
// the catalogues hold are comparable -- and a renamed message fails here rather than
|
||||
// rendering its own key to a customer.
|
||||
const used = new Set<string>()
|
||||
for (const file of walk(join(appDir, "apps/web"), ".tsx")) {
|
||||
for (const [, key] of readFileSync(file, "utf8").matchAll(/i18n\.message\("([^"]+)"/g)) used.add(key as string)
|
||||
}
|
||||
expect(used.size).toBeGreaterThan(10)
|
||||
for (const key of [...used].sort()) {
|
||||
const [module, ...rest] = key.split(".")
|
||||
const message = rest.join(".")
|
||||
expect(en[module as string]?.[message], `en is missing ${key}`).toBeTruthy()
|
||||
expect(lt[module as string]?.[message], `lt is missing ${key}`).toBeTruthy()
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user