@isentinel/jest-roblox 0.0.8 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isentinel/jest-roblox",
3
- "version": "0.0.8",
3
+ "version": "0.1.1",
4
4
  "description": "Jest-compatible CLI for running roblox-ts tests via Roblox Open Cloud",
5
5
  "keywords": [
6
6
  "jest",
@@ -39,8 +39,11 @@
39
39
  ],
40
40
  "dependencies": {
41
41
  "@jridgewell/trace-mapping": "0.3.31",
42
+ "@roblox-ts/rojo-resolver": "1.1.0",
42
43
  "arktype": "2.1.29",
43
- "c12": "3.3.3",
44
+ "c12": "4.0.0-beta.4",
45
+ "confbox": "0.2.2",
46
+ "defu": "6.1.4",
44
47
  "get-tsconfig": "4.13.6",
45
48
  "highlight.js": "11.11.1",
46
49
  "istanbul-lib-coverage": "3.2.2",
@@ -48,15 +51,18 @@
48
51
  "istanbul-reports": "3.2.0",
49
52
  "oxc-parser": "0.120.0",
50
53
  "picomatch": "4.0.3",
54
+ "std-env": "4.0.0",
51
55
  "tinyrainbow": "3.0.3",
52
56
  "ws": "8.18.0"
53
57
  },
54
58
  "devDependencies": {
55
59
  "@isentinel/eslint-config": "5.0.0-beta.9",
60
+ "@isentinel/tsconfig": "1.2.0",
56
61
  "@oxc-project/types": "0.120.0",
57
62
  "@rbxts/jest": "3.13.3-ts.1",
58
63
  "@rbxts/types": "1.0.911",
59
64
  "@total-typescript/shoehorn": "0.1.2",
65
+ "@tsdown/exe": "0.21.4",
60
66
  "@types/istanbul-lib-coverage": "2.0.6",
61
67
  "@types/istanbul-lib-report": "3.0.3",
62
68
  "@types/istanbul-reports": "3.0.4",
@@ -64,64 +70,33 @@
64
70
  "@types/picomatch": "4.0.2",
65
71
  "@types/ws": "8.5.13",
66
72
  "@typescript/native-preview": "7.0.0-dev.20260308.1",
73
+ "@vitest/coverage-v8": "4.1.0",
67
74
  "@vitest/eslint-plugin": "1.6.12",
68
75
  "better-typescript-lib": "2.12.0",
69
76
  "bumpp": "10.4.1",
70
77
  "eslint": "9.39.2",
71
78
  "eslint-plugin-jest-extended": "3.0.1",
79
+ "eslint-plugin-n": "17.23.1",
80
+ "eslint-plugin-pnpm": "1.4.3",
72
81
  "jest-extended": "7.0.0",
73
82
  "memfs": "4.56.11",
74
83
  "publint": "0.3.15",
75
- "tsdown": "0.20.1",
84
+ "tsdown": "0.21.4",
76
85
  "type-fest": "5.2.0",
86
+ "typescript": "5.9.3",
77
87
  "vitest": "4.1.0"
78
88
  },
79
89
  "engines": {
80
- "node": ">=20.0.0"
81
- },
82
- "nx": {
83
- "name": "jest-roblox-cli",
84
- "tags": [
85
- "scope:tooling",
86
- "type:cli"
87
- ],
88
- "projectType": "application",
89
- "includedScripts": [
90
- "!build"
91
- ],
92
- "targets": {
93
- "build": {
94
- "command": "pnpm build:bundle && tsdown && pnpm build:plugin",
95
- "options": {
96
- "cwd": "tools/jest-roblox-cli"
97
- }
98
- },
99
- "lint": {
100
- "command": "eslint",
101
- "hasTypeAwareRules": true
102
- },
103
- "build:bundle": {
104
- "command": "pnpm build:bundle",
105
- "options": {
106
- "cwd": "tools/jest-roblox-cli"
107
- }
108
- },
109
- "test": {
110
- "command": "vitest run",
111
- "dependsOn": [
112
- "build:bundle"
113
- ],
114
- "options": {
115
- "cwd": "tools/jest-roblox-cli"
116
- }
117
- }
118
- }
90
+ "node": ">=24.10.0"
119
91
  },
120
92
  "scripts": {
121
93
  "build": "pnpm build:bundle && tsdown && pnpm build:plugin",
122
94
  "build:bundle": "darklua process -c .darklua-bundle.json luau/entry.luau src/test-runner.bundled.luau",
123
95
  "build:plugin": "rm -rf plugin/out && darklua process -c .darklua-plugin.json luau/ plugin/out/shared/ && rojo build plugin/plugin.project.json -o plugin/JestRobloxRunner.rbxm",
96
+ "lint": "eslint --cache",
97
+ "lint:ci": "eslint --cache --cache-strategy content",
124
98
  "release": "bumpp",
99
+ "test": "nr typecheck && vitest run --coverage",
125
100
  "typecheck": "tsgo --build --emitDeclarationOnly",
126
101
  "watch": "tsdown --watch"
127
102
  }
Binary file
@@ -32,25 +32,20 @@ function module.getJest(config: { jestPath: string? }): ModuleScript
32
32
 
33
33
  local jestInstance = ReplicatedStorage:FindFirstChild("Jest", true)
34
34
  assert(jestInstance, "Failed to find Jest instance in ReplicatedStorage")
35
- assert(
36
- jestInstance:IsA("ModuleScript"),
37
- "Jest instance in ReplicatedStorage is not a ModuleScript"
38
- )
35
+ assert(jestInstance:IsA("ModuleScript"), "Jest instance in ReplicatedStorage is not a ModuleScript")
39
36
  return jestInstance
40
37
  end
41
38
 
42
39
  function module.findRobloxShared(jestModule: ModuleScript): Instance?
43
40
  local parent = jestModule.Parent
44
41
  if parent then
45
- local found = parent:FindFirstChild("RobloxShared")
46
- or parent:FindFirstChild("jest-roblox-shared")
42
+ local found = parent:FindFirstChild("RobloxShared") or parent:FindFirstChild("jest-roblox-shared")
47
43
  if found then
48
44
  return found
49
45
  end
50
46
 
51
47
  if parent.Parent then
52
- found = parent.Parent:FindFirstChild("RobloxShared")
53
- or parent.Parent:FindFirstChild("jest-roblox-shared")
48
+ found = parent.Parent:FindFirstChild("RobloxShared") or parent.Parent:FindFirstChild("jest-roblox-shared")
54
49
  if found then
55
50
  return found
56
51
  end
@@ -21,6 +21,28 @@ local function fail(err: string)
21
21
  }
22
22
  end
23
23
 
24
+ type CapturedMessage = { message: string, messageType: number, timestamp: number }
25
+
26
+ local function interceptWriteable(writeable: any, buffer: { CapturedMessage }, messageType: number)
27
+ local original = writeable._writeFn
28
+ if typeof(original) ~= "function" then
29
+ return function() end
30
+ end
31
+
32
+ writeable._writeFn = function(data: string)
33
+ table.insert(buffer, {
34
+ message = data,
35
+ messageType = messageType,
36
+ timestamp = os.clock(),
37
+ })
38
+ original(data)
39
+ end
40
+
41
+ return function()
42
+ writeable._writeFn = original
43
+ end
44
+ end
45
+
24
46
  local module = {}
25
47
 
26
48
  function module.run(callingScript: LuaSourceContainer, config: Config): (string, string)
@@ -37,8 +59,7 @@ function module.run(callingScript: LuaSourceContainer, config: Config): (string,
37
59
  return HttpService:JSONEncode(LogService:GetLogHistory())
38
60
  end)
39
61
 
40
- return HttpService:JSONEncode(fail(findValue :: any)),
41
- if logSuccess then logHistory else "[]"
62
+ return HttpService:JSONEncode(fail(findValue :: any)), if logSuccess then logHistory else "[]"
42
63
  end
43
64
 
44
65
  LogService:ClearOutput()
@@ -53,6 +74,28 @@ function module.run(callingScript: LuaSourceContainer, config: Config): (string,
53
74
  local Jest = (require :: any)(findValue)
54
75
  local t_requireJest = os.clock()
55
76
 
77
+ -- Intercept Jest's stdout/stderr to capture output synchronously.
78
+ -- Jest writes via process.stdout/stderr (Writeable objects whose _writeFn
79
+ -- defaults to print). Wrapping _writeFn captures messages like
80
+ -- "No tests found" that are printed just before exit(1) throws.
81
+ local capturedMessages: { CapturedMessage } = {}
82
+ local restoreStdout: (() -> ())?
83
+ local restoreStderr: (() -> ())?
84
+
85
+ local interceptOk = pcall(function()
86
+ local nodeModules = findValue.Parent.Parent.Parent :: any
87
+ local RobloxShared = (require :: any)(nodeModules["@rbxts-js"].RobloxShared)
88
+ local process = RobloxShared.nodeUtils.process
89
+
90
+ restoreStdout = interceptWriteable(process.stdout, capturedMessages, 0)
91
+ restoreStderr = interceptWriteable(process.stderr, capturedMessages, 1)
92
+ end)
93
+
94
+ if not interceptOk then
95
+ restoreStdout = nil
96
+ restoreStderr = nil
97
+ end
98
+
56
99
  local function runTests()
57
100
  local t_resolveProjects0 = os.clock()
58
101
  local projects = {}
@@ -142,17 +185,15 @@ function module.run(callingScript: LuaSourceContainer, config: Config): (string,
142
185
  end)
143
186
 
144
187
  local infiniteYieldMessage: string? = nil
145
- local watchdogConnection = LogService.MessageOut:Connect(
146
- function(message: string, messageType: Enum.MessageType)
147
- if
148
- messageType == Enum.MessageType.MessageWarning
149
- and string.find(message, "Infinite yield possible")
150
- and not infiniteYieldMessage
151
- then
152
- infiniteYieldMessage = message
153
- end
188
+ local watchdogConnection = LogService.MessageOut:Connect(function(message: string, messageType: Enum.MessageType)
189
+ if
190
+ messageType == Enum.MessageType.MessageWarning
191
+ and string.find(message, "Infinite yield possible")
192
+ and not infiniteYieldMessage
193
+ then
194
+ infiniteYieldMessage = message
154
195
  end
155
- )
196
+ end)
156
197
 
157
198
  while not jestDone and not infiniteYieldMessage do
158
199
  task.wait(0.1)
@@ -160,6 +201,14 @@ function module.run(callingScript: LuaSourceContainer, config: Config): (string,
160
201
 
161
202
  watchdogConnection:Disconnect()
162
203
 
204
+ if restoreStdout then
205
+ restoreStdout()
206
+ end
207
+
208
+ if restoreStderr then
209
+ restoreStderr()
210
+ end
211
+
163
212
  if not jestDone and infiniteYieldMessage then
164
213
  runSuccess = false
165
214
  runValue = "Infinite yield detected, aborting tests: " .. infiniteYieldMessage
@@ -175,7 +224,7 @@ function module.run(callingScript: LuaSourceContainer, config: Config): (string,
175
224
  end
176
225
 
177
226
  local logSuccess, logHistory = pcall(function()
178
- return HttpService:JSONEncode(LogService:GetLogHistory())
227
+ return HttpService:JSONEncode(capturedMessages)
179
228
  end)
180
229
 
181
230
  return jestResult, if logSuccess then logHistory else "[]"
@@ -46,8 +46,7 @@ function module.patch(jestModule: ModuleScript, snapshotWrites: { [string]: stri
46
46
 
47
47
  local getDataModelServiceChild = robloxSharedInstance:FindFirstChild("getDataModelService")
48
48
 
49
- local jestRuntimeModule =
50
- InstanceResolver.findSiblingPackage(jestModule, "JestRuntime", "jest-runtime")
49
+ local jestRuntimeModule = InstanceResolver.findSiblingPackage(jestModule, "JestRuntime", "jest-runtime")
51
50
  if not jestRuntimeModule then
52
51
  warn("Could not find JestRuntime; snapshot interception unavailable")
53
52
  return {
@@ -63,11 +62,7 @@ function module.patch(jestModule: ModuleScript, snapshotWrites: { [string]: stri
63
62
 
64
63
  Runtime.requireInternalModule = function(self: any, from: any, to: any, ...): any
65
64
  local target = if to ~= nil then to else from
66
- if
67
- getDataModelServiceChild
68
- and typeof(target) == "Instance"
69
- and target == getDataModelServiceChild
70
- then
65
+ if getDataModelServiceChild and typeof(target) == "Instance" and target == getDataModelServiceChild then
71
66
  return mockGetDataModelService
72
67
  end
73
68