92 lines
4.9 KiB
TypeScript
92 lines
4.9 KiB
TypeScript
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
// The tenant logos in a browser (plan item AT2.6, spec §10.1, §10.3): one uploaded logo stands in for the
|
|
// other theme, an image larger than a stored logo may be is resized and not refused, the organization's and
|
|
// the app's share images are drawn, and a damaged PNG is refused with nothing stored.
|
|
|
|
import { expect, test } from "@playwright/test"
|
|
import { dimensions, markPng, noisePng } from "../test/support/images"
|
|
import { makeOrganization, signIn, unique } from "./support/app"
|
|
|
|
test("one uploaded logo is used on both themes, a larger image is resized, and the share images are drawn", async ({
|
|
page,
|
|
browser,
|
|
}) => {
|
|
await signIn(page, `${unique("admin")}@example.test`)
|
|
const { org } = await makeOrganization(page, "logos")
|
|
|
|
// only the dark theme's logo, and larger than a stored logo may be: it is resized, not refused
|
|
await page.goto(`/${org}/branding`)
|
|
const upload = noisePng(700, 300)
|
|
expect(upload.length).toBeGreaterThan(256 * 1024)
|
|
await page
|
|
.getByLabel("Logo for the dark theme")
|
|
.setInputFiles({ name: "logo.png", mimeType: "image/png", buffer: upload })
|
|
await page.getByRole("button", { name: "Save the brand" }).click()
|
|
await expect(page.getByAltText("Current logo")).toHaveCount(1)
|
|
// the page says what each theme shows
|
|
await expect(page.getByText("Light theme: the dark logo, because this theme has none of its own")).toBeVisible()
|
|
await expect(page.getByText("Dark theme: its own logo")).toBeVisible()
|
|
|
|
// the organization layout draws that logo for both themes, at 1x and 2x
|
|
const html = await (await page.request.get(`/${org}`)).text()
|
|
const source = `src="/${org}/brand/logo/dark\\?v=[0-9a-f]{16}&s=1x"`
|
|
expect(html).toMatch(new RegExp(`class="app-logo app-logo-dark"[^>]*${source}`))
|
|
expect(html).toMatch(new RegExp(`class="app-logo app-logo-light"[^>]*${source}`))
|
|
// The page advertises the app's public share image: the organization's own (`/{org}/brand/og`)
|
|
// answers a member only, so a crawler could never fetch it, and an organization stays
|
|
// indistinguishable from a missing one to anyone outside it.
|
|
expect(html).toMatch(/<meta property="og:image" content="http:\/\/127.0.0.1:\d+\/api\/brand\/og"/)
|
|
expect(html).not.toContain(`/${org}/brand/og"`)
|
|
|
|
// the stored sizes are the standard ones, and nothing but a PNG is served
|
|
const one = await page.request.get(`/${org}/brand/logo/dark?s=1x`)
|
|
expect(one.headers()["content-type"]).toBe("image/png")
|
|
expect(dimensions(await one.body()).width).toBeLessThanOrEqual(420)
|
|
const two = await page.request.get(`/${org}/brand/logo/dark?s=2x`)
|
|
expect(dimensions(await two.body()).width).toBeLessThanOrEqual(840)
|
|
expect((await page.request.get(`/${org}/brand/logo/light`)).status()).toBe(404)
|
|
|
|
// the organization's share image: a member gets it, a stranger gets what a missing organization gets
|
|
const card = await page.request.get(`/${org}/brand/og`)
|
|
expect(card.status()).toBe(200)
|
|
expect(card.headers()["content-type"]).toBe("image/png")
|
|
expect(dimensions(await card.body())).toEqual({ width: 1200, height: 630 })
|
|
const stranger = await browser.newPage()
|
|
await signIn(stranger, `${unique("stranger")}@example.test`)
|
|
expect((await stranger.request.get(`/${org}/brand/og`)).status()).toBe(404)
|
|
await stranger.close()
|
|
|
|
// the app's own share image is public: a link preview is fetched without a session
|
|
const anonymous = await browser.newContext()
|
|
const appCard = await anonymous.request.get(`${new URL(page.url()).origin}/api/brand/og`)
|
|
expect(appCard.status()).toBe(200)
|
|
expect(dimensions(await appCard.body())).toEqual({ width: 1200, height: 630 })
|
|
await anonymous.close()
|
|
|
|
// removing the logo removes it from the page and from the store
|
|
await page.goto(`/${org}/branding`)
|
|
await page.getByRole("button", { name: "Remove the dark logo" }).click()
|
|
await expect(page.getByText("Dark theme: the app's logo, because no logo is set")).toBeVisible()
|
|
// the page reloads itself once the removal is stored
|
|
const reloaded = page.waitForEvent("load")
|
|
await page.getByRole("button", { name: "Save the brand" }).click()
|
|
await reloaded
|
|
await expect(page.getByAltText("Current logo")).toHaveCount(0)
|
|
expect((await page.request.get(`/${org}/brand/logo/dark`)).status()).toBe(404)
|
|
})
|
|
|
|
test("a PNG that is damaged is refused and nothing is stored", async ({ page }) => {
|
|
await signIn(page, `${unique("admin")}@example.test`)
|
|
const { org } = await makeOrganization(page, "damaged")
|
|
const png = Buffer.from(markPng(300, 80))
|
|
png[30] = (png[30] ?? 0) ^ 0xff
|
|
await page.goto(`/${org}/branding`)
|
|
await page
|
|
.getByLabel("Logo for the light theme")
|
|
.setInputFiles({ name: "logo.png", mimeType: "image/png", buffer: png })
|
|
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)
|
|
})
|