@@ -0,0 +1,116 @@
|
||||
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
||||
// What a page says about itself, rendered with a customer's brand: their name, their colours on the
|
||||
// first byte of the document, and no trace of ConusAI's (plan item AT2.6 done_when, the rendered-page
|
||||
// half; there are no emails or share images in the template to render).
|
||||
|
||||
import { brandTokenStyle, defineBrand } from "@app/config"
|
||||
import { createElement } from "react"
|
||||
import { renderToStaticMarkup } from "react-dom/server"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { brandHead, brandMetadata, brandTokensOnly, Document } from "../apps/web/src/ui/document"
|
||||
import { appCatalogues } from "../apps/web/src/ui/messages"
|
||||
|
||||
const customer = defineBrand({
|
||||
name: "Acme Review",
|
||||
shortName: "Acme",
|
||||
primary: "#1a1a8c",
|
||||
logo: { light: "/acme-light.svg", dark: "/acme-dark.svg", favicon: "/acme.svg" },
|
||||
})
|
||||
|
||||
// The layout's own consent wording in the page's locale, as RootLayout passes it.
|
||||
const consent = (key: string): string => {
|
||||
const text = appCatalogues.lt?.consent?.[key]
|
||||
if (!text) throw new Error(`missing lt consent.${key}`)
|
||||
return text
|
||||
}
|
||||
const privacy = {
|
||||
title: consent("title"),
|
||||
body: consent("body"),
|
||||
replay: consent("replay"),
|
||||
accept: consent("accept"),
|
||||
reject: consent("reject"),
|
||||
reopen: consent("reopen"),
|
||||
gpc: consent("gpc"),
|
||||
}
|
||||
|
||||
const render = (extra: { theme?: "dark" | "light"; observe?: { release: string; environment: string } } = {}): string =>
|
||||
renderToStaticMarkup(
|
||||
createElement(Document, {
|
||||
brand: { ...customer, style: brandTokenStyle(customer) },
|
||||
locale: "lt",
|
||||
theme: extra.theme ?? "dark",
|
||||
signedIn: false,
|
||||
signIn: "Prisijungti",
|
||||
signOut: null,
|
||||
controls: null,
|
||||
privacy,
|
||||
...(extra.observe ? { observe: extra.observe } : {}),
|
||||
children: createElement("main", null, "turinys"),
|
||||
}),
|
||||
)
|
||||
|
||||
const html = render()
|
||||
|
||||
describe("a page rendered with a customer's brand", () => {
|
||||
it("carries the customer's name and logos and no ConusAI name", () => {
|
||||
expect(html).toContain("Acme Review")
|
||||
expect(html).toContain('src="/acme-dark.svg"')
|
||||
expect(html.toLowerCase()).not.toContain("conusai")
|
||||
})
|
||||
|
||||
it("writes the brand layer as a stylesheet in the head, so the first paint is branded and a platform theme can win", () => {
|
||||
const root = /<html[^>]*>/.exec(html)?.[0] ?? ""
|
||||
expect(root).toContain('lang="lt"')
|
||||
expect(root).not.toContain("--primary")
|
||||
const layer = /<style data-brand-layer="app"[^>]*>([^<]*)<\/style>/.exec(html)?.[1] ?? ""
|
||||
expect(html.indexOf("data-brand-layer")).toBeLessThan(html.indexOf("</head>"))
|
||||
expect(layer).toMatch(/^:root:root\{.*--primary:light-dark\(#1a1a8c, ?#[0-9a-f]{6}\)/)
|
||||
expect(layer).toMatch(/--ring:light-dark\(#[0-9a-f]{6}, ?#[0-9a-f]{6}\)/)
|
||||
})
|
||||
|
||||
it("leaves the colours to the platform's organization theme when there is one, keeping the brand's own tokens", () => {
|
||||
expect(brandTokensOnly({ "--primary": "#1a1a8c", "--brand-font-size-root": "16px" })).toEqual({
|
||||
"--brand-font-size-root": "16px",
|
||||
})
|
||||
})
|
||||
|
||||
it("names the customer, and only the customer, in the tab and in a share card", () => {
|
||||
const metadata = JSON.stringify(brandMetadata(customer))
|
||||
expect(metadata).toContain("Acme Review")
|
||||
expect(metadata.toLowerCase()).not.toContain("conusai")
|
||||
})
|
||||
|
||||
it("points the share card at the app's own image, or at the organization's on its own pages", () => {
|
||||
const app = brandMetadata(customer)
|
||||
expect(app.openGraph?.images).toEqual([{ url: "/api/brand/og", width: 1200, height: 630, alt: "Acme Review" }])
|
||||
expect(app.twitter).toMatchObject({ card: "summary_large_image", images: ["/api/brand/og"] })
|
||||
const organization = brandMetadata(customer, "/acme/brand/og")
|
||||
expect(JSON.stringify(organization)).toContain("/acme/brand/og")
|
||||
expect(JSON.stringify(organization)).not.toContain("/api/brand/og")
|
||||
})
|
||||
|
||||
it("gives a link preview an absolute image address from the deployment's public URL", () => {
|
||||
const { meta } = brandHead(brandMetadata(customer), "https://app.example.test")
|
||||
expect(meta).toContainEqual({ property: "og:image", content: "https://app.example.test/api/brand/og" })
|
||||
expect(meta).toContainEqual({ name: "twitter:image", content: "https://app.example.test/api/brand/og" })
|
||||
})
|
||||
|
||||
it("has exactly one element that carries the theme class: the page, not a component", () => {
|
||||
expect((html.match(/class="[^"]*\blight\b/g) ?? []).length).toBeLessThanOrEqual(1)
|
||||
})
|
||||
|
||||
it("puts the light theme on the root element from the first byte, and the dark one on nothing", () => {
|
||||
expect(/<html[^>]*>/.exec(html)?.[0]).not.toMatch(/class="[^"]*\blight\b/)
|
||||
const light = render({ theme: "light" })
|
||||
expect(/<html[^>]*>/.exec(light)?.[0]).toMatch(/class="light"/)
|
||||
expect((light.match(/class="(?:[^"]* )?light(?: [^"]*)?"/g) ?? []).length).toBe(1)
|
||||
})
|
||||
|
||||
it("tells the browser to start observability only when the server turned it on, without naming ConusAI", () => {
|
||||
expect(html).not.toContain("app-observe")
|
||||
const observed = render({ observe: { release: "2026.10.1", environment: "production" } })
|
||||
expect(observed).toContain('name="app-observe"')
|
||||
expect(observed).toContain('data-release="2026.10.1"')
|
||||
expect(observed.toLowerCase()).not.toContain("conusai")
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user