// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // Real Postgres and the app worker's invitation relay; provider services remain simulated. import { expect, test } from "@playwright/test" import { call, callRaw, makeOrganization, signIn, unique, violations } from "./support/app" type Inbox = { items: Array<{ id: string; code: string; readAt: string | null }>; unreadCount: number } test("invitations reach only their recipients and the shared inbox marks them read", async ({ page, browser, }, testInfo) => { await page.setViewportSize({ width: 1280, height: 900 }) await signIn(page, `${unique("inbox-owner")}@example.test`) const { org } = await makeOrganization(page) const email = `${unique("inbox-viewer")}@example.test` const invitation = await call<{ token: string }>(page, "tenancy.member.invite", { org, email, role: "viewer" }) const context = await browser.newContext({ baseURL: new URL(page.url()).origin }) try { const member = await context.newPage() await signIn(member, email) await call(member, "tenancy.invitation.accept", { org, token: invitation.token }) await expect .poll(async () => (await call(page, "notifications.inbox.list", { org })).items.map((item) => item.code)) .toContain("invitation_accepted") await expect .poll(async () => (await call(member, "notifications.inbox.list", { org })).items.map((item) => item.code)) .toContain("member_joined") const ownerInbox = await call(page, "notifications.inbox.list", { org }) const accepted = ownerInbox.items.find((item) => item.code === "invitation_accepted") if (!accepted) throw new Error("the invitation relay did not deliver its notification") const csrf = await member.locator('meta[name="csrf-token"]').getAttribute("content") const denial = await callRaw(member.request, csrf ?? "", "notifications.inbox.markRead", { org, id: accepted.id }) expect(denial.status()).toBe(404) expect( (await call(page, "notifications.inbox.list", { org })).items.find((item) => item.id === accepted.id) ?.readAt, ).toBeNull() await page.goto(`/${org}`) await page.locator('[data-guide="frame.notifications"]').click() const panel = page.locator('[data-slot="side-panel"]') await expect(panel.getByText("An invitation was accepted", { exact: true })).toBeVisible() await panel.getByRole("button", { name: "Mark as read: An invitation was accepted", exact: true }).click() await expect .poll( async () => (await call(page, "notifications.inbox.list", { org })).items.find((item) => item.id === accepted.id) ?.readAt, ) .not.toBeNull() const bar = await page.locator('[data-slot="frame-bar"]').boundingBox() const head = await panel.locator(".kit-side-panel-head").boundingBox() expect(head?.y ?? -1).toBeGreaterThanOrEqual((bar?.y ?? 0) + (bar?.height ?? 0)) await page.getByRole("button", { name: "Reject analytics and replay", exact: true }).click({ trial: true }) expect(await violations(page)).toEqual([]) await page.screenshot({ path: testInfo.outputPath("notification-invitation-desktop.png") }) await panel.getByRole("button", { name: "Close", exact: true }).click() await expect(page.locator('[data-guide="frame.notifications"]')).toBeFocused() await page.setViewportSize({ width: 768, height: 900 }) await page.locator('[data-guide="frame.notifications"]').click() const overlay = page.locator('[data-slot="side-panel"]') // The entrance animation must not make the document wider at the overlay breakpoint. expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) const overlayHead = await overlay.locator(".kit-side-panel-head").boundingBox() const overlayBar = await page.locator('[data-slot="frame-bar"]').boundingBox() expect(overlayHead?.y ?? -1).toBeGreaterThanOrEqual((overlayBar?.y ?? 0) + (overlayBar?.height ?? 0)) await page.getByRole("button", { name: "Reject analytics and replay", exact: true }).click({ trial: true }) await page.screenshot({ path: testInfo.outputPath("notification-invitation-tablet.png") }) await overlay.getByRole("button", { name: "Close", exact: true }).click() await page.setViewportSize({ width: 360, height: 800 }) await page.locator('[data-guide="frame.notifications"]').click() const sheet = page.getByRole("dialog") await expect(sheet.getByRole("button", { name: "Close", exact: true })).toBeVisible() expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) await page.screenshot({ path: testInfo.outputPath("notification-invitation-mobile.png") }) await sheet.getByRole("button", { name: "Close", exact: true }).click() } finally { await context.close() } })