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:
@@ -19,6 +19,7 @@ import {
|
||||
CHATS_TAG,
|
||||
CUSTOM_SECTION_TAG_PREFIX,
|
||||
isSectionTag,
|
||||
reorderSection,
|
||||
} from "../../../../src/stores/room-list-v3/section";
|
||||
import { CreateSectionDialog } from "../../../../src/components/views/dialogs/CreateSectionDialog";
|
||||
import { RemoveSectionDialog } from "../../../../src/components/views/dialogs/RemoveSectionDialog";
|
||||
@@ -328,13 +329,129 @@ describe("section", () => {
|
||||
await deleteSection(tag, false);
|
||||
|
||||
const orderedCall = setValueSpy.mock.calls.find(([name]) => name === "RoomList.OrderedCustomSections");
|
||||
expect(orderedCall![3]).toEqual([otherTag]);
|
||||
// CHATS_TAG is appended because the stored order didn't include it (legacy default position).
|
||||
expect(orderedCall![3]).toEqual([otherTag, CHATS_TAG]);
|
||||
|
||||
const customDataCall = setValueSpy.mock.calls.find(([name]) => name === "RoomList.CustomSectionData");
|
||||
expect(customDataCall![3]).not.toHaveProperty(tag);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reorderSection", () => {
|
||||
const customTag = `${CUSTOM_SECTION_TAG_PREFIX}abc`;
|
||||
const customTag2 = `${CUSTOM_SECTION_TAG_PREFIX}def`;
|
||||
|
||||
function mockSettings(
|
||||
orderedTags: string[],
|
||||
customData: Record<string, { tag: string; name: string }> = {},
|
||||
): void {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
|
||||
if (setting === "RoomList.OrderedCustomSections") return orderedTags;
|
||||
if (setting === "RoomList.CustomSectionData") return customData;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
it.each<{
|
||||
description: string;
|
||||
initial: string[];
|
||||
customData: Record<string, { tag: string; name: string }>;
|
||||
source: string;
|
||||
target: string;
|
||||
expected: string[];
|
||||
}>([
|
||||
{
|
||||
description: "a custom section after another custom section",
|
||||
initial: [customTag, customTag2],
|
||||
customData: {
|
||||
[customTag]: { tag: customTag, name: "A" },
|
||||
[customTag2]: { tag: customTag2, name: "B" },
|
||||
},
|
||||
source: customTag,
|
||||
target: customTag2,
|
||||
expected: [customTag2, customTag, CHATS_TAG],
|
||||
},
|
||||
{
|
||||
description: "a custom section before another when dragging up",
|
||||
initial: [customTag2, customTag],
|
||||
customData: {
|
||||
[customTag]: { tag: customTag, name: "A" },
|
||||
[customTag2]: { tag: customTag2, name: "B" },
|
||||
},
|
||||
source: customTag,
|
||||
target: customTag2,
|
||||
expected: [customTag, customTag2, CHATS_TAG],
|
||||
},
|
||||
{
|
||||
description: "a custom section past the Chats tag",
|
||||
initial: [customTag, customTag2, CHATS_TAG],
|
||||
customData: {
|
||||
[customTag]: { tag: customTag, name: "A" },
|
||||
[customTag2]: { tag: customTag2, name: "B" },
|
||||
},
|
||||
source: customTag,
|
||||
target: CHATS_TAG,
|
||||
expected: [customTag2, CHATS_TAG, customTag],
|
||||
},
|
||||
{
|
||||
description: "the Chats tag above a custom section",
|
||||
initial: [customTag, customTag2, CHATS_TAG],
|
||||
customData: {
|
||||
[customTag]: { tag: customTag, name: "A" },
|
||||
[customTag2]: { tag: customTag2, name: "B" },
|
||||
},
|
||||
source: CHATS_TAG,
|
||||
target: customTag,
|
||||
expected: [CHATS_TAG, customTag, customTag2],
|
||||
},
|
||||
])(
|
||||
"moves $description and saves the new order at ACCOUNT level",
|
||||
async ({ initial, customData, source, target, expected }) => {
|
||||
mockSettings(initial, customData);
|
||||
const setValueSpy = jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
|
||||
|
||||
await reorderSection(source, target);
|
||||
|
||||
expect(setValueSpy).toHaveBeenCalledWith(
|
||||
"RoomList.OrderedCustomSections",
|
||||
null,
|
||||
expect.anything(),
|
||||
expected,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
description: "source and target are the same",
|
||||
source: customTag,
|
||||
target: customTag,
|
||||
},
|
||||
{
|
||||
description: "source custom section is not in the ordered list",
|
||||
source: `${CUSTOM_SECTION_TAG_PREFIX}unknown`,
|
||||
target: customTag,
|
||||
},
|
||||
{
|
||||
description: "target custom section is not in the ordered list",
|
||||
source: customTag,
|
||||
target: `${CUSTOM_SECTION_TAG_PREFIX}unknown`,
|
||||
},
|
||||
{
|
||||
description: "source is a default section",
|
||||
source: DefaultTagID.Favourite,
|
||||
target: customTag,
|
||||
},
|
||||
])("does nothing when $description", async ({ source, target }) => {
|
||||
mockSettings([customTag], { [customTag]: { tag: customTag, name: "A" } });
|
||||
const setValueSpy = jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
|
||||
|
||||
await reorderSection(source, target);
|
||||
|
||||
expect(setValueSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("isDefaultSectionTag", () => {
|
||||
it.each([DefaultTagID.Favourite, DefaultTagID.LowPriority, CHATS_TAG])("returns true for %s", (tag) => {
|
||||
expect(isDefaultSectionTag(tag)).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user