Room list: add drag and drop of sections to reorder them (#33606)
* feat(rls): centralize room-list section ordering via getOrderedSections Introduce a single source of truth for the ordered list of section tags (defaults + custom) in section.ts. RoomListStoreV3 no longer hard-codes the default tag order — it asks section.ts. * feat(vm): add section reorder logic to RoomListStoreV3 and view model * refactor(sc): extract RoomListSectionHeaderContent from RoomListSectionHeaderView * feat(sc): add drag-and-drop reordering for room-list sections * test(sc): update existing tests * test: add new tests * test(sc): add snapshot test for overlay * test(e2e): add playright test for dnd of sections * feat: tweak opacity * fix: section detection when dragged * feat: use relative position in section order * chore: move style to room list section header view * test: add e2e test for moving section before another * feat: make favourite and low priority no draggable * test: remove deprecated e2e test * fix: type correctly dnd provider and hooks * fix: use String instead of casting to string * fix: wrong a11y attributes on section header * test: fix keyboard navigation e2e tests * feat: use custom a11y announcement for dnd * fix: add aria-hidden to the overlay * chore: remove duplicated code in a11Y announcement * fix: increase keyboard drag offset * fix: tests * fix: virtuoso computed item key * fix: aria-expanded double annoncement why dragging * fix: re-add screen reader instructions * chore: remove unused import * fix: reduce keyboard drag offset * fix: improve text readback on unread section * feat: use a custom a11y plugin instead of buitin a11y plugin * chore: formatting * test: fix incorrect tests * chore: use randomUUID * fix: try to make test working * fix: put back mock * fix: circular import * Revert "fix: circular import" This reverts commit 6c69313ade9ed2ab17a4622c692b435fbdb94ad2. * chore: add @dnd-kit/abstract to optimizeDeps.include * test: fix e2e tests * fix: disable interaction with section when dragging * fix: be more explicit if the section will be dropped before or after * fix: add info that space is dropping too * fix: scroll to dropped section * test: fix virtualized room list test * chore: update lang * chore: fix dead code analyze * test: add provider section story * fix: lang * fix: again lang... * fix: improve voice over on chrome * test: upate snapshot * fix: add readback when a section is over a non droppable element
This commit is contained in:
@@ -117,6 +117,23 @@ describe("RoomListSectionHeaderViewModel", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("canBeReordered", () => {
|
||||
it.each([
|
||||
[DefaultTagID.Favourite, false],
|
||||
[DefaultTagID.LowPriority, false],
|
||||
[CHATS_TAG, true],
|
||||
["element.io.section.custom", true],
|
||||
])("should be %s for tag %s", (tag, expected) => {
|
||||
const vm = new RoomListSectionHeaderViewModel({
|
||||
tag,
|
||||
title: "Section",
|
||||
spaceId: "!space:server",
|
||||
onToggleExpanded,
|
||||
});
|
||||
expect(vm.getSnapshot().canBeReordered).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("onCustomSectionDataChange", () => {
|
||||
let watchCallback: () => void;
|
||||
|
||||
|
||||
@@ -1178,6 +1178,108 @@ describe("RoomListViewModel", () => {
|
||||
expect(snapshot.sections[0].roomIds[0]).toBe("!fav1:server");
|
||||
expect(snapshot.roomListState.activeRoomIndex).toBe(0);
|
||||
});
|
||||
|
||||
describe("Drag and drop", () => {
|
||||
beforeEach(() => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
// Ensure section header VMs are created before tests that interact with them
|
||||
viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
viewModel.getSectionHeaderViewModel(CHATS_TAG);
|
||||
viewModel.getSectionHeaderViewModel(DefaultTagID.LowPriority);
|
||||
});
|
||||
|
||||
it("should delegate changeSectionOrder to RoomListStoreV3.reorderSection", async () => {
|
||||
const reorderSpy = jest
|
||||
.spyOn(RoomListStoreV3.instance, "reorderSection")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
await viewModel.changeSectionOrder(DefaultTagID.Favourite, CHATS_TAG);
|
||||
|
||||
expect(reorderSpy).toHaveBeenCalledWith(DefaultTagID.Favourite, CHATS_TAG);
|
||||
});
|
||||
|
||||
it("should scroll the moved section back into view after reordering", async () => {
|
||||
jest.spyOn(RoomListStoreV3.instance, "reorderSection").mockResolvedValue(undefined);
|
||||
|
||||
await viewModel.changeSectionOrder(DefaultTagID.Favourite, CHATS_TAG);
|
||||
expect(viewModel.getSnapshot().roomListState.scrollToSectionTag).toBe(DefaultTagID.Favourite);
|
||||
});
|
||||
|
||||
it("should collapse every section on drag start", () => {
|
||||
expect(viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).isExpanded).toBe(true);
|
||||
|
||||
viewModel.onSectionDragStart();
|
||||
|
||||
expect(viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).isExpanded).toBe(false);
|
||||
expect(viewModel.getSectionHeaderViewModel(CHATS_TAG).isExpanded).toBe(false);
|
||||
expect(viewModel.getSectionHeaderViewModel(DefaultTagID.LowPriority).isExpanded).toBe(false);
|
||||
|
||||
for (const section of viewModel.getSnapshot().sections) {
|
||||
expect(section.roomIds).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
it("should restore the pre-drag expansion state on drag end", () => {
|
||||
// Collapse Favourite before the drag; other sections remain expanded
|
||||
viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).onClick();
|
||||
|
||||
viewModel.onSectionDragStart();
|
||||
viewModel.onSectionDragEnd();
|
||||
|
||||
expect(viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).isExpanded).toBe(false);
|
||||
expect(viewModel.getSectionHeaderViewModel(CHATS_TAG).isExpanded).toBe(true);
|
||||
expect(viewModel.getSectionHeaderViewModel(DefaultTagID.LowPriority).isExpanded).toBe(true);
|
||||
|
||||
const snapshot = viewModel.getSnapshot();
|
||||
expect(snapshot.sections.find((s) => s.id === DefaultTagID.Favourite)!.roomIds).toEqual([]);
|
||||
expect(snapshot.sections.find((s) => s.id === CHATS_TAG)!.roomIds).toEqual([
|
||||
"!reg1:server",
|
||||
"!reg2:server",
|
||||
]);
|
||||
expect(snapshot.sections.find((s) => s.id === DefaultTagID.LowPriority)!.roomIds).toEqual([
|
||||
"!low1:server",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should re-snapshot expansion state on each drag start", () => {
|
||||
// First cycle: Favourite is collapsed before the drag
|
||||
viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).onClick();
|
||||
viewModel.onSectionDragStart();
|
||||
viewModel.onSectionDragEnd();
|
||||
|
||||
// Between cycles: collapse CHATS_TAG as well
|
||||
viewModel.getSectionHeaderViewModel(CHATS_TAG).onClick();
|
||||
viewModel.onSectionDragStart();
|
||||
viewModel.onSectionDragEnd();
|
||||
|
||||
// The second drag end must restore the state captured at the second drag start
|
||||
// (Favourite collapsed, CHATS_TAG collapsed, LowPriority expanded), not the first cycle's snapshot.
|
||||
expect(viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).isExpanded).toBe(false);
|
||||
expect(viewModel.getSectionHeaderViewModel(CHATS_TAG).isExpanded).toBe(false);
|
||||
expect(viewModel.getSectionHeaderViewModel(DefaultTagID.LowPriority).isExpanded).toBe(true);
|
||||
});
|
||||
|
||||
it("should be a no-op when drag end is called without drag start", () => {
|
||||
viewModel.onSectionDragEnd();
|
||||
|
||||
expect(viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).isExpanded).toBe(true);
|
||||
expect(viewModel.getSectionHeaderViewModel(CHATS_TAG).isExpanded).toBe(true);
|
||||
expect(viewModel.getSectionHeaderViewModel(DefaultTagID.LowPriority).isExpanded).toBe(true);
|
||||
|
||||
const snapshot = viewModel.getSnapshot();
|
||||
expect(snapshot.sections.find((s) => s.id === DefaultTagID.Favourite)!.roomIds).toEqual([
|
||||
"!fav1:server",
|
||||
"!fav2:server",
|
||||
]);
|
||||
expect(snapshot.sections.find((s) => s.id === CHATS_TAG)!.roomIds).toEqual([
|
||||
"!reg1:server",
|
||||
"!reg2:server",
|
||||
]);
|
||||
expect(snapshot.sections.find((s) => s.id === DefaultTagID.LowPriority)!.roomIds).toEqual([
|
||||
"!low1:server",
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user