1import * as NodePath from "@effect/platform-node/NodePath";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, describe, it } from "@effect/vitest";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Layer from "effect/Layer";
7import * as Option from "effect/Option";
8import * as PlatformError from "effect/PlatformError";
9
10import * as DesktopAssets from "./DesktopAssets.ts";
11import * as DesktopConfig from "./DesktopConfig.ts";
12import * as DesktopEnvironment from "./DesktopEnvironment.ts";
13
14const layerEnvironment = DesktopEnvironment.layer({
15 dirname: "/repo/apps/desktop/dist-electron",
16 homeDirectory: "/Users/alice",
17 platform: "darwin",
18 processArch: "arm64",
19 appVersion: "1.2.3",
20 appPath: "/Applications/T3 Code.app/Contents/Resources/app.asar",
21 isPackaged: true,
22 resourcesPath: "/Applications/T3 Code.app/Contents/Resources",
23 runningUnderArm64Translation: false,
24}).pipe(
25 Layer.provide(
26 Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest({})),
27 ),
28);
29
30describe("DesktopAssets", () => {
31 it.effect("uses canonical source-tree icons for unpackaged development", () =>
32 Effect.gen(function* () {
33 const layerDevelopmentEnvironment = DesktopEnvironment.layer({
34 dirname: "/repo/apps/desktop/dist-electron",
35 homeDirectory: "/Users/alice",
36 platform: "linux",
37 processArch: "x64",
38 appVersion: "1.2.3",
39 appPath: "/repo",
40 isPackaged: false,
41 resourcesPath: "/repo/apps/desktop/resources",
42 runningUnderArm64Translation: false,
43 }).pipe(
44 Layer.provide(
45 Layer.mergeAll(
46 NodeServices.layer,
47 NodePath.layerPosix,
48 DesktopConfig.layerTest({ VITE_DEV_SERVER_URL: "http://localhost:5733" }),
49 ),
50 ),
51 );
52 const layerFileSystem = FileSystem.layerNoop({
53 exists: (path) => Effect.succeed(String(path).includes("/assets/knife/")),
54 });
55 const assets = yield* DesktopAssets.DesktopAssets.pipe(
56 Effect.provide(
57 DesktopAssets.layer.pipe(
58 Layer.provide(Layer.merge(layerFileSystem, layerDevelopmentEnvironment)),
59 ),
60 ),
61 );
62
63 const icons = yield* assets.iconPaths;
64
65 assert.match(Option.getOrThrow(icons.ico), /assets\/knife\/icon\.ico$/);
66 assert.match(Option.getOrThrow(icons.png), /assets\/knife\/icon-1024\.png$/);
67 assert.isTrue(Option.isNone(icons.icns));
68 }),
69 );
70
71 it.effect("preserves the failed asset candidate and filesystem cause", () =>
72 Effect.gen(function* () {
73 const fileName = "custom.bin";
74 const candidatePath = "/repo/apps/desktop/resources/custom.bin";
75 const cause = PlatformError.systemError({
76 _tag: "PermissionDenied",
77 module: "FileSystem",
78 method: "exists",
79 pathOrDescriptor: candidatePath,
80 description: "private filesystem diagnostic",
81 });
82 const layerFileSystem = FileSystem.layerNoop({
83 exists: (path) => (path === candidatePath ? Effect.fail(cause) : Effect.succeed(false)),
84 });
85 const layerAssets = DesktopAssets.layer.pipe(
86 Layer.provide(Layer.merge(layerFileSystem, layerEnvironment)),
87 );
88 const assets = yield* DesktopAssets.DesktopAssets.pipe(Effect.provide(layerAssets));
89
90 const error = yield* assets.resolveResourcePath(fileName).pipe(Effect.flip);
91
92 assert.instanceOf(error, DesktopAssets.DesktopAssetProbeError);
93 assert.equal(error.fileName, fileName);
94 assert.equal(error.candidatePath, candidatePath);
95 assert.strictEqual(error.cause, cause);
96 assert.equal(
97 error.message,
98 `Failed to probe desktop asset "${fileName}" at ${candidatePath}.`,
99 );
100 assert.notInclude(error.message, "private filesystem diagnostic");
101 }),
102 );
103});