1import type { AdvertisedEndpoint, DesktopServerExposureState } from "@t3tools/contracts";
2import * as Cause from "effect/Cause";
3import * as AsyncResult from "effect/reactivity/AsyncResult";
4import { AtomRegistry } from "effect/reactivity";
5import { describe, expect, it, vi } from "vite-plus/test";
6
7import { createDesktopNetworkAccessStateAtom } from "./desktopNetworkAccess";
8
9const serverExposureState: DesktopServerExposureState = {
10 advertisedHost: "192.168.1.10",
11 endpointUrl: "http://192.168.1.10:37737",
12 mode: "network-accessible",
13 tailscaleServeEnabled: false,
14 tailscaleServePort: 443,
15};
16
17const advertisedEndpoints: ReadonlyArray<AdvertisedEndpoint> = [];
18const serverExposureLoadCause = new Error("exposure failed");
19const advertisedEndpointsLoadCause = new Error("endpoints failed");
20
21describe("desktopNetworkAccessState", () => {
22 it("retains the loaded snapshot when the settings screen remounts", async () => {
23 const getServerExposureState = vi.fn(async () => serverExposureState);
24 const getAdvertisedEndpoints = vi.fn(async () => advertisedEndpoints);
25 const atom = createDesktopNetworkAccessStateAtom(() => ({
26 getAdvertisedEndpoints,
27 getServerExposureState,
28 }));
29 const registry = AtomRegistry.make();
30
31 const unmount = registry.mount(atom);
32 await vi.waitFor(() => {
33 expect(AsyncResult.value(registry.get(atom))).toEqual(
34 expect.objectContaining({ _tag: "Some" }),
35 );
36 });
37 unmount();
38
39 const remount = registry.mount(atom);
40 const result = registry.get(atom);
41 expect(AsyncResult.value(result)).toEqual(
42 expect.objectContaining({
43 _tag: "Some",
44 value: { advertisedEndpoints, serverExposureState },
45 }),
46 );
47 expect(getServerExposureState).toHaveBeenCalledTimes(1);
48 expect(getAdvertisedEndpoints).toHaveBeenCalledTimes(1);
49
50 remount();
51 registry.dispose();
52 });
53
54 it.each([
55 {
56 cause: serverExposureLoadCause,
57 expectedTag: "DesktopServerExposureStateLoadError",
58 getAdvertisedEndpoints: async () => advertisedEndpoints,
59 getServerExposureState: async () => Promise.reject(serverExposureLoadCause),
60 },
61 {
62 cause: advertisedEndpointsLoadCause,
63 expectedTag: "DesktopAdvertisedEndpointsLoadError",
64 getAdvertisedEndpoints: async () => Promise.reject(advertisedEndpointsLoadCause),
65 getServerExposureState: async () => serverExposureState,
66 },
67 ])("retains the $expectedTag cause", async (testCase) => {
68 const atom = createDesktopNetworkAccessStateAtom(() => ({
69 getAdvertisedEndpoints: testCase.getAdvertisedEndpoints,
70 getServerExposureState: testCase.getServerExposureState,
71 }));
72 const registry = AtomRegistry.make();
73 registry.mount(atom);
74
75 await vi.waitFor(() => expect(AsyncResult.isFailure(registry.get(atom))).toBe(true));
76 const result = registry.get(atom);
77 if (!AsyncResult.isFailure(result)) throw new Error("Expected network access load to fail.");
78
79 expect(Cause.squash(result.cause)).toEqual(
80 expect.objectContaining({
81 _tag: testCase.expectedTag,
82 cause: testCase.cause,
83 }),
84 );
85 registry.dispose();
86 });
87});