@@ -0,0 +1,210 @@
|
||||
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
||||
// The organization's theme from the platform, read on the server and written as the page's stylesheet
|
||||
// (brand-kit BK12). The platform's answers are fakes here: the derivation is the kit's own, and every
|
||||
// test looks at what the app would write, not at how the kit fetches it.
|
||||
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs"
|
||||
import { createRequire } from "node:module"
|
||||
import path from "node:path"
|
||||
import { deriveTheme, resolvedThemeStyles } from "@conusai-kit/brand"
|
||||
import { createPlatformThemeCache } from "@conusai-kit/brand/platform-theme"
|
||||
import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
import {
|
||||
cloudOrganizationLogo,
|
||||
createPlatformThemeReader,
|
||||
ORGANIZATION_LOGO_PATH,
|
||||
organizationIdentity,
|
||||
type PlatformThemeDeployment,
|
||||
} from "../apps/web/src/server/platform-theme.js"
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "..")
|
||||
|
||||
/** The platform's answer for the navy fixture: the kit's derivation as OKLCH role maps (BrandingResponse). */
|
||||
function branding(etag: string) {
|
||||
const styles = resolvedThemeStyles(deriveTheme({ primary: "#001f4d", accent: "#1f6feb" }))
|
||||
const roles = (mode: "light" | "dark") =>
|
||||
Object.fromEntries(Object.entries(styles[mode]).map(([name, value]) => [name.slice(2), value]))
|
||||
return {
|
||||
version: 1,
|
||||
seed: { primary: "#001f4d", accent: "#1f6feb" },
|
||||
light: roles("light"),
|
||||
dark: roles("dark"),
|
||||
logo_url: null,
|
||||
etag,
|
||||
}
|
||||
}
|
||||
|
||||
type Reply = { status: number; body?: unknown; etag?: string }
|
||||
|
||||
/** A fetch that answers from a script and records every request. */
|
||||
function platform(...replies: Reply[]) {
|
||||
const calls: Array<{ url: string; headers: Record<string, string> }> = []
|
||||
const fetch = (async (url: string | URL | Request, init?: RequestInit) => {
|
||||
calls.push({ url: String(url), headers: { ...((init?.headers ?? {}) as Record<string, string>) } })
|
||||
const reply = replies.shift()
|
||||
if (!reply) throw new Error("no scripted reply left")
|
||||
if (reply.status === 304) return new Response(null, { status: 304, headers: { etag: reply.etag ?? "" } })
|
||||
return new Response(reply.body === undefined ? null : JSON.stringify(reply.body), {
|
||||
status: reply.status,
|
||||
headers: { "content-type": "application/json", ...(reply.etag ? { etag: reply.etag } : {}) },
|
||||
})
|
||||
}) as typeof globalThis.fetch
|
||||
return { fetch, calls }
|
||||
}
|
||||
|
||||
const deployment = (org: string | undefined): PlatformThemeDeployment => ({
|
||||
CONUSAI_API_URL: "https://platform.example.test/api",
|
||||
CONUSAI_API_TOKEN: "dt_test_fixture",
|
||||
...(org === undefined ? {} : { CONUSAI_ORG_SLUG: org }),
|
||||
})
|
||||
|
||||
describe("the organization's theme on the server", () => {
|
||||
it("writes the organization's roles as the page's stylesheet, dark on the root and light under .light", async () => {
|
||||
const { fetch, calls } = platform({ status: 200, body: branding('"etag-1"'), etag: '"etag-1"' })
|
||||
const css = await createPlatformThemeReader({ fetch })(deployment("acme"))
|
||||
expect(calls[0]?.url).toBe("https://platform.example.test/api/v1/orgs/acme/branding")
|
||||
expect(calls[0]?.headers.authorization).toBe("Bearer dt_test_fixture")
|
||||
expect(css.startsWith(":root{--background:oklch(")).toBe(true)
|
||||
expect(css).toContain(".light{--background:oklch(")
|
||||
expect(css).toContain("--primary:oklch(")
|
||||
})
|
||||
|
||||
it("with no organization slug the app asks nothing and writes nothing: the kit defaults stand", async () => {
|
||||
const { fetch, calls } = platform()
|
||||
expect(await createPlatformThemeReader({ fetch })(deployment(undefined))).toBe("")
|
||||
expect(await createPlatformThemeReader({ fetch })(deployment(" "))).toBe("")
|
||||
expect(calls).toHaveLength(0)
|
||||
})
|
||||
|
||||
it("a cleared cache reads the organization again, with no validator", async () => {
|
||||
const first = platform({ status: 200, body: branding('"etag-1"'), etag: '"etag-1"' })
|
||||
await createPlatformThemeReader({ fetch: first.fetch })(deployment("acme"))
|
||||
const second = platform({ status: 200, body: branding('"etag-2"'), etag: '"etag-2"' })
|
||||
await createPlatformThemeReader({ fetch: second.fetch })(deployment("acme"))
|
||||
expect(second.calls[0]?.headers["if-none-match"]).toBeUndefined()
|
||||
})
|
||||
|
||||
it("an unavailable platform (503) gives the kit defaults, an empty stylesheet", async () => {
|
||||
const { fetch } = platform({ status: 503, body: { title: "Unavailable" } })
|
||||
expect(await createPlatformThemeReader({ fetch })(deployment("acme"))).toBe("")
|
||||
})
|
||||
|
||||
it("a 304 writes the cached theme again with the cached ETag as the validator", async () => {
|
||||
const cache = createPlatformThemeCache()
|
||||
const first = platform({ status: 200, body: branding('"etag-1"'), etag: '"etag-1"' })
|
||||
const fresh = await createPlatformThemeReader({ fetch: first.fetch, cache })(deployment("acme"))
|
||||
const again = platform({ status: 304, etag: '"etag-1"' })
|
||||
const cached = await createPlatformThemeReader({ fetch: again.fetch, cache })(deployment("acme"))
|
||||
expect(again.calls[0]?.headers["if-none-match"]).toBe('"etag-1"')
|
||||
expect(cached).toBe(fresh)
|
||||
})
|
||||
|
||||
it("a changed organization is never written with another organization's theme", async () => {
|
||||
const cache = createPlatformThemeCache()
|
||||
const acme = platform({ status: 200, body: branding('"etag-acme"'), etag: '"etag-acme"' })
|
||||
const acmeCss = await createPlatformThemeReader({ fetch: acme.fetch, cache })(deployment("acme"))
|
||||
const other = platform({ status: 404, body: { title: "Organization Not Found" } })
|
||||
const otherCss = await createPlatformThemeReader({ fetch: other.fetch, cache })(deployment("other"))
|
||||
expect(other.calls[0]?.url).toContain("/v1/orgs/other/branding")
|
||||
expect(other.calls[0]?.headers["if-none-match"]).toBeUndefined()
|
||||
expect(otherCss).toBe("")
|
||||
expect(acmeCss).not.toBe("")
|
||||
})
|
||||
})
|
||||
|
||||
describe("the organization's name and logo on Cloud", () => {
|
||||
const PNG = new Uint8Array(
|
||||
readFileSync(createRequire(import.meta.url).resolve("@conusai-kit/brand/logo/favicon.png")),
|
||||
)
|
||||
const on = { CONUSAI_API_URL: "https://platform.example.test/api", CONUSAI_API_TOKEN: "dt", CONUSAI_ORG_SLUG: "logo" }
|
||||
afterEach(() => vi.unstubAllGlobals())
|
||||
|
||||
it("names the app after the organization and serves its logo from the app, with the token only to the platform", async () => {
|
||||
const seen: Array<{ url: string; authorization?: string }> = []
|
||||
vi.stubGlobal("fetch", async (url: string | URL, init?: RequestInit) => {
|
||||
const headers = (init?.headers ?? {}) as Record<string, string>
|
||||
seen.push({ url: String(url), ...(headers.authorization ? { authorization: headers.authorization } : {}) })
|
||||
if (String(url).endsWith("/branding")) {
|
||||
return Response.json(
|
||||
{ ...branding('"n"'), name: "Navy UAB", logo_url: "/blob/org/7/logo.png" },
|
||||
{ headers: { etag: '"n"' } },
|
||||
)
|
||||
}
|
||||
return new Response(PNG, { headers: { "content-type": "image/png" } })
|
||||
})
|
||||
expect(await organizationIdentity(on)).toEqual({ name: "Navy UAB", logo: ORGANIZATION_LOGO_PATH })
|
||||
const logo = await cloudOrganizationLogo(on)
|
||||
expect(logo?.type).toBe("image/png")
|
||||
expect(seen.at(-1)).toEqual({
|
||||
url: "https://platform.example.test/blob/org/7/logo.png",
|
||||
authorization: "Bearer dt",
|
||||
})
|
||||
})
|
||||
|
||||
it("keeps the app's own when Cloud has none, and refuses what is not a logo", async () => {
|
||||
vi.stubGlobal("fetch", async (url: string | URL) =>
|
||||
String(url).endsWith("/branding")
|
||||
? Response.json(
|
||||
{ ...branding('"x"'), logo_url: "https://cdn.example.test/x.png" },
|
||||
{ headers: { etag: '"x"' } },
|
||||
)
|
||||
: new Response("<html>", { headers: { "content-type": "image/png" } }),
|
||||
)
|
||||
const deployment = { ...on, CONUSAI_ORG_SLUG: "nologo" }
|
||||
expect(await organizationIdentity(deployment)).toEqual({ logo: ORGANIZATION_LOGO_PATH })
|
||||
expect(await cloudOrganizationLogo(deployment)).toBeNull()
|
||||
expect(await organizationIdentity({ ...on, CONUSAI_ORG_SLUG: "" })).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe("the app's code and its deployment contract", () => {
|
||||
it("names no hex colour and no colour function in the app's pages, layout, stylesheet or server code", () => {
|
||||
const files = listFiles(path.join(root, "apps/web/src"), /\.(?:tsx?|css)$/)
|
||||
expect(files.some((file) => file.endsWith("src/routes/__root.tsx"))).toBe(true)
|
||||
expect(files.some((file) => file.endsWith("src/ui/globals.css"))).toBe(true)
|
||||
for (const file of files) {
|
||||
const code = readFileSync(file, "utf8")
|
||||
.replace(/\/\*[\s\S]*?\*\//g, "")
|
||||
.replace(/^\s*\/\/.*$/gm, "")
|
||||
.replace(/(?<=\s)\/\/ .*$/gm, "")
|
||||
const relative = path.relative(root, file)
|
||||
expect(code, relative).not.toMatch(/#[0-9a-fA-F]{3,8}\b/)
|
||||
expect(code, relative).not.toMatch(/\b(?:rgba?|hsla?|hwb|lab|lch|oklch)\(/)
|
||||
}
|
||||
})
|
||||
|
||||
it("reads only the font, type scale and radius brand tokens in its stylesheet, never a colour token (BK3)", () => {
|
||||
const css = readFileSync(path.join(root, "apps/web/src/ui/globals.css"), "utf8")
|
||||
const reads = css.match(/var\(--brand-[a-z0-9-]+/g) ?? []
|
||||
expect(reads.length).toBeGreaterThan(0)
|
||||
for (const read of reads) expect(read, read).toMatch(/--brand-(?:font|radius|text|leading|app-font-size)-/)
|
||||
})
|
||||
|
||||
it("draws the product UI at the app's 16px root and its one type scale, not the site's 18px root", () => {
|
||||
const css = readFileSync(path.join(root, "apps/web/src/ui/globals.css"), "utf8")
|
||||
expect(css).toMatch(/html:root \{\s*font-size: var\(--brand-app-font-size-root\);/)
|
||||
expect(css).not.toContain("--brand-font-size-root")
|
||||
expect(css).not.toContain("--brand-font-display")
|
||||
})
|
||||
|
||||
it("asks the platform for branding:read, and only that new scope", () => {
|
||||
const contract = JSON.parse(readFileSync(path.join(root, "conusai.json"), "utf8")) as {
|
||||
token: { permissions: string[] }
|
||||
}
|
||||
expect(contract.token.permissions).toContain("branding:read")
|
||||
expect(contract.token.permissions).toEqual(expect.arrayContaining(["blob:read", "blob:write"]))
|
||||
})
|
||||
|
||||
it("lists the files it adds as the template's own", () => {
|
||||
const list = readFileSync(path.join(root, "TEMPLATE-FILES"), "utf8").split("\n")
|
||||
expect(list).toContain("apps/web/src/server/platform-theme.ts")
|
||||
expect(list).toContain("test/platform-theme.test.ts")
|
||||
})
|
||||
})
|
||||
|
||||
function listFiles(directory: string, pattern: RegExp): string[] {
|
||||
return readdirSync(directory).flatMap((name) => {
|
||||
const full = path.join(directory, name)
|
||||
return statSync(full).isDirectory() ? listFiles(full, pattern) : pattern.test(name) ? [full] : []
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user