121 lines · 4.2 KB · TypeScript
RawDownload
import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell";import { EnvironmentId, ThreadId } from "@t3tools/contracts";import type { Atom } from "effect/unstable/reactivity";import { afterEach, describe, expect, it, vi } from "vite-plus/test"; import { createPendingThreadOrder } from "../features/threads/threadOrder";import { appAtomRegistry } from "./atom-registry";import {  beginPendingThreadOrder,  getPendingThreadOrder,  pendingThreadOrderAtom,} from "./thread-order";import { environmentThreadShells } from "./threads"; vi.mock("./atom-registry", async () => {  const { AtomRegistry } = await import("effect/unstable/reactivity");  return { appAtomRegistry: AtomRegistry.make() };});vi.mock("./threads", async () => {  const { Atom } = await import("effect/unstable/reactivity");  return { environmentThreadShells: { threadShellsAtom: Atom.make([]).pipe(Atom.keepAlive) } };});vi.mock("./server", async () => {  const { Atom } = await import("effect/unstable/reactivity");  return { environmentServerConfigsAtom: Atom.make(new Map()).pipe(Atom.keepAlive) };});vi.mock("./use-thread-outbox", async () => {  const { Atom } = await import("effect/unstable/reactivity");  return { queuedThreadKeysAtom: Atom.make(new Set<string>()).pipe(Atom.keepAlive) };}); // The mocked shell source is writable so tests can deliver canonical upserts.const shellsAtom = environmentThreadShells.threadShellsAtom as Atom.Writable<  readonly EnvironmentThreadShell[],  readonly EnvironmentThreadShell[]>; function fixture() {  // Only section membership and order fields are read by this coordinator.  const rows = ["a", "b"].map(    (id, index) =>      ({        id: ThreadId.make(id),        environmentId: EnvironmentId.make("env"),        createdAt: `2026-06-01T0${2 - index}:00:00.000Z`,        archivedAt: null,        pinnedAt: null,        activeOrderKey: null,      }) as EnvironmentThreadShell,  );  appAtomRegistry.set(shellsAtom, rows);  const pending = createPendingThreadOrder({    section: "active",    ordered: rows,    movedId: "env:b",    direction: "up",    assignments: [      { id: "env:b", orderKey: "aa" },      { id: "env:a", orderKey: "bb" },    ],  });  const start = () => beginPendingThreadOrder(pending);  const upsert = (id: string, key: string) => {    const current = appAtomRegistry.get(shellsAtom);    appAtomRegistry.set(      shellsAtom,      current.map((row) => (row.id === id ? { ...row, activeOrderKey: key } : row)),    );  };  return { rows, start, upsert };} afterEach(() => appAtomRegistry.reset()); describe("shared mobile pending move", () => {  it("blocks another pickup after receipts and clears on final canonical upsert", () => {    const { start, upsert } = fixture();    const move = start();    move.complete();    expect(getPendingThreadOrder()).not.toBeNull();    upsert("b", "aa");    expect(getPendingThreadOrder()).not.toBeNull();    upsert("a", "bb");    expect(getPendingThreadOrder()).toBeNull();    expect(move.isPending()).toBe(false);  });   it("waits for receipts when shells arrive first", () => {    const { start, upsert } = fixture();    const move = start();    upsert("b", "aa");    upsert("a", "bb");    expect(getPendingThreadOrder()).not.toBeNull();    move.complete();    expect(getPendingThreadOrder()).toBeNull();  });   it.each(["failure", "interruption"])("releases a %s without restoring old canonical keys", () => {    const { start, upsert } = fixture();    const move = start();    upsert("b", "aa");    move.cancel();    expect(getPendingThreadOrder()).toBeNull();    expect(appAtomRegistry.get(shellsAtom)[1]?.activeOrderKey).toBe("aa");    const next = start();    move.cancel();    expect(next.isPending()).toBe(true);    next.cancel();  });   it("stops remaining writes when a canonical membership change invalidates the move", () => {    const { rows, start } = fixture();    const move = start();    appAtomRegistry.set(shellsAtom, rows.slice(1));    expect(move.isPending()).toBe(false);    expect(appAtomRegistry.get(pendingThreadOrderAtom)).toBeNull();    move.complete();    appAtomRegistry.set(shellsAtom, rows);    expect(getPendingThreadOrder()).toBeNull();  });});