// @vitest-environment jsdom // // The rail's rows as targets of a drag that starts in another tree: which ones are, and the one lit up. import { describe, it, expect, vi, afterEach, beforeAll } from "vitest"; import { render, screen, cleanup, act } from "@testing-library/react"; import { FloatingTree } from "@floating-ui/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ReactNode } from "react"; vi.mock("../../lib/useRail", () => ({ useRail: () => ({ layout: { order: ["manage", "list"], collapsed: {}, hidden: {} }, setLayout: () => {}, collapsed: false, onToggleCollapse: () => {}, }), })); vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (k: string) => k, i18n: { language: "en" } }) }; }); vi.mock("./playlistsContext", () => ({ usePlaylists: () => ({ search: "", selectedId: 1 }), usePlaylistsActions: () => ({ setSelectedId: vi.fn() }), })); vi.mock("../../lib/useMe", () => ({ useMe: () => ({ can_write: true }) })); vi.mock("../../features/comfort/comfortPin", () => ({ useComfortPin: () => ({ item: () => [] }), pinPlaylist: (id: number) => id, })); vi.mock("../../components/ui/confirmContext", () => ({ useChoice: () => async () => null, useConfirm: () => async () => false, })); vi.mock("../../components/ui/FilterRail", () => ({ default: ({ items }: { items: { id: string; body: ReactNode }[] }) => ( <> {items.map((i) => (
{i.body}
))} ), })); import PlaylistsRail from "./PlaylistsRail"; import { api, type Playlist } from "../../lib/api"; import { startRailDrag, stopRailDrag } from "./railDrop"; beforeAll(() => { class Stub { observe() {} unobserve() {} disconnect() {} takeRecords() { return []; } } globalThis.ResizeObserver ??= Stub as unknown as typeof ResizeObserver; globalThis.IntersectionObserver ??= Stub as unknown as typeof IntersectionObserver; Element.prototype.scrollIntoView ??= () => {}; }); afterEach(() => { stopRailDrag(); cleanup(); vi.restoreAllMocks(); }); const pl = (id: number, name: string) => ({ id, name, kind: "user", source: "local", yt_playlist_id: null, item_count: 1, total_duration_seconds: 0, cover_thumbnail: null, dirty: false, }) as unknown as Playlist; async function mount() { vi.spyOn(api, "playlists").mockResolvedValue([pl(1, "Open"), pl(2, "Other")] as never); const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); render( , ); const wrap = async (name: string) => (await screen.findByText(name)).closest("[class*='group']") as HTMLElement; return { open: await wrap("Open"), other: await wrap("Other") }; } describe("PlaylistsRail — a row as a drop target", () => { it("offers every row but the open list", async () => { const { open, other } = await mount(); expect(other.getAttribute("data-playlist-drop")).toBe("2"); expect(open.hasAttribute("data-playlist-drop")).toBe(false); expect(open.closest("[data-playlist-drop-area]")).toBeTruthy(); }); it("lights up the row under the drag, and only while it is", async () => { const { open, other } = await mount(); document.elementsFromPoint = () => [other]; expect(other.className).not.toContain("ring-2"); act(() => { startRailDrag(0, 0); }); expect(other.className).toContain("ring-2"); expect(open.className).not.toContain("ring-2"); act(() => { stopRailDrag(); }); expect(other.className).not.toContain("ring-2"); }); });