313 lines · 11.6 KB · TypeScript
RawDownload
import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, describe, it } from "@effect/vitest";import {  ClientSettingsSchema,  DEFAULT_CLIENT_SETTINGS,  type ClientSettings,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as PlatformError from "effect/PlatformError";import * as Schema from "effect/Schema"; import * as DesktopConfig from "../app/DesktopConfig.ts";import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";import * as DesktopClientSettings from "./DesktopClientSettings.ts"; const clientSettings: ClientSettings = {  ...DEFAULT_CLIENT_SETTINGS,  appearanceContrast: 100,  browserDefaultViewport: { _tag: "preset", width: 1024, height: 600, presetId: "nest-hub" },  browserDefaultZoomFactor: 1.25,  browserDefaultAppearance: "dark",  browserRecordingFrameRate: 60,  browserLinkTarget: "app",  browserAutoShowFloatingPreview: false,  browserProfiles: [{ id: "work", name: "Work", kind: "persistent" }],  browserDefaultProfileId: "work",  confirmQuit: "double-click",  confirmThreadArchive: true,  confirmThreadDelete: false,  confirmThreadUnpin: false,  contextWindowMeterEnabled: false,  composerCollapseOnScroll: true,  dismissedProviderUpdateNotificationKeys: [],  diffIgnoreWhitespace: true,  diffLayout: "stacked",  environmentIdentificationMode: "artwork",  favorites: [],  fontFamilyCode: "",  fontFamilyComposer: "",  fontFamilySans: "",  fontFamilyTerminal: "",  fontSizeCode: 13,  fontSizeInterface: 16,  fontSizePrompt: 14,  fontSizeTerminal: 12,  fontSmoothing: true,  glassOpacity: 80,  onboardingCompletedAt: null,  panelAnimationDurationMs: 0,  planModeEnabled: false,  proactivePanelsEnabled: true,  showSkillsInSlashMenu: false,  providerModelPreferences: {},  sidebarProjectGroupingMode: "repository_path",  sidebarProjectGroupingOverrides: {    "environment-1:/tmp/project-a": "separate",  },  sidebarProjectSortOrder: "manual",  sidebarThreadSortOrder: "created_at",  sidebarThreadPreviewCount: 6,  legacySidebarEnabled: false,  loadBalancingEnabled: false,  loadBalancingWeights: { "environment-1": 75, "environment-2": 0 },  pullRequestMergeMethodOverrides: {},  timestampFormat: "24-hour",  wordWrap: true,}; const decodeClientSettingsJson = Schema.decodeEffect(Schema.fromJsonString(ClientSettingsSchema));const decodeRecordJson = Schema.decodeEffect(  Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)),);function makeLayer(baseDir: string) {  const environmentLayer = DesktopEnvironment.layer({    dirname: "/repo/apps/desktop/src",    homeDirectory: baseDir,    platform: "darwin",    processArch: "x64",    appVersion: "1.2.3",    appPath: "/repo",    isPackaged: true,    resourcesPath: "/missing/resources",    runningUnderArm64Translation: false,  }).pipe(    Layer.provide(      Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({ T3CODE_HOME: baseDir })),    ),  );   return DesktopClientSettings.layer.pipe(    Layer.provideMerge(environmentLayer),    Layer.provideMerge(NodeServices.layer),  );} const withClientSettings = <A, E, R>(  effect: Effect.Effect<A, E, R | DesktopClientSettings.DesktopClientSettings>,) =>  Effect.gen(function* () {    const fileSystem = yield* FileSystem.FileSystem;    const baseDir = yield* fileSystem.makeTempDirectoryScoped({      prefix: "t3-desktop-client-settings-test-",    });    return yield* effect.pipe(Effect.provide(makeLayer(baseDir)));  }).pipe(Effect.provide(NodeServices.layer), Effect.scoped); describe("DesktopClientSettings", () => {  it.effect("returns none when no client settings file exists", () =>    withClientSettings(      Effect.gen(function* () {        const settings = yield* DesktopClientSettings.DesktopClientSettings;        assert.isTrue(Option.isNone(yield* settings.get));      }),    ),  );   it.effect("persists and reloads client settings", () =>    withClientSettings(      Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        const fileSystem = yield* FileSystem.FileSystem;        const settings = yield* DesktopClientSettings.DesktopClientSettings;        yield* settings.set(clientSettings);         assert.deepEqual(yield* settings.get, Option.some(clientSettings));        assert.deepEqual(          yield* decodeClientSettingsJson(            yield* fileSystem.readFileString(environment.clientSettingsPath),          ),          clientSettings,        );        assert.isFalse(          Object.hasOwn(            yield* decodeRecordJson(              yield* fileSystem.readFileString(environment.clientSettingsPath),            ),            "settings",          ),        );      }),    ),  );   for (const failure of [    { label: "permission", reason: "PermissionDenied" },    { label: "I/O", reason: "Unknown" },  ] as const) {    it.effect(`preserves saved preferences across ${failure.label} read failures and retries`, () =>      withClientSettings(        Effect.gen(function* () {          const environment = yield* DesktopEnvironment.DesktopEnvironment;          const fileSystem = yield* FileSystem.FileSystem;          const settings = yield* DesktopClientSettings.DesktopClientSettings;          const savedSettings = {            ...clientSettings,            onboardingCompletedAt: "2026-09-05T12:00:00.000Z",          };          yield* settings.set(savedSettings);          const savedContents = yield* fileSystem.readFileString(environment.clientSettingsPath);          const cause = PlatformError.systemError({            _tag: failure.reason,            module: "FileSystem",            method: "readFileString",            pathOrDescriptor: environment.clientSettingsPath,          });          let failRead = true;          const retryableSettings = yield* DesktopClientSettings.make.pipe(            Effect.provideService(              FileSystem.FileSystem,              FileSystem.FileSystem.of({                ...fileSystem,                readFileString: (path) =>                  Effect.suspend(() =>                    failRead ? Effect.fail(cause) : fileSystem.readFileString(path),                  ),              }),            ),          );           const error = yield* retryableSettings.get.pipe(Effect.flip);          assert.instanceOf(error, DesktopClientSettings.DesktopClientSettingsReadError);          assert.equal(error.operation, "read-file");          assert.equal(error.path, environment.clientSettingsPath);          assert.strictEqual(error.cause, cause);          assert.equal(            yield* fileSystem.readFileString(environment.clientSettingsPath),            savedContents,          );           failRead = false;          assert.deepEqual(yield* retryableSettings.get, Option.some(savedSettings));        }),      ),    );  }   it.effect("reports the failed client settings write operation and path", () =>    withClientSettings(      Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        const fileSystem = yield* FileSystem.FileSystem;        const settings = yield* DesktopClientSettings.DesktopClientSettings;        yield* fileSystem.makeDirectory(environment.clientSettingsPath, { recursive: true });         const error = yield* settings.set(clientSettings).pipe(Effect.flip);        assert.instanceOf(error, DesktopClientSettings.DesktopClientSettingsWriteError);        assert.equal(error.operation, "replace-settings-file");        assert.equal(error.path, environment.clientSettingsPath);        assert.instanceOf(error.cause, PlatformError.PlatformError);        assert.isString(error.cause.stack);        assert.equal(          error.message,          `Desktop client settings write failed during replace-settings-file at ${environment.clientSettingsPath}.`,        );        assert.notInclude(error.message, error.cause.message);      }),    ),  );   it.effect("loads lenient direct client settings documents", () =>    withClientSettings(      Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        const fileSystem = yield* FileSystem.FileSystem;        const settings = yield* DesktopClientSettings.DesktopClientSettings;        yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });        yield* fileSystem.writeFileString(          environment.clientSettingsPath,          `{            // Matches server settings parsing.            "timestampFormat": "24-hour",          }\n`,        );         const persisted = yield* settings.get;        assert.isTrue(Option.isSome(persisted));        if (Option.isSome(persisted)) {          assert.equal(persisted.value.timestampFormat, "24-hour");        }      }),    ),  );   it.effect("loads legacy wrapped client settings documents", () =>    withClientSettings(      Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        const fileSystem = yield* FileSystem.FileSystem;        const settings = yield* DesktopClientSettings.DesktopClientSettings;        yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });        yield* fileSystem.writeFileString(          environment.clientSettingsPath,          `{            "settings": {              "timestampFormat": "12-hour"            }          }\n`,        );         const persisted = yield* settings.get;        assert.isTrue(Option.isSome(persisted));        if (Option.isSome(persisted)) {          assert.equal(persisted.value.timestampFormat, "12-hour");        }      }),    ),  );   it.effect("loads defaults from empty client settings documents", () =>    withClientSettings(      Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        const fileSystem = yield* FileSystem.FileSystem;        const settings = yield* DesktopClientSettings.DesktopClientSettings;        yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });        yield* fileSystem.writeFileString(environment.clientSettingsPath, "{}\n");         assert.deepEqual(yield* settings.get, Option.some(yield* decodeClientSettingsJson("{}")));      }),    ),  );   for (const document of [    { label: "malformed JSON", contents: "{not-json" },    { label: "invalid direct settings", contents: '{"fontSizeCode":"large"}' },    { label: "invalid legacy settings", contents: '{"settings":{"fontSizeCode":"large"}}' },  ]) {    it.effect(`reports ${document.label} without treating the settings file as absent`, () =>      withClientSettings(        Effect.gen(function* () {          const environment = yield* DesktopEnvironment.DesktopEnvironment;          const fileSystem = yield* FileSystem.FileSystem;          const settings = yield* DesktopClientSettings.DesktopClientSettings;          yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });          yield* fileSystem.writeFileString(environment.clientSettingsPath, document.contents);           const error = yield* settings.get.pipe(Effect.flip);          assert.instanceOf(error, DesktopClientSettings.DesktopClientSettingsReadError);          assert.equal(error.operation, "decode-document");          assert.equal(error.path, environment.clientSettingsPath);          assert.instanceOf(error.cause, Schema.SchemaError);          assert.equal(            yield* fileSystem.readFileString(environment.clientSettingsPath),            document.contents,          );        }),      ),    );  }});