1import type { EnvironmentShellSummary } from "@t3tools/client-runtime/state/shell";
2import {
3 BearerConnectionProfile,
4 BearerConnectionTarget,
5} from "@t3tools/client-runtime/connection";
6import { EnvironmentId } from "@t3tools/contracts";
7import { describe, expect, it } from "@effect/vitest";
8import * as Option from "effect/Option";
9
10import { projectWorkspaceEnvironment, projectWorkspaceState } from "./workspaceModel";
11import type { EnvironmentPresentation } from "./environments";
12
13const ENVIRONMENT_ID = EnvironmentId.make("environment-1");
14
15function environment(
16 phase: EnvironmentPresentation["connection"]["phase"],
17): EnvironmentPresentation {
18 const connectionId = `bearer:${ENVIRONMENT_ID}`;
19 return {
20 environmentId: ENVIRONMENT_ID,
21 label: "Julius's MacBook Pro",
22 displayUrl: "https://environment.example.test",
23 relayManaged: false,
24 entry: {
25 target: new BearerConnectionTarget({
26 environmentId: ENVIRONMENT_ID,
27 label: "Julius's MacBook Pro",
28 connectionId,
29 }),
30 profile: Option.some(
31 new BearerConnectionProfile({
32 connectionId,
33 environmentId: ENVIRONMENT_ID,
34 label: "Julius's MacBook Pro",
35 httpBaseUrl: "https://environment.example.test",
36 wsBaseUrl: "wss://environment.example.test",
37 }),
38 ),
39 enabled: true,
40 },
41 connection: {
42 phase,
43 error: phase === "error" ? "Connection failed." : null,
44 traceId: phase === "error" ? "trace-1" : null,
45 },
46 serverConfig: null,
47 };
48}
49
50const EMPTY_SHELL_SUMMARY: EnvironmentShellSummary = {
51 hasSnapshot: false,
52 hasSynchronizingShell: false,
53 hasCachedShell: false,
54 hasLiveShell: false,
55 firstError: null,
56};
57
58const CACHED_SHELL_SUMMARY: EnvironmentShellSummary = {
59 ...EMPTY_SHELL_SUMMARY,
60 hasSnapshot: true,
61 hasSynchronizingShell: true,
62 hasCachedShell: true,
63};
64
65describe("mobile workspace projection", () => {
66 it("preserves explicit offline state without presenting it as a connection error", () => {
67 const projected = projectWorkspaceEnvironment(ENVIRONMENT_ID, environment("offline"));
68
69 expect(projected.connectionState).toBe("offline");
70 expect(projected.connectionError).toBeNull();
71 });
72
73 it("reports offline before stale connected presentations", () => {
74 const environments = [projectWorkspaceEnvironment(ENVIRONMENT_ID, environment("connected"))];
75 const state = projectWorkspaceState({
76 isReady: true,
77 networkStatus: "offline",
78 environments,
79 shellSummary: EMPTY_SHELL_SUMMARY,
80 });
81
82 expect(state.connectionState).toBe("offline");
83 expect(state.networkStatus).toBe("offline");
84 expect(state.hasReadyEnvironment).toBe(false);
85 });
86
87 it("projects reconnecting environments dynamically from active phases", () => {
88 const environments = [
89 projectWorkspaceEnvironment(ENVIRONMENT_ID, environment("reconnecting")),
90 projectWorkspaceEnvironment(EnvironmentId.make("environment-2"), environment("connected")),
91 ];
92 const state = projectWorkspaceState({
93 isReady: true,
94 networkStatus: "online",
95 environments,
96 shellSummary: EMPTY_SHELL_SUMMARY,
97 });
98
99 expect(state.connectingEnvironments).toHaveLength(1);
100 expect(state.connectingEnvironments[0]?.connectionState).toBe("reconnecting");
101 expect(state.hasConnectingEnvironment).toBe(true);
102 expect(state.hasReadyEnvironment).toBe(true);
103 });
104
105 it("keeps retained snapshots visible while reconnecting without claiming readiness", () => {
106 const environments = [projectWorkspaceEnvironment(ENVIRONMENT_ID, environment("reconnecting"))];
107 const state = projectWorkspaceState({
108 isReady: true,
109 networkStatus: "online",
110 environments,
111 shellSummary: CACHED_SHELL_SUMMARY,
112 });
113
114 expect(state.hasLoadedShellSnapshot).toBe(true);
115 expect(state.hasPendingShellSnapshot).toBe(true);
116 expect(state.hasReadyEnvironment).toBe(false);
117 expect(state.connectionState).toBe("reconnecting");
118 });
119});