@isentinel/jest-roblox 0.3.7 → 0.3.9
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/README.md +79 -13
- package/bin/jest-roblox.js +8 -9
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +13 -3
- package/dist/index.d.mts +256 -3
- package/dist/index.mjs +52 -2
- package/dist/{run-Bh8G33J1.mjs → run-BM5sqclu.mjs} +1264 -375
- package/dist/{schema-CCF2E3k_.d.mts → schema-i-pZhLCV.d.mts} +23 -3
- package/loaders/luau-raw.d.mts +4 -4
- package/loaders/luau-raw.mjs +5 -5
- package/package.json +5 -5
- package/plugin/JestRobloxRunner.rbxm +0 -0
- package/plugin/src/init.server.luau +11 -4
- package/plugin/src/test-in-run-mode.server.luau +73 -20
|
@@ -819,7 +819,7 @@ interface TypecheckConfig {
|
|
|
819
819
|
}
|
|
820
820
|
//#endregion
|
|
821
821
|
//#region src/config/schema.d.ts
|
|
822
|
-
type Backend = "auto" | "open-cloud" | "studio";
|
|
822
|
+
type Backend = "auto" | "open-cloud" | "studio" | "studio-cli";
|
|
823
823
|
type CoverageReporter = keyof ReportOptions;
|
|
824
824
|
type FormatterEntry = [string, Record<string, unknown>] | string;
|
|
825
825
|
/** pretty-format options controlling how snapshots are serialized. */
|
|
@@ -1028,7 +1028,12 @@ interface GlobalTestConfig extends SharedTestConfig {
|
|
|
1028
1028
|
expand?: boolean;
|
|
1029
1029
|
/** A JSON string of globals to expose in every test environment. */
|
|
1030
1030
|
globals?: string;
|
|
1031
|
-
/**
|
|
1031
|
+
/**
|
|
1032
|
+
* Per-project globs selecting Runtime Test files. With no `projects`
|
|
1033
|
+
* configured, the run derives one project per `luauRoots` mount and runtime
|
|
1034
|
+
* discovery uses `testMatch` — this root-level `include` is not consumed
|
|
1035
|
+
* there.
|
|
1036
|
+
*/
|
|
1032
1037
|
include?: Array<string>;
|
|
1033
1038
|
/**
|
|
1034
1039
|
* Maximum worker count, or a percentage string like `"50%"`, for parallel
|
|
@@ -1081,7 +1086,10 @@ interface Config {
|
|
|
1081
1086
|
/**
|
|
1082
1087
|
* Execution backend. `"auto"` probes for a running Studio then falls back to
|
|
1083
1088
|
* Open Cloud; `"open-cloud"` uploads and runs via Roblox Open Cloud;
|
|
1084
|
-
* `"studio"` drives a locally running Studio
|
|
1089
|
+
* `"studio"` drives a locally running Studio; `"studio-cli"` launches its own
|
|
1090
|
+
* headless Studio via `--task RunScript` and quits it (pass `--headed` to show
|
|
1091
|
+
* the Studio window during the run). Default `"auto"`. `"studio-cli"` is never
|
|
1092
|
+
* selected by `"auto"` — request it explicitly.
|
|
1085
1093
|
*/
|
|
1086
1094
|
backend?: Backend;
|
|
1087
1095
|
/** Force ANSI colour in output. Default `true`. */
|
|
@@ -1148,6 +1156,12 @@ interface Config {
|
|
|
1148
1156
|
/** Map Luau stack traces back to TypeScript source. Default `true`. */
|
|
1149
1157
|
sourceMap?: boolean;
|
|
1150
1158
|
/**
|
|
1159
|
+
* Path to the Roblox Studio executable the `"studio-cli"` backend launches.
|
|
1160
|
+
* Overrides per-OS auto-discovery. Also settable via `--studioPath` or the
|
|
1161
|
+
* `JEST_ROBLOX_STUDIO_PATH` environment variable.
|
|
1162
|
+
*/
|
|
1163
|
+
studioPath?: string;
|
|
1164
|
+
/**
|
|
1151
1165
|
* The Jest options block. Every jest-passthrough setting lives here, kept
|
|
1152
1166
|
* separate from the CLI/runner keys above.
|
|
1153
1167
|
*/
|
|
@@ -1214,6 +1228,11 @@ interface CliOptions {
|
|
|
1214
1228
|
files?: Array<string>;
|
|
1215
1229
|
formatters?: Array<string>;
|
|
1216
1230
|
gameOutput?: string;
|
|
1231
|
+
/**
|
|
1232
|
+
* Show the Studio window during a `studio-cli` run (`--headed`). CLI-only;
|
|
1233
|
+
* inert for every other backend. Never sourced from config or env.
|
|
1234
|
+
*/
|
|
1235
|
+
headed?: boolean;
|
|
1217
1236
|
help?: boolean;
|
|
1218
1237
|
outputFile?: string;
|
|
1219
1238
|
packages?: string;
|
|
@@ -1229,6 +1248,7 @@ interface CliOptions {
|
|
|
1229
1248
|
showLuau?: boolean;
|
|
1230
1249
|
silent?: boolean;
|
|
1231
1250
|
sourceMap?: boolean;
|
|
1251
|
+
studioPath?: string;
|
|
1232
1252
|
testNamePattern?: string;
|
|
1233
1253
|
testPathPattern?: string;
|
|
1234
1254
|
timeout?: number;
|
package/loaders/luau-raw.d.mts
CHANGED
|
@@ -9,7 +9,7 @@ interface ResolveResult {
|
|
|
9
9
|
url: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
type NextResolve = (specifier: string, context: ResolveContext) =>
|
|
12
|
+
type NextResolve = (specifier: string, context: ResolveContext) => ResolveResult;
|
|
13
13
|
|
|
14
14
|
interface LoadContext {
|
|
15
15
|
conditions?: Array<string>;
|
|
@@ -23,12 +23,12 @@ interface LoadResult {
|
|
|
23
23
|
source: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
type NextLoad = (url: string, context: LoadContext) =>
|
|
26
|
+
type NextLoad = (url: string, context: LoadContext) => LoadResult;
|
|
27
27
|
|
|
28
28
|
export function resolve(
|
|
29
29
|
specifier: string,
|
|
30
30
|
context: ResolveContext,
|
|
31
31
|
nextResolve: NextResolve,
|
|
32
|
-
):
|
|
32
|
+
): ResolveResult;
|
|
33
33
|
|
|
34
|
-
export function load(url: string, context: LoadContext, nextLoad: NextLoad):
|
|
34
|
+
export function load(url: string, context: LoadContext, nextLoad: NextLoad): LoadResult;
|
package/loaders/luau-raw.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
const resolved =
|
|
4
|
+
export function resolve(specifier, context, nextResolve) {
|
|
5
|
+
const resolved = nextResolve(specifier, context);
|
|
6
6
|
|
|
7
7
|
if (resolved.url.endsWith(".luau") || resolved.url.endsWith(".lua")) {
|
|
8
8
|
return { ...resolved, format: "luau-raw" };
|
|
@@ -11,7 +11,7 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
11
11
|
return resolved;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export
|
|
14
|
+
export function load(url, context, nextLoad) {
|
|
15
15
|
if (context.format === "luau-raw") {
|
|
16
16
|
if (url.endsWith(".lua")) {
|
|
17
17
|
return {
|
|
@@ -21,7 +21,7 @@ export async function load(url, context, nextLoad) {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const content =
|
|
24
|
+
const content = readFileSync(fileURLToPath(url), "utf-8");
|
|
25
25
|
return {
|
|
26
26
|
format: "module",
|
|
27
27
|
shortCircuit: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isentinel/jest-roblox",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "Jest-compatible CLI for running roblox-ts tests via Roblox Open Cloud",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jest",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"ws": "8.20.1"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@isentinel/eslint-config": "
|
|
70
|
-
"@isentinel/roblox-ts": "4.0.
|
|
69
|
+
"@isentinel/eslint-config": "6.0.0-beta.3",
|
|
70
|
+
"@isentinel/roblox-ts": "4.0.10",
|
|
71
71
|
"@isentinel/tsconfig": "1.2.0",
|
|
72
72
|
"@isentinel/weld": "0.2.0",
|
|
73
73
|
"@oxc-project/types": "0.123.0",
|
|
@@ -86,9 +86,9 @@
|
|
|
86
86
|
"@vitest/eslint-plugin": "1.6.19",
|
|
87
87
|
"better-typescript-lib": "2.12.0",
|
|
88
88
|
"bumpp": "11.1.0",
|
|
89
|
-
"eslint": "
|
|
89
|
+
"eslint": "10.6.0",
|
|
90
90
|
"eslint-plugin-jest-extended": "3.0.1",
|
|
91
|
-
"eslint-plugin-n": "18.
|
|
91
|
+
"eslint-plugin-n": "18.2.1",
|
|
92
92
|
"eslint-plugin-pnpm": "1.6.1",
|
|
93
93
|
"jest-extended": "7.0.0",
|
|
94
94
|
"memfs": "4.57.2",
|
|
Binary file
|
|
@@ -12,11 +12,11 @@ local WEBSOCKET_URL = "ws://localhost:3001"
|
|
|
12
12
|
local RECONNECT_DELAY = 0.1
|
|
13
13
|
|
|
14
14
|
-- Plugin/CLI protocol version. Must match `STUDIO_PROTOCOL_VERSION` in
|
|
15
|
-
-- `src/backends/studio.ts`. Bumped when the runtime contract changes
|
|
16
|
-
--
|
|
17
|
-
-- versions with a `version_mismatch` response so users see a clear upgrade
|
|
15
|
+
-- `src/backends/studio.ts`. Bumped when the runtime contract changes — v3 added
|
|
16
|
+
-- the run-mode workspace dispatch + version echo. The plugin rejects mismatched
|
|
17
|
+
-- CLI versions with a `version_mismatch` response so users see a clear upgrade
|
|
18
18
|
-- message rather than running with stale behaviour or an opaque timeout.
|
|
19
|
-
local PROTOCOL_VERSION =
|
|
19
|
+
local PROTOCOL_VERSION = 3
|
|
20
20
|
|
|
21
21
|
local IsDebug = ReplicatedStorage:GetAttribute("JEST_ROBLOX_DEBUG") == true
|
|
22
22
|
ReplicatedStorage:GetAttributeChangedSignal("JEST_ROBLOX_DEBUG"):Connect(function(): ()
|
|
@@ -130,11 +130,18 @@ local function connect(): ()
|
|
|
130
130
|
local runOk, result = pcall(function(): any
|
|
131
131
|
return StudioTestService:ExecuteRunModeAsync({
|
|
132
132
|
test = true,
|
|
133
|
+
-- Echo the negotiated version into the Run-mode payload so
|
|
134
|
+
-- the run-mode runner's own handshake passes (it validates
|
|
135
|
+
-- `protocolVersion`, shared with the studio-cli path).
|
|
136
|
+
protocolVersion = PROTOCOL_VERSION,
|
|
133
137
|
config = message.config,
|
|
134
138
|
-- Filtered injection targets (parallel to configs)
|
|
135
139
|
-- so the Run Mode runner skips mounts that already
|
|
136
140
|
-- have a user-authored jest.config on disk.
|
|
137
141
|
runtimeStubMounts = message.runtimeStubMounts,
|
|
142
|
+
-- Present only for workspace runs: the staged entries the
|
|
143
|
+
-- run-mode runner dispatches to the embedded materializer.
|
|
144
|
+
workspace = message.workspace,
|
|
138
145
|
})
|
|
139
146
|
end)
|
|
140
147
|
|
|
@@ -7,11 +7,23 @@ if not RunService:IsRunning() then
|
|
|
7
7
|
return
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
-- Plugin/CLI protocol version. Must match `STUDIO_PROTOCOL_VERSION` in
|
|
11
|
+
-- `src/backends/studio.ts` and `STUDIO_CLI_PROTOCOL_VERSION` in
|
|
12
|
+
-- `src/backends/studio-cli.ts`. v3 added the run-mode workspace dispatch + this
|
|
13
|
+
-- version echo. The runner validates the version the CLI sent and echoes its own
|
|
14
|
+
-- in `EndTest`, so the CLI surfaces a clean "update the plugin" error against a
|
|
15
|
+
-- mismatched plugin rather than running with stale runtime semantics.
|
|
16
|
+
local PROTOCOL_VERSION = 3
|
|
17
|
+
|
|
18
|
+
-- Every result (success or failure) echoes `protocolVersion` so the studio-cli
|
|
19
|
+
-- host can detect a stale plugin: one predating this echo omits the field and
|
|
20
|
+
-- the host rejects it as a version mismatch.
|
|
21
|
+
local function endTest(jestOutput: string, gameOutput: string?): ()
|
|
11
22
|
local ok, endErr = pcall(function()
|
|
12
23
|
StudioTestService:EndTest({
|
|
13
|
-
jestOutput =
|
|
14
|
-
gameOutput = "[]",
|
|
24
|
+
jestOutput = jestOutput,
|
|
25
|
+
gameOutput = gameOutput or "[]",
|
|
26
|
+
protocolVersion = PROTOCOL_VERSION,
|
|
15
27
|
})
|
|
16
28
|
end)
|
|
17
29
|
if not ok then
|
|
@@ -19,6 +31,10 @@ local function endWithError(err: string): ()
|
|
|
19
31
|
end
|
|
20
32
|
end
|
|
21
33
|
|
|
34
|
+
local function endWithError(err: string): ()
|
|
35
|
+
endTest(HttpService:JSONEncode({ success = false, err = err }), "[]")
|
|
36
|
+
end
|
|
37
|
+
|
|
22
38
|
local testArgs = StudioTestService:GetTestArgs()
|
|
23
39
|
if testArgs == nil then
|
|
24
40
|
for _ = 1, 50 do
|
|
@@ -34,16 +50,24 @@ if testArgs == nil then
|
|
|
34
50
|
return
|
|
35
51
|
end
|
|
36
52
|
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
-- Version handshake: reject a missing or mismatched `protocolVersion` before
|
|
54
|
+
-- running anything, mirroring the WebSocket `version_mismatch` path. The error
|
|
55
|
+
-- envelope still echoes our version (via endTest), so the CLI surfaces the
|
|
56
|
+
-- upgrade message either through this error or the version echo.
|
|
57
|
+
local clientVersion = if typeof(testArgs.protocolVersion) == "number"
|
|
58
|
+
then testArgs.protocolVersion
|
|
59
|
+
else nil
|
|
60
|
+
if clientVersion ~= PROTOCOL_VERSION then
|
|
39
61
|
endWithError(
|
|
40
|
-
"
|
|
62
|
+
"Studio plugin protocol version mismatch — plugin v"
|
|
63
|
+
.. tostring(PROTOCOL_VERSION)
|
|
64
|
+
.. ", CLI v"
|
|
65
|
+
.. tostring(clientVersion)
|
|
66
|
+
.. ". Update the jest-roblox Studio plugin to match the CLI."
|
|
41
67
|
)
|
|
42
68
|
return
|
|
43
69
|
end
|
|
44
70
|
|
|
45
|
-
local configs = testArgs.config.configs
|
|
46
|
-
|
|
47
71
|
local loadStringEnabled = pcall(function()
|
|
48
72
|
loadstring("return true")
|
|
49
73
|
end)
|
|
@@ -53,6 +77,41 @@ if not loadStringEnabled then
|
|
|
53
77
|
return
|
|
54
78
|
end
|
|
55
79
|
|
|
80
|
+
-- Dispatch by payload shape. A `workspace` payload drives the staged
|
|
81
|
+
-- materializer (clone each package from the mega-place's `__pkg_stage`, run,
|
|
82
|
+
-- reset) via the shared single-process embedded runner — the same code OCALE
|
|
83
|
+
-- runs, minus the MemoryStore queue. Otherwise the existing single-/multi-
|
|
84
|
+
-- project configs path runs `Runner.runProjects`.
|
|
85
|
+
if testArgs.workspace ~= nil then
|
|
86
|
+
local embeddedOk, EmbeddedRunner =
|
|
87
|
+
pcall(require, script.Parent.shared.staging["embedded-runner"])
|
|
88
|
+
if not embeddedOk then
|
|
89
|
+
endWithError(
|
|
90
|
+
"Failed to require shared.staging.embedded-runner: " .. tostring(EmbeddedRunner)
|
|
91
|
+
)
|
|
92
|
+
return
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
local runOk, entriesOrErr = pcall(EmbeddedRunner.runEmbedded, script, testArgs.workspace)
|
|
96
|
+
if not runOk then
|
|
97
|
+
endWithError("EmbeddedRunner.runEmbedded threw: " .. tostring(entriesOrErr))
|
|
98
|
+
return
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
endTest(HttpService:JSONEncode({ entries = entriesOrErr }), "[]")
|
|
102
|
+
return
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if testArgs.config == nil or testArgs.config.configs == nil then
|
|
106
|
+
local keysOk, keys = pcall(HttpService.JSONEncode, HttpService, testArgs)
|
|
107
|
+
endWithError(
|
|
108
|
+
"testArgs.config.configs is nil. Keys: " .. (if keysOk then keys else "<unencodable>")
|
|
109
|
+
)
|
|
110
|
+
return
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
local configs = testArgs.config.configs
|
|
114
|
+
|
|
56
115
|
local requireOk, Runner = pcall(require, script.Parent.shared.runner)
|
|
57
116
|
if not requireOk then
|
|
58
117
|
endWithError("Failed to require shared.runner: " .. tostring(Runner))
|
|
@@ -117,11 +176,7 @@ local function injectStubsForConfig(cfg, configIndex: number, injectionPaths: {
|
|
|
117
176
|
)
|
|
118
177
|
local stub = Instance.new("ModuleScript")
|
|
119
178
|
stub.Name = "jest.config"
|
|
120
|
-
stub.Source = string.format(
|
|
121
|
-
'return _G["%s"].configs[%d]',
|
|
122
|
-
SESSION_KEY,
|
|
123
|
-
configIndex
|
|
124
|
-
)
|
|
179
|
+
stub.Source = string.format('return _G["%s"].configs[%d]', SESSION_KEY, configIndex)
|
|
125
180
|
stub.Parent = leaf
|
|
126
181
|
-- Record immediately after parenting so a subsequent mount's
|
|
127
182
|
-- failure does not strand this one.
|
|
@@ -133,7 +188,9 @@ local function cleanupStubsForConfig(configIndex: number)
|
|
|
133
188
|
local injected = injectedPerConfig[configIndex]
|
|
134
189
|
if injected ~= nil then
|
|
135
190
|
for _, inst in injected do
|
|
136
|
-
pcall(function()
|
|
191
|
+
pcall(function()
|
|
192
|
+
inst:Destroy()
|
|
193
|
+
end)
|
|
137
194
|
end
|
|
138
195
|
injectedPerConfig[configIndex] = nil
|
|
139
196
|
end
|
|
@@ -159,8 +216,7 @@ end
|
|
|
159
216
|
-- nested-project mounts (e.g. `ReplicatedStorage` and `ReplicatedStorage/Foo`)
|
|
160
217
|
-- never have stubs in DataModel simultaneously — which would let Jest's
|
|
161
218
|
-- parent-traversal config lookup resolve the wrong project's config.
|
|
162
|
-
local runtimeStubMounts: { { string } } = testArgs.runtimeStubMounts
|
|
163
|
-
or table.create(#configs, {})
|
|
219
|
+
local runtimeStubMounts: { { string } } = testArgs.runtimeStubMounts or table.create(#configs, {})
|
|
164
220
|
|
|
165
221
|
local hooks = {
|
|
166
222
|
beforeConfig = function(cfg, index)
|
|
@@ -181,7 +237,4 @@ if not runOk then
|
|
|
181
237
|
return
|
|
182
238
|
end
|
|
183
239
|
|
|
184
|
-
|
|
185
|
-
jestOutput = HttpService:JSONEncode({ entries = entriesOrErr }),
|
|
186
|
-
gameOutput = "[]",
|
|
187
|
-
})
|
|
240
|
+
endTest(HttpService:JSONEncode({ entries = entriesOrErr }), "[]")
|