ConusAI vendor v0.1.0-beta.114
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-beta.114
2026-11-14 09:56:45 +00:00
commit 1ce8d23c35
867 changed files with 122104 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
// 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, type Locator, 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)
}
type ColourProperty = "backgroundColor" | "color" | "textDecorationColor" | "outlineColor"
/** A computed colour as sRGB, whatever space the stylesheet wrote it in (the theme roles are OKLCH). */
const rgbOf = (locator: Locator, property: ColourProperty): Promise<string> =>
locator.evaluate((el, name) => {
const canvas = document.createElement("canvas")
canvas.width = 1
canvas.height = 1
const context = canvas.getContext("2d") as CanvasRenderingContext2D
context.fillStyle = getComputedStyle(el)[name]
context.fillRect(0, 0, 1, 1)
const [r, g, b] = context.getImageData(0, 0, 1, 1).data
return `rgb(${r}, ${g}, ${b})`
}, property)
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: 2, 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([])
// Confirm the write and loader refresh without losing the document or its open panels.
const documentStarted = await page.evaluate(() => performance.timeOrigin)
await page.getByRole("button", { name: "Save the brand" }).click()
await expect(page.getByText("The brand was saved.", { exact: true })).toBeVisible()
expect(await page.evaluate(() => performance.timeOrigin)).toBe(documentStarted)
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()
// The brand layer is the runtime theme roles (brand-kit BK6): the primary action, the focus ring and
// the sidebar's primary, never the kit's internal --brand-* tier.
expect(html).toMatch(/data-tenant-brand="#1a1a8c"[^>]*style="[^"]*--primary:light-dark\(#1a1a8c, #[0-9a-f]{6}\)/)
expect(html).toMatch(
/data-tenant-brand="#1a1a8c"[^>]*style="[^"]*--sidebar-primary:light-dark\(#1a1a8c, #[0-9a-f]{6}\)/,
)
expect(html).toMatch(/data-tenant-brand="#1a1a8c"[^>]*style="[^"]*--ring:light-dark\(/)
expect(html).not.toMatch(/data-tenant-brand="#1a1a8c"[^>]*style="[^"]*--brand-accent/)
expect(html).toContain("Acme Review")
// Agent/Help deliberately ship their localized attribution label in loader
// data. It must not brand the app's own screen, metadata or asset URLs.
const appHtml = html.replaceAll('poweredBy:"Powered by ConusAI"', "").replaceAll(/\/@fs\/[^"]+/g, "")
expect(appHtml.toLowerCase()).not.toContain("conusai")
expect((await page.locator("body").innerText()).toLowerCase()).not.toContain("conusai")
// the organization's colour moves the sidebar and the focus ring of the shell, not only the preview
await page.goto(`/${org}`)
const sidebar = await rgbOf(page.locator('[data-sidebar="sidebar"]').first(), "backgroundColor")
const navLink = page.getByRole("navigation", { name: "Main navigation" }).getByRole("link").first()
await page.keyboard.press("Tab")
await navLink.focus()
expect(await navLink.evaluate((el) => el.matches(":focus-visible"))).toBe(true)
const ring = await rgbOf(navLink, "outlineColor")
// #1a1a8c is 1.9:1 on the dark page, so the ring is the brand colour moved towards white: the dark
// half of the light-dark() pair the organization layout wrote, not the ConusAI teal.
const declared = await navLink.evaluate((el) => getComputedStyle(el).getPropertyValue("--sidebar-ring").trim())
const darkRing = /^light-dark\(#[0-9a-f]{6}, ?#([0-9a-f]{6})\)$/.exec(declared)?.[1]
expect(darkRing, `--sidebar-ring is ${declared}`).toBeDefined()
expect(channels(ring), "the focus ring follows the brand").toEqual(
[0, 2, 4].map((at) => Number.parseInt((darkRing as string).slice(at, at + 2), 16)),
)
expect(channels(ring)).not.toEqual([0x80, 0xcd, 0xc6])
expect(contrast(ring, sidebar), "the focus ring on the sidebar").toBeGreaterThanOrEqual(3)
expect(await violations(page)).toEqual([])
// 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(".kit-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('[data-slot="brand-preview-action"]')
const fill = await rgbOf(action, "backgroundColor")
const onFill = await rgbOf(action, "color")
expect(channels(fill), theme).toEqual(theme === "dark" ? [0x5f, 0x5f, 0xaf] : [0x1a, 0x1a, 0x8c])
expect(contrast(fill, onFill), `${theme} text on the fill`).toBeGreaterThanOrEqual(4.5)
const link = panel.locator(".kit-brand-preview-link")
const ink = await rgbOf(link, "color")
const page_ = await rgbOf(panel, "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('<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>'),
})
await page.getByRole("button", { name: "Save the brand" }).click()
await expect(page.getByRole("alert").filter({ hasText: "That did not work" })).toBeVisible()
await page.reload()
await expect(page.getByAltText("Current logo")).toHaveCount(0)
})