/* * Copyright 2026 Element Creations Ltd. * * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial * Please see LICENSE files in the repository root for full details. */ import React from "react"; import { render, screen, fireEvent, waitFor } from "@test-utils"; import { VirtuosoMockContext } from "react-virtuoso"; import { composeStories } from "@storybook/react-vite"; import { describe, it, expect, beforeEach } from "vitest"; import userEvent from "@testing-library/user-event"; import * as stories from "./VirtualizedRoomListView.stories"; import { KEYBOARD_DRAG_OFFSET } from "./VirtualizedRoomListView"; const { Default, Sections } = composeStories(stories); const renderWithMockContext = (component: React.ReactElement): ReturnType => { return render(component, { wrapper: ({ children }) => ( {children} ), }); }; describe("", () => { it("renders Default story", () => { const { container } = renderWithMockContext(); expect(container).toMatchSnapshot(); }); it("should render the room list listbox", () => { renderWithMockContext(); expect(screen.getByRole("listbox", { name: "Room list" })).toBeInTheDocument(); }); it("should render room items", () => { renderWithMockContext(); const items = screen.getAllByRole("option"); expect(items.length).toBeGreaterThan(0); }); it("should mark selected room with aria-selected true", () => { renderWithMockContext(); const items = screen.getAllByRole("option"); // The first item (index 0) should be selected based on Default story (activeRoomIndex: 0) expect(items[0]).toHaveAttribute("aria-selected", "true"); }); it("should handle focus state correctly", () => { renderWithMockContext(); const listbox = screen.getByRole("listbox", { name: "Room list" }); fireEvent.focus(listbox); const items = screen.getAllByRole("option"); // First item should have tabIndex 0 (focusable) when list is focused expect(items[0]).toHaveAttribute("tabIndex", "0"); }); it("should call updateVisibleRooms on render", () => { renderWithMockContext(); expect(Default.args.updateVisibleRooms).toHaveBeenCalled(); }); describe("updateVisibleRooms range reporting", () => { beforeEach(() => { (Default.args.updateVisibleRooms as any).mockClear?.(); (Sections.args.updateVisibleRooms as any).mockClear?.(); }); it("reports an exclusive end bound in flat mode", () => { renderWithMockContext(); // 10 rooms, all rendered by the mock viewport: Virtuoso reports the inclusive // range [0, 9], which must reach the view model as the exclusive window [0, 10). expect(Default.args.updateVisibleRooms).toHaveBeenLastCalledWith(0, 10); }); it("maps entry-space indices to room indices in grouped mode", () => { renderWithMockContext(); // 13 entries (3 section headers + 10 rooms) are all rendered: Virtuoso reports the // inclusive entry range [0, 12], which must map back to the room window [0, 10). expect(Sections.args.updateVisibleRooms).toHaveBeenLastCalledWith(0, 10); }); }); describe("drag and drop", () => { beforeEach(() => { // Storybook fn() spies are shared across tests; vi.clearAllMocks() may not // reach them, so explicitly reset call history for the spies under test. (Sections.args.changeRoomSection as any).mockClear?.(); (Sections.args.changeSectionOrder as any).mockClear?.(); (Sections.args.onSectionDragStart as any).mockClear?.(); (Sections.args.onSectionDragEnd as any).mockClear?.(); }); it("should call changeRoomSection when drag ends successfully", async () => { // KeyboardSensor: Space=start, each ArrowDown moves the drag position by // KEYBOARD_DRAG_OFFSET px, Space=drop. We need to travel ~150px down from "General" // (room 0) so the drag position enters the target section header's droppable area; // derive the keypress count from the offset so this stays correct if the offset changes. const presses = Math.round(150 / KEYBOARD_DRAG_OFFSET); const user = userEvent.setup(); renderWithMockContext(); const roomButton = await screen.findByRole("button", { name: "Open room General" }); roomButton.focus(); await user.keyboard(" "); // start drag for (let i = 0; i < presses; i++) { await user.keyboard("{ArrowDown}"); } await user.keyboard(" "); // drop onto current target await waitFor(() => { expect(Sections.args.changeRoomSection).toHaveBeenCalledWith("!room0:server", "low-priority"); }); }); it("does not reflect aria-pressed onto draggable room items or section headers", async () => { // dnd-kit's built-in Accessibility plugin reflects aria-pressed onto the draggable //