Skip to content

Commit 546083b

Browse files
authored
Room list: assign room to section when section is created (#33240)
* feat(rls): return section tag when created * feat(vm): assign section tag to room when section created * test: update exisiting tests * test(e2e): check that room is in section
1 parent bb4a7e9 commit 546083b

7 files changed

Lines changed: 44 additions & 27 deletions

File tree

apps/web/playwright/e2e/left-panel/room-list-panel/room-list-custom-sections.spec.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ test.describe("Room list custom sections", () => {
4040
await expect(dialog).not.toBeVisible();
4141
}
4242

43+
/**
44+
* Asserts a room is nested under a specific section using the treegrid aria-level hierarchy.
45+
* Section header rows sit at aria-level=1; room rows nested within a section sit at aria-level=2.
46+
* Verifies that the closest preceding aria-level=1 row is the expected section header.
47+
*/
48+
async function assertRoomInSection(page: Page, sectionName: string, roomName: string): Promise<void> {
49+
const roomList = getRoomList(page);
50+
const roomRow = roomList.getByRole("row", { name: `Open room ${roomName}` });
51+
// Room row must be at aria-level=2 (i.e. inside a section)
52+
await expect(roomRow).toHaveAttribute("aria-level", "2");
53+
// The closest preceding aria-level=1 row must be the expected section header.
54+
// XPath preceding:: axis returns nodes before the context in document order; [1] picks the nearest one.
55+
const closestSectionHeader = roomRow.locator(`xpath=preceding::*[@role="row" and @aria-level="1"][1]`);
56+
await expect(closestSectionHeader).toContainText(sectionName);
57+
}
58+
4359
test.beforeEach(async ({ page, app, user }) => {
4460
// The notification toast is displayed above the search section
4561
await app.closeNotificationToast();
@@ -97,6 +113,9 @@ test.describe("Room list custom sections", () => {
97113

98114
// The custom section should be created
99115
await expect(getSectionHeader(page, "Projects")).toBeVisible();
116+
117+
// Room should be moved to the new section
118+
await assertRoomInSection(page, "Projects", "my room");
100119
});
101120

102121
test("should cancel section creation when dialog is dismissed", async ({ page, app }) => {
@@ -177,22 +196,6 @@ test.describe("Room list custom sections", () => {
177196
});
178197

179198
test.describe("Adding a room to a custom section", () => {
180-
/**
181-
* Asserts a room is nested under a specific section using the treegrid aria-level hierarchy.
182-
* Section header rows sit at aria-level=1; room rows nested within a section sit at aria-level=2.
183-
* Verifies that the closest preceding aria-level=1 row is the expected section header.
184-
*/
185-
async function assertRoomInSection(page: Page, sectionName: string, roomName: string): Promise<void> {
186-
const roomList = getRoomList(page);
187-
const roomRow = roomList.getByRole("row", { name: `Open room ${roomName}` });
188-
// Room row must be at aria-level=2 (i.e. inside a section)
189-
await expect(roomRow).toHaveAttribute("aria-level", "2");
190-
// The closest preceding aria-level=1 row must be the expected section header.
191-
// XPath preceding:: axis returns nodes before the context in document order; [1] picks the nearest one.
192-
const closestSectionHeader = roomRow.locator(`xpath=preceding::*[@role="row" and @aria-level="1"][1]`);
193-
await expect(closestSectionHeader).toContainText(sectionName);
194-
}
195-
196199
test("should add a room to a custom section via the More Options menu", async ({ page, app }) => {
197200
await app.client.createRoom({ name: "my room" });
198201
await createCustomSection(page, "Work");

apps/web/src/stores/room-list-v3/RoomListStoreV3.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,10 +491,11 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
491491
* Create a new section.
492492
* Emits {@link SECTION_CREATED_EVENT} if the section was successfully created.
493493
*/
494-
public async createSection(): Promise<void> {
494+
public async createSection(): Promise<string | undefined> {
495495
const tag = await createSection();
496496
if (!tag) return;
497497
this.emit(SECTION_CREATED_EVENT, tag);
498+
return tag;
498499
}
499500

500501
/**

apps/web/src/viewmodels/room-list/RoomListItemViewModel.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,12 @@ export class RoomListItemViewModel
400400
echoChamber.notificationVolume = elementNotifState;
401401
};
402402

403-
public onCreateSection = (): void => {
404-
RoomListStoreV3.instance.createSection();
403+
public onCreateSection = async (): Promise<void> => {
404+
const newTag = await RoomListStoreV3.instance.createSection();
405+
// Add the room to the section
406+
if (newTag) {
407+
tagRoom(this.props.room, newTag);
408+
}
405409
};
406410

407411
public onToggleSection = (tag: string): void => {

apps/web/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,11 +1021,10 @@ describe("RoomListStoreV3", () => {
10211021
await store.start();
10221022

10231023
const sectionCreatedListener = jest.fn();
1024-
const listsUpdateListener = jest.fn();
10251024
store.on(SECTION_CREATED_EVENT, sectionCreatedListener);
1026-
store.on(LISTS_UPDATE_EVENT, listsUpdateListener);
10271025

1028-
await store.createSection();
1026+
const tag = await store.createSection();
1027+
expect(tag).toBe("element.io.section.test-tag");
10291028

10301029
expect(sectionCreatedListener).toHaveBeenCalledWith("element.io.section.test-tag");
10311030
});

apps/web/test/unit-tests/stores/room-list-v3/section-test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ describe("createSection", () => {
2323
it.each([
2424
[false, "", undefined],
2525
[true, "", undefined],
26-
])("returns undefined when shouldCreate=%s and name='%s'", async (shouldCreate, name, expected) => {
26+
[true, "My Section", expect.stringMatching(/^element\.io\.section\./)],
27+
])("returns %s when shouldCreate=%s and name='%s'", async (shouldCreate, name, expected) => {
2728
jest.spyOn(Modal, "createDialog").mockReturnValue({
2829
finished: Promise.resolve([shouldCreate, name]),
2930
close: jest.fn(),
3031
} as any);
3132

3233
const result = await createSection();
33-
expect(result).toBe(expected);
34+
expect(result).toEqual(expected);
3435
});
3536

3637
it("returns the new tag when section is created", async () => {

apps/web/test/viewmodels/room-list/RoomListHeaderViewModel-test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,9 @@ describe("RoomListHeaderViewModel", () => {
315315
});
316316

317317
it("should call createSection on RoomListStoreV3 when createSection is called", () => {
318-
const createSectionSpy = jest.spyOn(RoomListStoreV3.instance, "createSection").mockResolvedValue();
318+
const createSectionSpy = jest
319+
.spyOn(RoomListStoreV3.instance, "createSection")
320+
.mockResolvedValue("element.io.section.work");
319321
vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
320322
vm.createSection();
321323
expect(createSectionSpy).toHaveBeenCalled();

apps/web/test/viewmodels/room-list/RoomListItemViewModel-test.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
type RoomMember,
1616
} from "matrix-js-sdk/src/matrix";
1717
import { CallType } from "matrix-js-sdk/src/webrtc/call";
18+
import { waitFor } from "jest-matrix-react";
1819

1920
import { createTestClient, flushPromises } from "../../test-utils";
2021
import { RoomNotificationState } from "../../../src/stores/notifications/RoomNotificationState";
@@ -591,11 +592,17 @@ describe("RoomListItemViewModel", () => {
591592
});
592593
});
593594

594-
it("should call createSection on RoomListStoreV3 when onCreateSection is called", () => {
595-
const createSectionSpy = jest.spyOn(RoomListStoreV3.instance, "createSection").mockResolvedValue();
595+
it("should call createSection on RoomListStoreV3 when onCreateSection is called", async () => {
596+
const createSectionSpy = jest
597+
.spyOn(RoomListStoreV3.instance, "createSection")
598+
.mockResolvedValue("element.io.section.work");
599+
const tagRoomSpy = jest.spyOn(tagRoomModule, "tagRoom").mockImplementation(() => {});
600+
596601
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
597602
viewModel.onCreateSection();
598603
expect(createSectionSpy).toHaveBeenCalled();
604+
605+
await waitFor(() => expect(tagRoomSpy).toHaveBeenCalledWith(room, "element.io.section.work"));
599606
});
600607

601608
it("should call tagRoom when onToggleSection is called", () => {

0 commit comments

Comments
 (0)