@simplysm/sd-cli 14.0.63 → 14.0.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capacitor/capacitor-android.d.ts +2 -0
- package/dist/capacitor/capacitor-android.d.ts.map +1 -1
- package/dist/capacitor/capacitor-android.js +13 -0
- package/dist/capacitor/capacitor-android.js.map +1 -1
- package/dist/capacitor/capacitor-npm-config.d.ts.map +1 -1
- package/dist/capacitor/capacitor-npm-config.js +2 -6
- package/dist/capacitor/capacitor-npm-config.js.map +1 -1
- package/dist/electron/electron.d.ts.map +1 -1
- package/dist/electron/electron.js +1 -2
- package/dist/electron/electron.js.map +1 -1
- package/package.json +8 -8
- package/src/capacitor/capacitor-android.ts +14 -0
- package/src/capacitor/capacitor-npm-config.ts +2 -6
- package/src/electron/electron.ts +1 -2
- package/tests/angular/ngtsc-build-core.acc.spec.ts +36 -94
- package/tests/capacitor/capacitor-android.spec.ts +65 -28
- package/tests/capacitor/capacitor-build.spec.ts +40 -385
- package/tests/capacitor/capacitor-config-writer.acc.spec.ts +3 -17
- package/tests/capacitor/capacitor-config-writer.spec.ts +3 -17
- package/tests/capacitor/capacitor-init.spec.ts +40 -636
- package/tests/capacitor/capacitor-npm-config.acc.spec.ts +38 -168
- package/tests/capacitor/capacitor-npm-config.spec.ts +33 -71
- package/tests/commands/check.spec.ts +25 -36
- package/tests/commands/deployment-phase.acc.spec.ts +17 -26
- package/tests/commands/git-phase.acc.spec.ts +13 -112
- package/tests/commands/lint.spec.ts +7 -24
- package/tests/commands/post-publish-phase.acc.spec.ts +5 -10
- package/tests/commands/typecheck.spec.ts +43 -65
- package/tests/electron/electron.spec.ts +22 -46
- package/tests/engines/base-engine.spec.ts +4 -13
- package/tests/engines/engine-selection.spec.ts +14 -17
- package/tests/engines/engine-typecheck-selection.acc.spec.ts +13 -16
- package/tests/engines/esbuild-client-engine.acc.spec.ts +36 -40
- package/tests/engines/esbuild-client-engine.spec.ts +4 -23
- package/tests/engines/ngtsc-engine.spec.ts +3 -10
- package/tests/engines/server-esbuild-engine.spec.ts +3 -10
- package/tests/engines/tsc-engine.spec.ts +3 -10
- package/tests/esbuild/esbuild-tsc-plugin.acc.spec.ts +3 -8
- package/tests/esbuild/esbuild-tsc-plugin.spec.ts +3 -8
- package/tests/orchestrators/build-orchestrator.spec.ts +57 -102
- package/tests/orchestrators/dev-orchestrator.spec.ts +68 -109
- package/tests/orchestrators/typecheck-orchestrator.spec.ts +25 -57
- package/tests/orchestrators/watch-orchestrator.spec.ts +73 -99
- package/tests/sd-cli-entry.spec.ts +17 -20
- package/tests/utils/angular-source-file-cache.spec.ts +4 -8
- package/tests/utils/copy-src.spec.ts +9 -20
- package/tests/utils/esbuild-client-config.acc.spec.ts +9 -15
- package/tests/utils/esbuild-client-config.spec.ts +12 -24
- package/tests/utils/esbuild-config.spec.ts +51 -42
- package/tests/utils/lint-core.spec.ts +13 -19
- package/tests/utils/lint-utils.spec.ts +8 -15
- package/tests/utils/lint-with-program.spec.ts +3 -7
- package/tests/utils/ngtsc-build-core.spec.ts +2 -99
- package/tests/utils/orchestrator-utils.spec.ts +7 -20
- package/tests/utils/output-utils.spec.ts +5 -11
- package/tests/utils/sd-config.spec.ts +4 -12
- package/tests/utils/typecheck-env.spec.ts +49 -77
- package/tests/utils/typecheck-non-package.spec.ts +23 -16
- package/tests/workers/build-watch-paths.acc.spec.ts +4 -10
- package/tests/workers/build-watch-paths.spec.ts +4 -9
- package/tests/workers/client-worker.acc.spec.ts +64 -137
- package/tests/workers/client-worker.spec.ts +63 -89
- package/tests/workers/library-build-lint.spec.ts +19 -30
- package/tests/workers/library-build-worker.spec.ts +28 -55
- package/tests/workers/server-esbuild-context.acc.spec.ts +6 -15
- package/tests/workers/server-esbuild-context.spec.ts +7 -16
- package/tests/workers/server-runtime-worker.spec.ts +8 -10
- package/tests/workers/shared-worker-lifecycle.acc.spec.ts +3 -5
- package/tests/workers/shared-worker-lifecycle.spec.ts +4 -5
- package/tests/capacitor/capacitor-icon.spec.ts +0 -285
- package/tests/capacitor/capacitor-run.spec.ts +0 -256
- package/tests/capacitor/capacitor-workspace.spec.ts +0 -203
- package/tests/commands/device.spec.ts +0 -237
- package/tests/commands/publish.spec.ts +0 -1183
- package/tests/utils/external-modules.spec.ts +0 -217
- package/tests/workers/server-build-lint.spec.ts +0 -201
- package/tests/workers/server-build-worker.spec.ts +0 -765
- package/tests/workers/server-watch-manager.acc.spec.ts +0 -162
- package/tests/workers/server-watch-manager.spec.ts +0 -199
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-restricted-properties -- 테스트 환경변수 조작 필요 */
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
4
|
-
|
|
5
|
-
//#region Mocks
|
|
6
|
-
|
|
7
|
-
const mockFsxExists = vi.fn();
|
|
8
|
-
const mockFsxRead = vi.fn();
|
|
9
|
-
const mockFsxWrite = vi.fn().mockResolvedValue(undefined);
|
|
10
|
-
const mockFsxReadJson = vi.fn();
|
|
11
|
-
const mockFsxWriteJson = vi.fn().mockResolvedValue(undefined);
|
|
12
|
-
const mockFsxMkdir = vi.fn().mockResolvedValue(undefined);
|
|
13
|
-
const mockFsxRm = vi.fn().mockResolvedValue(undefined);
|
|
14
|
-
const mockFsxGlob = vi.fn();
|
|
15
|
-
const mockFsxCopy = vi.fn().mockResolvedValue(undefined);
|
|
16
|
-
|
|
17
|
-
vi.mock("@simplysm/core-node", async (importOriginal) => {
|
|
18
|
-
const original = await importOriginal<typeof import("@simplysm/core-node")>();
|
|
19
|
-
return {
|
|
20
|
-
...original,
|
|
21
|
-
fsx: {
|
|
22
|
-
exists: mockFsxExists,
|
|
23
|
-
read: mockFsxRead,
|
|
24
|
-
write: mockFsxWrite,
|
|
25
|
-
readJson: mockFsxReadJson,
|
|
26
|
-
writeJson: mockFsxWriteJson,
|
|
27
|
-
mkdir: mockFsxMkdir,
|
|
28
|
-
rm: mockFsxRm,
|
|
29
|
-
glob: mockFsxGlob,
|
|
30
|
-
copy: mockFsxCopy,
|
|
31
|
-
},
|
|
32
|
-
cpx: {
|
|
33
|
-
spawn: mockCpxSpawn,
|
|
34
|
-
spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const execaCalls: { command: string; args: string[] }[] = [];
|
|
40
|
-
const mockCpxSpawn = vi.fn((...args: unknown[]) => {
|
|
41
|
-
execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
|
|
42
|
-
return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const mockFsWriteFile = vi.fn().mockResolvedValue(undefined);
|
|
46
|
-
vi.mock("node:fs", () => ({
|
|
47
|
-
default: { promises: { writeFile: (...args: unknown[]) => mockFsWriteFile(...args) } },
|
|
48
|
-
existsSync: (p: string) => {
|
|
49
|
-
if (p.includes("pnpm-workspace.yaml")) return true;
|
|
50
|
-
return false;
|
|
51
|
-
},
|
|
52
|
-
}));
|
|
53
|
-
|
|
54
|
-
vi.mock("sharp", () => ({
|
|
55
|
-
default: vi.fn().mockReturnValue({
|
|
56
|
-
resize: vi.fn().mockReturnThis(),
|
|
57
|
-
composite: vi.fn().mockReturnThis(),
|
|
58
|
-
png: vi.fn().mockReturnThis(),
|
|
59
|
-
toBuffer: vi.fn().mockResolvedValue(new Uint8Array([0])),
|
|
60
|
-
toFile: vi.fn().mockResolvedValue(undefined),
|
|
61
|
-
}),
|
|
62
|
-
}));
|
|
63
|
-
|
|
64
|
-
const mockSymlink = vi.fn().mockResolvedValue(undefined);
|
|
65
|
-
vi.mock("fs/promises", () => ({
|
|
66
|
-
symlink: mockSymlink,
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
vi.mock("module", () => ({
|
|
70
|
-
createRequire: () => ({
|
|
71
|
-
resolve: (id: string) => {
|
|
72
|
-
if (id.includes("capacitor-plugin-auto-update")) {
|
|
73
|
-
return path.resolve("/fake/workspace/packages/capacitor-plugin-auto-update/package.json");
|
|
74
|
-
}
|
|
75
|
-
throw new Error(`Cannot find module '${id}'`);
|
|
76
|
-
},
|
|
77
|
-
}),
|
|
78
|
-
}));
|
|
79
|
-
|
|
80
|
-
//#endregion
|
|
81
|
-
|
|
82
|
-
//#region Helpers
|
|
83
|
-
|
|
84
|
-
const PKG_PATH = "/fake/pkg";
|
|
85
|
-
|
|
86
|
-
function setupDefaultMocks() {
|
|
87
|
-
mockFsxExists.mockImplementation((p: string) => {
|
|
88
|
-
const n = p.replace(/\\/g, "/");
|
|
89
|
-
if (n.includes(".capacitor.lock")) return false;
|
|
90
|
-
return true;
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
94
|
-
const normalized = p.replace(/\\/g, "/");
|
|
95
|
-
if (normalized.includes(".capacitor/package.json")) {
|
|
96
|
-
return {
|
|
97
|
-
name: "com.test.app",
|
|
98
|
-
version: "1.0.0",
|
|
99
|
-
dependencies: {
|
|
100
|
-
"@capacitor/core": "^7.0.0",
|
|
101
|
-
"@capacitor/app": "^7.0.0",
|
|
102
|
-
"@capacitor/android": "^7.0.0",
|
|
103
|
-
},
|
|
104
|
-
devDependencies: {
|
|
105
|
-
"@capacitor/cli": "^7.0.0",
|
|
106
|
-
"@capacitor/assets": "^3.0.0",
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
name: "test-pkg",
|
|
112
|
-
version: "1.0.0",
|
|
113
|
-
dependencies: {
|
|
114
|
-
"@simplysm/capacitor-plugin-auto-update": "workspace:*",
|
|
115
|
-
"@capacitor/camera": "^7.0.0",
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
mockFsxRead.mockImplementation((p: string) => {
|
|
121
|
-
if (p.includes("AndroidManifest.xml")) {
|
|
122
|
-
return '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n<application>\n<activity android:name=".MainActivity">\n</activity>\n</application>\n</manifest>';
|
|
123
|
-
}
|
|
124
|
-
if (p.includes("build.gradle")) {
|
|
125
|
-
return `android {
|
|
126
|
-
defaultConfig {
|
|
127
|
-
versionCode 1
|
|
128
|
-
versionName "1.0"
|
|
129
|
-
minSdkVersion rootProject.ext.minSdkVersion
|
|
130
|
-
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
131
|
-
}
|
|
132
|
-
buildTypes {
|
|
133
|
-
release {
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}`;
|
|
137
|
-
}
|
|
138
|
-
if (p.includes("gradle.properties")) {
|
|
139
|
-
return "org.gradle.jvmargs=-Xmx2048m";
|
|
140
|
-
}
|
|
141
|
-
return "";
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
mockFsxGlob.mockResolvedValue(["C:/Program Files/Amazon Corretto/jdk21.0.1"]);
|
|
145
|
-
process.env["ANDROID_HOME"] = "C:/Android/Sdk";
|
|
146
|
-
execaCalls.length = 0;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
//#endregion
|
|
150
|
-
|
|
151
|
-
let savedEnv: Record<string, string | undefined>;
|
|
152
|
-
beforeEach(() => {
|
|
153
|
-
savedEnv = { ...process.env };
|
|
154
|
-
});
|
|
155
|
-
afterEach(() => {
|
|
156
|
-
process.env = savedEnv;
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe("workspace:* 플러그인 해석", () => {
|
|
160
|
-
beforeEach(() => {
|
|
161
|
-
vi.clearAllMocks();
|
|
162
|
-
setupDefaultMocks();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("workspace:* 플러그인이 link: 프로토콜로 package.json에 등록된다", async () => {
|
|
166
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
167
|
-
|
|
168
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
169
|
-
appId: "com.test.app",
|
|
170
|
-
appName: "Test App",
|
|
171
|
-
plugins: {
|
|
172
|
-
"@simplysm/capacitor-plugin-auto-update": true,
|
|
173
|
-
"@capacitor/camera": true,
|
|
174
|
-
},
|
|
175
|
-
platform: { android: {} },
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
await cap.initialize();
|
|
179
|
-
|
|
180
|
-
const writeJsonCalls = mockFsxWriteJson.mock.calls;
|
|
181
|
-
const capPkgWrite = writeJsonCalls.find(
|
|
182
|
-
(call) => typeof call[0] === "string" && call[0].includes("package.json"),
|
|
183
|
-
);
|
|
184
|
-
expect(capPkgWrite).toBeDefined();
|
|
185
|
-
const deps = (capPkgWrite![1] as Record<string, unknown>)["dependencies"] as Record<
|
|
186
|
-
string,
|
|
187
|
-
string
|
|
188
|
-
>;
|
|
189
|
-
|
|
190
|
-
// 1. workspace:* 플러그인이 link: 프로토콜로 등록되어야 한다
|
|
191
|
-
expect(deps["@simplysm/capacitor-plugin-auto-update"]).toMatch(/^link:/);
|
|
192
|
-
|
|
193
|
-
// 2. link: 경로는 상대경로여야 한다 (절대경로가 아님)
|
|
194
|
-
const linkPath = deps["@simplysm/capacitor-plugin-auto-update"].replace(/^link:/, "");
|
|
195
|
-
expect(linkPath).not.toMatch(/^[A-Z]:|^\//i);
|
|
196
|
-
|
|
197
|
-
// 3. 일반 npm 플러그인은 버전 문자열로 등록되어야 한다
|
|
198
|
-
expect(deps["@capacitor/camera"]).toBe("^7.0.0");
|
|
199
|
-
|
|
200
|
-
// 4. 수동 symlink 생성이 호출되지 않아야 한다
|
|
201
|
-
expect(mockSymlink).not.toHaveBeenCalled();
|
|
202
|
-
});
|
|
203
|
-
});
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { Capacitor } from "../../src/capacitor/capacitor";
|
|
3
|
-
import { Electron } from "../../src/electron/electron";
|
|
4
|
-
import * as sdConfigModule from "../../src/utils/sd-config";
|
|
5
|
-
import { runDevice } from "../../src/commands/device";
|
|
6
|
-
|
|
7
|
-
const mocks = vi.hoisted(() => ({
|
|
8
|
-
readFileSync: vi.fn(),
|
|
9
|
-
existsSync: vi.fn().mockReturnValue(false),
|
|
10
|
-
httpGet: vi.fn(),
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
vi.mock("node:fs", () => ({
|
|
14
|
-
default: {
|
|
15
|
-
readFileSync: (...args: any[]) => mocks.readFileSync(...args),
|
|
16
|
-
existsSync: (...args: any[]) => mocks.existsSync(...args),
|
|
17
|
-
},
|
|
18
|
-
readFileSync: (...args: any[]) => mocks.readFileSync(...args),
|
|
19
|
-
existsSync: (...args: any[]) => mocks.existsSync(...args),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
vi.mock("node:http", () => ({
|
|
23
|
-
default: {
|
|
24
|
-
get: (...args: any[]) => mocks.httpGet(...args),
|
|
25
|
-
},
|
|
26
|
-
get: (...args: any[]) => mocks.httpGet(...args),
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
describe("runDevice", () => {
|
|
30
|
-
const mockCapacitorInstance = {
|
|
31
|
-
initialize: vi.fn().mockResolvedValue(undefined),
|
|
32
|
-
run: vi.fn().mockResolvedValue(undefined),
|
|
33
|
-
build: vi.fn().mockResolvedValue(undefined),
|
|
34
|
-
};
|
|
35
|
-
const mockElectronInstance = {
|
|
36
|
-
initialize: vi.fn().mockResolvedValue(undefined),
|
|
37
|
-
run: vi.fn().mockResolvedValue(undefined),
|
|
38
|
-
build: vi.fn().mockResolvedValue(undefined),
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
beforeEach(() => {
|
|
42
|
-
vi.clearAllMocks();
|
|
43
|
-
vi.spyOn(Capacitor, "create").mockResolvedValue(mockCapacitorInstance as any);
|
|
44
|
-
vi.spyOn(Electron, "create").mockResolvedValue(mockElectronInstance as any);
|
|
45
|
-
vi.spyOn(sdConfigModule, "loadSdConfig");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
afterEach(() => {
|
|
49
|
-
vi.restoreAllMocks();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Acceptance: Scenario "device 명령어로 Capacitor 앱 실행"
|
|
53
|
-
it("runs Capacitor.create + run when capacitor config exists", async () => {
|
|
54
|
-
vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
|
|
55
|
-
packages: {
|
|
56
|
-
"client-pda": {
|
|
57
|
-
target: "client",
|
|
58
|
-
server: 40480,
|
|
59
|
-
capacitor: { appId: "com.test.app", appName: "TestApp" },
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
await runDevice({ target: "client-pda", options: [] });
|
|
65
|
-
|
|
66
|
-
expect(Capacitor.create).toHaveBeenCalledWith(
|
|
67
|
-
expect.stringContaining("client-pda"),
|
|
68
|
-
{ appId: "com.test.app", appName: "TestApp" },
|
|
69
|
-
undefined,
|
|
70
|
-
);
|
|
71
|
-
expect(mockCapacitorInstance.run).toHaveBeenCalledWith("http://localhost:40480/client-pda/");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Acceptance: Scenario "device 명령어로 Electron 앱 실행"
|
|
75
|
-
it("runs Electron.create + run when electron config exists", async () => {
|
|
76
|
-
vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
|
|
77
|
-
packages: {
|
|
78
|
-
"my-client": {
|
|
79
|
-
target: "client",
|
|
80
|
-
server: 4200,
|
|
81
|
-
electron: { appId: "com.test.electron" },
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
await runDevice({ target: "my-client", options: [] });
|
|
87
|
-
|
|
88
|
-
expect(Electron.create).toHaveBeenCalledWith(
|
|
89
|
-
expect.stringContaining("my-client"),
|
|
90
|
-
{ appId: "com.test.electron" },
|
|
91
|
-
undefined,
|
|
92
|
-
);
|
|
93
|
-
expect(mockElectronInstance.run).toHaveBeenCalledWith("http://localhost:4200/my-client/");
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Acceptance: Scenario "device 명령어에 URL 옵션 지정"
|
|
97
|
-
it("uses provided URL instead of auto-generated one", async () => {
|
|
98
|
-
vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
|
|
99
|
-
packages: {
|
|
100
|
-
"client-pda": {
|
|
101
|
-
target: "client",
|
|
102
|
-
server: 40480,
|
|
103
|
-
capacitor: { appId: "com.test.app", appName: "TestApp" },
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
await runDevice({ target: "client-pda", url: "http://192.168.1.100:4200", options: [] });
|
|
109
|
-
|
|
110
|
-
expect(mockCapacitorInstance.run).toHaveBeenCalledWith("http://192.168.1.100:4200");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Unit: electron이 capacitor보다 우선 (v13 동작)
|
|
114
|
-
it("prefers electron over capacitor when both are configured", async () => {
|
|
115
|
-
vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
|
|
116
|
-
packages: {
|
|
117
|
-
"my-client": {
|
|
118
|
-
target: "client",
|
|
119
|
-
server: 4200,
|
|
120
|
-
capacitor: { appId: "com.test.app", appName: "TestApp" },
|
|
121
|
-
electron: { appId: "com.test.electron" },
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
await runDevice({ target: "my-client", options: [] });
|
|
127
|
-
|
|
128
|
-
expect(Electron.create).toHaveBeenCalled();
|
|
129
|
-
expect(Capacitor.create).not.toHaveBeenCalled();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Unit: 존재하지 않는 패키지 에러
|
|
133
|
-
it("throws when package does not exist", async () => {
|
|
134
|
-
vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
|
|
135
|
-
packages: {
|
|
136
|
-
"other-pkg": { target: "node" },
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
await expect(runDevice({ target: "nonexistent", options: [] })).rejects.toThrow();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Unit: client가 아닌 패키지 에러
|
|
144
|
-
it("throws when package is not a client target", async () => {
|
|
145
|
-
vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
|
|
146
|
-
packages: {
|
|
147
|
-
"my-server": { target: "server" },
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
await expect(runDevice({ target: "my-server", options: [] })).rejects.toThrow();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// Acceptance: Scenario "server가 string일 때 서버 패키지의 .dev-port에서 포트 읽기"
|
|
155
|
-
it("reads .dev-port from server package directory when server is a string", async () => {
|
|
156
|
-
vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
|
|
157
|
-
packages: {
|
|
158
|
-
"client-devtool": {
|
|
159
|
-
target: "client",
|
|
160
|
-
server: "my-server",
|
|
161
|
-
electron: { appId: "com.test.electron" },
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
mocks.readFileSync.mockReturnValue("3000");
|
|
167
|
-
|
|
168
|
-
// HTTP 헬스체크 성공 mock
|
|
169
|
-
mocks.httpGet.mockImplementation((_url: string, cb: Function) => {
|
|
170
|
-
const res = { resume: vi.fn() };
|
|
171
|
-
cb(res);
|
|
172
|
-
return { on: vi.fn(), setTimeout: vi.fn() };
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
await runDevice({ target: "client-devtool", options: [] });
|
|
176
|
-
|
|
177
|
-
// 서버 패키지 경로의 .dev-port를 읽어야 함
|
|
178
|
-
expect(mocks.readFileSync).toHaveBeenCalledWith(
|
|
179
|
-
expect.stringContaining("my-server"),
|
|
180
|
-
"utf-8",
|
|
181
|
-
);
|
|
182
|
-
expect(mockElectronInstance.run).toHaveBeenCalledWith(
|
|
183
|
-
"http://localhost:3000/client-devtool/",
|
|
184
|
-
);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// Acceptance: Scenario "dev 서버 미실행 시 에러"
|
|
188
|
-
it("throws when .dev-port file does not exist and server is a string", async () => {
|
|
189
|
-
vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
|
|
190
|
-
packages: {
|
|
191
|
-
"client-devtool": {
|
|
192
|
-
target: "client",
|
|
193
|
-
server: "my-server",
|
|
194
|
-
electron: { appId: "com.test.electron" },
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
mocks.readFileSync.mockImplementation(() => {
|
|
200
|
-
throw new Error("ENOENT");
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
await expect(runDevice({ target: "client-devtool", options: [] })).rejects.toThrow(
|
|
204
|
-
"dev 서버가 실행 중이 아닙니다",
|
|
205
|
-
);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// Acceptance: Scenario "stale 포트 파일 존재 시 헬스체크 실패 에러"
|
|
209
|
-
it("throws when .dev-port exists but health check fails", async () => {
|
|
210
|
-
vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
|
|
211
|
-
packages: {
|
|
212
|
-
"client-devtool": {
|
|
213
|
-
target: "client",
|
|
214
|
-
server: "my-server",
|
|
215
|
-
electron: { appId: "com.test.electron" },
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
mocks.readFileSync.mockReturnValue("5173");
|
|
221
|
-
|
|
222
|
-
// HTTP 헬스체크 실패 mock
|
|
223
|
-
mocks.httpGet.mockImplementation((_url: string, _cb: Function) => {
|
|
224
|
-
const req = {
|
|
225
|
-
on: vi.fn((event: string, handler: Function) => {
|
|
226
|
-
if (event === "error") handler(new Error("ECONNREFUSED"));
|
|
227
|
-
}),
|
|
228
|
-
setTimeout: vi.fn(),
|
|
229
|
-
};
|
|
230
|
-
return req;
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
await expect(runDevice({ target: "client-devtool", options: [] })).rejects.toThrow(
|
|
234
|
-
"dev 서버가 응답하지 않습니다",
|
|
235
|
-
);
|
|
236
|
-
});
|
|
237
|
-
});
|