// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // The tenant brand in a browser (plan item AT2.6): an administrator chooses colours and a logo and a // member sees them, legibly in both themes, from the first byte; a member who is not an administrator // cannot change them; and a logo that could run script is refused. import { expect, test } from "@playwright/test" import { call, callRaw, makeOrganization, signIn, unique, violations } from "./support/app" const channels = (color: string): [number, number, number] => { const match = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(color) if (!match) throw new Error(`not an rgb colour: ${color}`) return [Number(match[1]), Number(match[2]), Number(match[3])] } const luminance = ([r, g, b]: [number, number, number]): number => { const lin = (v: number) => (v / 255 <= 0.03928 ? v / 255 / 12.92 : ((v / 255 + 0.055) / 1.055) ** 2.4) return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b) } const contrast = (a: string, b: string): number => { const [hi, lo] = [luminance(channels(a)), luminance(channels(b))].sort((x, y) => y - x) as [number, number] return (hi + 0.05) / (lo + 0.05) } test("a tenant brand applies in both themes, from the first paint", async ({ page }) => { await signIn(page, `${unique("admin")}@example.test`) const { org } = await makeOrganization(page, "brand") await page.goto(`/${org}/branding`) await expect(page.getByRole("heading", { level: 1, name: "Brand" })).toBeVisible() await page.getByLabel("Display name").fill("Acme Review") await page.getByLabel("Primary colour").fill("#1a1a8c") await page.getByLabel("Accent colour").fill("#f4c430") // the preview answers before anything is saved, and says what the contrast is await expect(page.getByText(/Text on the colour: \d+\.\d to 1/).first()).toBeVisible() expect(await violations(page)).toEqual([]) // Saving reloads the page (brand-form.tsx); wait for that reload, so a later navigation of the // test's own never races it ("interrupted by another navigation"). const reloaded = page.waitForEvent("load") await page.getByRole("button", { name: "Save the brand" }).click() await reloaded await expect(page.getByLabel("Primary colour")).toHaveValue("#1a1a8c") // server-rendered: the raw HTML of the organization page already carries the brand layer const html = await (await page.request.get(`/${org}`)).text() expect(html).toMatch(/data-tenant-brand="#1a1a8c"[^>]*style="[^"]*--brand-accent:#1a1a8c/) expect(html).toContain("Acme Review") // No vendor name in what the tenant's users get. Asset URLs under /_next/ are left out: a dev // server names its chunks after the package they start in (".../_next/static/chunks/..._%40conusai-kit_brand_...js"), // while a production build hashes them. expect(html.replace(/\/_next\/[^"'\s\\]+/g, "").toLowerCase()).not.toContain("conusai") // the primary action's fill is the brand's colour on both themes, and what is written as text on it reads await page.goto(`/${org}/branding`) const panels = page.locator(".brand-preview-panel") await expect(panels).toHaveCount(2) for (const [index, theme] of ["dark", "light"].entries()) { const panel = panels.nth(index) const action = panel.locator(".brand-preview-action") const fill = await action.evaluate((el) => getComputedStyle(el).backgroundColor) const onFill = await action.evaluate((el) => getComputedStyle(el).color) expect(channels(fill), theme).toEqual([0x1a, 0x1a, 0x8c]) expect(contrast(fill, onFill), `${theme} text on the fill`).toBeGreaterThanOrEqual(4.5) const link = panel.locator(".brand-preview-link") const ink = await link.evaluate((el) => getComputedStyle(el).color) const page_ = await panel.evaluate((el) => getComputedStyle(el).backgroundColor) expect(contrast(ink, page_), `${theme} link on the page`).toBeGreaterThanOrEqual(4.5) } // one element carries the theme class: the light preview, and nothing else on the page expect(await page.evaluate(() => document.querySelectorAll(".light").length)).toBeLessThanOrEqual(1) }) test("a member who is not an administrator reads the brand and cannot change it", async ({ page, browser }) => { const adminEmail = `${unique("admin")}@example.test` await signIn(page, adminEmail) const { org } = await makeOrganization(page, "perm") const memberEmail = `${unique("member")}@example.test` const invite = await call<{ token: string }>(page, "tenancy.member.invite", { org, email: memberEmail, role: "member", }) const member = await browser.newPage() await signIn(member, memberEmail) await call(member, "tenancy.invitation.accept", { org, token: invite.token }) await member.goto(`/${org}/branding`) await expect(member.getByText("Only administrators can change the brand.")).toBeVisible() await expect(member.getByRole("button", { name: "Save the brand" })).toHaveCount(0) // and the procedure answers them as it answers a stranger const csrf = (await member.locator('meta[name="csrf-token"]').getAttribute("content")) ?? "" const refused = await callRaw(member.request, csrf, "branding.brand.set", { org, primary: "#000000" }) expect(refused.status()).toBe(404) await member.close() }) test("a logo that carries a script is refused, and nothing is stored", async ({ page }) => { await signIn(page, `${unique("admin")}@example.test`) const { org } = await makeOrganization(page, "logo") await page.goto(`/${org}/branding`) await page.getByLabel("Logo for the light theme").setInputFiles({ name: "logo.svg", mimeType: "image/svg+xml", buffer: Buffer.from(''), }) await page.getByRole("button", { name: "Save the brand" }).click() await expect(page.locator(".app-error")).toContainText("That did not work") await page.reload() await expect(page.getByAltText("Current logo")).toHaveCount(0) })