163 lines · 6.1 KB · TypeScript
RawDownload
import { beforeEach, describe, expect, it } from "vite-plus/test"; import { scopeThreadRef } from "@t3tools/client-runtime/environment";import { type EnvironmentId, ThreadId } from "@t3tools/contracts"; import {  isSameSidebarThreadRef,  useSidebarPendingFileDropStore,  type SidebarPendingFileDrop,} from "./sidebarPendingFileDropStore"; function makeFiles(...names: string[]): File[] {  return names.map((name) => new File(["x"], name));} function makeEntry(  environmentId: string,  threadId: string,  files: File[],): Omit<SidebarPendingFileDrop, "id"> {  return {    threadRef: scopeThreadRef(environmentId as EnvironmentId, ThreadId.make(threadId)),    files,  };} function fileNames(files: File[]): string[] {  return files.map((file) => file.name);} function refOf(environmentId: string, threadId: string) {  return makeEntry(environmentId, threadId, []).threadRef;} beforeEach(() => {  useSidebarPendingFileDropStore.setState({ pending: [] });}); describe("sidebarPendingFileDropStore", () => {  it("starts empty", () => {    expect(useSidebarPendingFileDropStore.getState().pending).toEqual([]);  });   it("stashes and consumes a drop for the matching thread", () => {    const files = makeFiles("a.png", "b.png");    const store = useSidebarPendingFileDropStore.getState();    store.queuePendingFileDrop(makeEntry("env-1", "thread-1", files));     expect(      useSidebarPendingFileDropStore.getState().consumePendingFileDrop(refOf("env-1", "thread-1")),    ).toEqual(files);    expect(useSidebarPendingFileDropStore.getState().pending).toEqual([]);  });   it("accumulates repeat drops onto the same thread instead of replacing", () => {    const store = useSidebarPendingFileDropStore.getState();    store.queuePendingFileDrop(makeEntry("env-1", "thread-1", makeFiles("a.png")));    store.queuePendingFileDrop(makeEntry("env-1", "thread-1", makeFiles("b.png")));     expect(      fileNames(        useSidebarPendingFileDropStore          .getState()          .consumePendingFileDrop(refOf("env-1", "thread-1")) ?? [],      ),    ).toEqual(["a.png", "b.png"]);    expect(useSidebarPendingFileDropStore.getState().pending).toEqual([]);  });   it("keeps drops for other threads when consuming one thread", () => {    const store = useSidebarPendingFileDropStore.getState();    store.queuePendingFileDrop(makeEntry("env-1", "thread-1", makeFiles("a.png")));    store.queuePendingFileDrop(makeEntry("env-1", "thread-2", makeFiles("b.png")));     expect(      fileNames(        useSidebarPendingFileDropStore          .getState()          .consumePendingFileDrop(refOf("env-1", "thread-2")) ?? [],      ),    ).toEqual(["b.png"]);    expect(useSidebarPendingFileDropStore.getState().pending).toHaveLength(1);  });   it("clears only the drop matching a stale navigation's id", () => {    const store = useSidebarPendingFileDropStore.getState();    const firstId = store.queuePendingFileDrop(makeEntry("env-1", "thread-1", makeFiles("a.png")));    store.queuePendingFileDrop(makeEntry("env-1", "thread-1", makeFiles("b.png")));     useSidebarPendingFileDropStore.getState().clearPendingFileDrop(firstId);    expect(      fileNames(        useSidebarPendingFileDropStore          .getState()          .consumePendingFileDrop(refOf("env-1", "thread-1")) ?? [],      ),    ).toEqual(["b.png"]);  });   it("keeps a newer drop deliverable after the first navigation fails", () => {    // Mirrors handleThreadFileDrop: two drops queued for the same unopened    // thread, then the first navigation fails (or lands elsewhere) and cleans    // up by its own drop id. The newer drop must survive with its files    // intact so the thread opening still attaches them.    const store = useSidebarPendingFileDropStore.getState();    const firstId = store.queuePendingFileDrop(makeEntry("env-1", "thread-1", makeFiles("a.png")));    const secondId = store.queuePendingFileDrop(makeEntry("env-1", "thread-1", makeFiles("b.png")));     // First navigation fails: handler clears only its own drop.    useSidebarPendingFileDropStore.getState().clearPendingFileDrop(firstId);    const remaining = useSidebarPendingFileDropStore.getState().pending;    expect(remaining).toHaveLength(1);    expect(remaining[0]?.id).toBe(secondId);    expect(fileNames(remaining[0]?.files ?? [])).toEqual(["b.png"]);     // Thread opens: the surviving drop is still attached.    expect(      fileNames(        useSidebarPendingFileDropStore          .getState()          .consumePendingFileDrop(refOf("env-1", "thread-1")) ?? [],      ),    ).toEqual(["b.png"]);    expect(useSidebarPendingFileDropStore.getState().pending).toEqual([]);  });   it("clears every drop for a missing thread", () => {    const store = useSidebarPendingFileDropStore.getState();    store.queuePendingFileDrop(makeEntry("env-1", "thread-1", makeFiles("a.png")));    store.queuePendingFileDrop(makeEntry("env-1", "thread-1", makeFiles("b.png")));    store.queuePendingFileDrop(makeEntry("env-1", "thread-2", makeFiles("c.png")));     useSidebarPendingFileDropStore      .getState()      .clearPendingFileDropsForThread(refOf("env-1", "thread-1"));    expect(      fileNames(        useSidebarPendingFileDropStore          .getState()          .consumePendingFileDrop(refOf("env-1", "thread-2")) ?? [],      ),    ).toEqual(["c.png"]);    expect(useSidebarPendingFileDropStore.getState().pending).toEqual([]);  });   it("does not confuse refs whose joined keys collide on colons", () => {    const store = useSidebarPendingFileDropStore.getState();    store.queuePendingFileDrop(makeEntry("a", "b:c", makeFiles("a.png")));     expect(      useSidebarPendingFileDropStore.getState().consumePendingFileDrop(refOf("a:b", "c")),    ).toBeNull();    expect(useSidebarPendingFileDropStore.getState().pending).toHaveLength(1);  });}); describe("isSameSidebarThreadRef", () => {  it("compares fields, not joined keys", () => {    expect(isSameSidebarThreadRef(refOf("a", "b:c"), refOf("a", "b:c"))).toBe(true);    expect(isSameSidebarThreadRef(refOf("a", "b:c"), refOf("a:b", "c"))).toBe(false);    expect(isSameSidebarThreadRef(refOf("a", "b"), refOf("a", "c"))).toBe(false);  });});