1import { EnvironmentId, ProjectId } from "@t3tools/contracts";
2import { Atom, AtomRegistry } from "effect/reactivity";
3import { describe, expect, it, vi } from "vite-plus/test";
4
5// The live sources pull in the connection runtime; the factory gets its own.
6vi.mock("./threads", () => ({
7 environmentThreadShells: { environmentThreadRefsByProjectAtom: () => null },
8 olderSettledThreads: { olderSettledAtom: () => null },
9}));
10
11import { makeProjectHasThreadsAtom } from "./project-has-threads";
12
13const environmentId = EnvironmentId.make("env-1");
14const mine = ProjectId.make("project-mine");
15const other = ProjectId.make("project-other");
16
17function setup() {
18 const refs = Atom.make<ReadonlyMap<ProjectId, ReadonlyArray<string>>>(new Map());
19 const older = Atom.make<{ readonly counts: ReadonlyMap<ProjectId, number> } | null>(null);
20 const family = makeProjectHasThreadsAtom({
21 threadRefsByProject: () => refs,
22 olderSettled: () => older,
23 });
24 const registry = AtomRegistry.make();
25 const onChange = vi.fn();
26 const atom = family(environmentId)(mine);
27 // Immediate, as useAtomValue reads the value when it subscribes.
28 const unsubscribe = registry.subscribe(atom, onChange, { immediate: true });
29 onChange.mockClear();
30 return { registry, refs, older, atom, onChange, unsubscribe };
31}
32
33describe("project has threads", () => {
34 it("notifies its reader only when its own project's answer flips", () => {
35 const { registry, refs, atom, onChange, unsubscribe } = setup();
36 expect(registry.get(atom)).toBe(false);
37
38 // Another project's threads come and go (streaming shells, new threads).
39 const broad = vi.fn();
40 const unsubscribeBroad = registry.subscribe(refs, broad);
41 registry.set(refs, new Map([[other, ["a"]]]));
42 registry.set(refs, new Map([[other, ["a", "b"]]]));
43 // A thread-list reader sees both changes; this project's reader sees none.
44 expect(broad).toHaveBeenCalledTimes(2);
45 expect(onChange).not.toHaveBeenCalled();
46 unsubscribeBroad();
47
48 registry.set(
49 refs,
50 new Map([
51 [other, ["a", "b"]],
52 [mine, ["c"]],
53 ]),
54 );
55 expect(registry.get(atom)).toBe(true);
56 expect(onChange).toHaveBeenCalledTimes(1);
57
58 registry.set(
59 refs,
60 new Map([
61 [other, ["a"]],
62 [mine, ["c", "d"]],
63 ]),
64 );
65 expect(onChange).toHaveBeenCalledTimes(1);
66 unsubscribe();
67 });
68
69 it("counts older settled threads the client does not hold", () => {
70 const { registry, older, atom, unsubscribe } = setup();
71 registry.set(older, { counts: new Map([[mine, 2]]) });
72 expect(registry.get(atom)).toBe(true);
73 unsubscribe();
74 });
75});