import { describe, expect, test } from "vitest"; import { act, renderHook } from "@testing-library/react"; import { type ReactNode, useState } from "react"; import { defaultWSValue, WSContext, type WSContextValue, } from "../../../contexts/ws/WSContext.ts"; import type { Playlist } from "../../../../shared/playlist.ts"; import { usePlaylistListSync } from "../../../hooks/playlist/usePlaylistListSync.ts"; import { makePlaylist } from "../../fixtures.ts"; /** A WS context whose value a test can push new events into. */ function liveWS() { let push: (patch: Partial) => void = () => {}; const base = defaultWSValue; function Wrapper({ children }: { children: ReactNode }) { const [value, setValue] = useState(base); push = (patch) => setValue((v) => ({ ...v, ...patch })); return {children}; } return { Wrapper, emit: (patch: Partial) => act(() => push(patch)), }; } function useList(initial: T[]) { const [items, setItems] = useState(initial); return { items, setItems }; } const ids = (xs: { id: string }[]) => xs.map((x) => x.id); describe("usePlaylistListSync", () => { function setup( initial: Playlist[], opts?: Parameters[1], ) { const ws = liveWS(); const hook = renderHook(() => { const list = useList(initial); usePlaylistListSync(list.setItems, opts); return list; }, { wrapper: ws.Wrapper }); return { ...ws, hook }; } test("prepends a created playlist once", () => { const { hook, emit } = setup([makePlaylist({ id: "a" })]); const p = makePlaylist({ id: "b" }); emit({ lastPlaylistEvent: { type: "created", playlistId: "b", playlist: p }, }); emit({ lastPlaylistEvent: { type: "created", playlistId: "b", playlist: { ...p }, }, }); expect(ids(hook.result.current.items)).toEqual(["b", "a"]); }); test("created: filtered by owner, privacy and noNewEntries", () => { const p = makePlaylist({ id: "p", userId: "them" }); const byOwner = setup([], { ownerId: "me" }); byOwner.emit({ lastPlaylistEvent: { type: "created", playlistId: "p", playlist: p }, }); expect(byOwner.hook.result.current.items).toEqual([]); const priv = setup([]); priv.emit({ lastPlaylistEvent: { type: "created", playlistId: "p", playlist: { ...p, isPublic: false }, }, }); expect(priv.hook.result.current.items).toEqual([]); const followed = setup([], { noNewEntries: true }); followed.emit({ lastPlaylistEvent: { type: "created", playlistId: "p", playlist: p }, }); expect(followed.hook.result.current.items).toEqual([]); }); test("updated: in place, removed when private, never re-added by itself", () => { const p = makePlaylist({ id: "p", title: "one" }); const { hook, emit } = setup([p]); emit({ lastPlaylistEvent: { type: "updated", playlistId: "p", playlist: { ...p, title: "two" }, }, }); expect(hook.result.current.items[0].title).toBe("two"); emit({ lastPlaylistEvent: { type: "updated", playlistId: "p", playlist: { ...p, isPublic: false }, }, }); expect(hook.result.current.items).toEqual([]); emit({ lastPlaylistEvent: { type: "updated", playlistId: "p", playlist: { ...p, title: "back" }, }, }); // usePositionAwareSync puts it back, where it was. expect(hook.result.current.items).toEqual([]); }); test("deleted and deletedPlaylistIds both remove", () => { const { hook, emit } = setup([ makePlaylist({ id: "a" }), makePlaylist({ id: "b" }), makePlaylist({ id: "c" }), ]); emit({ lastPlaylistEvent: { type: "deleted", playlistId: "a" } }); emit({ deletedPlaylistIds: new Set(["b"]) }); expect(ids(hook.result.current.items)).toEqual(["c"]); }); test("dumps_updated sets the count", () => { const { hook, emit } = setup([makePlaylist({ id: "a", dumpCount: 1 })]); emit({ lastPlaylistEvent: { type: "dumps_updated", playlistId: "a", dumpIds: ["x", "y", "z"], }, }); expect(hook.result.current.items[0].dumpCount).toBe(3); }); test("dumps_updated counts the members this viewer can't see too", () => { const { hook, emit } = setup([makePlaylist({ id: "a", dumpCount: 3 })]); emit({ lastPlaylistEvent: { type: "dumps_updated", playlistId: "a", dumpIds: ["x", "y"], dumpCount: 4, }, }); expect(hook.result.current.items[0].dumpCount).toBe(4); }); });