577 lines
30 KiB
TypeScript
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`)
|
|
})
|