Room list: fix keyboard navigation on sections (#33809)

* fix: keyboard navigation on sections

* test: add e2e test for keyboard navigation

* fix: add more keyboard navigation

* test: add e2e tests

* chore: remove useless check
This commit is contained in:
Florian Duros
2026-06-11 18:14:36 +02:00
committed by GitHub
parent e441ca5d7c
commit 36a39f7fd6
6 changed files with 311 additions and 12 deletions
@@ -290,4 +290,131 @@ test.describe("Room list sections", () => {
await expect(roomList.getByRole("row", { name: "no unread room" })).not.toBeVisible();
});
});
test.describe("Section keyboard navigation", () => {
test.beforeEach(async ({ app }) => {
// A favourite room forces section mode and gives us a non-trivial first section.
const favouriteId = await app.client.createRoom({ name: "favourite room" });
await app.client.evaluate(async (client, roomId) => {
await client.setRoomTag(roomId, "m.favourite");
}, favouriteId);
// A chats-section room so we have a second section to navigate to.
await app.client.createRoom({ name: "chat room" });
});
test("Arrow Down/Up move focus through sections and rooms", async ({ page }) => {
const roomList = getRoomList(page);
const favouritesHeader = getSectionHeader(page, "Favourites");
// In treegrid mode, a room renders as <div role="row"><div role="gridcell"><button …></button></div></div>.
// Only the inner <button> is focusable, so target it by role for focus assertions.
const favRoomButton = roomList.getByRole("button", { name: "Open room favourite room" });
const chatsHeader = getSectionHeader(page, "Chats");
await expect(favouritesHeader).toBeVisible();
await expect(favRoomButton).toBeVisible();
await expect(chatsHeader).toBeVisible();
await favouritesHeader.focus();
await expect(favouritesHeader).toBeFocused();
// Down moves into the favourite section's room.
await page.keyboard.press("ArrowDown");
await expect(favRoomButton).toBeFocused();
// Down again jumps to the next section header.
await page.keyboard.press("ArrowDown");
await expect(chatsHeader).toBeFocused();
// Up reverses the traversal.
await page.keyboard.press("ArrowUp");
await expect(favRoomButton).toBeFocused();
await page.keyboard.press("ArrowUp");
await expect(favouritesHeader).toBeFocused();
});
test("Arrow Right expands a collapsed section", async ({ page }) => {
const favouritesHeader = getSectionHeader(page, "Favourites");
const favRoom = getRoomList(page).getByRole("row", { name: "Open room favourite room" });
// Collapse the section via click so we know we start expanded=false.
await favouritesHeader.click();
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "false");
await expect(favRoom).not.toBeVisible();
await favouritesHeader.focus();
await page.keyboard.press("ArrowRight");
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "true");
await expect(favRoom).toBeVisible();
});
test("Arrow Right is a no-op on an already-expanded section", async ({ page }) => {
const favouritesHeader = getSectionHeader(page, "Favourites");
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "true");
await favouritesHeader.focus();
await page.keyboard.press("ArrowRight");
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "true");
});
test("Arrow Left collapses an expanded section", async ({ page }) => {
const favouritesHeader = getSectionHeader(page, "Favourites");
const favRoom = getRoomList(page).getByRole("row", { name: "Open room favourite room" });
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "true");
await expect(favRoom).toBeVisible();
await favouritesHeader.focus();
await page.keyboard.press("ArrowLeft");
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "false");
await expect(favRoom).not.toBeVisible();
});
test("Arrow Left is a no-op on an already-collapsed section", async ({ page }) => {
const favouritesHeader = getSectionHeader(page, "Favourites");
await favouritesHeader.click();
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "false");
await favouritesHeader.focus();
await page.keyboard.press("ArrowLeft");
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "false");
});
test("Arrow Right on an expanded section with rooms moves focus to its first room", async ({ page }) => {
const favouritesHeader = getSectionHeader(page, "Favourites");
const favRoomButton = getRoomList(page).getByRole("button", { name: "Open room favourite room" });
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "true");
await favouritesHeader.focus();
await expect(favouritesHeader).toBeFocused();
await page.keyboard.press("ArrowRight");
// Focus must move to the first room in the section, not the next section header.
await expect(favRoomButton).toBeFocused();
// The section should remain expanded.
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "true");
});
test("Arrow Left on the first room of a section moves focus back to the section header", async ({ page }) => {
const favouritesHeader = getSectionHeader(page, "Favourites");
const favRoomButton = getRoomList(page).getByRole("button", { name: "Open room favourite room" });
await expect(favouritesHeader).toHaveAttribute("aria-expanded", "true");
await favRoomButton.focus();
await expect(favRoomButton).toBeFocused();
await page.keyboard.press("ArrowLeft");
await expect(favouritesHeader).toBeFocused();
});
});
});