@typokit/cli 0.1.4
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/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +13 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/build.d.ts +42 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +302 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/dev.d.ts +106 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +536 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/generate.d.ts +65 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +430 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/inspect.d.ts +26 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +579 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/migrate.d.ts +70 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +570 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/scaffold.d.ts +70 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/scaffold.js +483 -0
- package/dist/commands/scaffold.js.map +1 -0
- package/dist/commands/test.d.ts +56 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +248 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +69 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +245 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +33 -0
- package/dist/logger.js.map +1 -0
- package/package.json +33 -0
- package/src/bin.ts +22 -0
- package/src/commands/build.ts +433 -0
- package/src/commands/dev.ts +822 -0
- package/src/commands/generate.ts +640 -0
- package/src/commands/inspect.ts +885 -0
- package/src/commands/migrate.ts +800 -0
- package/src/commands/scaffold.ts +627 -0
- package/src/commands/test.ts +353 -0
- package/src/config.ts +93 -0
- package/src/dev.test.ts +285 -0
- package/src/env.d.ts +86 -0
- package/src/generate.test.ts +304 -0
- package/src/index.test.ts +217 -0
- package/src/index.ts +397 -0
- package/src/inspect.test.ts +411 -0
- package/src/logger.ts +49 -0
- package/src/migrate.test.ts +205 -0
- package/src/scaffold.test.ts +256 -0
- package/src/test.test.ts +230 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// Tests for @typokit/cli scaffold commands
|
|
2
|
+
import { describe, it, expect } from "@rstest/core";
|
|
3
|
+
import {
|
|
4
|
+
toPascalCase,
|
|
5
|
+
toCamelCase,
|
|
6
|
+
generateRouteContracts,
|
|
7
|
+
generateRouteHandlers,
|
|
8
|
+
generateRouteMiddleware,
|
|
9
|
+
generateService,
|
|
10
|
+
generatePackageJson,
|
|
11
|
+
generateTsconfig,
|
|
12
|
+
generateAppTs,
|
|
13
|
+
generateTypesTs,
|
|
14
|
+
executeScaffold,
|
|
15
|
+
} from "./commands/scaffold.js";
|
|
16
|
+
import type {
|
|
17
|
+
InitOptions,
|
|
18
|
+
ScaffoldCommandOptions,
|
|
19
|
+
} from "./commands/scaffold.js";
|
|
20
|
+
import { createLogger } from "./logger.js";
|
|
21
|
+
|
|
22
|
+
const logger = createLogger({ verbose: false });
|
|
23
|
+
|
|
24
|
+
describe("scaffold — utility functions", () => {
|
|
25
|
+
it("toPascalCase converts kebab-case", () => {
|
|
26
|
+
expect(toPascalCase("user-profile")).toBe("UserProfile");
|
|
27
|
+
expect(toPascalCase("auth")).toBe("Auth");
|
|
28
|
+
expect(toPascalCase("my-cool-service")).toBe("MyCoolService");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("toPascalCase converts snake_case", () => {
|
|
32
|
+
expect(toPascalCase("user_profile")).toBe("UserProfile");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("toCamelCase converts names", () => {
|
|
36
|
+
expect(toCamelCase("user-profile")).toBe("userProfile");
|
|
37
|
+
expect(toCamelCase("auth")).toBe("auth");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("scaffold — template generation", () => {
|
|
42
|
+
it("generateRouteContracts produces valid template", () => {
|
|
43
|
+
const result = generateRouteContracts("users");
|
|
44
|
+
expect(result).toContain("import type { RouteContract }");
|
|
45
|
+
expect(result).toContain("export interface Users {");
|
|
46
|
+
expect(result).toContain("export interface CreateUsersBody {");
|
|
47
|
+
expect(result).toContain("export interface UpdateUsersBody {");
|
|
48
|
+
expect(result).toContain("export interface UsersRoutes {");
|
|
49
|
+
expect(result).toContain('"GET /users"');
|
|
50
|
+
expect(result).toContain('"POST /users"');
|
|
51
|
+
expect(result).toContain('"DELETE /users/:id"');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("generateRouteHandlers produces valid template", () => {
|
|
55
|
+
const result = generateRouteHandlers("posts");
|
|
56
|
+
expect(result).toContain("import type { RouteHandler, RequestContext }");
|
|
57
|
+
expect(result).toContain("export const listPosts");
|
|
58
|
+
expect(result).toContain("export const getPosts");
|
|
59
|
+
expect(result).toContain("export const createPosts");
|
|
60
|
+
expect(result).toContain("export const updatePosts");
|
|
61
|
+
expect(result).toContain("export const deletePosts");
|
|
62
|
+
expect(result).toContain("export default {");
|
|
63
|
+
expect(result).toContain('"GET /posts"');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("generateRouteMiddleware produces valid template", () => {
|
|
67
|
+
const result = generateRouteMiddleware("users");
|
|
68
|
+
expect(result).toContain("import type { MiddlewareFn }");
|
|
69
|
+
expect(result).toContain("export const usersMiddleware");
|
|
70
|
+
expect(result).toContain("return next(ctx)");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("generateService produces valid template", () => {
|
|
74
|
+
const result = generateService("auth");
|
|
75
|
+
expect(result).toContain("export class AuthService {");
|
|
76
|
+
expect(result).toContain("export const authService = new AuthService()");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("generateService handles kebab-case names", () => {
|
|
80
|
+
const result = generateService("user-profile");
|
|
81
|
+
expect(result).toContain("export class UserProfileService {");
|
|
82
|
+
expect(result).toContain(
|
|
83
|
+
"export const userProfileService = new UserProfileService()",
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("generatePackageJson produces valid JSON", () => {
|
|
88
|
+
const options: InitOptions = {
|
|
89
|
+
name: "my-app",
|
|
90
|
+
server: "native",
|
|
91
|
+
db: "none",
|
|
92
|
+
};
|
|
93
|
+
const result = generatePackageJson(options);
|
|
94
|
+
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
95
|
+
expect(parsed["name"]).toBe("my-app");
|
|
96
|
+
expect(parsed["type"]).toBe("module");
|
|
97
|
+
|
|
98
|
+
const deps = parsed["dependencies"] as Record<string, string>;
|
|
99
|
+
expect(deps["@typokit/core"]).toBe("^0.1.0");
|
|
100
|
+
expect(deps["@typokit/server-native"]).toBe("^0.1.0");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("generatePackageJson includes server adapter dependency", () => {
|
|
104
|
+
const options: InitOptions = {
|
|
105
|
+
name: "my-app",
|
|
106
|
+
server: "fastify",
|
|
107
|
+
db: "drizzle",
|
|
108
|
+
};
|
|
109
|
+
const result = generatePackageJson(options);
|
|
110
|
+
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
111
|
+
const deps = parsed["dependencies"] as Record<string, string>;
|
|
112
|
+
expect(deps["@typokit/server-fastify"]).toBe("^0.1.0");
|
|
113
|
+
expect(deps["@typokit/db-drizzle"]).toBe("^0.1.0");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("generateTsconfig produces valid JSON", () => {
|
|
117
|
+
const result = generateTsconfig();
|
|
118
|
+
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
119
|
+
const co = parsed["compilerOptions"] as Record<string, unknown>;
|
|
120
|
+
expect(co["target"]).toBe("ES2022");
|
|
121
|
+
expect(co["module"]).toBe("NodeNext");
|
|
122
|
+
expect(co["strict"]).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("generateAppTs uses correct server adapter", () => {
|
|
126
|
+
const native = generateAppTs({ name: "app", server: "native", db: "none" });
|
|
127
|
+
expect(native).toContain(
|
|
128
|
+
'import { nativeServer } from "@typokit/server-native"',
|
|
129
|
+
);
|
|
130
|
+
expect(native).toContain("nativeServer()");
|
|
131
|
+
|
|
132
|
+
const fastify = generateAppTs({
|
|
133
|
+
name: "app",
|
|
134
|
+
server: "fastify",
|
|
135
|
+
db: "none",
|
|
136
|
+
});
|
|
137
|
+
expect(fastify).toContain(
|
|
138
|
+
'import { fastifyServer } from "@typokit/server-fastify"',
|
|
139
|
+
);
|
|
140
|
+
expect(fastify).toContain("fastifyServer()");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("generateTypesTs includes example type with JSDoc tags", () => {
|
|
144
|
+
const result = generateTypesTs();
|
|
145
|
+
expect(result).toContain("/** @table */");
|
|
146
|
+
expect(result).toContain("export interface Example {");
|
|
147
|
+
expect(result).toContain("/** @id @generated */");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("scaffold — executeScaffold dispatcher", () => {
|
|
152
|
+
it("returns error for unknown subcommand", async () => {
|
|
153
|
+
const opts: ScaffoldCommandOptions = {
|
|
154
|
+
rootDir: "/tmp/test",
|
|
155
|
+
logger,
|
|
156
|
+
subcommand: "unknown",
|
|
157
|
+
positional: [],
|
|
158
|
+
flags: {},
|
|
159
|
+
verbose: false,
|
|
160
|
+
};
|
|
161
|
+
const result = await executeScaffold(opts);
|
|
162
|
+
expect(result.success).toBe(false);
|
|
163
|
+
expect(result.errors[0]).toContain("Unknown scaffold subcommand");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("returns error for route without name", async () => {
|
|
167
|
+
const opts: ScaffoldCommandOptions = {
|
|
168
|
+
rootDir: "/tmp/test",
|
|
169
|
+
logger,
|
|
170
|
+
subcommand: "route",
|
|
171
|
+
positional: [],
|
|
172
|
+
flags: {},
|
|
173
|
+
verbose: false,
|
|
174
|
+
};
|
|
175
|
+
const result = await executeScaffold(opts);
|
|
176
|
+
expect(result.success).toBe(false);
|
|
177
|
+
expect(result.errors[0]).toContain("Route name is required");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("returns error for service without name", async () => {
|
|
181
|
+
const opts: ScaffoldCommandOptions = {
|
|
182
|
+
rootDir: "/tmp/test",
|
|
183
|
+
logger,
|
|
184
|
+
subcommand: "service",
|
|
185
|
+
positional: [],
|
|
186
|
+
flags: {},
|
|
187
|
+
verbose: false,
|
|
188
|
+
};
|
|
189
|
+
const result = await executeScaffold(opts);
|
|
190
|
+
expect(result.success).toBe(false);
|
|
191
|
+
expect(result.errors[0]).toContain("Service name is required");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("init uses default name when none provided", async () => {
|
|
195
|
+
const opts: ScaffoldCommandOptions = {
|
|
196
|
+
rootDir: "/tmp/nonexistent-scaffold-test-" + Date.now(),
|
|
197
|
+
logger,
|
|
198
|
+
subcommand: "init",
|
|
199
|
+
positional: [],
|
|
200
|
+
flags: {},
|
|
201
|
+
verbose: false,
|
|
202
|
+
};
|
|
203
|
+
// This may fail due to permissions, but we check the flow
|
|
204
|
+
const result = await executeScaffold(opts);
|
|
205
|
+
// It either succeeds or fails with a real FS error, not a name error
|
|
206
|
+
if (!result.success) {
|
|
207
|
+
expect(result.errors[0]).not.toContain("name is required");
|
|
208
|
+
}
|
|
209
|
+
// Clean up if succeeded
|
|
210
|
+
if (result.success) {
|
|
211
|
+
const { rmSync } = (await import("fs")) as {
|
|
212
|
+
rmSync: (
|
|
213
|
+
p: string,
|
|
214
|
+
o?: { recursive?: boolean; force?: boolean },
|
|
215
|
+
) => void;
|
|
216
|
+
};
|
|
217
|
+
const { join } = (await import("path")) as {
|
|
218
|
+
join: (...args: string[]) => string;
|
|
219
|
+
};
|
|
220
|
+
try {
|
|
221
|
+
rmSync(join(opts.rootDir, "my-app"), { recursive: true, force: true });
|
|
222
|
+
} catch {
|
|
223
|
+
/* ignore */
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("scaffold — route contracts match arch doc Section 4.4", () => {
|
|
230
|
+
it("creates contracts.ts, handlers.ts, middleware.ts", () => {
|
|
231
|
+
const contracts = generateRouteContracts("users");
|
|
232
|
+
const handlers = generateRouteHandlers("users");
|
|
233
|
+
const middleware = generateRouteMiddleware("users");
|
|
234
|
+
|
|
235
|
+
// All three files are generated
|
|
236
|
+
expect(contracts.length).toBeGreaterThan(0);
|
|
237
|
+
expect(handlers.length).toBeGreaterThan(0);
|
|
238
|
+
expect(middleware.length).toBeGreaterThan(0);
|
|
239
|
+
|
|
240
|
+
// Contracts has CRUD route signatures
|
|
241
|
+
expect(contracts).toContain("GET /users");
|
|
242
|
+
expect(contracts).toContain("POST /users");
|
|
243
|
+
expect(contracts).toContain("PUT /users/:id");
|
|
244
|
+
expect(contracts).toContain("DELETE /users/:id");
|
|
245
|
+
|
|
246
|
+
// Handlers export a default object for registration
|
|
247
|
+
expect(handlers).toContain("export default");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("handler imports match contracts types", () => {
|
|
251
|
+
const handlers = generateRouteHandlers("products");
|
|
252
|
+
expect(handlers).toContain(
|
|
253
|
+
'import type { Products, CreateProductsBody, UpdateProductsBody } from "./contracts.ts"',
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
});
|
package/src/test.test.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Tests for @typokit/cli test commands
|
|
2
|
+
import { describe, it, expect } from "@rstest/core";
|
|
3
|
+
import {
|
|
4
|
+
detectTestRunner,
|
|
5
|
+
buildRunnerCommand,
|
|
6
|
+
schemasChanged,
|
|
7
|
+
executeTest,
|
|
8
|
+
} from "./commands/test.js";
|
|
9
|
+
import { createLogger } from "./logger.js";
|
|
10
|
+
import { loadConfig } from "./config.js";
|
|
11
|
+
|
|
12
|
+
const logger = createLogger({ verbose: false });
|
|
13
|
+
|
|
14
|
+
describe("test — detectTestRunner", () => {
|
|
15
|
+
it("returns vitest as default when no config files exist", async () => {
|
|
16
|
+
// Use a temp dir with no config files
|
|
17
|
+
const runner = await detectTestRunner("/nonexistent/path/for/test");
|
|
18
|
+
expect(runner).toBe("vitest");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("test — buildRunnerCommand", () => {
|
|
23
|
+
it("builds jest command for all tests", () => {
|
|
24
|
+
const { command, args } = buildRunnerCommand("jest", "all", "/root", false);
|
|
25
|
+
expect(command).toBe("jest");
|
|
26
|
+
expect(args).toContain("--passWithNoTests");
|
|
27
|
+
expect(args).not.toContain("--testPathPattern");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("builds jest command for contracts", () => {
|
|
31
|
+
const { command, args } = buildRunnerCommand(
|
|
32
|
+
"jest",
|
|
33
|
+
"contracts",
|
|
34
|
+
"/root",
|
|
35
|
+
false,
|
|
36
|
+
);
|
|
37
|
+
expect(command).toBe("jest");
|
|
38
|
+
expect(args).toContain("--testPathPattern");
|
|
39
|
+
expect(args).toContain("__generated__/.*\\.contract\\.test");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("builds jest command for integration", () => {
|
|
43
|
+
const { command, args } = buildRunnerCommand(
|
|
44
|
+
"jest",
|
|
45
|
+
"integration",
|
|
46
|
+
"/root",
|
|
47
|
+
false,
|
|
48
|
+
);
|
|
49
|
+
expect(command).toBe("jest");
|
|
50
|
+
expect(args).toContain("--testPathPattern");
|
|
51
|
+
expect(args).toContain("integration");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("builds jest verbose command", () => {
|
|
55
|
+
const { command, args } = buildRunnerCommand("jest", "all", "/root", true);
|
|
56
|
+
expect(command).toBe("jest");
|
|
57
|
+
expect(args).toContain("--verbose");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("builds vitest command for all tests", () => {
|
|
61
|
+
const { command, args } = buildRunnerCommand(
|
|
62
|
+
"vitest",
|
|
63
|
+
"all",
|
|
64
|
+
"/root",
|
|
65
|
+
false,
|
|
66
|
+
);
|
|
67
|
+
expect(command).toBe("vitest");
|
|
68
|
+
expect(args).toContain("run");
|
|
69
|
+
expect(args).toContain("--passWithNoTests");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("builds vitest command for contracts", () => {
|
|
73
|
+
const { command, args } = buildRunnerCommand(
|
|
74
|
+
"vitest",
|
|
75
|
+
"contracts",
|
|
76
|
+
"/root",
|
|
77
|
+
false,
|
|
78
|
+
);
|
|
79
|
+
expect(command).toBe("vitest");
|
|
80
|
+
expect(args).toContain("run");
|
|
81
|
+
expect(args).toContain("__generated__/");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("builds vitest command for integration", () => {
|
|
85
|
+
const { command, args } = buildRunnerCommand(
|
|
86
|
+
"vitest",
|
|
87
|
+
"integration",
|
|
88
|
+
"/root",
|
|
89
|
+
false,
|
|
90
|
+
);
|
|
91
|
+
expect(command).toBe("vitest");
|
|
92
|
+
expect(args).toContain("--dir");
|
|
93
|
+
expect(args).toContain("tests/integration");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("builds vitest verbose command", () => {
|
|
97
|
+
const { args } = buildRunnerCommand("vitest", "all", "/root", true);
|
|
98
|
+
expect(args).toContain("--reporter");
|
|
99
|
+
expect(args).toContain("verbose");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("builds rstest command for all tests", () => {
|
|
103
|
+
const { command, args } = buildRunnerCommand(
|
|
104
|
+
"rstest",
|
|
105
|
+
"all",
|
|
106
|
+
"/root",
|
|
107
|
+
false,
|
|
108
|
+
);
|
|
109
|
+
expect(command).toBe("rstest");
|
|
110
|
+
expect(args).toContain("run");
|
|
111
|
+
expect(args).toContain("--passWithNoTests");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("builds rstest command for contracts", () => {
|
|
115
|
+
const { command, args } = buildRunnerCommand(
|
|
116
|
+
"rstest",
|
|
117
|
+
"contracts",
|
|
118
|
+
"/root",
|
|
119
|
+
false,
|
|
120
|
+
);
|
|
121
|
+
expect(command).toBe("rstest");
|
|
122
|
+
expect(args).toContain("--testPathPattern");
|
|
123
|
+
expect(args).toContain("__generated__/.*\\.contract\\.test");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("builds rstest command for integration", () => {
|
|
127
|
+
const { command, args } = buildRunnerCommand(
|
|
128
|
+
"rstest",
|
|
129
|
+
"integration",
|
|
130
|
+
"/root",
|
|
131
|
+
false,
|
|
132
|
+
);
|
|
133
|
+
expect(command).toBe("rstest");
|
|
134
|
+
expect(args).toContain("--testPathPattern");
|
|
135
|
+
expect(args).toContain("integration");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("test — schemasChanged", () => {
|
|
140
|
+
it("returns true when no cache exists", async () => {
|
|
141
|
+
const config = await loadConfig("/nonexistent/path");
|
|
142
|
+
const changed = await schemasChanged("/nonexistent/path", config);
|
|
143
|
+
expect(changed).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("test — executeTest", () => {
|
|
148
|
+
it("rejects unknown runner", async () => {
|
|
149
|
+
const config = await loadConfig("/tmp");
|
|
150
|
+
const result = await executeTest({
|
|
151
|
+
rootDir: "/tmp",
|
|
152
|
+
config,
|
|
153
|
+
logger,
|
|
154
|
+
subcommand: "all",
|
|
155
|
+
flags: { runner: "unknown-runner" },
|
|
156
|
+
verbose: false,
|
|
157
|
+
});
|
|
158
|
+
expect(result.success).toBe(false);
|
|
159
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
160
|
+
expect(result.errors[0]).toContain("Unknown test runner");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("accepts valid --runner flag", async () => {
|
|
164
|
+
const config = await loadConfig("/tmp");
|
|
165
|
+
// This will fail to spawn jest but will detect the runner correctly
|
|
166
|
+
const result = await executeTest({
|
|
167
|
+
rootDir: "/tmp",
|
|
168
|
+
config,
|
|
169
|
+
logger,
|
|
170
|
+
subcommand: "all",
|
|
171
|
+
flags: { runner: "jest" },
|
|
172
|
+
verbose: false,
|
|
173
|
+
});
|
|
174
|
+
// The runner should be set correctly even if spawn fails
|
|
175
|
+
expect(result.runner).toBe("jest");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("maps test:contracts to contracts subcommand", async () => {
|
|
179
|
+
const config = await loadConfig("/tmp");
|
|
180
|
+
const result = await executeTest({
|
|
181
|
+
rootDir: "/tmp",
|
|
182
|
+
config,
|
|
183
|
+
logger,
|
|
184
|
+
subcommand: "contracts",
|
|
185
|
+
flags: { runner: "vitest" },
|
|
186
|
+
verbose: false,
|
|
187
|
+
});
|
|
188
|
+
expect(result.runner).toBe("vitest");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("maps test:integration to integration subcommand", async () => {
|
|
192
|
+
const config = await loadConfig("/tmp");
|
|
193
|
+
const result = await executeTest({
|
|
194
|
+
rootDir: "/tmp",
|
|
195
|
+
config,
|
|
196
|
+
logger,
|
|
197
|
+
subcommand: "integration",
|
|
198
|
+
flags: { runner: "vitest" },
|
|
199
|
+
verbose: false,
|
|
200
|
+
});
|
|
201
|
+
expect(result.runner).toBe("vitest");
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("test — CLI routing", () => {
|
|
206
|
+
it("exports parseArgs that handles test command", async () => {
|
|
207
|
+
const { parseArgs } = await import("./index.js");
|
|
208
|
+
const parsed = parseArgs(["node", "typokit", "test"]);
|
|
209
|
+
expect(parsed.command).toBe("test");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("exports parseArgs that handles test:contracts", async () => {
|
|
213
|
+
const { parseArgs } = await import("./index.js");
|
|
214
|
+
const parsed = parseArgs(["node", "typokit", "test:contracts"]);
|
|
215
|
+
expect(parsed.command).toBe("test:contracts");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("exports parseArgs that handles test:integration", async () => {
|
|
219
|
+
const { parseArgs } = await import("./index.js");
|
|
220
|
+
const parsed = parseArgs(["node", "typokit", "test:integration"]);
|
|
221
|
+
expect(parsed.command).toBe("test:integration");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("exports parseArgs with --runner flag", async () => {
|
|
225
|
+
const { parseArgs } = await import("./index.js");
|
|
226
|
+
const parsed = parseArgs(["node", "typokit", "test", "--runner", "jest"]);
|
|
227
|
+
expect(parsed.command).toBe("test");
|
|
228
|
+
expect(parsed.flags["runner"]).toBe("jest");
|
|
229
|
+
});
|
|
230
|
+
});
|