@simplysm/sd-cli 14.0.64 → 14.0.66
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 +13 -13
- 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,285 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-restricted-properties -- 테스트 환경변수 조작 필요 */
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
-
import { consola } from "consola";
|
|
4
|
-
|
|
5
|
-
//#region Mocks
|
|
6
|
-
|
|
7
|
-
// fsx mock
|
|
8
|
-
const mockFsxExists = vi.fn();
|
|
9
|
-
const mockFsxRead = vi.fn();
|
|
10
|
-
const mockFsxWrite = vi.fn().mockResolvedValue(undefined);
|
|
11
|
-
const mockFsxReadJson = vi.fn();
|
|
12
|
-
const mockFsxWriteJson = vi.fn().mockResolvedValue(undefined);
|
|
13
|
-
const mockFsxMkdir = vi.fn().mockResolvedValue(undefined);
|
|
14
|
-
const mockFsxRm = vi.fn().mockResolvedValue(undefined);
|
|
15
|
-
const mockFsxGlob = vi.fn();
|
|
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
|
-
},
|
|
31
|
-
cpx: {
|
|
32
|
-
spawn: mockCpxSpawn,
|
|
33
|
-
spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// cpx mock (was execa)
|
|
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
|
-
// sharp mock
|
|
55
|
-
const mockSharpToBuffer = vi.fn().mockResolvedValue(new Uint8Array([0]));
|
|
56
|
-
const mockSharpToFile = vi.fn().mockResolvedValue(undefined);
|
|
57
|
-
const mockSharpInstance = {
|
|
58
|
-
resize: vi.fn().mockReturnThis(),
|
|
59
|
-
composite: vi.fn().mockReturnThis(),
|
|
60
|
-
png: vi.fn().mockReturnThis(),
|
|
61
|
-
toBuffer: mockSharpToBuffer,
|
|
62
|
-
toFile: mockSharpToFile,
|
|
63
|
-
};
|
|
64
|
-
const mockSharp = vi.fn().mockReturnValue(mockSharpInstance);
|
|
65
|
-
vi.mock("sharp", () => ({ default: mockSharp }));
|
|
66
|
-
|
|
67
|
-
// consola mock (logger assertion 필요)
|
|
68
|
-
const mockLoggerWarn = vi.fn();
|
|
69
|
-
vi.spyOn(consola, "withTag").mockReturnValue({ debug: vi.fn(), warn: mockLoggerWarn, error: vi.fn(), info: vi.fn(), success: vi.fn(), start: vi.fn() } as any);
|
|
70
|
-
|
|
71
|
-
//#endregion
|
|
72
|
-
|
|
73
|
-
//#region Helpers
|
|
74
|
-
|
|
75
|
-
const PKG_PATH = "/fake/pkg";
|
|
76
|
-
const _CAP_PATH = "/fake/pkg/.capacitor";
|
|
77
|
-
|
|
78
|
-
function setupDefaultMocks() {
|
|
79
|
-
// fsx.exists: 모든 것 존재, lock만 없음
|
|
80
|
-
mockFsxExists.mockImplementation((p: string) => {
|
|
81
|
-
if (p.includes(".capacitor.lock")) return false;
|
|
82
|
-
return true;
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// fsx.readJson: package.json 응답
|
|
86
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
87
|
-
const normalized = p.replace(/\\/g, "/");
|
|
88
|
-
if (normalized.includes(".capacitor/package.json")) {
|
|
89
|
-
return {
|
|
90
|
-
name: "com.test.app",
|
|
91
|
-
version: "1.0.0",
|
|
92
|
-
dependencies: {
|
|
93
|
-
"@capacitor/core": "^7.0.0",
|
|
94
|
-
"@capacitor/app": "^7.0.0",
|
|
95
|
-
"@capacitor/android": "^7.0.0",
|
|
96
|
-
},
|
|
97
|
-
devDependencies: {
|
|
98
|
-
"@capacitor/cli": "^7.0.0",
|
|
99
|
-
"@capacitor/assets": "^3.0.0",
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return { name: "test-pkg", version: "1.0.0" };
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// fsx.read: Android 설정 파일
|
|
107
|
-
mockFsxRead.mockImplementation((p: string) => {
|
|
108
|
-
if (p.includes("AndroidManifest.xml")) {
|
|
109
|
-
return '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n<application>\n</application>\n</manifest>';
|
|
110
|
-
}
|
|
111
|
-
if (p.includes("build.gradle")) {
|
|
112
|
-
return `android {
|
|
113
|
-
defaultConfig {
|
|
114
|
-
versionCode 1
|
|
115
|
-
versionName "1.0"
|
|
116
|
-
minSdkVersion rootProject.ext.minSdkVersion
|
|
117
|
-
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
118
|
-
}
|
|
119
|
-
buildTypes {
|
|
120
|
-
release {
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}`;
|
|
124
|
-
}
|
|
125
|
-
if (p.includes("gradle.properties")) {
|
|
126
|
-
return "org.gradle.jvmargs=-Xmx2048m";
|
|
127
|
-
}
|
|
128
|
-
return "";
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// fsx.glob: Java 21 경로
|
|
132
|
-
mockFsxGlob.mockResolvedValue(["C:/Program Files/Amazon Corretto/jdk21.0.1"]);
|
|
133
|
-
|
|
134
|
-
// env: Android SDK
|
|
135
|
-
process.env["ANDROID_HOME"] = "C:/Android/Sdk";
|
|
136
|
-
|
|
137
|
-
// execa 호출 기록 초기화
|
|
138
|
-
execaCalls.length = 0;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
//#endregion
|
|
142
|
-
|
|
143
|
-
let savedEnv: Record<string, string | undefined>;
|
|
144
|
-
beforeEach(() => {
|
|
145
|
-
savedEnv = { ...process.env };
|
|
146
|
-
});
|
|
147
|
-
afterEach(() => {
|
|
148
|
-
process.env = savedEnv;
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
describe("Capacitor 아이콘 처리", () => {
|
|
152
|
-
beforeEach(() => {
|
|
153
|
-
vi.clearAllMocks();
|
|
154
|
-
setupDefaultMocks();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe("인수 테스트", () => {
|
|
158
|
-
it("icon 경로가 지정되어 있으면 initialize 시 아이콘을 생성한다", async () => {
|
|
159
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
160
|
-
|
|
161
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
162
|
-
appId: "com.test.app",
|
|
163
|
-
appName: "Test App",
|
|
164
|
-
icon: "assets/icon.png",
|
|
165
|
-
platform: { android: {} },
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
await cap.initialize();
|
|
169
|
-
|
|
170
|
-
// capacitor-assets generate가 실행되었는지 확인
|
|
171
|
-
const assetsCmd = execaCalls.find(
|
|
172
|
-
(c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
|
|
173
|
-
);
|
|
174
|
-
if (assetsCmd == null) {
|
|
175
|
-
throw new Error("capacitor-assets generate 명령이 실행되지 않았습니다.");
|
|
176
|
-
}
|
|
177
|
-
const iconBackgroundColorArgIndex = assetsCmd.args.indexOf("--iconBackgroundColor");
|
|
178
|
-
expect(iconBackgroundColorArgIndex).not.toBe(-1);
|
|
179
|
-
expect(assetsCmd.args[iconBackgroundColorArgIndex + 1]).toBe("#ffffff");
|
|
180
|
-
|
|
181
|
-
// sharp가 소스 이미지를 처리했는지 확인
|
|
182
|
-
expect(mockSharp).toHaveBeenCalled();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("icon이 미지정이면 기본 아이콘을 유지한다", async () => {
|
|
186
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
187
|
-
|
|
188
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
189
|
-
appId: "com.test.app",
|
|
190
|
-
appName: "Test App",
|
|
191
|
-
platform: { android: {} },
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
await cap.initialize();
|
|
195
|
-
|
|
196
|
-
// sharp가 호출되지 않아야 한다
|
|
197
|
-
expect(mockSharp).not.toHaveBeenCalled();
|
|
198
|
-
|
|
199
|
-
// capacitor-assets generate가 실행되지 않아야 한다
|
|
200
|
-
const assetsCmd = execaCalls.find(
|
|
201
|
-
(c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
|
|
202
|
-
);
|
|
203
|
-
expect(assetsCmd).toBeUndefined();
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it("icon 경로의 파일이 없으면 경고 후 기본 아이콘을 사용한다", async () => {
|
|
207
|
-
// icon 파일만 존재하지 않도록 설정
|
|
208
|
-
mockFsxExists.mockImplementation((p: string) => {
|
|
209
|
-
if (p.includes(".capacitor.lock")) return false;
|
|
210
|
-
if (p.includes("icon.png")) return false;
|
|
211
|
-
return true;
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
215
|
-
|
|
216
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
217
|
-
appId: "com.test.app",
|
|
218
|
-
appName: "Test App",
|
|
219
|
-
icon: "assets/icon.png",
|
|
220
|
-
platform: { android: {} },
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
await cap.initialize();
|
|
224
|
-
|
|
225
|
-
// 경고가 출력되었는지 확인
|
|
226
|
-
expect(mockLoggerWarn).toHaveBeenCalledWith(
|
|
227
|
-
expect.stringContaining("icon"),
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// sharp가 호출되지 않아야 한다
|
|
231
|
-
expect(mockSharp).not.toHaveBeenCalled();
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
describe("단위", () => {
|
|
236
|
-
it("sharp로 소스 이미지를 리사이즈하고 1024x1024 캔버스에 합성한다", async () => {
|
|
237
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
238
|
-
|
|
239
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
240
|
-
appId: "com.test.app",
|
|
241
|
-
appName: "Test App",
|
|
242
|
-
icon: "assets/icon.png",
|
|
243
|
-
platform: { android: {} },
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
await cap.initialize();
|
|
247
|
-
|
|
248
|
-
// sharp가 소스 이미지 경로로 호출되었는지 확인
|
|
249
|
-
const sourceCall = mockSharp.mock.calls.find(
|
|
250
|
-
(call) => typeof call[0] === "string" && call[0].includes("icon.png"),
|
|
251
|
-
);
|
|
252
|
-
expect(sourceCall).toBeDefined();
|
|
253
|
-
|
|
254
|
-
// resize가 호출되었는지 확인
|
|
255
|
-
expect(mockSharpInstance.resize).toHaveBeenCalled();
|
|
256
|
-
|
|
257
|
-
// composite가 호출되었는지 확인 (캔버스 합성)
|
|
258
|
-
expect(mockSharpInstance.composite).toHaveBeenCalled();
|
|
259
|
-
|
|
260
|
-
// toFile로 logo.png에 저장되었는지 확인
|
|
261
|
-
const toFileCalls = mockSharpToFile.mock.calls;
|
|
262
|
-
const logoCall = toFileCalls.find(
|
|
263
|
-
(call) => typeof call[0] === "string" && call[0].includes("logo.png"),
|
|
264
|
-
);
|
|
265
|
-
expect(logoCall).toBeDefined();
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it("capacitor-assets generate 명령에 흰색 아이콘 배경색을 전달한다", async () => {
|
|
269
|
-
const { setupIcon } = await import("../../src/capacitor/capacitor-icon.js");
|
|
270
|
-
|
|
271
|
-
await setupIcon(PKG_PATH, _CAP_PATH, "assets/icon.png");
|
|
272
|
-
|
|
273
|
-
const assetsCmd = execaCalls.find(
|
|
274
|
-
(c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
|
|
275
|
-
);
|
|
276
|
-
if (assetsCmd == null) {
|
|
277
|
-
throw new Error("capacitor-assets generate 명령이 실행되지 않았습니다.");
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const iconBackgroundColorArgIndex = assetsCmd.args.indexOf("--iconBackgroundColor");
|
|
281
|
-
expect(iconBackgroundColorArgIndex).not.toBe(-1);
|
|
282
|
-
expect(assetsCmd.args[iconBackgroundColorArgIndex + 1]).toBe("#ffffff");
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
});
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-restricted-properties -- 테스트 환경변수 조작 필요 */
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
-
|
|
4
|
-
//#region Mocks
|
|
5
|
-
|
|
6
|
-
// fsx mock
|
|
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().mockResolvedValue([]);
|
|
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
|
-
// cpx mock (was execa) — tracks commands and resolves immediately
|
|
40
|
-
const execaCalls: { command: string; args: string[] }[] = [];
|
|
41
|
-
let execaFactory: (...args: unknown[]) => Promise<{ stdout: string; stderr: string; exitCode: number }> = () =>
|
|
42
|
-
Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
43
|
-
|
|
44
|
-
const mockCpxSpawn = vi.fn((...args: unknown[]) => {
|
|
45
|
-
execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
|
|
46
|
-
return execaFactory(...args);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const mockFsWriteFile = vi.fn().mockResolvedValue(undefined);
|
|
50
|
-
vi.mock("node:fs", () => ({
|
|
51
|
-
default: { promises: { writeFile: (...args: unknown[]) => mockFsWriteFile(...args) } },
|
|
52
|
-
existsSync: (p: string) => {
|
|
53
|
-
if (p.includes("pnpm-workspace.yaml")) return true;
|
|
54
|
-
return false;
|
|
55
|
-
},
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
// sharp mock
|
|
59
|
-
vi.mock("sharp", () => ({
|
|
60
|
-
default: vi.fn().mockReturnValue({
|
|
61
|
-
resize: vi.fn().mockReturnThis(),
|
|
62
|
-
composite: vi.fn().mockReturnThis(),
|
|
63
|
-
png: vi.fn().mockReturnThis(),
|
|
64
|
-
toBuffer: vi.fn().mockResolvedValue(new Uint8Array([0])),
|
|
65
|
-
toFile: vi.fn().mockResolvedValue(undefined),
|
|
66
|
-
}),
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//#endregion
|
|
71
|
-
|
|
72
|
-
//#region Helpers
|
|
73
|
-
|
|
74
|
-
const PKG_PATH = "/fake/pkg";
|
|
75
|
-
|
|
76
|
-
function setupDefaultMocks() {
|
|
77
|
-
mockFsxExists.mockResolvedValue(true);
|
|
78
|
-
|
|
79
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
80
|
-
const normalized = p.replace(/\\/g, "/");
|
|
81
|
-
if (normalized.includes(".capacitor/package.json")) {
|
|
82
|
-
return {
|
|
83
|
-
name: "com.test.app",
|
|
84
|
-
version: "1.2.3",
|
|
85
|
-
dependencies: {
|
|
86
|
-
"@capacitor/core": "^7.0.0",
|
|
87
|
-
"@capacitor/app": "^7.0.0",
|
|
88
|
-
"@capacitor/android": "^7.0.0",
|
|
89
|
-
},
|
|
90
|
-
devDependencies: {
|
|
91
|
-
"@capacitor/cli": "^7.0.0",
|
|
92
|
-
"@capacitor/assets": "^3.0.0",
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
return { name: "test-pkg", version: "1.2.3" };
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
mockFsxRead.mockImplementation((p: string) => {
|
|
100
|
-
const normalized = p.replace(/\\/g, "/");
|
|
101
|
-
if (normalized.includes("capacitor.config.ts")) {
|
|
102
|
-
return `import type { CapacitorConfig } from "@capacitor/cli";
|
|
103
|
-
const config: CapacitorConfig = {
|
|
104
|
-
appId: "com.test.app",
|
|
105
|
-
appName: "Test App",
|
|
106
|
-
server: {
|
|
107
|
-
url: "http://old-url:3000",
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
export default config;`;
|
|
111
|
-
}
|
|
112
|
-
return "";
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
mockFsxGlob.mockImplementation((pattern: string) => {
|
|
116
|
-
if (pattern.includes("Corretto") || pattern.includes("jdk")) {
|
|
117
|
-
return ["C:/Program Files/Amazon Corretto/jdk21.0.1"];
|
|
118
|
-
}
|
|
119
|
-
return [];
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
process.env["ANDROID_HOME"] = "C:/Android/Sdk";
|
|
123
|
-
|
|
124
|
-
execaCalls.length = 0;
|
|
125
|
-
execaFactory = () => Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
126
|
-
mockFsWriteFile.mockReset();
|
|
127
|
-
mockFsWriteFile.mockResolvedValue(undefined);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
//#endregion
|
|
131
|
-
|
|
132
|
-
let savedEnv: Record<string, string | undefined>;
|
|
133
|
-
beforeEach(() => {
|
|
134
|
-
savedEnv = { ...process.env };
|
|
135
|
-
});
|
|
136
|
-
afterEach(() => {
|
|
137
|
-
process.env = savedEnv;
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe("Capacitor.run()", () => {
|
|
141
|
-
beforeEach(() => {
|
|
142
|
-
vi.clearAllMocks();
|
|
143
|
-
setupDefaultMocks();
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Acceptance: Scenario "Capacitor 디바이스 실행 (run 메서드 단위 테스트)"
|
|
147
|
-
it("updates server URL, runs cap copy and cap run for android", async () => {
|
|
148
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
149
|
-
|
|
150
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
151
|
-
appId: "com.test.app",
|
|
152
|
-
appName: "Test App",
|
|
153
|
-
platform: { android: {} },
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
await cap.run("http://localhost:4200");
|
|
157
|
-
|
|
158
|
-
// _updateServerUrl should have written the new URL
|
|
159
|
-
expect(mockFsxWrite).toHaveBeenCalledWith(
|
|
160
|
-
expect.stringContaining("capacitor.config.ts"),
|
|
161
|
-
expect.stringContaining("http://localhost:4200"),
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
// cap copy android + cap run android
|
|
165
|
-
const capCmds = execaCalls.filter(
|
|
166
|
-
(c) => c.command === "pnpm" && c.args.includes("cap"),
|
|
167
|
-
);
|
|
168
|
-
expect(capCmds.some((c) => c.args.includes("copy") && c.args.includes("android"))).toBe(true);
|
|
169
|
-
expect(capCmds.some((c) => c.args.includes("run") && c.args.includes("android"))).toBe(true);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// Unit: cap run 실패 시 adb kill-server 호출 후 에러를 re-throw한다
|
|
173
|
-
it("calls adb kill-server and re-throws on android platform cap run failure", async () => {
|
|
174
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
175
|
-
|
|
176
|
-
execaFactory = (...args: unknown[]) => {
|
|
177
|
-
const cmd = args[0] as string;
|
|
178
|
-
const cmdArgs = (args[1] as string[] | undefined) ?? [];
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
cmd === "pnpm" &&
|
|
182
|
-
cmdArgs.includes("cap") &&
|
|
183
|
-
cmdArgs.includes("run") &&
|
|
184
|
-
cmdArgs.includes("android")
|
|
185
|
-
) {
|
|
186
|
-
return Promise.reject(new Error("cap run failed"));
|
|
187
|
-
}
|
|
188
|
-
return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
192
|
-
appId: "com.test.app",
|
|
193
|
-
appName: "Test App",
|
|
194
|
-
platform: { android: {} },
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
await expect(cap.run("http://localhost:4200")).rejects.toThrow("cap run failed");
|
|
198
|
-
|
|
199
|
-
// adb kill-server should have been called
|
|
200
|
-
expect(
|
|
201
|
-
execaCalls.some((c) => c.command === "adb" && c.args.includes("kill-server")),
|
|
202
|
-
).toBe(true);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// Unit: _updateServerUrl replaces existing url
|
|
206
|
-
it("replaces existing url in capacitor.config.ts", async () => {
|
|
207
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
208
|
-
|
|
209
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
210
|
-
appId: "com.test.app",
|
|
211
|
-
appName: "Test App",
|
|
212
|
-
platform: { android: {} },
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
await cap.run("http://localhost:5555");
|
|
216
|
-
|
|
217
|
-
const writeCall = mockFsxWrite.mock.calls.find(
|
|
218
|
-
(c: string[]) => c[0].replace(/\\/g, "/").includes("capacitor.config.ts"),
|
|
219
|
-
);
|
|
220
|
-
expect(writeCall).toBeDefined();
|
|
221
|
-
const content = writeCall![1] as string;
|
|
222
|
-
expect(content).toContain('url: "http://localhost:5555"');
|
|
223
|
-
expect(content).not.toContain("http://old-url:3000");
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// Unit: _updateServerUrl inserts url when server block exists but no url field
|
|
227
|
-
it("inserts url when server block has no url field", async () => {
|
|
228
|
-
mockFsxRead.mockImplementation((p: string) => {
|
|
229
|
-
if (p.replace(/\\/g, "/").includes("capacitor.config.ts")) {
|
|
230
|
-
return `const config = {
|
|
231
|
-
server: {
|
|
232
|
-
hostname: "localhost",
|
|
233
|
-
},
|
|
234
|
-
};`;
|
|
235
|
-
}
|
|
236
|
-
return "";
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
240
|
-
|
|
241
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
242
|
-
appId: "com.test.app",
|
|
243
|
-
appName: "Test App",
|
|
244
|
-
platform: { android: {} },
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
await cap.run("http://localhost:4200");
|
|
248
|
-
|
|
249
|
-
const writeCall = mockFsxWrite.mock.calls.find(
|
|
250
|
-
(c: string[]) => c[0].replace(/\\/g, "/").includes("capacitor.config.ts"),
|
|
251
|
-
);
|
|
252
|
-
expect(writeCall).toBeDefined();
|
|
253
|
-
const content = writeCall![1] as string;
|
|
254
|
-
expect(content).toContain('url: "http://localhost:4200"');
|
|
255
|
-
});
|
|
256
|
-
});
|