@isentinel/jest-roblox 0.0.2 → 0.0.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/cli.d.mts +2 -2
- package/dist/cli.mjs +114 -75
- package/dist/{game-output-BDatGA9S.mjs → game-output-DO5fOpW3.mjs} +801 -317
- package/dist/index.d.mts +74 -30
- package/dist/index.mjs +2 -2
- package/dist/{schema-CEsyg-FX.d.mts → schema-ua9HqdbX.d.mts} +26 -4
- package/package.json +36 -6
- package/plugin/JestRobloxRunner.rbxm +0 -0
- package/plugin/out/shared/entry.luau +8 -0
- package/plugin/out/shared/instance-resolver.luau +93 -0
- package/plugin/out/shared/mock/CoreScriptSyncService.luau +19 -0
- package/plugin/out/shared/mock/FileSystemService.luau +30 -0
- package/plugin/out/shared/runner.luau +172 -0
- package/plugin/out/shared/snapshot-patch.luau +99 -0
- package/plugin/plugin.project.json +4 -1
- package/plugin/src/test-in-run-mode.server.luau +2 -15
- package/plugin/sourcemap.json +0 -1
- package/plugin/src/patch/CoreScriptSyncService.luau +0 -19
- package/plugin/src/patch/FileSystemService.luau +0 -27
- package/plugin/src/patch/getDataModelService.luau +0 -19
- package/plugin/src/test-runner.luau +0 -217
package/dist/cli.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as CliOptions } from "./schema-
|
|
1
|
+
import { t as CliOptions } from "./schema-ua9HqdbX.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/cli.d.ts
|
|
4
|
-
declare function main(): Promise<void>;
|
|
5
4
|
declare function parseArgs(args: Array<string>): CliOptions;
|
|
6
5
|
declare function run(args: Array<string>): Promise<number>;
|
|
6
|
+
declare function main(): Promise<void>;
|
|
7
7
|
//#endregion
|
|
8
8
|
export { main, parseArgs, run };
|
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as LuauScriptError, _ as createStudioBackend, a as execute, d as loadConfig, h as isValidBackend, i as runTypecheck, m as VALID_BACKENDS, n as parseGameOutput, r as writeGameOutput, s as writeJsonFile, t as formatGameOutputNotice, y as createOpenCloudBackend } from "./game-output-DO5fOpW3.mjs";
|
|
2
2
|
import * as path$1 from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { parseArgs as parseArgs$1 } from "node:util";
|
|
@@ -6,7 +6,7 @@ import { WebSocketServer } from "ws";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
|
|
8
8
|
//#region package.json
|
|
9
|
-
var version = "0.0.
|
|
9
|
+
var version = "0.0.4";
|
|
10
10
|
|
|
11
11
|
//#endregion
|
|
12
12
|
//#region src/backends/auto.ts
|
|
@@ -38,17 +38,20 @@ async function probeStudioPlugin(port, timeoutMs, createServer = (wsPort) => {
|
|
|
38
38
|
const wss = createServer(port);
|
|
39
39
|
const timer = setTimeout(() => {
|
|
40
40
|
wss.close();
|
|
41
|
-
resolve(false);
|
|
41
|
+
resolve({ detected: false });
|
|
42
42
|
}, timeoutMs);
|
|
43
|
-
wss.on("connection", () => {
|
|
43
|
+
wss.on("connection", (ws) => {
|
|
44
44
|
clearTimeout(timer);
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
resolve({
|
|
46
|
+
detected: true,
|
|
47
|
+
server: wss,
|
|
48
|
+
socket: ws
|
|
49
|
+
});
|
|
47
50
|
});
|
|
48
51
|
wss.on("error", () => {
|
|
49
52
|
clearTimeout(timer);
|
|
50
53
|
wss.close();
|
|
51
|
-
resolve(false);
|
|
54
|
+
resolve({ detected: false });
|
|
52
55
|
});
|
|
53
56
|
});
|
|
54
57
|
}
|
|
@@ -58,10 +61,15 @@ async function resolveBackend(config, probe = probeStudioPlugin) {
|
|
|
58
61
|
timeout: config.timeout
|
|
59
62
|
});
|
|
60
63
|
if (config.backend === "open-cloud") return createOpenCloudBackend();
|
|
61
|
-
|
|
64
|
+
const probeResult = await probe(config.port, 500);
|
|
65
|
+
if (probeResult.detected) {
|
|
62
66
|
process.stderr.write("Backend: studio (plugin detected)\n");
|
|
63
67
|
const studio = createStudioBackend({
|
|
64
68
|
port: config.port,
|
|
69
|
+
preConnected: {
|
|
70
|
+
server: probeResult.server,
|
|
71
|
+
socket: probeResult.socket
|
|
72
|
+
},
|
|
65
73
|
timeout: config.timeout
|
|
66
74
|
});
|
|
67
75
|
if (hasOpenCloudCredentials()) return new StudioWithFallback(studio);
|
|
@@ -77,40 +85,6 @@ function hasOpenCloudCredentials() {
|
|
|
77
85
|
return process.env["ROBLOX_OPEN_CLOUD_API_KEY"] !== void 0 && process.env["ROBLOX_UNIVERSE_ID"] !== void 0 && process.env["ROBLOX_PLACE_ID"] !== void 0;
|
|
78
86
|
}
|
|
79
87
|
|
|
80
|
-
//#endregion
|
|
81
|
-
//#region src/patch.ts
|
|
82
|
-
const PATCH_MARKER = "-- jest-roblox-cli patch: enable snapshot mock override";
|
|
83
|
-
const PATCH_LINES = `${PATCH_MARKER}
|
|
84
|
-
if _G.__mockGetDataModelService then
|
|
85
|
-
\treturn _G.__mockGetDataModelService
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
`;
|
|
89
|
-
const PACKAGE_SUBPATH = path$1.join("node_modules", "@rbxts-js", "roblox-shared", "src", "getDataModelService.lua");
|
|
90
|
-
function patchGetDataModelService(rootDirectory) {
|
|
91
|
-
const filePath = findSourceFile(rootDirectory);
|
|
92
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
93
|
-
if (content.includes(PATCH_MARKER)) return {
|
|
94
|
-
filePath,
|
|
95
|
-
status: "already-patched"
|
|
96
|
-
};
|
|
97
|
-
fs.writeFileSync(filePath, PATCH_LINES + content);
|
|
98
|
-
return {
|
|
99
|
-
filePath,
|
|
100
|
-
status: "patched"
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
function findSourceFile(rootDirectory) {
|
|
104
|
-
let current = path$1.resolve(rootDirectory);
|
|
105
|
-
const { root } = path$1.parse(current);
|
|
106
|
-
while (current !== root) {
|
|
107
|
-
const candidate = path$1.join(current, PACKAGE_SUBPATH);
|
|
108
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
109
|
-
current = path$1.dirname(current);
|
|
110
|
-
}
|
|
111
|
-
throw new Error(`Cannot find @rbxts-js/roblox-shared. Searched from ${rootDirectory} to filesystem root.`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
88
|
//#endregion
|
|
115
89
|
//#region src/utils/glob.ts
|
|
116
90
|
function globSync(pattern, options = {}) {
|
|
@@ -138,6 +112,7 @@ function walkDirectory(directoryPath, baseDirectory) {
|
|
|
138
112
|
|
|
139
113
|
//#endregion
|
|
140
114
|
//#region src/cli.ts
|
|
115
|
+
const TYPE_TEST_PATTERN = /\.(test-d|spec-d)\.ts$/;
|
|
141
116
|
const HELP_TEXT = `
|
|
142
117
|
Usage: jest-roblox [options] [files...]
|
|
143
118
|
|
|
@@ -162,7 +137,11 @@ Options:
|
|
|
162
137
|
--pollInterval <ms> Open Cloud poll interval in ms (default: 500)
|
|
163
138
|
--projects <path...> DataModel paths to search for tests
|
|
164
139
|
--setupFiles <path...> DataModel paths to setup scripts
|
|
140
|
+
--setupFilesAfterEnv <path...> DataModel paths to post-env setup scripts
|
|
165
141
|
--no-show-luau Hide Luau code in failure output
|
|
142
|
+
--typecheck Enable type testing (*.test-d.ts, *.spec-d.ts)
|
|
143
|
+
--typecheckOnly Run only type tests, skip runtime tests
|
|
144
|
+
--typecheckTsconfig <path> tsconfig for type testing
|
|
166
145
|
--help Show this help message
|
|
167
146
|
--version Show version number
|
|
168
147
|
|
|
@@ -178,10 +157,6 @@ Examples:
|
|
|
178
157
|
jest-roblox -t "should spawn" Run tests matching pattern
|
|
179
158
|
jest-roblox --json --outputFile results.json
|
|
180
159
|
`;
|
|
181
|
-
async function main() {
|
|
182
|
-
const exitCode = await run(process.argv.slice(2));
|
|
183
|
-
process.exit(exitCode);
|
|
184
|
-
}
|
|
185
160
|
function parseArgs(args) {
|
|
186
161
|
const { positionals, values } = parseArgs$1({
|
|
187
162
|
allowPositionals: true,
|
|
@@ -214,6 +189,10 @@ function parseArgs(args) {
|
|
|
214
189
|
multiple: true,
|
|
215
190
|
type: "string"
|
|
216
191
|
},
|
|
192
|
+
"setupFilesAfterEnv": {
|
|
193
|
+
multiple: true,
|
|
194
|
+
type: "string"
|
|
195
|
+
},
|
|
217
196
|
"showLuau": { type: "boolean" },
|
|
218
197
|
"silent": { type: "boolean" },
|
|
219
198
|
"sourceMap": { type: "boolean" },
|
|
@@ -223,6 +202,9 @@ function parseArgs(args) {
|
|
|
223
202
|
},
|
|
224
203
|
"testPathPattern": { type: "string" },
|
|
225
204
|
"timeout": { type: "string" },
|
|
205
|
+
"typecheck": { type: "boolean" },
|
|
206
|
+
"typecheckOnly": { type: "boolean" },
|
|
207
|
+
"typecheckTsconfig": { type: "string" },
|
|
226
208
|
"updateSnapshot": {
|
|
227
209
|
short: "u",
|
|
228
210
|
type: "boolean"
|
|
@@ -240,7 +222,7 @@ function parseArgs(args) {
|
|
|
240
222
|
const port = values.port !== void 0 ? Number.parseInt(values.port, 10) : void 0;
|
|
241
223
|
const timeout = values.timeout !== void 0 ? Number.parseInt(values.timeout, 10) : void 0;
|
|
242
224
|
return {
|
|
243
|
-
backend: values.backend,
|
|
225
|
+
backend: validateBackend(values.backend),
|
|
244
226
|
cache: values["no-cache"] === true ? false : values.cache,
|
|
245
227
|
color: values["no-color"] === true ? false : values.color,
|
|
246
228
|
compact: values.compact,
|
|
@@ -256,12 +238,16 @@ function parseArgs(args) {
|
|
|
256
238
|
projects: values.projects,
|
|
257
239
|
rojoProject: values.rojoProject,
|
|
258
240
|
setupFiles: values.setupFiles,
|
|
241
|
+
setupFilesAfterEnv: values.setupFilesAfterEnv,
|
|
259
242
|
showLuau: values["no-show-luau"] === true ? false : values.showLuau,
|
|
260
243
|
silent: values.silent,
|
|
261
244
|
sourceMap: values.sourceMap,
|
|
262
245
|
testNamePattern: values.testNamePattern,
|
|
263
246
|
testPathPattern: values.testPathPattern,
|
|
264
247
|
timeout,
|
|
248
|
+
typecheck: values.typecheckOnly === true ? true : values.typecheck,
|
|
249
|
+
typecheckOnly: values.typecheckOnly,
|
|
250
|
+
typecheckTsconfig: values.typecheckTsconfig,
|
|
265
251
|
updateSnapshot: values.updateSnapshot,
|
|
266
252
|
verbose: values.verbose,
|
|
267
253
|
version: values.version
|
|
@@ -275,6 +261,10 @@ async function run(args) {
|
|
|
275
261
|
return 2;
|
|
276
262
|
}
|
|
277
263
|
}
|
|
264
|
+
async function main() {
|
|
265
|
+
const exitCode = await run(process.argv.slice(2));
|
|
266
|
+
process.exit(exitCode);
|
|
267
|
+
}
|
|
278
268
|
function printError(err) {
|
|
279
269
|
if (err instanceof LuauScriptError) {
|
|
280
270
|
const hint = getLuauErrorHint(err.message);
|
|
@@ -284,8 +274,33 @@ function printError(err) {
|
|
|
284
274
|
} else if (err instanceof Error) console.error(`Error: ${err.message}`);
|
|
285
275
|
else console.error("An unknown error occurred");
|
|
286
276
|
}
|
|
277
|
+
async function executeRuntimeTests(config, testFiles, totalFiles) {
|
|
278
|
+
if (!config.silent && (!config.compact || config.verbose) && !config.json && testFiles.length !== totalFiles) process.stderr.write(`Running ${String(testFiles.length)} of ${String(totalFiles)} test files\n`);
|
|
279
|
+
return execute({
|
|
280
|
+
backend: await resolveBackend(config),
|
|
281
|
+
config,
|
|
282
|
+
testFiles,
|
|
283
|
+
version
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
function writeGameOutputIfConfigured(config, gameOutput, options) {
|
|
287
|
+
if (config.gameOutput === void 0) return;
|
|
288
|
+
const entries = parseGameOutput(gameOutput);
|
|
289
|
+
writeGameOutput(config.gameOutput, entries);
|
|
290
|
+
if (!config.silent && options.hintsShown !== true) {
|
|
291
|
+
const notice = formatGameOutputNotice(config.gameOutput, entries.length);
|
|
292
|
+
if (notice) console.error(notice);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async function outputResults(config, typecheckResult, runtimeResult) {
|
|
296
|
+
const mergedResult = mergeResults(typecheckResult, runtimeResult?.result);
|
|
297
|
+
if (runtimeResult !== void 0 && runtimeResult.output !== "") console.log(runtimeResult.output);
|
|
298
|
+
if (typecheckResult !== void 0 && !config.silent) printTypecheckSummary(typecheckResult);
|
|
299
|
+
if (config.outputFile !== void 0) await writeJsonFile(mergedResult, config.outputFile);
|
|
300
|
+
if (runtimeResult !== void 0) writeGameOutputIfConfigured(config, runtimeResult.gameOutput, { hintsShown: !mergedResult.success });
|
|
301
|
+
return mergedResult.success ? 0 : 1;
|
|
302
|
+
}
|
|
287
303
|
async function runInner(args) {
|
|
288
|
-
if (args[0] === "patch") return runPatch();
|
|
289
304
|
const cli = parseArgs(args);
|
|
290
305
|
if (cli.help === true) {
|
|
291
306
|
console.log(HELP_TEXT);
|
|
@@ -301,32 +316,24 @@ async function runInner(args) {
|
|
|
301
316
|
console.error("No test files found");
|
|
302
317
|
return 2;
|
|
303
318
|
}
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
version
|
|
310
|
-
});
|
|
311
|
-
if (result.output) console.log(result.output);
|
|
312
|
-
if (config.outputFile !== void 0) await writeJsonFile(result.result, config.outputFile);
|
|
313
|
-
writeGameOutputIfConfigured(config, result.gameOutput);
|
|
314
|
-
return result.exitCode;
|
|
315
|
-
}
|
|
316
|
-
function writeGameOutputIfConfigured(config, gameOutput) {
|
|
317
|
-
if (config.gameOutput === void 0) return;
|
|
318
|
-
const entries = parseGameOutput(gameOutput);
|
|
319
|
-
writeGameOutput(config.gameOutput, entries);
|
|
320
|
-
if (!config.silent) {
|
|
321
|
-
const notice = formatGameOutputNotice(config.gameOutput, entries.length);
|
|
322
|
-
if (notice) console.error(notice);
|
|
319
|
+
const typeTestFiles = config.typecheck ? discovery.files.filter((file) => TYPE_TEST_PATTERN.test(file)) : [];
|
|
320
|
+
const runtimeTestFiles = config.typecheckOnly ? [] : discovery.files.filter((file) => !TYPE_TEST_PATTERN.test(file));
|
|
321
|
+
if (typeTestFiles.length === 0 && runtimeTestFiles.length === 0) {
|
|
322
|
+
console.error("No test files found for the selected mode");
|
|
323
|
+
return 2;
|
|
323
324
|
}
|
|
325
|
+
return outputResults(config, typeTestFiles.length > 0 ? runTypecheck({
|
|
326
|
+
files: typeTestFiles,
|
|
327
|
+
rootDir: config.rootDir,
|
|
328
|
+
tsconfig: config.typecheckTsconfig
|
|
329
|
+
}) : void 0, runtimeTestFiles.length > 0 ? await executeRuntimeTests(config, runtimeTestFiles, discovery.totalFiles) : void 0);
|
|
324
330
|
}
|
|
325
331
|
const LUAU_ERROR_HINTS = [
|
|
326
332
|
[/Failed to find Jest instance in ReplicatedStorage/, "Set \"jestPath\" in your config to specify the Jest module location, e.g. \"ReplicatedStorage/rbxts_include/node_modules/@rbxts/jest/src\""],
|
|
327
333
|
[/Failed to find Jest instance at path/, "The configured jestPath does not resolve to a valid instance. Verify the path matches your Rojo project tree."],
|
|
328
334
|
[/Failed to find service/, "The first segment of jestPath must be a valid Roblox service name (e.g. ReplicatedStorage, ServerScriptService)."],
|
|
329
|
-
[/No projects configured/, "Set \"projects\" in jest.config.ts (e.g. [\"ReplicatedStorage/client\", \"ServerScriptService/server\"]) or pass --projects."]
|
|
335
|
+
[/No projects configured/, "Set \"projects\" in jest.config.ts (e.g. [\"ReplicatedStorage/client\", \"ServerScriptService/server\"]) or pass --projects."],
|
|
336
|
+
[/Infinite yield detected/, "A :WaitForChild() call is waiting for an instance that doesn't exist. Check your DataModel paths and Rojo project configuration."]
|
|
330
337
|
];
|
|
331
338
|
function discoverTestFiles(config, cliFiles) {
|
|
332
339
|
if (cliFiles && cliFiles.length > 0) {
|
|
@@ -356,6 +363,14 @@ function discoverTestFiles(config, cliFiles) {
|
|
|
356
363
|
totalFiles
|
|
357
364
|
};
|
|
358
365
|
}
|
|
366
|
+
function validateBackend(value) {
|
|
367
|
+
if (value === void 0) return;
|
|
368
|
+
if (!isValidBackend(value)) {
|
|
369
|
+
const valid = [...VALID_BACKENDS].join(", ");
|
|
370
|
+
throw new Error(`Invalid backend "${value}". Must be one of: ${valid}`);
|
|
371
|
+
}
|
|
372
|
+
return value;
|
|
373
|
+
}
|
|
359
374
|
function getLuauErrorHint(message) {
|
|
360
375
|
for (const [pattern, hint] of LUAU_ERROR_HINTS) if (pattern.test(message)) return hint;
|
|
361
376
|
}
|
|
@@ -375,24 +390,48 @@ function mergeCliWithConfig(cli, config) {
|
|
|
375
390
|
projects: cli.projects ?? config.projects,
|
|
376
391
|
rojoProject: cli.rojoProject ?? config.rojoProject,
|
|
377
392
|
setupFiles: cli.setupFiles ?? config.setupFiles,
|
|
393
|
+
setupFilesAfterEnv: cli.setupFilesAfterEnv ?? config.setupFilesAfterEnv,
|
|
378
394
|
showLuau: cli.showLuau ?? config.showLuau,
|
|
379
395
|
silent: cli.silent ?? config.silent,
|
|
380
396
|
sourceMap: cli.sourceMap ?? config.sourceMap,
|
|
381
397
|
testNamePattern: cli.testNamePattern ?? config.testNamePattern,
|
|
382
398
|
testPathPattern: cli.testPathPattern ?? config.testPathPattern,
|
|
383
399
|
timeout: cli.timeout ?? config.timeout,
|
|
400
|
+
typecheck: cli.typecheck ?? config.typecheck,
|
|
401
|
+
typecheckOnly: cli.typecheckOnly ?? config.typecheckOnly,
|
|
402
|
+
typecheckTsconfig: cli.typecheckTsconfig ?? config.typecheckTsconfig,
|
|
384
403
|
updateSnapshot: cli.updateSnapshot ?? config.updateSnapshot,
|
|
385
404
|
verbose: cli.verbose ?? config.verbose
|
|
386
405
|
};
|
|
387
406
|
}
|
|
388
|
-
function
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
407
|
+
function mergeResults(typecheck, runtime) {
|
|
408
|
+
if (typecheck !== void 0 && runtime !== void 0) return {
|
|
409
|
+
numFailedTests: typecheck.numFailedTests + runtime.numFailedTests,
|
|
410
|
+
numPassedTests: typecheck.numPassedTests + runtime.numPassedTests,
|
|
411
|
+
numPendingTests: typecheck.numPendingTests + runtime.numPendingTests,
|
|
412
|
+
numTotalTests: typecheck.numTotalTests + runtime.numTotalTests,
|
|
413
|
+
startTime: Math.min(typecheck.startTime, runtime.startTime),
|
|
414
|
+
success: typecheck.success && runtime.success,
|
|
415
|
+
testResults: [...typecheck.testResults, ...runtime.testResults]
|
|
416
|
+
};
|
|
417
|
+
if (typecheck !== void 0) return typecheck;
|
|
418
|
+
if (runtime !== void 0) return runtime;
|
|
419
|
+
throw new Error("mergeResults requires at least one result");
|
|
420
|
+
}
|
|
421
|
+
function printTypecheckFailures(result) {
|
|
422
|
+
for (const file of result.testResults) for (const test of file.testResults) {
|
|
423
|
+
if (test.status !== "failed") continue;
|
|
424
|
+
process.stderr.write(` FAIL ${test.fullName}\n`);
|
|
425
|
+
for (const message of test.failureMessages) process.stderr.write(` ${message}\n`);
|
|
394
426
|
}
|
|
395
|
-
|
|
427
|
+
}
|
|
428
|
+
function printTypecheckSummary(result) {
|
|
429
|
+
const passed = result.numPassedTests;
|
|
430
|
+
const failed = result.numFailedTests;
|
|
431
|
+
const total = result.numTotalTests;
|
|
432
|
+
if (failed > 0) printTypecheckFailures(result);
|
|
433
|
+
const failedPart = failed > 0 ? `${String(failed)} failed, ` : "";
|
|
434
|
+
process.stderr.write(`\nType Tests: ${failedPart}${String(passed)} passed, ${String(total)} total\n`);
|
|
396
435
|
}
|
|
397
436
|
|
|
398
437
|
//#endregion
|