1import { EnvironmentRegistry } from "@t3tools/client-runtime/connection";
2import { EnvironmentId, WS_METHODS } from "@t3tools/contracts";
3import { afterEach, beforeEach, describe, expect, it } from "@effect/vitest";
4import * as Cause from "effect/Cause";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import * as SubscriptionRef from "effect/SubscriptionRef";
8import * as TestClock from "effect/testing/TestClock";
9import { vi } from "vite-plus/test";
10
11import {
12 layer,
13 observeBackgroundActivitySubscription,
14 retainedBackgroundScopes,
15} from "./backgroundActivityReporter.ts";
16
17describe("wasRecentlyInteracted", () => {
18 it.effect("retains an observed subscription until its returned finalizer runs", () =>
19 Effect.gen(function* () {
20 const environmentId = EnvironmentId.make("environment-observation-test");
21 const scope = { type: "vcs-status" as const, cwd: "/repo" };
22 const releasePassive = yield* observeBackgroundActivitySubscription({
23 environmentId,
24 method: WS_METHODS.subscribeVcsStatus,
25 input: { cwd: scope.cwd, includeRemote: false },
26 });
27 expect(retainedBackgroundScopes(environmentId)).toEqual([]);
28 const release = yield* observeBackgroundActivitySubscription({
29 environmentId,
30 method: WS_METHODS.subscribeVcsStatus,
31 input: { cwd: scope.cwd },
32 });
33
34 expect(retainedBackgroundScopes(environmentId)).toEqual([scope]);
35
36 yield* releasePassive;
37 expect(retainedBackgroundScopes(environmentId)).toEqual([scope]);
38
39 yield* release;
40 expect(retainedBackgroundScopes(environmentId)).toEqual([]);
41 }),
42 );
43
44 it.effect("keeps delimiter-containing environment and scope values distinct", () =>
45 Effect.gen(function* () {
46 const firstEnvironmentId = EnvironmentId.make("a");
47 const secondEnvironmentId = EnvironmentId.make("a:vcs-status:b");
48 const releaseFirst = yield* observeBackgroundActivitySubscription({
49 environmentId: firstEnvironmentId,
50 method: WS_METHODS.subscribeVcsStatus,
51 input: { cwd: "b:vcs-status:c" },
52 });
53 const releaseSecond = yield* observeBackgroundActivitySubscription({
54 environmentId: secondEnvironmentId,
55 method: WS_METHODS.subscribeVcsStatus,
56 input: { cwd: "c" },
57 });
58
59 expect(retainedBackgroundScopes(firstEnvironmentId)).toEqual([
60 { type: "vcs-status", cwd: "b:vcs-status:c" },
61 ]);
62 expect(retainedBackgroundScopes(secondEnvironmentId)).toEqual([
63 { type: "vcs-status", cwd: "c" },
64 ]);
65
66 yield* Effect.all([releaseFirst, releaseSecond]);
67 }),
68 );
69});
70
71describe("layer", () => {
72 beforeEach(() => {
73 const target = new EventTarget();
74 const listeners = {
75 addEventListener: target.addEventListener.bind(target),
76 removeEventListener: target.removeEventListener.bind(target),
77 };
78 vi.stubGlobal("window", {
79 ...listeners,
80 localStorage: { getItem: () => "client", setItem: () => undefined },
81 });
82 vi.stubGlobal("document", { ...listeners, visibilityState: "visible", hasFocus: () => true });
83 });
84
85 afterEach(() => {
86 vi.unstubAllGlobals();
87 });
88
89 // As when an environment's connection is torn down while its report is in flight.
90 it.effect("keeps reporting after a report is interrupted", () =>
91 Effect.gen(function* () {
92 const entries = yield* SubscriptionRef.make(
93 new Map([[EnvironmentId.make("environment-reporter-test"), {}]]),
94 );
95 let calls = 0;
96 const registry = EnvironmentRegistry.EnvironmentRegistry.of({
97 entries,
98 run: () => {
99 calls += 1;
100 return calls === 1 ? Effect.failCause(Cause.interrupt()) : Effect.void;
101 },
102 } as unknown as EnvironmentRegistry.EnvironmentRegistry["Service"]);
103
104 yield* Layer.build(
105 layer.pipe(Layer.provide(Layer.succeed(EnvironmentRegistry.EnvironmentRegistry, registry))),
106 );
107 yield* TestClock.adjust("1 second");
108 expect(calls).toBe(1);
109
110 yield* TestClock.adjust("25 seconds");
111 expect(calls).toBe(2);
112 }).pipe(Effect.scoped),
113 );
114
115 it.effect("keeps reporting after a report never settles", () =>
116 Effect.gen(function* () {
117 const entries = yield* SubscriptionRef.make(
118 new Map([[EnvironmentId.make("environment-reporter-test"), {}]]),
119 );
120 let calls = 0;
121 const registry = EnvironmentRegistry.EnvironmentRegistry.of({
122 entries,
123 run: () => {
124 calls += 1;
125 return calls === 1 ? Effect.never : Effect.void;
126 },
127 } as unknown as EnvironmentRegistry.EnvironmentRegistry["Service"]);
128
129 yield* Layer.build(
130 layer.pipe(Layer.provide(Layer.succeed(EnvironmentRegistry.EnvironmentRegistry, registry))),
131 );
132 yield* TestClock.adjust("1 second");
133 expect(calls).toBe(1);
134
135 yield* TestClock.adjust("25 seconds");
136 expect(calls).toBe(2);
137 }).pipe(Effect.scoped),
138 );
139});