71 lines
3.1 KiB
TypeScript
71 lines
3.1 KiB
TypeScript
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
// The app brand (plan item AT2.6). A customer replaces the ConusAI defaults in config/src/brand.ts with
|
|
// `defineBrand`, and what they give it has to be safe to write into a stylesheet and a page. The
|
|
// legibility of their colours is @conusai-kit/brand's own test: this file checks what the app does with them.
|
|
|
|
import { appBrand, brandContrast, brandTokenStyle, defineBrand } from "@app/config"
|
|
import { describe, expect, it } from "vitest"
|
|
|
|
const base = { name: "Acme Review", shortName: "Acme" }
|
|
|
|
describe("defineBrand", () => {
|
|
it("returns the brand, frozen, with its colours as a style", () => {
|
|
const brand = defineBrand({ ...base, primary: "#1a1a8c" })
|
|
expect(brand.name).toBe("Acme Review")
|
|
expect(Object.isFrozen(brand)).toBe(true)
|
|
expect(brand.attribution).toBe(false)
|
|
expect(brandTokenStyle(brand)["--brand-accent"]).toBe("#1a1a8c")
|
|
})
|
|
|
|
it("reports the contrast its colours have on each theme", () => {
|
|
const report = brandContrast(defineBrand({ ...base, primary: "#1a1a8c" }))
|
|
expect(report.dark?.inkContrast).toBeGreaterThanOrEqual(4.5)
|
|
expect(report.light?.inkContrast).toBeGreaterThanOrEqual(4.5)
|
|
expect(brandContrast(defineBrand(base))).toEqual({})
|
|
})
|
|
|
|
it("refuses a colour that is not hex, naming the field and not the value", () => {
|
|
expect(() => defineBrand({ ...base, primary: "red" })).toThrow(/primary/)
|
|
expect(() => defineBrand({ ...base, primary: "secret-colour" })).not.toThrow(/secret-colour/)
|
|
expect(() => defineBrand({ ...base, accent: "#12" })).toThrow(/accent/)
|
|
})
|
|
|
|
it("refuses a name that is empty, too long or carries a control character", () => {
|
|
for (const name of ["", " ", "x".repeat(61), "bad\u0007name"]) {
|
|
expect(() => defineBrand({ name, shortName: "x" })).toThrow(/name/)
|
|
}
|
|
})
|
|
|
|
it("refuses a logo that is not a path on this app's own origin", () => {
|
|
for (const logo of [
|
|
"https://cdn.example.test/logo.svg",
|
|
"//cdn.example.test/logo.svg",
|
|
"javascript:alert(1)",
|
|
"data:image/svg+xml,<svg/>",
|
|
"logo.svg",
|
|
]) {
|
|
expect(() => defineBrand({ ...base, logo: { light: logo, dark: "/a.svg", favicon: "/a.svg" } }), logo).toThrow(
|
|
/logo/,
|
|
)
|
|
}
|
|
expect(defineBrand({ ...base, logo: { light: "/l.svg", dark: "/d.svg", favicon: "/f.svg" } }).logo.dark).toBe(
|
|
"/d.svg",
|
|
)
|
|
})
|
|
|
|
it("refuses a token that could break out of a style attribute", () => {
|
|
for (const value of ["red; background: url(//evil)", "#fff}", "url(//evil)", "expression(1)", "a\\b", "<b>"]) {
|
|
expect(() => defineBrand({ ...base, tokens: { "--brand-accent": value } }), value).toThrow(/token/)
|
|
}
|
|
expect(() => defineBrand({ ...base, tokens: { "not a name": "#fff" } })).toThrow(/token/)
|
|
expect(brandTokenStyle(defineBrand({ ...base, tokens: { "radius-panel": "0.5rem" } }))).toEqual({
|
|
"--brand-radius-panel": "0.5rem",
|
|
})
|
|
})
|
|
|
|
it("is what the app ships: a neutral name, no ConusAI wording, no attribution", () => {
|
|
expect(JSON.stringify(appBrand).toLowerCase()).not.toContain("conusai")
|
|
expect(appBrand.attribution).toBe(false)
|
|
})
|
|
})
|