Files
2026-11-14 09:56:45 +00:00

577 lines
30 KiB
TypeScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The app frame in a browser: it stays mounted while a person moves, so an open right panel stays open; Settings
// is one page of sections by permission, and it renames the organization and sets its language; the theme can
// follow the device; the layout comes back from its cookies; and a phone gets the sidebar in a sheet.
import { expect, type Page, test } from "@playwright/test"
import { moduleSelected } from "../modules/inclusion"
import { call, makeOrganization, openPersonMenu, openScope, signIn, unique, violations } from "./support/app"
/** Marks the frame's element: a remount, or a full load, loses the mark. */
const markFrame = (page: Page): Promise<void> =>
page.locator('[data-slot="app-frame"]').evaluate((frame) => {
;(frame as HTMLElement & { kept?: boolean }).kept = true
})
const frameKept = (page: Page): Promise<boolean> =>
page.locator('[data-slot="app-frame"]').evaluate((frame) => (frame as HTMLElement & { kept?: boolean }).kept === true)
test("the frame stays mounted across pages, and the Agent stays open with what the person was writing", async ({
page,
}) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org, workspace, project } = await makeOrganization(page)
await page.goto(`/${org}/${workspace}/${project}/knowledge`)
await markFrame(page)
await page.getByRole("button", { name: "Agent" }).click()
const panel = page.locator('[data-slot="side-panel"]')
await expect(panel.getByRole("heading").first()).toBeVisible()
const composer = panel.getByRole("textbox")
const workingIn = panel.locator(".kit-assistant-context")
await expect(workingIn).toHaveText("Working in Review")
await composer.fill("What is left to do?")
expect(await violations(page)).toEqual([])
// the page fills the centre beside the panel
const centre = page.getByRole("main")
expect((await centre.boundingBox())?.width).toBeGreaterThan(400)
// A project section is a link in the sidebar.
await page.getByRole("navigation", { name: "Main navigation" }).getByRole("link", { name: "Documents" }).click()
await expect(page).toHaveURL(new RegExp(`/${project}/documents$`))
await expect(composer).toHaveValue("What is left to do?")
// the sidebar
await page
.getByRole("navigation", { name: "Main navigation" })
.getByRole("link", { name: "Workspace overview" })
.click()
await expect(page).toHaveURL(new RegExp(`/${org}/${workspace}$`))
await expect(composer).toHaveValue("What is left to do?")
// the open panel follows to the workspace's own conversation, with no project open
await expect(workingIn).toHaveText("Working in Main")
// a plain link of a page
await page.getByRole("link", { name: "Review" }).first().click()
await expect(page).toHaveURL(new RegExp(`/${workspace}/${project}$`))
await expect(composer).toHaveValue("What is left to do?")
await expect(workingIn).toHaveText("Working in Review")
expect(await frameKept(page)).toBe(true)
})
test("the Agent is on the projects list, working in the workspace", async ({ page }) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org, workspace } = await makeOrganization(page)
await page.goto(`/${org}/${workspace}`)
await page.getByRole("button", { name: "Agent" }).click()
const panel = page.locator('[data-slot="side-panel"]')
await expect(panel.locator(".kit-assistant-context")).toHaveText("Working in Main")
await expect(panel.getByRole("textbox")).toBeEditable()
expect(await violations(page)).toEqual([])
})
test("organization settings, the workspace and a project remain connected by navigation", async ({
page,
}, testInfo) => {
const errors: string[] = []
page.on("pageerror", (error) => errors.push(error.message))
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text())
})
page.on("response", (response) => {
if (response.status() >= 400) errors.push(`${response.status()} ${new URL(response.url()).pathname}`)
})
await signIn(page, "ada@example.test")
await page.goto("/acme-review/general")
await page.getByRole("link", { name: "Organization settings", exact: true }).click()
const sections = page.getByRole("navigation", { name: "Settings sections" })
await expect(sections.getByRole("link").first()).toBeVisible()
const destinations = await sections
.getByRole("link")
.evaluateAll((links) =>
links.map((link) => ({ href: link.getAttribute("href") ?? "", label: link.textContent?.trim() ?? "" })),
)
expect(destinations.length).toBeGreaterThan(1)
for (const { href, label } of destinations) {
await sections.getByRole("link", { name: label, exact: true }).click()
await expect(page).toHaveURL(new RegExp(`${href.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`))
await expect(page.getByRole("heading", { name: "Settings", exact: true })).toBeVisible()
await expect(sections.getByRole("link", { name: label, exact: true })).toHaveAttribute("aria-current", "page")
}
await page
.getByRole("navigation", { name: "Main navigation" })
.getByRole("link", { name: "Workspace overview" })
.click()
await expect(page).toHaveURL(/\/acme-review\/general$/)
await page.getByRole("link", { name: "Team notes", exact: true }).click()
await expect(page).toHaveURL(/\/acme-review\/general\/notes$/)
await expect(page.getByRole("main")).toBeVisible()
await page.screenshot({ path: testInfo.outputPath("project-navigation.png") })
expect(errors).toEqual([])
})
test("breadcrumbs distinguish repeated slugs at different scope levels", async ({ page }) => {
await signIn(page, `${unique("owner")}@example.test`)
const slug = unique("scope")
await call(page, "tenancy.org.create", { slug, name: "Organization" })
await call(page, "tenancy.workspace.create", { org: slug, slug, name: "Workspace", visibility: "org" })
await call(page, "tenancy.project.create", {
org: slug,
workspace: slug,
slug,
name: "Project",
moduleKey: "notes",
})
await page.goto(`/${slug}/${slug}/${slug}/notes`)
const breadcrumb = page.getByRole("navigation", { name: "Breadcrumb" })
await expect(breadcrumb.getByRole("link", { name: "Workspace" })).toHaveAttribute("href", `/${slug}/${slug}`)
await expect(breadcrumb.getByRole("link", { name: "Project", exact: true })).toHaveAttribute(
"href",
`/${slug}/${slug}/${slug}`,
)
await breadcrumb.getByRole("link", { name: "Workspace" }).click()
await expect(page).toHaveURL(new RegExp(`/${slug}/${slug}$`))
await openScope(page)
await page.getByRole("menuitemradio", { name: "General", exact: true }).click()
await expect(page).toHaveURL(new RegExp(`/${slug}/general$`))
await openScope(page)
await page.getByRole("menuitemradio", { name: "Workspace", exact: true }).click()
await expect(page).toHaveURL(new RegExp(`/${slug}/${slug}$`))
})
test("a newly created workspace can be selected and switched back", async ({ page }, testInfo) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org, workspace } = await makeOrganization(page)
await page.goto(`/${org}/${workspace}`)
await page.goto(`/${org}/workspaces`)
await page.getByRole("button", { name: "Create a workspace", exact: true }).click()
await page.screenshot({ path: testInfo.outputPath("workspace-create-desktop.png") })
await page.setViewportSize({ width: 375, height: 812 })
await expect(page.getByRole("dialog")).toBeVisible()
await page.screenshot({ path: testInfo.outputPath("workspace-create-mobile.png") })
await page.setViewportSize({ width: 1280, height: 800 })
await page.getByLabel("Name", { exact: true }).fill("Research")
await page.getByLabel("Short name").fill("research")
await page.getByRole("button", { name: "Create", exact: true }).click()
await expect(page.getByRole("link", { name: "Research", exact: true })).toBeVisible()
await openScope(page)
await page.getByRole("menuitemradio", { name: "Research", exact: true }).click()
await expect(page).toHaveURL(new RegExp(`/${org}/research$`))
await expect(page.locator('[data-guide="frame.scope"]')).toContainText("Research")
await openScope(page)
await page.getByRole("menuitemradio", { name: "Main", exact: true }).click()
await expect(page).toHaveURL(new RegExp(`/${org}/${workspace}$`))
await expect(page.locator('[data-guide="frame.scope"]')).toContainText("Main")
})
test("organization, workspace and settings breadcrumbs follow the route hierarchy", async ({ page }) => {
await signIn(page, "ada@example.test")
await page.goto("/acme-review")
await expect(page.getByRole("heading", { name: "Workspaces", level: 1 })).toBeVisible()
const list = page.getByRole("grid", { name: "Workspaces" })
await expect(list.getByRole("link", { name: "General", exact: true })).toBeVisible()
await list.getByRole("link", { name: "General", exact: true }).click()
await expect(page).toHaveURL(/\/acme-review\/general$/)
const breadcrumb = page.getByRole("navigation", { name: "Breadcrumb" })
await expect(breadcrumb.getByRole("link", { name: "Acme Review", exact: true })).toHaveCount(0)
await expect(breadcrumb.locator('[aria-current="page"]')).toHaveText("General")
await page.getByRole("link", { name: "Organization settings", exact: true }).click()
await expect(breadcrumb.getByRole("link", { name: "Acme Review", exact: true })).toBeVisible()
await expect(breadcrumb.getByRole("link", { name: "General", exact: true })).toHaveCount(0)
})
test("account settings follow the last accessible workspace", async ({ page }) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org, workspace } = await makeOrganization(page)
await page.goto(`/${org}/${workspace}`)
await expect
.poll(async () => (await page.context().cookies()).find((cookie) => cookie.name === "app_workspace")?.value)
.toBe(encodeURIComponent(`${org}/${workspace}`))
await page.goto("/settings/account")
await expect(page).toHaveURL(new RegExp(`/${org}/settings/account$`))
await expect(page.getByRole("heading", { name: "Your account", exact: true })).toBeVisible()
await expect(page.locator('[data-slot="app-frame"]')).toBeVisible()
})
test("member roles use the same select control as invitations", async ({ browser, page }, testInfo) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org } = await makeOrganization(page)
const email = `${unique("member")}@example.test`
const invite = await call<{ token: string }>(page, "tenancy.member.invite", { org, email, role: "viewer" })
const context = await browser.newContext({ baseURL: new URL(page.url()).origin })
const member = await context.newPage()
await signIn(member, email)
await call(member, "tenancy.invitation.accept", { org, token: invite.token })
await context.close()
await page.goto(`/${org}/members`)
const row = page.getByRole("row").filter({ hasText: email })
const role = row.getByRole("button", { name: /New role for/ })
const inviteRole = page.getByRole("button", { name: /Role$/ }).last()
await expect(role).toBeVisible()
const form = page.locator(".kit-tenancy-invite form")
const emailField = form.getByLabel("E-mail address")
const formRole = form.getByRole("button", { name: /Role/ })
const emailBox = await emailField.boundingBox()
const formRoleBox = await formRole.boundingBox()
expect(Math.abs((emailBox?.y ?? 0) - (formRoleBox?.y ?? 0))).toBeLessThan(2)
const save = row.getByRole("button", { name: /Save/ })
expect((await role.boundingBox())?.height).toBe((await save.boundingBox())?.height)
expect((await role.boundingBox())?.height).toBeGreaterThanOrEqual(24)
const nameCell = await row.getByRole("rowheader").boundingBox()
const roleControl = await role.boundingBox()
expect(
Math.abs((nameCell?.y ?? 0) + (nameCell?.height ?? 0) / 2 - (roleControl?.y ?? 0) - (roleControl?.height ?? 0) / 2),
).toBeLessThan(2)
expect((await role.boundingBox())?.height).toBeLessThanOrEqual((await inviteRole.boundingBox())?.height ?? 0)
await page.screenshot({ path: testInfo.outputPath("members-selects.png"), fullPage: true })
await role.focus()
await page.keyboard.press("Enter")
await page.getByRole("option", { name: "Editor", exact: true }).click()
await row.getByRole("button", { name: /Save/ }).click()
await expect(row.getByRole("cell", { name: "Editor", exact: true })).toBeVisible()
await page.setViewportSize({ width: 375, height: 812 })
await page.reload()
const mobileForm = page.locator(".kit-tenancy-invite form")
const mobilePositions = await mobileForm.evaluate((form) => {
const email = form.querySelector<HTMLInputElement>('input[name="email"]')?.getBoundingClientRect()
const role = form.querySelector<HTMLButtonElement>('[data-slot="select-trigger"]')?.getBoundingClientRect()
return { emailBottom: email?.bottom ?? 0, roleTop: role?.top ?? 0 }
})
expect(mobilePositions.roleTop).toBeGreaterThan(mobilePositions.emailBottom)
const mobileRole = page
.getByRole("row")
.filter({ hasText: email })
.getByRole("button", { name: /New role for/ })
await mobileRole.scrollIntoViewIfNeeded()
const membersRegion = page.getByRole("region", { name: "Members of the organization" })
expect(await membersRegion.evaluate((region) => region.scrollWidth <= region.clientWidth)).toBe(true)
await mobileRole.click()
await expect(page.getByRole("option", { name: "Viewer", exact: true })).toBeVisible()
await page.screenshot({ path: testInfo.outputPath("members-selects-mobile.png"), fullPage: true })
await page.keyboard.press("Escape")
await expect(mobileRole).toBeFocused()
expect(await violations(page)).toEqual([])
})
test("a refused owner role change stays clear and contained in its table row", async ({ page }, testInfo) => {
const email = `${unique("owner")}@example.test`
await signIn(page, email)
const { org } = await makeOrganization(page)
await page.goto(`/${org}/members`)
const row = page.getByRole("row").filter({ hasText: email })
const role = row.getByRole("button", { name: /New role for/ })
await role.click()
await page.getByRole("option", { name: "Editor", exact: true }).click()
await row.getByRole("button", { name: /Save/ }).click()
const alert = row.getByRole("alert")
await expect(alert).toContainText("last owner")
await expect(role).toHaveAttribute("aria-describedby", (await alert.getAttribute("id")) ?? "")
expect(await violations(page)).toEqual([])
await page.screenshot({ path: testInfo.outputPath("members-role-error.png"), fullPage: true })
})
test("invitation actions remain aligned in a table cell", async ({ page }, testInfo) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org } = await makeOrganization(page)
const email = `${unique("invite")}@example.test`
await call(page, "tenancy.member.invite", { org, email, role: "viewer" })
await page.goto(`/${org}/invitations`)
const row = page.getByRole("row").filter({ hasText: email })
const resend = row.getByRole("button", { name: /New link/ })
const revoke = row.getByRole("button", { name: /Revoke/ })
await expect(resend).toBeVisible()
await expect(revoke).toBeVisible()
expect(await resend.locator("xpath=ancestor::td").evaluate((cell) => getComputedStyle(cell).display)).toBe(
"table-cell",
)
expect((await resend.boundingBox())?.height).toBe((await revoke.boundingBox())?.height)
expect(await violations(page)).toEqual([])
await page.screenshot({ path: testInfo.outputPath("invitation-actions.png"), fullPage: true })
})
test("custom role forms use kit selects for creation and reassignment", async ({ page }, testInfo) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org } = await makeOrganization(page)
await page.goto(`/${org}/roles`)
const errors: string[] = []
page.on("pageerror", (error) => errors.push(error.message))
await expect(page.getByRole("textbox", { name: "Name", exact: true })).toHaveCount(0)
await page.getByRole("button", { name: "Create a role", exact: true }).click()
const name = page.getByRole("textbox", { name: "Name", exact: true })
const create = page.getByRole("button", { name: "Create the role" })
await create.click()
await expect(name).toBeFocused()
await expect(name).toHaveAttribute("aria-invalid", "true")
await name.fill("Review readers")
await page.getByRole("button", { name: /Copy the permissions of/ }).click()
await page.getByRole("option", { name: "Administrator", exact: true }).click()
await create.click()
const dialog = page.getByRole("alertdialog")
await expect(dialog).toBeVisible()
await expect(dialog.getByRole("button", { name: "Cancel" })).toBeFocused()
expect(await violations(page)).toEqual([])
await page.keyboard.press("Escape")
await expect(dialog).toBeHidden()
await expect(create).toBeFocused()
await page.keyboard.press("Escape")
await expect(page.getByRole("alertdialog")).toBeVisible()
await page.getByRole("alertdialog").getByRole("button", { name: /Keep/ }).click()
await expect(name).toHaveValue("Review readers")
await page.setViewportSize({ width: 375, height: 812 })
await name.scrollIntoViewIfNeeded()
await page.screenshot({ path: testInfo.outputPath("role-form-mobile.png"), fullPage: true })
expect(await name.evaluate((field) => field.getBoundingClientRect().right <= innerWidth)).toBe(true)
await page.setViewportSize({ width: 1280, height: 800 })
await page.screenshot({ path: testInfo.outputPath("role-form-desktop.png"), fullPage: true })
expect(errors).toEqual([])
await page.getByRole("button", { name: /Where it can be assigned/ }).click()
await page.getByRole("option", { name: "Workspace", exact: true }).click()
await page.getByRole("button", { name: /Copy the permissions of/ }).click()
await page.getByRole("option", { name: "Viewer", exact: true }).click()
await page.getByRole("button", { name: "Create the role" }).click()
await expect(page.getByRole("heading", { name: "Review readers" })).toBeVisible()
const boxes = page.locator(".kit-tenancy-matrix input[type=checkbox]")
const dimensions = await boxes.evaluateAll((controls) =>
controls.map((control) => {
const box = control.getBoundingClientRect()
return { width: box.width, height: box.height, padding: getComputedStyle(control).padding }
}),
)
expect(dimensions.length).toBeGreaterThan(0)
for (const box of dimensions) {
expect(box.width).toBe(box.height)
expect(box.padding).toBe("0px")
}
await expect(page.locator(".kit-tenancy-matrix input[type=checkbox]:disabled").first()).toBeDisabled()
const permission = page.locator('.kit-tenancy-matrix li[data-permission="notes.note.read"] input')
await permission.focus()
await expect(permission).toBeFocused()
const checked = await permission.isChecked()
await page.keyboard.press("Space")
await expect(permission).toBeChecked({ checked: !checked })
await page.keyboard.press("Space")
await expect(permission).toBeChecked({ checked })
await page.screenshot({ path: testInfo.outputPath("role-permission-checkboxes.png"), fullPage: true })
await page.getByRole("button", { name: /Move its members to/ }).click()
await page.getByRole("option", { name: "Editor", exact: true }).click()
await page.getByRole("button", { name: "Delete the role" }).click()
await expect(page.getByRole("alertdialog")).toContainText("Editor")
await page.getByRole("button", { name: "Delete it" }).click()
await expect(page).toHaveURL(new RegExp(`/${org}/roles$`))
})
test("a confirmed sensitive role opens its full-page editor", async ({ page }) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org } = await makeOrganization(page)
await page.goto(`/${org}/roles`)
await page.getByRole("button", { name: "Create a role", exact: true }).click()
await page.getByRole("textbox", { name: "Name", exact: true }).fill("Operations")
await page.getByRole("button", { name: /Copy the permissions of/ }).click()
await page.getByRole("option", { name: "Administrator", exact: true }).click()
await page.getByRole("button", { name: "Create the role", exact: true }).click()
await page.getByRole("alertdialog").getByRole("button", { name: "Create it", exact: true }).click()
await expect(page.getByRole("heading", { name: "Operations", exact: true })).toBeVisible()
await expect(page.locator('[data-slot="form-panel"]')).toHaveCount(0)
})
test("an excluded Documents route shows the kit not-found state", async ({ page }) => {
test.skip(moduleSelected("documents"), "This checks the default-off configuration")
await signIn(page, `${unique("owner")}@example.test`)
const { org, workspace, project } = await makeOrganization(page)
const response = await page.goto(`/${org}/${workspace}/${project}/documents`)
expect(response?.status()).toBe(404)
await expect(page.getByRole("heading", { name: "Page not found", exact: true })).toBeVisible()
await expect(page.locator(".app-shell-header")).toBeHidden()
})
test("a record in the panel of the real frame: its address, the Help button, and Escape back to the row", async ({
page,
}) => {
await signIn(page, `${unique("owner")}@example.test`)
const scope = await makeOrganization(page)
const { noteId } = await call<{ noteId: string }>(page, "notes.note.create", { ...scope, title: "Plan", body: "b" })
const list = `/${scope.org}/${scope.workspace}/${scope.project}/notes`
await page.goto(list)
await markFrame(page)
await page.getByRole("rowheader", { name: "Plan" }).click()
await expect(page).toHaveURL(new RegExp(`\\?record=${noteId}$`))
const panel = page.locator('[data-slot="side-panel"]')
await expect(panel.getByLabel("Title")).toHaveValue("Plan")
// Help is a second occupant, and the record keeps its place
await page.getByRole("button", { name: "Help" }).click()
await expect(panel.getByRole("tab", { name: "Support" })).toBeVisible()
await expect(panel.getByRole("button", { name: /launch|chat/i })).toHaveCount(0)
await panel.getByRole("tab", { name: "Plan" }).click()
await expect(panel.getByLabel("Title")).toHaveValue("Plan")
// no launcher floats over the page
await expect(page.locator(".support-launcher, [data-support-launcher]")).toHaveCount(0)
expect(await frameKept(page)).toBe(true)
})
test("Settings has one row of sections, filtered by permission", async ({ browser, page }) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org } = await makeOrganization(page, "settings")
await page.goto(`/${org}`)
await page.getByRole("link", { name: "Organization settings", exact: true }).click()
await expect(page).toHaveURL(new RegExp(`/${org}/settings$`))
const sections = page.getByRole("navigation", { name: "Settings sections" })
await expect(sections.getByRole("link", { name: "General", exact: true })).toBeVisible()
await expect(sections.getByRole("link", { name: "Workspaces", exact: true })).toBeVisible()
await expect(sections.getByRole("link", { name: "Account", exact: true })).toBeVisible()
await expect(sections.getByRole("list")).toHaveCount(0)
expect(await violations(page)).toEqual([])
// the organization's name and language, saved through tenancy.org.update, and the frame follows
await sections.getByRole("link", { name: "General" }).click()
await expect(page.getByLabel("Name", { exact: true })).toBeVisible()
await page.getByLabel("Name", { exact: true }).fill("Renamed")
await page.getByRole("button", { name: /Default language/ }).click()
await page.getByRole("option", { name: "Lietuvių" }).click()
await page.getByRole("button", { name: "Save" }).click()
await expect(page.getByText("Saved", { exact: true })).toBeVisible()
await expect(page.locator('[data-guide="frame.scope"]')).toContainText("Renamed")
await page.reload()
await expect(page.getByLabel("Name", { exact: true })).toHaveValue("Renamed")
expect(await violations(page)).toEqual([])
// a new member starts in the organization's language
const invited = `${unique("invited")}@example.test`
const { token } = await call<{ token: string }>(page, "tenancy.member.invite", {
org,
email: invited,
role: "member",
})
const member = await (await browser.newContext({ baseURL: new URL(page.url()).origin })).newPage()
await signIn(member, invited)
await member.goto(`/invitations/${org}/${token}`)
await member
.getByRole("button", { name: "Priimti kvietimą" })
.or(member.getByRole("button", { name: "Accept the invitation" }))
.click()
await member.waitForURL(new RegExp(`/${org}/[a-z0-9-]+$`))
await member.goto(`/${org}`)
await expect(member.locator("html")).toHaveAttribute("lang", "lt")
// a member sees what they may: the personal section, and no General
await member.goto(`/${org}/settings/account`)
const theirs = member.getByRole("navigation", { name: "Nustatymų skyriai" })
await expect(theirs.getByRole("link", { name: "Bendrieji" })).toHaveCount(0)
await member.context().close()
})
test("the theme can follow the device, from the first paint", async ({ page }) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org } = await makeOrganization(page)
await page.emulateMedia({ colorScheme: "light" })
await page.goto(`/${org}`)
const root = page.locator("html")
await expect(root).not.toHaveClass(/light/)
await openPersonMenu(page)
await page.getByRole("menuitemradio", { name: "System" }).click()
await expect(root).toHaveClass(/light/)
await page.reload()
await expect(root).toHaveClass(/light/)
await page.emulateMedia({ colorScheme: "dark" })
await expect(root).not.toHaveClass(/light/)
await page.reload()
await expect(root).not.toHaveClass(/light/)
expect(await violations(page)).toEqual([])
})
test("the person's menu and the page's foot hold what the header held", async ({ page }) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org } = await makeOrganization(page)
await page.goto(`/${org}`)
await expect(page.locator(".app-shell-header")).toBeHidden()
// The privacy choices are two steps away, in the person's menu, and not a button at the foot of every page.
await expect(page.getByRole("main").getByRole("button", { name: "Privacy choices" })).toHaveCount(0)
await openPersonMenu(page)
await page.getByRole("menuitem", { name: "Privacy choices" }).click()
await expect(page.getByRole("region", { name: "Privacy choices" })).toBeVisible()
await openPersonMenu(page)
await expect(page.getByRole("menuitem", { name: "Sign out" })).toBeVisible()
await page.getByRole("menuitem", { name: "Sign out" }).click()
await expect(page).toHaveURL(/\/auth\/signed-out/)
})
test("the sidebar comes back as it was left, and a phone gets it in a sheet", async ({ page }) => {
await signIn(page, `${unique("owner")}@example.test`)
const { org, workspace } = await makeOrganization(page)
await page.goto(`/${org}/${workspace}`)
const toggle = page.locator('[data-guide="frame.sidebar"]')
await expect
.poll(async () => (await page.context().cookies()).find((c) => c.name === "app_workspace")?.value)
.toBe(encodeURIComponent(`${org}/${workspace}`))
await page.getByRole("link", { name: "Organization settings", exact: true }).click()
await expect(page).toHaveURL(new RegExp(`/${org}/settings(?:/account)?$`))
await page
.getByRole("navigation", { name: "Main navigation" })
.getByRole("link", { name: "Workspace overview" })
.click()
await expect(page).toHaveURL(new RegExp(`/${org}/${workspace}$`))
await toggle.click()
await expect
.poll(async () => (await page.context().cookies()).find((c) => c.name === "app_sidebar")?.value)
.toBe("closed")
await page.reload()
await expect(page.locator('[data-slot="sidebar"][data-state="collapsed"]').first()).toBeVisible()
await page.setViewportSize({ width: 375, height: 812 })
await page.reload()
await page.waitForLoadState("networkidle")
await toggle.click()
const sheet = page.getByRole("dialog")
await expect(sheet.getByRole("link", { name: "Workspace overview" })).toBeVisible()
expect(await violations(page)).toEqual([])
// Leaving Settings must not redirect back while its layout is still mounted.
await sheet.getByRole("link", { name: "Organization settings", exact: true }).click()
await expect(page).toHaveURL(new RegExp(`/${org}/settings(?:/account)?$`))
await toggle.click()
await page.getByRole("dialog").getByRole("link", { name: "Workspace overview" }).click()
await expect(page).toHaveURL(new RegExp(`/${org}/${workspace}$`))
await expect(page.getByRole("heading", { name: "Projects" })).toBeVisible()
await expect(page.getByRole("dialog")).toHaveCount(0)
await toggle.click()
await page.getByRole("dialog").getByRole("link", { name: "Organization settings", exact: true }).press("Enter")
await expect(page).toHaveURL(new RegExp(`/${org}/settings(?:/account)?$`))
await toggle.click()
await page.getByRole("dialog").getByRole("link", { name: "Workspace overview" }).press("Enter")
await expect(page).toHaveURL(new RegExp(`/${org}/${workspace}$`))
})
test("the workspace picker can create and switch projects from the keyboard", async ({ page }) => {
await signIn(page, "ada@example.test")
const workspace = "/acme-review/general"
await page.goto(workspace)
// Its name holds what it shows (WCAG 2.5.3): the words with no project chosen, the project's name once one is.
const picker = page.getByRole("button", { name: /^(Choose a project$|Switch project: )/ })
await expect(page.getByRole("button", { name: "Choose a project", exact: true })).toBeEnabled()
await expect(picker).toBeEnabled()
await picker.press("Enter")
await page.getByRole("menuitem", { name: "Create a project" }).press("Enter")
await expect(page).toHaveURL(`${workspace}/modules?create=true`)
await page.reload()
const slug = unique("navigation")
await page.getByRole("textbox", { name: "Name", exact: true }).fill("Navigation test")
await page.getByRole("textbox", { name: "Short name", exact: true }).fill(slug)
await page.getByRole("button", { name: "Create", exact: true }).click()
await expect(page).toHaveURL(`${workspace}/${slug}`)
await expect(page.getByRole("link", { name: "Project overview" })).toHaveAttribute("aria-current", "page")
await expect(page.getByRole("button", { name: "Switch project: Navigation test", exact: true })).toBeEnabled()
await picker.press("Enter")
await page.getByRole("menuitemradio", { name: "Team notes" }).press("Enter")
await expect(page).toHaveURL(`${workspace}/notes`)
await page.goBack()
await expect(page).toHaveURL(`${workspace}/${slug}`)
await page.goForward()
await expect(page).toHaveURL(`${workspace}/notes`)
})