authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-04 13:33:19-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 20:33:19+00:00
logcc60753aa2279722149e182fb942ca87f70ff804
treed2f25a587141262dedc2e379df713b673445ac47
parentbfef973d9ec64e580fff4197ac53085207471ffd
signature Signed by PGP key B5690EEEBB952194

test: stop path and platform tests depending on the host OS (#9564)

Co-authored-by: Claude Code <noreply@anthropic.com>

10 files changed, 65 insertions(+), 33 deletions(-)

apps/desktop/src/app/DesktopAppIdentity.test.ts+2
......@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
12import * as NodeServices from "@effect/platform-node/NodeServices";
23import { assert, describe, it } from "@effect/vitest";
34import * as Effect from "effect/Effect";
......@@ -88,6 +89,7 @@ const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => {
8889 Layer.provide(
8990 Layer.mergeAll(
9091 NodeServices.layer,
92 NodePath.layerPosix,
9193 DesktopConfig.layerTest({
9294 ...env,
9395 }),
apps/desktop/src/app/DesktopAssets.test.ts+7-1
......@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
12import * as NodeServices from "@effect/platform-node/NodeServices";
23import { assert, describe, it } from "@effect/vitest";
34import * as Effect from "effect/Effect";
......@@ -20,7 +21,11 @@ const environmentLayer = DesktopEnvironment.layer({
2021 isPackaged: true,
2122 resourcesPath: "/Applications/T3 Code.app/Contents/Resources",
2223 runningUnderArm64Translation: false,
23}).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({}))));
24}).pipe(
25 Layer.provide(
26 Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest({})),
27 ),
28);
2429
2530describe("DesktopAssets", () => {
2631 it.effect("uses canonical source-tree icons for unpackaged development", () =>
......@@ -39,6 +44,7 @@ describe("DesktopAssets", () => {
3944 Layer.provide(
4045 Layer.mergeAll(
4146 NodeServices.layer,
47 NodePath.layerPosix,
4248 DesktopConfig.layerTest({ VITE_DEV_SERVER_URL: "http://localhost:5733" }),
4349 ),
4450 ),
apps/desktop/src/app/DesktopEnvironment.test.ts+6-1
......@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
12import * as NodeServices from "@effect/platform-node/NodeServices";
23import { assert, describe, it } from "@effect/vitest";
34import * as Effect from "effect/Effect";
......@@ -26,7 +27,11 @@ const makeEnvironmentLayer = (
2627 DesktopEnvironment.layer({
2728 ...defaultInput,
2829 ...overrides,
29 }).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest(env))));
30 }).pipe(
31 Layer.provide(
32 Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest(env)),
33 ),
34 );
3035
3136const makeEnvironment = (
3237 overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},
apps/server/src/auth/ServerSecretStore.test.ts+2-2
......@@ -109,7 +109,7 @@ const ConcurrentReadMissFileSystemLayer = Layer.effect(
109109 return {
110110 ...fileSystem,
111111 readFile: (path) =>
112 String(path).endsWith("/session-signing-key.bin")
112 /[\\/]session-signing-key\.bin$/.test(String(path))
113113 ? Ref.updateAndGet(readCountRef, (count) => count + 1).pipe(
114114 Effect.flatMap((count) => {
115115 if (count > 2) {
......@@ -219,7 +219,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
219219 yield* secretStore.set("session-signing-key", Uint8Array.from([1, 2, 3]));
220220
221221 assert.isTrue(
222 chmodCalls.some((call) => call.mode === 0o700 && call.path.endsWith("/secrets")),
222 chmodCalls.some((call) => call.mode === 0o700 && /[\\/]secrets$/.test(call.path)),
223223 );
224224 assert.isAtLeast(chmodCalls.filter((call) => call.mode === 0o600).length, 2);
225225 }).pipe(Effect.provide(NodeServices.layer)),
apps/server/src/project/ProjectFaviconResolver.test.ts+3-2
......@@ -54,11 +54,12 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => {
5454 it.effect("serves repeated resolves from cache instead of re-walking candidates", () =>
5555 Effect.gen(function* () {
5656 const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;
57 const path = yield* Path.Path;
5758 const cwd = yield* makeTempDir;
5859 yield* writeTextFile(cwd, "public/favicon.svg", "<svg>public</svg>");
5960
6061 const resolved = yield* resolver.resolvePath(cwd);
61 expect(resolved?.endsWith("public/favicon.svg")).toBe(true);
62 expect(resolved?.endsWith(path.join("public", "favicon.svg"))).toBe(true);
6263
6364 // `favicon.svg` outranks `public/favicon.svg`, so a resolver that walked
6465 // the candidate list again would switch to it. Staying on the original
......@@ -71,7 +72,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => {
7172
7273 yield* TestClock.adjust(Duration.minutes(11));
7374
74 expect((yield* resolver.resolvePath(cwd))?.endsWith("/favicon.svg")).toBe(true);
75 expect(yield* resolver.resolvePath(cwd)).toBe(path.join(cwd, "favicon.svg"));
7576 expect(yield* resolver.resolvePath(cwd)).not.toBe(resolved);
7677 }).pipe(Effect.provide(TestClock.layer())),
7778 );
apps/server/src/provider/Drivers/ClaudeSkills.test.ts+13-7
......@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
12import * as NodeServices from "@effect/platform-node/NodeServices";
23import { assert, it } from "@effect/vitest";
34import * as Effect from "effect/Effect";
......@@ -492,7 +493,11 @@ it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => {
492493
493494 it.effect("lets the administrator's managed policy outrank every other settings file", () =>
494495 Effect.gen(function* () {
495 const path = yield* Path.Path;
496 // The function is pure on its `path` argument, so hand it the
497 // implementation matching each platform under test rather than the
498 // host's.
499 const path = yield* Path.Path.pipe(Effect.provide(NodePath.layerPosix));
500 const win32Path = yield* Path.Path.pipe(Effect.provide(NodePath.layerWin32));
496501
497502 for (const [platform, expected] of [
498503 ["darwin", "/Library/Application Support/ClaudeCode/managed-settings.json"],
......@@ -508,14 +513,15 @@ it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => {
508513 }
509514
510515 assert.deepEqual(
511 skillOverrideSettingsPaths(path, "/home/.claude", undefined, "win32", {
512 PROGRAMDATA: "C:/ProgramData",
516 skillOverrideSettingsPaths(win32Path, "C:\\Users\\me\\.claude", undefined, "win32", {
517 PROGRAMDATA: "C:\\ProgramData",
513518 }).at(-1),
514 "C:/ProgramData/ClaudeCode/managed-settings.json",
519 "C:\\ProgramData\\ClaudeCode\\managed-settings.json",
520 );
521 assert.deepEqual(
522 skillOverrideSettingsPaths(win32Path, "C:\\Users\\me\\.claude", undefined, "win32", {}),
523 ["C:\\Users\\me\\.claude\\settings.json"],
515524 );
516 assert.deepEqual(skillOverrideSettingsPaths(path, "/home/.claude", undefined, "win32", {}), [
517 "/home/.claude/settings.json",
518 ]);
519525
520526 // Only the repository root's local file joins in, after the
521527 // workspace's own local file so it wins.
packages/ssh/src/config.test.ts+2-1
......@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
12import * as NodeServices from "@effect/platform-node/NodeServices";
23import { assert, describe, it } from "@effect/vitest";
34import * as Effect from "effect/Effect";
......@@ -133,6 +134,6 @@ describe("ssh config", () => {
133134 "/tmp/home",
134135 );
135136 assert.equal(pattern, "/tmp/home/.ssh/config.d/*.conf");
136 }).pipe(Effect.provide(NodeServices.layer)),
137 }).pipe(Effect.provide(NodePath.layerPosix)),
137138 );
138139});
packages/tailscale/src/tailscale.test.ts+14-12
......@@ -8,6 +8,7 @@ import * as Sink from "effect/Sink";
88import * as Stream from "effect/Stream";
99import * as TestClock from "effect/testing/TestClock";
1010import { ChildProcessSpawner } from "effect/unstable/process";
11import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
1112
1213import {
1314 buildTailscaleHttpsBaseUrl,
......@@ -98,14 +99,22 @@ function neverFinishingMockHandle() {
9899 });
99100}
100101
102// The executable name depends on the host platform (`tailscale.exe` on
103// Windows), so pin it: these tests assert the posix spelling.
104function spawnerLayer(spawner: ChildProcessSpawner.ChildProcessSpawner["Service"]) {
105 return Layer.merge(
106 Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
107 Layer.succeed(HostProcessPlatform, "linux"),
108 );
109}
110
101111function mockSpawnerLayer(
102112 handler: (
103113 command: string,
104114 args: ReadonlyArray<string>,
105115 ) => { stdout?: string; stderr?: string; code?: number },
106116) {
107 return Layer.succeed(
108 ChildProcessSpawner.ChildProcessSpawner,
117 return spawnerLayer(
109118 ChildProcessSpawner.make((command) => {
110119 const childProcess = command as unknown as {
111120 readonly command: string;
......@@ -194,10 +203,7 @@ describe("tailscale", () => {
194203 method: "spawn",
195204 cause: systemCause,
196205 });
197 const layer = Layer.succeed(
198 ChildProcessSpawner.ChildProcessSpawner,
199 ChildProcessSpawner.make(() => Effect.fail(cause)),
200 );
206 const layer = spawnerLayer(ChildProcessSpawner.make(() => Effect.fail(cause)));
201207
202208 return Effect.gen(function* () {
203209 const error = yield* readTailscaleStatus.pipe(Effect.flip, Effect.provide(layer));
......@@ -218,8 +224,7 @@ describe("tailscale", () => {
218224 // inside an `Effect.callback` registration, so that throw arrives as a
219225 // defect rather than a typed error - the shape reproduced here.
220226 const defect = Object.assign(new Error("spawn tailscale ENOTDIR"), { code: "ENOTDIR" });
221 const layer = Layer.succeed(
222 ChildProcessSpawner.ChildProcessSpawner,
227 const layer = spawnerLayer(
223228 ChildProcessSpawner.make(() =>
224229 Effect.callback<never, never>(() => {
225230 throw defect;
......@@ -297,10 +302,7 @@ describe("tailscale", () => {
297302 it.effect("times out tailscale status through TestClock", () => {
298303 const layer = Layer.merge(
299304 TestClock.layer(),
300 Layer.succeed(
301 ChildProcessSpawner.ChildProcessSpawner,
302 ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle())),
303 ),
305 spawnerLayer(ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle()))),
304306 );
305307
306308 return Effect.gen(function* () {
scripts/mobile-showcase.test.ts+14-6
......@@ -1,3 +1,5 @@
1// @effect-diagnostics nodeBuiltinImport:off - expectations mirror the host path joins the script under test performs.
2import * as NodePath from "node:path";
13import { assert, it } from "@effect/vitest";
24import { PNG } from "pngjs";
35
......@@ -167,8 +169,14 @@ it("expands both appearances into independent upload-ready directories", () => {
167169 directory: showcaseCaptureDirectory("/captures", capture),
168170 })),
169171 [
170 { appearance: "light", directory: "/captures/apple/iphone-test/light/t3-code" },
171 { appearance: "dark", directory: "/captures/apple/iphone-test/dark/t3-code" },
172 {
173 appearance: "light",
174 directory: NodePath.join("/captures", "apple", "iphone-test", "light", "t3-code"),
175 },
176 {
177 appearance: "dark",
178 directory: NodePath.join("/captures", "apple", "iphone-test", "dark", "t3-code"),
179 },
172180 ],
173181 );
174182});
......@@ -192,10 +200,10 @@ it("expands themes into independent upload-ready directories per appearance", ()
192200 showcaseCaptureDirectory("/captures", capture),
193201 ),
194202 [
195 "/captures/apple/iphone-test/light/ocean",
196 "/captures/apple/iphone-test/light/ember",
197 "/captures/apple/iphone-test/dark/ocean",
198 "/captures/apple/iphone-test/dark/ember",
203 NodePath.join("/captures", "apple", "iphone-test", "light", "ocean"),
204 NodePath.join("/captures", "apple", "iphone-test", "light", "ember"),
205 NodePath.join("/captures", "apple", "iphone-test", "dark", "ocean"),
206 NodePath.join("/captures", "apple", "iphone-test", "dark", "ember"),
199207 ],
200208 );
201209});
scripts/mobile-showcase.ts+2-1
......@@ -53,7 +53,8 @@ export function resolveAndroidSdkRoot(
5353 const configured = environment.ANDROID_HOME ?? environment.ANDROID_SDK_ROOT;
5454 if (configured) return configured;
5555 const home = environment.HOME ?? environment.USERPROFILE ?? "";
56 return NodePath.join(home, platform === "darwin" ? "Library/Android/sdk" : "Android/Sdk");
56 const path = platform === "win32" ? NodePath.win32 : NodePath.posix;
57 return path.join(home, platform === "darwin" ? "Library/Android/sdk" : "Android/Sdk");
5758}
5859
5960const ANDROID_SDK_ROOT = resolveAndroidSdkRoot(NodeProcess.env);