@isentinel/jest-roblox 0.2.1 → 0.2.2

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.
@@ -3,6 +3,7 @@ local HttpService = game:GetService("HttpService")
3
3
  local LogService = game:GetService("LogService")
4
4
 
5
5
  local InstanceResolver = require(script.Parent:FindFirstChild('instance-resolver'))
6
+ local SetupTiming = require(script.Parent:FindFirstChild('setup-timing'))
6
7
  local SnapshotPatch = require(script.Parent:FindFirstChild('snapshot-patch'))
7
8
 
8
9
  type Config = {
@@ -133,6 +134,12 @@ function module.run(callingScript: LuaSourceContainer, config: Config): (string,
133
134
  end
134
135
  local t_resolveSetupFiles = os.clock()
135
136
 
137
+ local setupTimingState = SetupTiming.patch(
138
+ findValue,
139
+ config.setupFiles :: any,
140
+ config.setupFilesAfterEnv :: any
141
+ )
142
+
136
143
  -- Strip private keys before Jest.runCLI (safe: single-task execution per VM)
137
144
  config._timing = nil :: any
138
145
  config._coverage = nil :: any
@@ -142,14 +149,29 @@ function module.run(callingScript: LuaSourceContainer, config: Config): (string,
142
149
  end
143
150
 
144
151
  local t_jestRunCLI0 = os.clock()
145
- local jestResult = Jest.runCLI(callingScript, config, projects):expect()
152
+ local runCLIOk, runCLIValue = pcall(function()
153
+ return Jest.runCLI(callingScript, config, projects):expect()
154
+ end)
146
155
  local t_jestRunCLI = os.clock()
147
156
 
157
+ local setupSeconds = SetupTiming.getSeconds(setupTimingState)
158
+ SetupTiming.unpatch(setupTimingState)
159
+
160
+ if not runCLIOk then
161
+ error(runCLIValue, 0)
162
+ end
163
+
164
+ local jestResult = runCLIValue
165
+
148
166
  local result: { [string]: any } = {
149
167
  success = true,
150
168
  value = jestResult,
151
169
  }
152
170
 
171
+ if setupSeconds > 0 then
172
+ result._setup = setupSeconds
173
+ end
174
+
153
175
  if timingEnabled then
154
176
  result._timing = {
155
177
  findJest = t_findJest - t_findJest0,
@@ -230,4 +252,50 @@ function module.run(callingScript: LuaSourceContainer, config: Config): (string,
230
252
  return jestResult, if logSuccess then logHistory else "[]"
231
253
  end
232
254
 
255
+ type ProjectEntry = {
256
+ jestOutput: string,
257
+ gameOutput: string,
258
+ elapsedMs: number,
259
+ }
260
+
261
+ local function encodeExecutionError(err: any): string
262
+ return HttpService:JSONEncode({
263
+ success = true,
264
+ value = {
265
+ kind = "ExecutionError",
266
+ error = tostring(err),
267
+ },
268
+ })
269
+ end
270
+
271
+ -- TODO(runner-tests): dogfood harness for Runner.runProjects
272
+ function module.runProjects(
273
+ callingScript: LuaSourceContainer,
274
+ configs: { Config }
275
+ ): { ProjectEntry }
276
+ local entries: { ProjectEntry } = {}
277
+
278
+ for index, cfg in configs do
279
+ local start = os.clock()
280
+ local ok, jestOutput, gameOutput = pcall(module.run, callingScript, cfg)
281
+ local elapsedMs = math.floor((os.clock() - start) * 1000)
282
+
283
+ if ok then
284
+ entries[index] = {
285
+ jestOutput = jestOutput :: string,
286
+ gameOutput = gameOutput :: string,
287
+ elapsedMs = elapsedMs,
288
+ }
289
+ else
290
+ entries[index] = {
291
+ jestOutput = encodeExecutionError(jestOutput),
292
+ gameOutput = "[]",
293
+ elapsedMs = elapsedMs,
294
+ }
295
+ end
296
+ end
297
+
298
+ return entries
299
+ end
300
+
233
301
  return module
@@ -0,0 +1,89 @@
1
+ --!strict
2
+ local InstanceResolver = require(script.Parent:FindFirstChild('instance-resolver'))
3
+
4
+ local module = {}
5
+
6
+ export type PatchState = {
7
+ Runtime: any,
8
+ originalRequireModule: any,
9
+ accumulatedSeconds: { value: number },
10
+ }
11
+
12
+ function module.patch(
13
+ jestModule: ModuleScript,
14
+ setupFiles: { Instance }?,
15
+ setupFilesAfterEnv: { Instance }?
16
+ ): PatchState?
17
+ local setupSet: { [Instance]: boolean } = {}
18
+
19
+ if setupFiles then
20
+ for _, inst in setupFiles do
21
+ setupSet[inst] = true
22
+ end
23
+ end
24
+
25
+ if setupFilesAfterEnv then
26
+ for _, inst in setupFilesAfterEnv do
27
+ setupSet[inst] = true
28
+ end
29
+ end
30
+
31
+ if not next(setupSet) then
32
+ return nil
33
+ end
34
+
35
+ local jestRuntimeModule = InstanceResolver.findSiblingPackage(jestModule, "JestRuntime", "jest-runtime")
36
+ if not jestRuntimeModule then
37
+ warn("Could not find JestRuntime; setup timing unavailable")
38
+ return nil
39
+ end
40
+
41
+ local Runtime = (require :: any)(jestRuntimeModule)
42
+ local originalRequireModule = Runtime.requireModule
43
+ local accumulated = { value = 0 }
44
+ local insideSetupRequire = false
45
+
46
+ Runtime.requireModule = function(self: any, moduleName: any, ...): any
47
+ if not insideSetupRequire and typeof(moduleName) == "Instance" and setupSet[moduleName] then
48
+ insideSetupRequire = true
49
+ local t0 = os.clock()
50
+ local results = table.pack(pcall(originalRequireModule, self, moduleName, ...))
51
+ accumulated.value += os.clock() - t0
52
+ insideSetupRequire = false
53
+
54
+ if not results[1] then
55
+ error(results[2], 0)
56
+ end
57
+
58
+ return table.unpack(results, 2, results.n)
59
+ end
60
+
61
+ return originalRequireModule(self, moduleName, ...)
62
+ end
63
+
64
+ return {
65
+ Runtime = Runtime,
66
+ originalRequireModule = originalRequireModule,
67
+ accumulatedSeconds = accumulated,
68
+ }
69
+ end
70
+
71
+ function module.unpatch(state: PatchState?)
72
+ if not state then
73
+ return
74
+ end
75
+
76
+ if state.Runtime and state.originalRequireModule then
77
+ state.Runtime.requireModule = state.originalRequireModule
78
+ end
79
+ end
80
+
81
+ function module.getSeconds(state: PatchState?): number
82
+ if not state then
83
+ return 0
84
+ end
85
+
86
+ return state.accumulatedSeconds.value
87
+ end
88
+
89
+ return module
@@ -4,7 +4,7 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
4
4
  local RunService = game:GetService("RunService")
5
5
  local StudioTestService = game:GetService("StudioTestService")
6
6
 
7
- if RunService:IsRunning() then
7
+ if not RunService:IsEdit() then
8
8
  return
9
9
  end
10
10
 
@@ -8,7 +8,17 @@ if not RunService:IsRunning() then
8
8
  end
9
9
 
10
10
  local testArgs = StudioTestService:GetTestArgs()
11
- if not testArgs or not testArgs.test then
11
+ if testArgs == nil then
12
+ for _ = 1, 50 do
13
+ task.wait(0.1)
14
+ testArgs = StudioTestService:GetTestArgs()
15
+ if testArgs ~= nil then
16
+ break
17
+ end
18
+ end
19
+ end
20
+
21
+ if testArgs == nil or testArgs.configs == nil then
12
22
  return
13
23
  end
14
24
 
@@ -28,9 +38,9 @@ if not loadStringEnabled then
28
38
  end
29
39
 
30
40
  local Runner = require(script.Parent.shared.runner)
31
- local jestOutput, gameOutput = Runner.run(script, testArgs.config or {})
41
+ local entries = Runner.runProjects(script, testArgs.configs)
32
42
 
33
43
  StudioTestService:EndTest({
34
- jestOutput = jestOutput,
35
- gameOutput = gameOutput,
44
+ jestOutput = HttpService:JSONEncode({ entries = entries }),
45
+ gameOutput = "[]",
36
46
  })