@@ -0,0 +1,79 @@
|
||||
// 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<Inbox>(page, "notifications.inbox.list", { org })).items.map((item) => item.code))
|
||||
.toContain("invitation_accepted")
|
||||
await expect
|
||||
.poll(async () => (await call<Inbox>(member, "notifications.inbox.list", { org })).items.map((item) => item.code))
|
||||
.toContain("member_joined")
|
||||
const ownerInbox = await call<Inbox>(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<Inbox>(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<Inbox>(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()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user