Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions tests/browser/global-search.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,66 @@ test.describe("public search destination", () => {
).toBe(true);
});
});

test("keyboard selection follows its action while recent conversations arrive above it", async ({
page,
app,
}) => {
await page.goto(app.origin);
const rail = page.getByRole("button", {
name: "Switch to Primary",
exact: true,
});
await expect(rail).toBeVisible();
// Hold the membership read across a reload so the channel list arrives after
// the palette has opened, as it can after a restored conversation. Other
// reads continue, so the held read stays well inside its own deadline.
const held = [];
let holding = true;
await page.route("**/api/relay/*/query", async (route) => {
const filters = route.request().postDataJSON();
if (holding && filters.some(({ kinds }) => kinds?.includes(39002)))
await new Promise((resolve) => held.push(resolve));
await route.continue();
});
try {
await page.reload();
await expect(rail).toBeVisible();
await button(page, "Search Buzz").click();
const dialog = page.getByRole("dialog", {
name: "Search Buzz",
exact: true,
});
const input = dialog.getByRole("combobox", { name: "Search Buzz" });
const recent = dialog
.getByRole("group", { name: "Recent activity" })
.getByRole("option");
const projects = dialog
.getByRole("group", { name: "Actions" })
.getByRole("option", { name: "Projects", exact: true });
await expect(projects).toBeVisible();
// The connected palette is mounted, but its channel list is still held.
await expect(
dialog.getByText("Connecting to this community…", { exact: true }),
).toHaveCount(0);
await expect(recent).toHaveCount(0);
const id = await projects.getAttribute("id");
while ((await input.getAttribute("aria-activedescendant")) !== id)
await input.press("ArrowDown");
const before = await projects.boundingBox();
holding = false;
for (const resolve of held.splice(0)) resolve();
await expect(recent.first()).toBeVisible();
// The arrivals moved the action; the selection stays with it.
expect((await projects.boundingBox()).y).toBeGreaterThan(before.y);
await expect(input).toHaveAttribute("aria-activedescendant", id);
await input.press("Enter");
await expect(dialog).toHaveCount(0);
await expect(
page.getByRole("heading", { name: "Projects", exact: true }),
).toBeVisible();
} finally {
holding = false;
for (const resolve of held.splice(0)) resolve();
}
});
2 changes: 1 addition & 1 deletion tests/browser/navigation-repairs.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test("Retry navigation reconnects the failed target session", async ({
return route.continue();
});
await page.goto(app.origin);
await openPage(page, "Messages");
await openPage(page, "Messages", { connected: false });
await expect(
page.getByRole("heading", { name: "This destination couldn’t open" }),
).toBeVisible();
Expand Down
42 changes: 25 additions & 17 deletions tests/browser/navigation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,39 @@ export async function pageChoices(page) {
.getByRole("group", { name: "Actions", exact: true });
}

export async function openPage(page, name) {
export async function openPage(page, name, { connected = true } = {}) {
// The community rail appears after local startup has replaced the launch view.
await expect(
page.getByRole("button", { name: "Switch to Primary", exact: true }),
).toBeVisible();
const choices = await pageChoices(page);
const dialog = page.getByRole("dialog", { name: "Search Buzz", exact: true });
const input = dialog.getByRole("combobox", { name: "Search Buzz" });
const choice = choices.getByRole("option", { name, exact: true });
// Registered page actions are visible only after the plugin catalog is ready.
await expect(choice).toBeVisible();
// The list enters with a delayed transform. Its moving hit targets can miss
// a click in WebKit even after the option first becomes visible.
await expect
.poll(
() =>
dialog
.locator(".search-palette-scroll")
.evaluate((element) =>
element
.getAnimations()
.every((animation) => animation.playState === "finished"),
),
{ message: "search choices finish opening" },
)
.toBe(true);
await choice.click();
// Until the community connects, the palette is a placeholder. The connected
// palette replaces it, which resets selection and option ids, so select only
// in the final palette. Pass `connected: false` when the test holds or fails
// the relay session: the placeholder then stays.
await expect(
dialog.getByText("Connecting to this community…", { exact: true }),
).toHaveCount(connected ? 0 : 1);
// Conversation groups above Actions arrive with the channel list and move
// every row below them, so a pointer click can land between rows. Keyboard
// selection follows the choice itself, not its position.
const id = await choice.getAttribute("id");
const limit = await dialog.getByRole("option").count();
for (
let step = 0;
(await input.getAttribute("aria-activedescendant")) !== id;
step++
) {
// Rows that arrive later are inserted above the selection, so moving down
// still reaches the choice. The bound only turns a regression into a failure.
expect(step, `keyboard reaches ${name}`).toBeLessThan(limit + 16);
await input.press("ArrowDown");
}
await input.press("Enter");
await expect(dialog).not.toBeVisible();
}
2 changes: 1 addition & 1 deletion tests/browser/plugins.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ test("companion remains usable while Channels connection is pending", async ({
});
try {
await page.goto(app.origin);
await openPage(page, "Messages");
await openPage(page, "Messages", { connected: false });
await expect(
page.getByText("Connecting to your relay…", { exact: true }),
).toBeVisible();
Expand Down
Loading