import { EnvironmentId, ProjectId, ThreadId, type OrchestrationV2ShellSnapshot,} from "@t3tools/contracts";import * as Option from "effect/Option";import { Atom, AtomRegistry } from "effect/unstable/reactivity";import { describe, expect, it } from "vite-plus/test"; import { PrimaryConnectionTarget } from "../connection/model.ts";import { v2ShellSnapshot, v2ThreadShell } from "./orchestrationV2TestFixtures.ts";import { createEnvironmentThreadShellAtoms } from "./threadShell.ts"; const environmentId = EnvironmentId.make("environment-v2");const remoteEnvironmentId = EnvironmentId.make("remote-environment-v2");const otherProjectId = ProjectId.make("other-project"); function makeHarness(environmentIds: ReadonlyArray<EnvironmentId> = [environmentId]) { const snapshotAtom = Atom.family((_environmentId: EnvironmentId) => Atom.make<OrchestrationV2ShellSnapshot | null>(v2ShellSnapshot), ); const catalogValueAtom = Atom.make({ isReady: true, entries: new Map( environmentIds.map((id) => [ id, { target: new PrimaryConnectionTarget({ environmentId: id, label: "Environment", httpBaseUrl: "https://example.test", wsBaseUrl: "wss://example.test", }), profile: Option.none(), }, ]), ), }); return { registry: AtomRegistry.make(), snapshotAtom, catalogValueAtom, threads: createEnvironmentThreadShellAtoms({ catalogValueAtom, snapshotAtom }), };} describe("v2 thread shell lists", () => { it("shares point and list values without retaining an atom for every listed thread", () => { const harness = makeHarness(); const snapshot = { ...v2ShellSnapshot, threads: Array.from({ length: 200 }, (_, index) => ({ ...v2ThreadShell, id: ThreadId.make(`thread-${index}`), })), }; harness.registry.set(harness.snapshotAtom(environmentId), snapshot); const listAtom = harness.threads.threadShellsAtom; const projectListAtom = harness.threads.threadShellsForProjectRefsAtom([ { environmentId, projectId: v2ThreadShell.projectId }, ]); const disposeList = harness.registry.mount(listAtom); const disposeProjectList = harness.registry.mount(projectListAtom); try { const before = harness.registry.get(listAtom); expect(before).toHaveLength(200); expect(harness.registry.get(projectListAtom)).toEqual(before); expect(harness.registry.getNodes().size).toBeLessThan(20); const firstAtom = harness.threads.threadShellAtom({ environmentId, threadId: snapshot.threads[0]!.id, }); expect(harness.registry.get(firstAtom)).toBe(before[0]); harness.registry.set(harness.snapshotAtom(environmentId), { ...snapshot, snapshotSequence: 1, threads: snapshot.threads.map((thread, index) => index === 199 ? { ...thread, title: "Updated last thread" } : thread, ), }); const after = harness.registry.get(listAtom); expect(after[0]).toBe(before[0]); expect(after.at(-1)).not.toBe(before.at(-1)); expect(after.at(-1)?.title).toBe("Updated last thread"); expect(harness.registry.get(projectListAtom).at(-1)).toBe(after.at(-1)); expect(harness.registry.get(firstAtom)).toBe(after[0]); expect(harness.registry.getNodes().size).toBeLessThan(20); } finally { disposeProjectList(); disposeList(); harness.registry.dispose(); } }); it("preserves project memberships on updates and keeps the same thread separate per environment", () => { const harness = makeHarness([environmentId, remoteEnvironmentId]); const otherThread = { ...v2ThreadShell, id: ThreadId.make("other-thread"), projectId: otherProjectId, }; const snapshot = { ...v2ShellSnapshot, threads: [v2ThreadShell, otherThread] }; harness.registry.set(harness.snapshotAtom(environmentId), snapshot); const membershipAtom = harness.threads.environmentThreadRefsByProjectAtom(environmentId); const listAtom = harness.threads.threadShellsForProjectRefsAtom([ { environmentId: remoteEnvironmentId, projectId: v2ThreadShell.projectId }, { environmentId, projectId: v2ThreadShell.projectId }, ]); const disposeList = harness.registry.mount(listAtom); try { const membership = harness.registry.get(membershipAtom); const before = harness.registry.get(listAtom); expect(before).toHaveLength(2); expect(before[0]).not.toBe(before[1]); expect(before.map((thread) => thread.environmentId)).toEqual([ remoteEnvironmentId, environmentId, ]); harness.registry.set(harness.snapshotAtom(environmentId), { ...snapshot, threads: [v2ThreadShell, { ...otherThread, title: "Changed elsewhere" }], }); expect(harness.registry.get(membershipAtom)).toBe(membership); expect(harness.registry.get(listAtom)).toBe(before); harness.registry.set(harness.snapshotAtom(environmentId), { ...snapshot, threads: [{ ...v2ThreadShell, projectId: otherProjectId }, otherThread], }); expect(harness.registry.get(listAtom)).toEqual([before[0]]); harness.registry.set(harness.snapshotAtom(remoteEnvironmentId), { ...v2ShellSnapshot, threads: [], }); expect(harness.registry.get(listAtom)).toEqual([]); } finally { disposeList(); harness.registry.dispose(); } });});
import {
EnvironmentId,
ProjectId,
ThreadId,
type OrchestrationV2ShellSnapshot,
} from "@t3tools/contracts";
import * as Option from "effect/Option";
import { Atom, AtomRegistry } from "effect/unstable/reactivity";
import { describe, expect, it } from "vite-plus/test";
import { PrimaryConnectionTarget } from "../connection/model.ts";
import { v2ShellSnapshot, v2ThreadShell } from "./orchestrationV2TestFixtures.ts";
import { createEnvironmentThreadShellAtoms } from "./threadShell.ts";
const environmentId = EnvironmentId.make("environment-v2");
const remoteEnvironmentId = EnvironmentId.make("remote-environment-v2");
const otherProjectId = ProjectId.make("other-project");
function makeHarness(environmentIds: ReadonlyArray<EnvironmentId> = [environmentId]) {
const snapshotAtom = Atom.family((_environmentId: EnvironmentId) =>
Atom.make<OrchestrationV2ShellSnapshot | null>(v2ShellSnapshot),
);
const catalogValueAtom = Atom.make({
isReady: true,
entries: new Map(
environmentIds.map((id) => [
id,
{
target: new PrimaryConnectionTarget({
environmentId: id,
label: "Environment",
httpBaseUrl: "https://example.test",
wsBaseUrl: "wss://example.test",
}),
profile: Option.none(),
},
]),
),
});
return {
registry: AtomRegistry.make(),
snapshotAtom,
catalogValueAtom,
threads: createEnvironmentThreadShellAtoms({ catalogValueAtom, snapshotAtom }),
};
}
describe("v2 thread shell lists", () => {
it("shares point and list values without retaining an atom for every listed thread", () => {
const harness = makeHarness();
const snapshot = {
...v2ShellSnapshot,
threads: Array.from({ length: 200 }, (_, index) => ({
...v2ThreadShell,
id: ThreadId.make(`thread-${index}`),
})),
};
harness.registry.set(harness.snapshotAtom(environmentId), snapshot);
const listAtom = harness.threads.threadShellsAtom;
const projectListAtom = harness.threads.threadShellsForProjectRefsAtom([
{ environmentId, projectId: v2ThreadShell.projectId },
]);
const disposeList = harness.registry.mount(listAtom);
const disposeProjectList = harness.registry.mount(projectListAtom);
try {
const before = harness.registry.get(listAtom);
expect(before).toHaveLength(200);
expect(harness.registry.get(projectListAtom)).toEqual(before);
expect(harness.registry.getNodes().size).toBeLessThan(20);
const firstAtom = harness.threads.threadShellAtom({
environmentId,
threadId: snapshot.threads[0]!.id,
});
expect(harness.registry.get(firstAtom)).toBe(before[0]);
harness.registry.set(harness.snapshotAtom(environmentId), {
...snapshot,
snapshotSequence: 1,
threads: snapshot.threads.map((thread, index) =>
index === 199 ? { ...thread, title: "Updated last thread" } : thread,
),
});
const after = harness.registry.get(listAtom);
expect(after[0]).toBe(before[0]);
expect(after.at(-1)).not.toBe(before.at(-1));
expect(after.at(-1)?.title).toBe("Updated last thread");
expect(harness.registry.get(projectListAtom).at(-1)).toBe(after.at(-1));
expect(harness.registry.get(firstAtom)).toBe(after[0]);
expect(harness.registry.getNodes().size).toBeLessThan(20);
} finally {
disposeProjectList();
disposeList();
harness.registry.dispose();
}
});
it("preserves project memberships on updates and keeps the same thread separate per environment", () => {
const harness = makeHarness([environmentId, remoteEnvironmentId]);
const otherThread = {
...v2ThreadShell,
id: ThreadId.make("other-thread"),
projectId: otherProjectId,
};
const snapshot = { ...v2ShellSnapshot, threads: [v2ThreadShell, otherThread] };
harness.registry.set(harness.snapshotAtom(environmentId), snapshot);
const membershipAtom = harness.threads.environmentThreadRefsByProjectAtom(environmentId);
const listAtom = harness.threads.threadShellsForProjectRefsAtom([
{ environmentId: remoteEnvironmentId, projectId: v2ThreadShell.projectId },
{ environmentId, projectId: v2ThreadShell.projectId },
]);
const disposeList = harness.registry.mount(listAtom);
try {
const membership = harness.registry.get(membershipAtom);
const before = harness.registry.get(listAtom);
expect(before).toHaveLength(2);
expect(before[0]).not.toBe(before[1]);
expect(before.map((thread) => thread.environmentId)).toEqual([
remoteEnvironmentId,
environmentId,
]);
harness.registry.set(harness.snapshotAtom(environmentId), {
...snapshot,
threads: [v2ThreadShell, { ...otherThread, title: "Changed elsewhere" }],
});
expect(harness.registry.get(membershipAtom)).toBe(membership);
expect(harness.registry.get(listAtom)).toBe(before);
harness.registry.set(harness.snapshotAtom(environmentId), {
...snapshot,
threads: [{ ...v2ThreadShell, projectId: otherProjectId }, otherThread],
});
expect(harness.registry.get(listAtom)).toEqual([before[0]]);
harness.registry.set(harness.snapshotAtom(remoteEnvironmentId), {
...v2ShellSnapshot,
threads: [],
});
expect(harness.registry.get(listAtom)).toEqual([]);
} finally {
disposeList();
harness.registry.dispose();
}
});
});