1import type { AdvertisedEndpoint, DesktopWslState } from "@t3tools/contracts";
2import { describe, expect, it, vi } from "vite-plus/test";
3import {
4 applyWslEnableSelection,
5 canRevokeOtherClients,
6 isQrShareableEndpoint,
7 isWslSettingsRowVisible,
8 selectQrEndpointOption,
9 togglePairingScopeSelection,
10} from "./ConnectionsSettings.logic";
11
12describe("togglePairingScopeSelection", () => {
13 it.each([
14 {
15 label: "adds terminal:read when terminal:operate is selected",
16 current: ["orchestration:read"],
17 scope: "terminal:operate",
18 checked: true,
19 expected: ["orchestration:read", "terminal:operate", "terminal:read"],
20 },
21 {
22 label: "drops terminal:operate when terminal:read is cleared",
23 current: ["terminal:read", "terminal:operate", "relay:read"],
24 scope: "terminal:read",
25 checked: false,
26 expected: ["relay:read"],
27 },
28 {
29 label: "keeps terminal:read when terminal:operate is cleared",
30 current: ["terminal:read", "terminal:operate"],
31 scope: "terminal:operate",
32 checked: false,
33 expected: ["terminal:read"],
34 },
35 {
36 label: "toggles unrelated scopes on their own",
37 current: ["terminal:read"],
38 scope: "filesystem:read",
39 checked: true,
40 expected: ["terminal:read", "filesystem:read"],
41 },
42 ] as const)("$label", ({ current, scope, checked, expected }) => {
43 expect(togglePairingScopeSelection(current, scope, checked)).toEqual(expected);
44 });
45});
46
47const baseWslState: DesktopWslState = {
48 enabled: false,
49 distro: null,
50 available: true,
51 wslOnly: true,
52 distros: [],
53 preflightError: null,
54};
55
56describe("isWslSettingsRowVisible", () => {
57 it("shows the retry row when the WSL state failed to load", () => {
58 expect(isWslSettingsRowVisible({ state: null, error: "load failed" })).toBe(true);
59 });
60
61 it("hides an unavailable and unused WSL snapshot", () => {
62 expect(
63 isWslSettingsRowVisible({
64 state: { ...baseWslState, available: false, wslOnly: false },
65 error: null,
66 }),
67 ).toBe(false);
68 });
69
70 it("shows an available WSL snapshot", () => {
71 expect(isWslSettingsRowVisible({ state: baseWslState, error: null })).toBe(true);
72 });
73});
74
75describe("applyWslEnableSelection", () => {
76 it("clears WSL-only and updates the distro before enabling both backends", async () => {
77 const calls: Array<string> = [];
78 let persistedWslOnly = true;
79 let persistedDistro: string | null = "Ubuntu";
80 const setWslDistro = vi.fn(async (distro: string | null) => {
81 calls.push(`setWslDistro:${distro ?? "default"}`);
82 persistedDistro = distro;
83 return { ...baseWslState, distro, wslOnly: persistedWslOnly };
84 });
85 const setWslBackendEnabled = vi.fn(async (enabled: boolean) => {
86 calls.push(`setWslBackendEnabled:${enabled}`);
87 return {
88 ...baseWslState,
89 enabled,
90 distro: persistedDistro,
91 wslOnly: persistedWslOnly,
92 };
93 });
94 const setWslOnly = vi.fn(async (enabled: boolean) => {
95 calls.push(`setWslOnly:${enabled}`);
96 persistedWslOnly = enabled;
97 return { ...baseWslState, distro: persistedDistro, wslOnly: enabled };
98 });
99
100 const state = await applyWslEnableSelection({
101 bridge: { setWslDistro, setWslBackendEnabled, setWslOnly },
102 mode: "both",
103 nextDistro: "Debian",
104 persistedDistro: "Ubuntu",
105 });
106
107 expect(calls).toEqual(["setWslOnly:false", "setWslDistro:Debian", "setWslBackendEnabled:true"]);
108 expect(state).toMatchObject({ enabled: true, distro: "Debian", wslOnly: false });
109 });
110
111 it("stages WSL-only before enabling without rewriting an unchanged distro", async () => {
112 const calls: Array<string> = [];
113 let persistedWslOnly = false;
114 const setWslDistro = vi.fn(async () => baseWslState);
115 const setWslOnly = vi.fn(async (enabled: boolean) => {
116 calls.push(`setWslOnly:${enabled}`);
117 persistedWslOnly = enabled;
118 return { ...baseWslState, wslOnly: enabled };
119 });
120 const setWslBackendEnabled = vi.fn(async (enabled: boolean) => {
121 calls.push(`setWslBackendEnabled:${enabled}`);
122 return { ...baseWslState, enabled, wslOnly: persistedWslOnly };
123 });
124
125 const state = await applyWslEnableSelection({
126 bridge: { setWslDistro, setWslBackendEnabled, setWslOnly },
127 mode: "wsl-only",
128 nextDistro: null,
129 persistedDistro: null,
130 });
131
132 expect(calls).toEqual(["setWslOnly:true", "setWslBackendEnabled:true"]);
133 expect(setWslDistro).not.toHaveBeenCalled();
134 expect(state).toMatchObject({ enabled: true, wslOnly: true });
135 });
136});
137
138function makeEndpoint(overrides: Partial<AdvertisedEndpoint>): AdvertisedEndpoint {
139 return {
140 id: "desktop-lan:http://192.168.1.42:4780",
141 label: "Local network",
142 provider: { id: "desktop-core", label: "Desktop", kind: "core", isAddon: false },
143 httpBaseUrl: "http://192.168.1.42:4780",
144 wsBaseUrl: "ws://192.168.1.42:4780",
145 reachability: "lan",
146 compatibility: { hostedHttpsApp: "unknown", desktopApp: "compatible" },
147 source: "desktop-core",
148 status: "available",
149 ...overrides,
150 };
151}
152
153describe("isQrShareableEndpoint", () => {
154 it("excludes loopback endpoints so a scanned phone never dials itself", () => {
155 expect(
156 isQrShareableEndpoint(
157 makeEndpoint({
158 id: "desktop-loopback:4780",
159 reachability: "loopback",
160 httpBaseUrl: "http://127.0.0.1:4780",
161 }),
162 ),
163 ).toBe(false);
164 });
165
166 it("excludes unavailable endpoints and keeps reachable ones", () => {
167 expect(isQrShareableEndpoint(makeEndpoint({ status: "unavailable" }))).toBe(false);
168 expect(isQrShareableEndpoint(makeEndpoint({}))).toBe(true);
169 expect(
170 isQrShareableEndpoint(makeEndpoint({ reachability: "private-network", status: "unknown" })),
171 ).toBe(true);
172 });
173});
174
175describe("selectQrEndpointOption", () => {
176 const options = [
177 {
178 id: "desktop-loopback:4780",
179 preferenceKey: "desktop-core:loopback:http",
180 qrShareable: false,
181 },
182 {
183 id: "tailscale-ip:http://100.84.12.7:4780",
184 preferenceKey: "tailscale:ip:http",
185 qrShareable: true,
186 },
187 {
188 id: "tailscale-ip:http://100.84.12.8:4780",
189 preferenceKey: "tailscale:ip:http",
190 qrShareable: true,
191 },
192 {
193 id: "desktop-lan:http://192.168.1.42:4780",
194 preferenceKey: "desktop-core:lan:http",
195 qrShareable: true,
196 },
197 ];
198
199 it("resolves an explicit selection by unique endpoint id, not the shared preference key", () => {
200 expect(selectQrEndpointOption(options, "tailscale-ip:http://100.84.12.8:4780", null)?.id).toBe(
201 "tailscale-ip:http://100.84.12.8:4780",
202 );
203 });
204
205 it("falls back to the saved default preference key when nothing is selected", () => {
206 expect(selectQrEndpointOption(options, null, "desktop-core:lan:http")?.id).toBe(
207 "desktop-lan:http://192.168.1.42:4780",
208 );
209 });
210
211 it("skips non-QR-shareable options in the fallback so the panel never opens on loopback", () => {
212 expect(selectQrEndpointOption(options, "tailscale-ip:gone", "nope")?.id).toBe(
213 "tailscale-ip:http://100.84.12.7:4780",
214 );
215 });
216
217 it("returns the first option when nothing is QR-shareable, and null when empty", () => {
218 const loopbackOnly = options.slice(0, 1);
219 expect(selectQrEndpointOption(loopbackOnly, null, null)?.id).toBe("desktop-loopback:4780");
220 expect(selectQrEndpointOption([], "anything", "anything")).toBeNull();
221 });
222});
223
224describe("canRevokeOtherClients", () => {
225 it("permits revocation when a write-only grant cannot list clients", () => {
226 expect(canRevokeOtherClients(null)).toBe(true);
227 });
228
229 it("disables revocation only when a loaded list has no other clients", () => {
230 expect(canRevokeOtherClients([])).toBe(false);
231 expect(canRevokeOtherClients([{ current: true }])).toBe(false);
232 expect(canRevokeOtherClients([{ current: true }, { current: false }])).toBe(true);
233 });
234});