@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,55 +1,9 @@
|
|
|
1
1
|
/* eslint-disable no-restricted-properties -- 테스트 환경변수 조작 필요 */
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
}));
|
|
2
|
+
import { describe, it, expect, vi, beforeAll, afterAll, beforeEach, afterEach } from "vitest";
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fsx, cpx } from "@simplysm/core-node";
|
|
53
7
|
|
|
54
8
|
vi.mock("sharp", () => ({
|
|
55
9
|
default: vi.fn().mockReturnValue({
|
|
@@ -61,22 +15,41 @@ vi.mock("sharp", () => ({
|
|
|
61
15
|
}),
|
|
62
16
|
}));
|
|
63
17
|
|
|
64
|
-
const
|
|
65
|
-
vi.spyOn(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
18
|
+
const mockFsxExists = vi.spyOn(fsx, "exists");
|
|
19
|
+
const mockFsxRead = vi.spyOn(fsx, "read");
|
|
20
|
+
vi.spyOn(fsx, "write").mockResolvedValue(undefined);
|
|
21
|
+
const mockFsxReadJson = vi.spyOn(fsx, "readJson");
|
|
22
|
+
vi.spyOn(fsx, "writeJson").mockResolvedValue(undefined);
|
|
23
|
+
vi.spyOn(fsx, "mkdir").mockResolvedValue(undefined);
|
|
24
|
+
vi.spyOn(fsx, "rm").mockResolvedValue(undefined);
|
|
25
|
+
const mockFsxGlob = vi.spyOn(fsx, "glob");
|
|
26
|
+
vi.spyOn(fsx, "copy").mockResolvedValue(undefined);
|
|
27
|
+
|
|
28
|
+
vi.spyOn(cpx, "spawn").mockResolvedValue({ stdout: "", stderr: "", exitCode: 0 });
|
|
29
|
+
vi.spyOn(cpx, "spawnSync").mockReturnValue({ stdout: "", stderr: "", exitCode: 0 });
|
|
30
|
+
|
|
31
|
+
let tmpRoot: string;
|
|
32
|
+
let PKG_PATH: string;
|
|
33
|
+
|
|
34
|
+
beforeAll(() => {
|
|
35
|
+
tmpRoot = mkdtempSync(path.join(tmpdir(), "cap-init-"));
|
|
36
|
+
writeFileSync(path.join(tmpRoot, "pnpm-workspace.yaml"), "");
|
|
37
|
+
PKG_PATH = path.join(tmpRoot, "pkg");
|
|
38
|
+
// capacitor.ts가 .capacitor/.capacitor.lock 파일을 작성하므로 디렉토리 미리 생성
|
|
39
|
+
mkdirSync(path.join(PKG_PATH, ".capacitor"), { recursive: true });
|
|
40
|
+
});
|
|
70
41
|
|
|
71
|
-
|
|
42
|
+
afterAll(() => {
|
|
43
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
44
|
+
});
|
|
72
45
|
|
|
73
46
|
function setupDefaultMocks() {
|
|
74
|
-
mockFsxExists.mockImplementation((p: string) => {
|
|
47
|
+
mockFsxExists.mockImplementation(((p: string) => {
|
|
75
48
|
if (p.includes(".capacitor.lock")) return false;
|
|
76
49
|
return true;
|
|
77
|
-
});
|
|
50
|
+
}) as never);
|
|
78
51
|
|
|
79
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
52
|
+
mockFsxReadJson.mockImplementation(((p: string) => {
|
|
80
53
|
const normalized = p.replace(/\\/g, "/");
|
|
81
54
|
if (normalized.includes(".capacitor/package.json")) {
|
|
82
55
|
return {
|
|
@@ -97,9 +70,9 @@ function setupDefaultMocks() {
|
|
|
97
70
|
return { name: "test-pkg", version: "1.0.0" };
|
|
98
71
|
}
|
|
99
72
|
return {};
|
|
100
|
-
});
|
|
73
|
+
}) as never);
|
|
101
74
|
|
|
102
|
-
mockFsxRead.mockImplementation((p: string) => {
|
|
75
|
+
mockFsxRead.mockImplementation(((p: string) => {
|
|
103
76
|
if (p.includes("AndroidManifest.xml")) {
|
|
104
77
|
return '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n<application>\n<activity android:name=".MainActivity">\n</activity>\n</application>\n</manifest>';
|
|
105
78
|
}
|
|
@@ -111,45 +84,19 @@ function setupDefaultMocks() {
|
|
|
111
84
|
minSdkVersion rootProject.ext.minSdkVersion
|
|
112
85
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
113
86
|
}
|
|
114
|
-
buildTypes {
|
|
115
|
-
release {
|
|
116
|
-
}
|
|
117
|
-
}
|
|
87
|
+
buildTypes { release { } }
|
|
118
88
|
}`;
|
|
119
89
|
}
|
|
120
90
|
if (p.includes("gradle.properties")) {
|
|
121
91
|
return "org.gradle.jvmargs=-Xmx2048m";
|
|
122
92
|
}
|
|
123
|
-
if (p.includes("styles.xml")) {
|
|
124
|
-
return `<?xml version="1.0" encoding="utf-8"?>
|
|
125
|
-
<resources>
|
|
126
|
-
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
|
127
|
-
<item name="colorPrimary">@color/colorPrimary</item>
|
|
128
|
-
</style>
|
|
129
|
-
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
|
130
|
-
<item name="windowActionBar">false</item>
|
|
131
|
-
<item name="windowNoTitle">true</item>
|
|
132
|
-
<item name="android:background">@null</item>
|
|
133
|
-
</style>
|
|
134
|
-
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
|
135
|
-
<item name="android:background">@drawable/splash</item>
|
|
136
|
-
</style>
|
|
137
|
-
</resources>`;
|
|
138
|
-
}
|
|
139
93
|
return "";
|
|
140
|
-
});
|
|
94
|
+
}) as never);
|
|
141
95
|
|
|
142
96
|
mockFsxGlob.mockResolvedValue(["C:/Program Files/Amazon Corretto/jdk21.0.1"]);
|
|
143
|
-
|
|
144
97
|
process.env["ANDROID_HOME"] = "C:/Android/Sdk";
|
|
145
|
-
|
|
146
|
-
execaCalls.length = 0;
|
|
147
|
-
mockFsWriteFile.mockReset();
|
|
148
|
-
mockFsWriteFile.mockResolvedValue(undefined);
|
|
149
98
|
}
|
|
150
99
|
|
|
151
|
-
//#endregion
|
|
152
|
-
|
|
153
100
|
let savedEnv: Record<string, string | undefined>;
|
|
154
101
|
beforeEach(() => {
|
|
155
102
|
savedEnv = { ...process.env };
|
|
@@ -204,199 +151,15 @@ describe("Capacitor 설정 검증", () => {
|
|
|
204
151
|
});
|
|
205
152
|
});
|
|
206
153
|
|
|
207
|
-
describe("Capacitor 초기화", () => {
|
|
208
|
-
beforeEach(() => {
|
|
209
|
-
vi.clearAllMocks();
|
|
210
|
-
setupDefaultMocks();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("최초 초기화: pnpm install, cap init, cap add android를 실행한다", async () => {
|
|
214
|
-
let androidAdded = false;
|
|
215
|
-
mockFsxExists.mockImplementation((p: string) => {
|
|
216
|
-
const n = p.replace(/\\/g, "/");
|
|
217
|
-
if (n.includes(".capacitor.lock")) return false;
|
|
218
|
-
if (n.includes("node_modules")) return false;
|
|
219
|
-
if (n.includes("capacitor.config.ts")) return false;
|
|
220
|
-
// _addPlatforms에서 cap add 후 _configureAndroid에서 확인할 때는 true
|
|
221
|
-
if (n.endsWith(".capacitor/android")) {
|
|
222
|
-
if (!androidAdded) { androidAdded = true; return false; }
|
|
223
|
-
return true;
|
|
224
|
-
}
|
|
225
|
-
return true;
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
229
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
230
|
-
appId: "com.test.app",
|
|
231
|
-
appName: "Test App",
|
|
232
|
-
platform: { android: {} },
|
|
233
|
-
});
|
|
234
|
-
await cap.initialize();
|
|
235
|
-
|
|
236
|
-
expect(execaCalls.some((c) => c.command === "pnpm" && c.args.includes("install"))).toBe(true);
|
|
237
|
-
expect(
|
|
238
|
-
execaCalls.some((c) => c.command === "pnpm" && c.args.includes("cap") && c.args.includes("init")),
|
|
239
|
-
).toBe(true);
|
|
240
|
-
expect(
|
|
241
|
-
execaCalls.some(
|
|
242
|
-
(c) => c.command === "pnpm" && c.args.includes("cap") && c.args.includes("add"),
|
|
243
|
-
),
|
|
244
|
-
).toBe(true);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("재초기화: 설정 미변경 시 pnpm install을 건너뛴다", async () => {
|
|
248
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
249
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
250
|
-
appId: "com.test.app",
|
|
251
|
-
appName: "Test App",
|
|
252
|
-
platform: { android: {} },
|
|
253
|
-
});
|
|
254
|
-
await cap.initialize();
|
|
255
|
-
|
|
256
|
-
expect(execaCalls.some((c) => c.command === "pnpm" && c.args.includes("install"))).toBe(false);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it("플러그인 추가: package.json에 플러그인을 추가하고 pnpm install을 실행한다", async () => {
|
|
260
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
261
|
-
|
|
262
|
-
// 클라이언트 패키지의 deps에 플러그인 포함
|
|
263
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
264
|
-
const normalized = p.replace(/\\/g, "/");
|
|
265
|
-
if (normalized.includes(".capacitor/package.json")) {
|
|
266
|
-
return {
|
|
267
|
-
name: "com.test.app",
|
|
268
|
-
version: "1.0.0",
|
|
269
|
-
dependencies: {
|
|
270
|
-
"@capacitor/core": "^7.0.0",
|
|
271
|
-
"@capacitor/app": "^7.0.0",
|
|
272
|
-
"@capacitor/android": "^7.0.0",
|
|
273
|
-
},
|
|
274
|
-
devDependencies: {
|
|
275
|
-
"@capacitor/cli": "^7.0.0",
|
|
276
|
-
"@capacitor/assets": "^3.0.0",
|
|
277
|
-
},
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
return {
|
|
281
|
-
name: "test-pkg",
|
|
282
|
-
version: "1.0.0",
|
|
283
|
-
dependencies: { "@capacitor/camera": "^7.0.0" },
|
|
284
|
-
};
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
288
|
-
appId: "com.test.app",
|
|
289
|
-
appName: "Test App",
|
|
290
|
-
plugins: { "@capacitor/camera": true },
|
|
291
|
-
platform: { android: {} },
|
|
292
|
-
});
|
|
293
|
-
await cap.initialize();
|
|
294
|
-
|
|
295
|
-
// writeJson에 plugin이 포함되었는지 확인
|
|
296
|
-
const writeJsonCalls = mockFsxWriteJson.mock.calls;
|
|
297
|
-
const capPkgWrite = writeJsonCalls.find(
|
|
298
|
-
(call) =>
|
|
299
|
-
typeof call[0] === "string" && call[0].includes("package.json"),
|
|
300
|
-
);
|
|
301
|
-
expect(capPkgWrite).toBeDefined();
|
|
302
|
-
const writtenConfig = capPkgWrite![1] as Record<string, unknown>;
|
|
303
|
-
const deps = writtenConfig["dependencies"] as Record<string, string>;
|
|
304
|
-
expect(deps["@capacitor/camera"]).toBe("^7.0.0");
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it("플러그인 제거: package.json에서 이전 플러그인을 삭제한다", async () => {
|
|
308
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
309
|
-
|
|
310
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
311
|
-
const normalized = p.replace(/\\/g, "/");
|
|
312
|
-
if (normalized.includes(".capacitor/package.json")) {
|
|
313
|
-
return {
|
|
314
|
-
name: "com.test.app",
|
|
315
|
-
version: "1.0.0",
|
|
316
|
-
dependencies: {
|
|
317
|
-
"@capacitor/core": "^7.0.0",
|
|
318
|
-
"@capacitor/app": "^7.0.0",
|
|
319
|
-
"@capacitor/android": "^7.0.0",
|
|
320
|
-
"@capacitor/camera": "^7.0.0",
|
|
321
|
-
},
|
|
322
|
-
devDependencies: {
|
|
323
|
-
"@capacitor/cli": "^7.0.0",
|
|
324
|
-
"@capacitor/assets": "^3.0.0",
|
|
325
|
-
},
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
return { name: "test-pkg", version: "1.0.0" };
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
// plugins가 비어있으면 camera가 제거되어야 함
|
|
332
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
333
|
-
appId: "com.test.app",
|
|
334
|
-
appName: "Test App",
|
|
335
|
-
platform: { android: {} },
|
|
336
|
-
});
|
|
337
|
-
await cap.initialize();
|
|
338
|
-
|
|
339
|
-
const writeJsonCalls = mockFsxWriteJson.mock.calls;
|
|
340
|
-
const capPkgWrite = writeJsonCalls.find(
|
|
341
|
-
(call) =>
|
|
342
|
-
typeof call[0] === "string" && call[0].includes("package.json"),
|
|
343
|
-
);
|
|
344
|
-
expect(capPkgWrite).toBeDefined();
|
|
345
|
-
const writtenConfig = capPkgWrite![1] as Record<string, unknown>;
|
|
346
|
-
const deps = writtenConfig["dependencies"] as Record<string, string>;
|
|
347
|
-
expect(deps["@capacitor/camera"]).toBeUndefined();
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
|
|
351
154
|
describe("Android 개발 도구 감지", () => {
|
|
352
155
|
beforeEach(() => {
|
|
353
156
|
vi.clearAllMocks();
|
|
354
157
|
setupDefaultMocks();
|
|
355
158
|
});
|
|
356
159
|
|
|
357
|
-
it("Java 21이 표준 경로에 설치되면 gradle.properties에 설정한다", async () => {
|
|
358
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
359
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
360
|
-
appId: "com.test.app",
|
|
361
|
-
appName: "Test App",
|
|
362
|
-
platform: { android: {} },
|
|
363
|
-
});
|
|
364
|
-
await cap.initialize();
|
|
365
|
-
|
|
366
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
367
|
-
const gradleWrite = writeCalls.find(
|
|
368
|
-
(call) =>
|
|
369
|
-
typeof call[0] === "string" &&
|
|
370
|
-
call[0].includes("gradle.properties") &&
|
|
371
|
-
typeof call[1] === "string" &&
|
|
372
|
-
call[1].includes("org.gradle.java.home"),
|
|
373
|
-
);
|
|
374
|
-
expect(gradleWrite).toBeDefined();
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it("ANDROID_HOME 환경변수로 SDK를 감지한다", async () => {
|
|
378
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
379
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
380
|
-
appId: "com.test.app",
|
|
381
|
-
appName: "Test App",
|
|
382
|
-
platform: { android: {} },
|
|
383
|
-
});
|
|
384
|
-
await cap.initialize();
|
|
385
|
-
|
|
386
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
387
|
-
const sdkWrite = writeCalls.find(
|
|
388
|
-
(call) =>
|
|
389
|
-
typeof call[0] === "string" &&
|
|
390
|
-
call[0].includes("local.properties") &&
|
|
391
|
-
typeof call[1] === "string" &&
|
|
392
|
-
call[1].includes("sdk.dir"),
|
|
393
|
-
);
|
|
394
|
-
expect(sdkWrite).toBeDefined();
|
|
395
|
-
});
|
|
396
|
-
|
|
397
160
|
it("Android SDK 미설치 시 에러가 발생한다", async () => {
|
|
398
161
|
delete process.env["ANDROID_HOME"];
|
|
399
|
-
mockFsxExists.mockImplementation((p: string) => {
|
|
162
|
+
mockFsxExists.mockImplementation(((p: string) => {
|
|
400
163
|
const n = p.replace(/\\/g, "/");
|
|
401
164
|
if (n.includes(".capacitor.lock")) return false;
|
|
402
165
|
if (n.includes("Android/Sdk")) return false;
|
|
@@ -404,7 +167,7 @@ describe("Android 개발 도구 감지", () => {
|
|
|
404
167
|
if (n.includes("Program Files/Android")) return false;
|
|
405
168
|
if (n.includes("C:/Android")) return false;
|
|
406
169
|
return true;
|
|
407
|
-
});
|
|
170
|
+
}) as never);
|
|
408
171
|
|
|
409
172
|
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
410
173
|
const cap = await Capacitor.create(PKG_PATH, {
|
|
@@ -416,362 +179,3 @@ describe("Android 개발 도구 감지", () => {
|
|
|
416
179
|
await expect(cap.initialize()).rejects.toThrow("Android SDK");
|
|
417
180
|
});
|
|
418
181
|
});
|
|
419
|
-
|
|
420
|
-
describe("Android 네이티브 설정", () => {
|
|
421
|
-
beforeEach(() => {
|
|
422
|
-
vi.clearAllMocks();
|
|
423
|
-
setupDefaultMocks();
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
describe("AndroidManifest.xml", () => {
|
|
427
|
-
it("permissions를 AndroidManifest.xml에 추가한다", async () => {
|
|
428
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
429
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
430
|
-
appId: "com.test.app",
|
|
431
|
-
appName: "Test App",
|
|
432
|
-
platform: {
|
|
433
|
-
android: {
|
|
434
|
-
permissions: [{ name: "CAMERA" }],
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
});
|
|
438
|
-
await cap.initialize();
|
|
439
|
-
|
|
440
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
441
|
-
const manifestWrite = writeCalls.find(
|
|
442
|
-
(call) =>
|
|
443
|
-
typeof call[0] === "string" &&
|
|
444
|
-
call[0].includes("AndroidManifest.xml") &&
|
|
445
|
-
typeof call[1] === "string" &&
|
|
446
|
-
call[1].includes("android.permission.CAMERA"),
|
|
447
|
-
);
|
|
448
|
-
expect(manifestWrite).toBeDefined();
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
it("usesCleartextTraffic을 자동 추가한다", async () => {
|
|
452
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
453
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
454
|
-
appId: "com.test.app",
|
|
455
|
-
appName: "Test App",
|
|
456
|
-
platform: { android: {} },
|
|
457
|
-
});
|
|
458
|
-
await cap.initialize();
|
|
459
|
-
|
|
460
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
461
|
-
const manifestWrite = writeCalls.find(
|
|
462
|
-
(call) =>
|
|
463
|
-
typeof call[0] === "string" &&
|
|
464
|
-
call[0].includes("AndroidManifest.xml") &&
|
|
465
|
-
typeof call[1] === "string" &&
|
|
466
|
-
call[1].includes("usesCleartextTraffic"),
|
|
467
|
-
);
|
|
468
|
-
expect(manifestWrite).toBeDefined();
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
it("intentFilters를 MainActivity에 추가한다", async () => {
|
|
472
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
473
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
474
|
-
appId: "com.test.app",
|
|
475
|
-
appName: "Test App",
|
|
476
|
-
platform: {
|
|
477
|
-
android: {
|
|
478
|
-
intentFilters: [{ action: "android.intent.action.VIEW" }],
|
|
479
|
-
},
|
|
480
|
-
},
|
|
481
|
-
});
|
|
482
|
-
await cap.initialize();
|
|
483
|
-
|
|
484
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
485
|
-
const manifestWrite = writeCalls.find(
|
|
486
|
-
(call) =>
|
|
487
|
-
typeof call[0] === "string" &&
|
|
488
|
-
call[0].includes("AndroidManifest.xml") &&
|
|
489
|
-
typeof call[1] === "string" &&
|
|
490
|
-
call[1].includes("android.intent.action.VIEW"),
|
|
491
|
-
);
|
|
492
|
-
expect(manifestWrite).toBeDefined();
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
it("application 태그에 커스텀 속성을 추가한다", async () => {
|
|
496
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
497
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
498
|
-
appId: "com.test.app",
|
|
499
|
-
appName: "Test App",
|
|
500
|
-
platform: {
|
|
501
|
-
android: { config: { label: "Custom Label" } },
|
|
502
|
-
},
|
|
503
|
-
});
|
|
504
|
-
await cap.initialize();
|
|
505
|
-
|
|
506
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
507
|
-
const manifestWrite = writeCalls.find(
|
|
508
|
-
(call) =>
|
|
509
|
-
typeof call[0] === "string" &&
|
|
510
|
-
call[0].includes("AndroidManifest.xml") &&
|
|
511
|
-
typeof call[1] === "string" &&
|
|
512
|
-
call[1].includes('android:label="Custom Label"'),
|
|
513
|
-
);
|
|
514
|
-
expect(manifestWrite).toBeDefined();
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
describe("build.gradle", () => {
|
|
519
|
-
it("sdkVersion을 build.gradle에 설정한다", async () => {
|
|
520
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
521
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
522
|
-
appId: "com.test.app",
|
|
523
|
-
appName: "Test App",
|
|
524
|
-
platform: { android: { sdkVersion: 33 } },
|
|
525
|
-
});
|
|
526
|
-
await cap.initialize();
|
|
527
|
-
|
|
528
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
529
|
-
const gradleWrite = writeCalls.find(
|
|
530
|
-
(call) =>
|
|
531
|
-
typeof call[0] === "string" &&
|
|
532
|
-
call[0].includes("build.gradle") &&
|
|
533
|
-
typeof call[1] === "string" &&
|
|
534
|
-
call[1].includes("minSdkVersion 33"),
|
|
535
|
-
);
|
|
536
|
-
expect(gradleWrite).toBeDefined();
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
it("versionCode를 package.json version으로 계산한다", async () => {
|
|
540
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
541
|
-
const normalized = p.replace(/\\/g, "/");
|
|
542
|
-
if (normalized.includes(".capacitor/package.json")) {
|
|
543
|
-
return {
|
|
544
|
-
name: "com.test.app",
|
|
545
|
-
version: "1.2.3",
|
|
546
|
-
dependencies: { "@capacitor/core": "^7.0.0", "@capacitor/app": "^7.0.0", "@capacitor/android": "^7.0.0" },
|
|
547
|
-
devDependencies: { "@capacitor/cli": "^7.0.0", "@capacitor/assets": "^3.0.0" },
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
return { name: "test-pkg", version: "1.2.3" };
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
554
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
555
|
-
appId: "com.test.app",
|
|
556
|
-
appName: "Test App",
|
|
557
|
-
platform: { android: {} },
|
|
558
|
-
});
|
|
559
|
-
await cap.initialize();
|
|
560
|
-
|
|
561
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
562
|
-
const gradleWrite = writeCalls.find(
|
|
563
|
-
(call) =>
|
|
564
|
-
typeof call[0] === "string" &&
|
|
565
|
-
call[0].includes("build.gradle") &&
|
|
566
|
-
typeof call[1] === "string" &&
|
|
567
|
-
call[1].includes("versionCode 1002003"),
|
|
568
|
-
);
|
|
569
|
-
expect(gradleWrite).toBeDefined();
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
describe("styles.xml", () => {
|
|
574
|
-
it("styles.xml의 Theme.SplashScreen parent를 변경하고 android:background를 android:windowBackground로 변경한다", async () => {
|
|
575
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
576
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
577
|
-
appId: "com.test.app",
|
|
578
|
-
appName: "Test App",
|
|
579
|
-
platform: { android: {} },
|
|
580
|
-
});
|
|
581
|
-
await cap.initialize();
|
|
582
|
-
|
|
583
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
584
|
-
const stylesWrite = writeCalls.find(
|
|
585
|
-
(call) =>
|
|
586
|
-
typeof call[0] === "string" &&
|
|
587
|
-
call[0].includes("styles.xml") &&
|
|
588
|
-
typeof call[1] === "string" &&
|
|
589
|
-
call[1].includes('parent="Theme.AppCompat.DayNight.NoActionBar"'),
|
|
590
|
-
);
|
|
591
|
-
expect(stylesWrite).toBeDefined();
|
|
592
|
-
const content = stylesWrite![1] as string;
|
|
593
|
-
// @drawable/splash는 유지
|
|
594
|
-
expect(content).toContain("@drawable/splash");
|
|
595
|
-
// Theme.SplashScreen은 제거됨
|
|
596
|
-
expect(content).not.toContain('parent="Theme.SplashScreen"');
|
|
597
|
-
// android:background → android:windowBackground로 변경됨
|
|
598
|
-
expect(content).toContain('"android:windowBackground">@drawable/splash');
|
|
599
|
-
expect(content).not.toContain('"android:background">@drawable/splash');
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
it("이미 변경된 styles.xml은 재변경하지 않는다", async () => {
|
|
603
|
-
mockFsxRead.mockImplementation((p: string) => {
|
|
604
|
-
if (p.includes("styles.xml")) {
|
|
605
|
-
return `<?xml version="1.0" encoding="utf-8"?>
|
|
606
|
-
<resources>
|
|
607
|
-
<style name="AppTheme.NoActionBarLaunch" parent="Theme.AppCompat.DayNight.NoActionBar">
|
|
608
|
-
<item name="android:windowBackground">@drawable/splash</item>
|
|
609
|
-
</style>
|
|
610
|
-
</resources>`;
|
|
611
|
-
}
|
|
612
|
-
if (p.includes("AndroidManifest.xml")) {
|
|
613
|
-
return '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n<application>\n<activity android:name=".MainActivity">\n</activity>\n</application>\n</manifest>';
|
|
614
|
-
}
|
|
615
|
-
if (p.includes("build.gradle")) {
|
|
616
|
-
return `android {
|
|
617
|
-
defaultConfig {
|
|
618
|
-
versionCode 1
|
|
619
|
-
versionName "1.0"
|
|
620
|
-
minSdkVersion rootProject.ext.minSdkVersion
|
|
621
|
-
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
622
|
-
}
|
|
623
|
-
buildTypes { release { } }
|
|
624
|
-
}`;
|
|
625
|
-
}
|
|
626
|
-
if (p.includes("gradle.properties")) {
|
|
627
|
-
return "org.gradle.jvmargs=-Xmx2048m";
|
|
628
|
-
}
|
|
629
|
-
return "";
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
633
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
634
|
-
appId: "com.test.app",
|
|
635
|
-
appName: "Test App",
|
|
636
|
-
platform: { android: {} },
|
|
637
|
-
});
|
|
638
|
-
await cap.initialize();
|
|
639
|
-
|
|
640
|
-
const writeCalls = mockFsxWrite.mock.calls;
|
|
641
|
-
const stylesWrite = writeCalls.find(
|
|
642
|
-
(call) =>
|
|
643
|
-
typeof call[0] === "string" &&
|
|
644
|
-
call[0].includes("styles.xml"),
|
|
645
|
-
);
|
|
646
|
-
expect(stylesWrite).toBeUndefined();
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
it("styles.xml이 없으면 warn을 출력한다", async () => {
|
|
650
|
-
mockFsxExists.mockImplementation((p: string) => {
|
|
651
|
-
const n = p.replace(/\\/g, "/");
|
|
652
|
-
if (n.includes(".capacitor.lock")) return false;
|
|
653
|
-
if (n.includes("styles.xml")) return false;
|
|
654
|
-
return true;
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
658
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
659
|
-
appId: "com.test.app",
|
|
660
|
-
appName: "Test App",
|
|
661
|
-
platform: { android: {} },
|
|
662
|
-
});
|
|
663
|
-
await cap.initialize();
|
|
664
|
-
|
|
665
|
-
expect(mockLoggerWarn).toHaveBeenCalledWith(
|
|
666
|
-
expect.stringContaining("styles.xml"),
|
|
667
|
-
);
|
|
668
|
-
});
|
|
669
|
-
});
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
describe("플러그인 의존성 해석", () => {
|
|
673
|
-
beforeEach(() => {
|
|
674
|
-
vi.clearAllMocks();
|
|
675
|
-
setupDefaultMocks();
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
it("일반 npm 플러그인: root package.json에서 버전을 resolve한다", async () => {
|
|
679
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
680
|
-
const normalized = p.replace(/\\/g, "/");
|
|
681
|
-
if (normalized.includes(".capacitor/package.json")) {
|
|
682
|
-
return {
|
|
683
|
-
name: "com.test.app",
|
|
684
|
-
version: "1.0.0",
|
|
685
|
-
dependencies: { "@capacitor/core": "^7.0.0", "@capacitor/app": "^7.0.0", "@capacitor/android": "^7.0.0" },
|
|
686
|
-
devDependencies: { "@capacitor/cli": "^7.0.0", "@capacitor/assets": "^3.0.0" },
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
return {
|
|
690
|
-
name: "test-pkg",
|
|
691
|
-
version: "1.0.0",
|
|
692
|
-
dependencies: { "@capacitor/camera": "^7.1.0" },
|
|
693
|
-
};
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
697
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
698
|
-
appId: "com.test.app",
|
|
699
|
-
appName: "Test App",
|
|
700
|
-
plugins: { "@capacitor/camera": true },
|
|
701
|
-
platform: { android: {} },
|
|
702
|
-
});
|
|
703
|
-
await cap.initialize();
|
|
704
|
-
|
|
705
|
-
const writeJsonCalls = mockFsxWriteJson.mock.calls;
|
|
706
|
-
const capPkgWrite = writeJsonCalls.find(
|
|
707
|
-
(call) => typeof call[0] === "string" && call[0].includes("package.json"),
|
|
708
|
-
);
|
|
709
|
-
const deps = (capPkgWrite![1] as Record<string, unknown>)["dependencies"] as Record<string, string>;
|
|
710
|
-
expect(deps["@capacitor/camera"]).toBe("^7.1.0");
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
it("exclude 패키지를 .capacitor/package.json에 추가한다", async () => {
|
|
714
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
715
|
-
const normalized = p.replace(/\\/g, "/");
|
|
716
|
-
if (normalized.includes(".capacitor/package.json")) {
|
|
717
|
-
return {
|
|
718
|
-
name: "com.test.app",
|
|
719
|
-
version: "1.0.0",
|
|
720
|
-
dependencies: { "@capacitor/core": "^7.0.0", "@capacitor/app": "^7.0.0", "@capacitor/android": "^7.0.0" },
|
|
721
|
-
devDependencies: { "@capacitor/cli": "^7.0.0", "@capacitor/assets": "^3.0.0" },
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
return {
|
|
725
|
-
name: "test-pkg",
|
|
726
|
-
version: "1.0.0",
|
|
727
|
-
dependencies: { "jeep-sqlite": "^2.0.0" },
|
|
728
|
-
};
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
732
|
-
const cap = await Capacitor.create(
|
|
733
|
-
PKG_PATH,
|
|
734
|
-
{ appId: "com.test.app", appName: "Test App", platform: { android: {} } },
|
|
735
|
-
["jeep-sqlite"],
|
|
736
|
-
);
|
|
737
|
-
await cap.initialize();
|
|
738
|
-
|
|
739
|
-
const writeJsonCalls = mockFsxWriteJson.mock.calls;
|
|
740
|
-
const capPkgWrite = writeJsonCalls.find(
|
|
741
|
-
(call) => typeof call[0] === "string" && call[0].includes("package.json"),
|
|
742
|
-
);
|
|
743
|
-
const deps = (capPkgWrite![1] as Record<string, unknown>)["dependencies"] as Record<string, string>;
|
|
744
|
-
expect(deps["jeep-sqlite"]).toBe("^2.0.0");
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
it("플러그인 버전이 root에 없으면 *를 사용한다", async () => {
|
|
748
|
-
mockFsxReadJson.mockImplementation((p: string) => {
|
|
749
|
-
const normalized = p.replace(/\\/g, "/");
|
|
750
|
-
if (normalized.includes(".capacitor/package.json")) {
|
|
751
|
-
return {
|
|
752
|
-
name: "com.test.app",
|
|
753
|
-
version: "1.0.0",
|
|
754
|
-
dependencies: { "@capacitor/core": "^7.0.0", "@capacitor/app": "^7.0.0", "@capacitor/android": "^7.0.0" },
|
|
755
|
-
devDependencies: { "@capacitor/cli": "^7.0.0", "@capacitor/assets": "^3.0.0" },
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
return { name: "test-pkg", version: "1.0.0" };
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
762
|
-
const cap = await Capacitor.create(PKG_PATH, {
|
|
763
|
-
appId: "com.test.app",
|
|
764
|
-
appName: "Test App",
|
|
765
|
-
plugins: { "unknown-plugin": true },
|
|
766
|
-
platform: { android: {} },
|
|
767
|
-
});
|
|
768
|
-
await cap.initialize();
|
|
769
|
-
|
|
770
|
-
const writeJsonCalls = mockFsxWriteJson.mock.calls;
|
|
771
|
-
const capPkgWrite = writeJsonCalls.find(
|
|
772
|
-
(call) => typeof call[0] === "string" && call[0].includes("package.json"),
|
|
773
|
-
);
|
|
774
|
-
const deps = (capPkgWrite![1] as Record<string, unknown>)["dependencies"] as Record<string, string>;
|
|
775
|
-
expect(deps["unknown-plugin"]).toBe("*");
|
|
776
|
-
});
|
|
777
|
-
});
|