@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
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
local HttpService = game:GetService("HttpService")
|
|
3
|
+
local LogService = game:GetService("LogService")
|
|
4
|
+
|
|
5
|
+
local InstanceResolver = require(script.Parent:FindFirstChild('instance-resolver'))
|
|
6
|
+
local SnapshotPatch = require(script.Parent:FindFirstChild('snapshot-patch'))
|
|
7
|
+
|
|
8
|
+
type Config = {
|
|
9
|
+
jestPath: string?,
|
|
10
|
+
projects: { string }?,
|
|
11
|
+
setupFiles: { string }?,
|
|
12
|
+
setupFilesAfterEnv: { string }?,
|
|
13
|
+
_timing: boolean?,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
local function fail(err: string)
|
|
17
|
+
return {
|
|
18
|
+
success = false,
|
|
19
|
+
err = err,
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
local module = {}
|
|
24
|
+
|
|
25
|
+
function module.run(callingScript: LuaSourceContainer, config: Config): (string, string)
|
|
26
|
+
local t0 = os.clock()
|
|
27
|
+
local timingEnabled = config._timing
|
|
28
|
+
|
|
29
|
+
local t_findJest0 = os.clock()
|
|
30
|
+
local findSuccess, findValue = pcall(InstanceResolver.getJest, config)
|
|
31
|
+
local t_findJest = os.clock()
|
|
32
|
+
|
|
33
|
+
if not findSuccess then
|
|
34
|
+
local logSuccess, logHistory = pcall(function()
|
|
35
|
+
return HttpService:JSONEncode(LogService:GetLogHistory())
|
|
36
|
+
end)
|
|
37
|
+
|
|
38
|
+
return HttpService:JSONEncode(fail(findValue :: any)),
|
|
39
|
+
if logSuccess then logHistory else "[]"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
LogService:ClearOutput()
|
|
43
|
+
|
|
44
|
+
local snapshotWrites: { [string]: string } = {}
|
|
45
|
+
|
|
46
|
+
local t_patchSnapshot0 = os.clock()
|
|
47
|
+
local patchState = SnapshotPatch.patch(findValue, snapshotWrites)
|
|
48
|
+
local t_patchSnapshot = os.clock()
|
|
49
|
+
|
|
50
|
+
local t_requireJest0 = os.clock()
|
|
51
|
+
local Jest = (require :: any)(findValue)
|
|
52
|
+
local t_requireJest = os.clock()
|
|
53
|
+
|
|
54
|
+
local function runTests()
|
|
55
|
+
local t_resolveProjects0 = os.clock()
|
|
56
|
+
local projects = {}
|
|
57
|
+
|
|
58
|
+
assert(
|
|
59
|
+
config.projects and #config.projects > 0,
|
|
60
|
+
"No projects configured. Set 'projects' in jest.config.ts or pass --projects."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
for _, projectPath in config.projects do
|
|
64
|
+
table.insert(projects, InstanceResolver.findInstance(projectPath))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
config.projects = {}
|
|
68
|
+
local t_resolveProjects = os.clock()
|
|
69
|
+
|
|
70
|
+
local t_resolveSetupFiles0 = os.clock()
|
|
71
|
+
if config.setupFiles and #config.setupFiles > 0 then
|
|
72
|
+
local resolved = {}
|
|
73
|
+
|
|
74
|
+
for _, setupPath in config.setupFiles do
|
|
75
|
+
table.insert(resolved, InstanceResolver.findInstance(setupPath))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
config.setupFiles = resolved :: any
|
|
79
|
+
end
|
|
80
|
+
if config.setupFilesAfterEnv and #config.setupFilesAfterEnv > 0 then
|
|
81
|
+
local resolved = {}
|
|
82
|
+
|
|
83
|
+
for _, setupPath in config.setupFilesAfterEnv do
|
|
84
|
+
table.insert(resolved, InstanceResolver.findInstance(setupPath))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
config.setupFilesAfterEnv = resolved :: any
|
|
88
|
+
end
|
|
89
|
+
local t_resolveSetupFiles = os.clock()
|
|
90
|
+
|
|
91
|
+
config._timing = nil :: any
|
|
92
|
+
|
|
93
|
+
local t_jestRunCLI0 = os.clock()
|
|
94
|
+
local jestResult = Jest.runCLI(callingScript, config, projects):expect()
|
|
95
|
+
local t_jestRunCLI = os.clock()
|
|
96
|
+
|
|
97
|
+
local result: { [string]: any } = {
|
|
98
|
+
success = true,
|
|
99
|
+
value = jestResult,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if timingEnabled then
|
|
103
|
+
result._timing = {
|
|
104
|
+
findJest = t_findJest - t_findJest0,
|
|
105
|
+
patchSnapshot = t_patchSnapshot - t_patchSnapshot0,
|
|
106
|
+
requireJest = t_requireJest - t_requireJest0,
|
|
107
|
+
resolveProjects = t_resolveProjects - t_resolveProjects0,
|
|
108
|
+
resolveSetupFiles = t_resolveSetupFiles - t_resolveSetupFiles0,
|
|
109
|
+
jestRunCLI = t_jestRunCLI - t_jestRunCLI0,
|
|
110
|
+
total = os.clock() - t0,
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if next(snapshotWrites) then
|
|
115
|
+
result._snapshotWrites = snapshotWrites
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
return result
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
local jestDone = false
|
|
122
|
+
local runSuccess = false
|
|
123
|
+
local runValue: any = nil
|
|
124
|
+
|
|
125
|
+
task.spawn(function()
|
|
126
|
+
local ok, val = pcall(runTests)
|
|
127
|
+
jestDone = true
|
|
128
|
+
runSuccess = ok
|
|
129
|
+
runValue = val
|
|
130
|
+
end)
|
|
131
|
+
|
|
132
|
+
local infiniteYieldMessage: string? = nil
|
|
133
|
+
local watchdogConnection = LogService.MessageOut:Connect(
|
|
134
|
+
function(message: string, messageType: Enum.MessageType)
|
|
135
|
+
if
|
|
136
|
+
messageType == Enum.MessageType.MessageWarning
|
|
137
|
+
and string.find(message, "Infinite yield possible")
|
|
138
|
+
and not infiniteYieldMessage
|
|
139
|
+
then
|
|
140
|
+
infiniteYieldMessage = message
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
while not jestDone and not infiniteYieldMessage do
|
|
146
|
+
task.wait(0.1)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
watchdogConnection:Disconnect()
|
|
150
|
+
|
|
151
|
+
if not jestDone and infiniteYieldMessage then
|
|
152
|
+
runSuccess = false
|
|
153
|
+
runValue = "Infinite yield detected, aborting tests: " .. infiniteYieldMessage
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
SnapshotPatch.unpatch(patchState)
|
|
157
|
+
|
|
158
|
+
local jestResult
|
|
159
|
+
if not runSuccess then
|
|
160
|
+
jestResult = HttpService:JSONEncode(fail(runValue :: any))
|
|
161
|
+
else
|
|
162
|
+
jestResult = HttpService:JSONEncode(runValue)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
local logSuccess, logHistory = pcall(function()
|
|
166
|
+
return HttpService:JSONEncode(LogService:GetLogHistory())
|
|
167
|
+
end)
|
|
168
|
+
|
|
169
|
+
return jestResult, if logSuccess then logHistory else "[]"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
return module
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
local CoreScriptSyncService = require(script.Parent:FindFirstChild('mock'):FindFirstChild('CoreScriptSyncService'))
|
|
3
|
+
local InstanceResolver = require(script.Parent:FindFirstChild('instance-resolver'))
|
|
4
|
+
|
|
5
|
+
local module = {}
|
|
6
|
+
|
|
7
|
+
function module.createMockGetDataModelService(snapshotWrites: { [string]: string })
|
|
8
|
+
local FileSystemService = require(script.Parent:FindFirstChild('mock'):FindFirstChild('FileSystemService'))(snapshotWrites)
|
|
9
|
+
|
|
10
|
+
return function(service: string): any
|
|
11
|
+
if service == "FileSystemService" then
|
|
12
|
+
return FileSystemService
|
|
13
|
+
elseif service == "CoreScriptSyncService" then
|
|
14
|
+
return CoreScriptSyncService
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
local success, result = pcall(function()
|
|
18
|
+
local service_ = game:GetService(service)
|
|
19
|
+
local _ = service_.Name
|
|
20
|
+
return service_
|
|
21
|
+
end)
|
|
22
|
+
|
|
23
|
+
return if success then result else nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
export type PatchState = {
|
|
28
|
+
robloxSharedExports: any,
|
|
29
|
+
originalGetDataModelService: any,
|
|
30
|
+
Runtime: any,
|
|
31
|
+
originalRequireInternalModule: any,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function module.patch(jestModule: ModuleScript, snapshotWrites: { [string]: string }): PatchState?
|
|
35
|
+
local mockGetDataModelService = module.createMockGetDataModelService(snapshotWrites)
|
|
36
|
+
|
|
37
|
+
local robloxSharedInstance = InstanceResolver.findRobloxShared(jestModule)
|
|
38
|
+
if not robloxSharedInstance then
|
|
39
|
+
warn("Could not find RobloxShared; snapshot support unavailable")
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
local robloxSharedExports = (require :: any)(robloxSharedInstance)
|
|
44
|
+
local originalGetDataModelService = robloxSharedExports.getDataModelService
|
|
45
|
+
robloxSharedExports.getDataModelService = mockGetDataModelService
|
|
46
|
+
|
|
47
|
+
local getDataModelServiceChild = robloxSharedInstance:FindFirstChild("getDataModelService")
|
|
48
|
+
|
|
49
|
+
local jestRuntimeModule =
|
|
50
|
+
InstanceResolver.findSiblingPackage(jestModule, "JestRuntime", "jest-runtime")
|
|
51
|
+
if not jestRuntimeModule then
|
|
52
|
+
warn("Could not find JestRuntime; snapshot interception unavailable")
|
|
53
|
+
return {
|
|
54
|
+
robloxSharedExports = robloxSharedExports,
|
|
55
|
+
originalGetDataModelService = originalGetDataModelService,
|
|
56
|
+
Runtime = nil,
|
|
57
|
+
originalRequireInternalModule = nil,
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
local Runtime = (require :: any)(jestRuntimeModule)
|
|
62
|
+
local originalRequireInternalModule = Runtime.requireInternalModule
|
|
63
|
+
|
|
64
|
+
Runtime.requireInternalModule = function(self: any, from: any, to: any, ...): any
|
|
65
|
+
local target = if to ~= nil then to else from
|
|
66
|
+
if
|
|
67
|
+
getDataModelServiceChild
|
|
68
|
+
and typeof(target) == "Instance"
|
|
69
|
+
and target == getDataModelServiceChild
|
|
70
|
+
then
|
|
71
|
+
return mockGetDataModelService
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return originalRequireInternalModule(self, from, to, ...)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
robloxSharedExports = robloxSharedExports,
|
|
79
|
+
originalGetDataModelService = originalGetDataModelService,
|
|
80
|
+
Runtime = Runtime,
|
|
81
|
+
originalRequireInternalModule = originalRequireInternalModule,
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
function module.unpatch(state: PatchState?)
|
|
86
|
+
if not state then
|
|
87
|
+
return
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if state.robloxSharedExports and state.originalGetDataModelService then
|
|
91
|
+
state.robloxSharedExports.getDataModelService = state.originalGetDataModelService
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if state.Runtime and state.originalRequireInternalModule then
|
|
95
|
+
state.Runtime.requireInternalModule = state.originalRequireInternalModule
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
return module
|
|
@@ -27,21 +27,8 @@ if not loadStringEnabled then
|
|
|
27
27
|
return
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
local
|
|
31
|
-
local jestOutput, gameOutput =
|
|
32
|
-
|
|
33
|
-
-- Inject snapshot writes from shared global table into jestOutput
|
|
34
|
-
local snapshotWrites = _G.__snapshotWrites
|
|
35
|
-
if snapshotWrites and next(snapshotWrites) then
|
|
36
|
-
local ok, decoded = pcall(function()
|
|
37
|
-
return HttpService:JSONDecode(jestOutput)
|
|
38
|
-
end)
|
|
39
|
-
|
|
40
|
-
if ok and type(decoded) == "table" then
|
|
41
|
-
decoded._snapshotWrites = snapshotWrites
|
|
42
|
-
jestOutput = HttpService:JSONEncode(decoded)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
30
|
+
local Runner = require(script.Parent.shared.runner)
|
|
31
|
+
local jestOutput, gameOutput = Runner.run(script, testArgs.config or {})
|
|
45
32
|
|
|
46
33
|
StudioTestService:EndTest({
|
|
47
34
|
jestOutput = jestOutput,
|
package/plugin/sourcemap.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"name":"JestRobloxRunner","className":"Script","filePaths":["src\\init.server.luau","plugin.project.json"],"children":[{"name":"test-in-run-mode","className":"Script","filePaths":["src\\test-in-run-mode.server.luau"]},{"name":"test-runner","className":"ModuleScript","filePaths":["src\\test-runner.luau"]}]}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
-- Mock CoreScriptSyncService: builds instance-to-path mapping
|
|
2
|
-
-- Uses custom getInstancePath that stops at game (excludes DataModel name)
|
|
3
|
-
local function getInstancePath(instance: Instance): string
|
|
4
|
-
local parts = {}
|
|
5
|
-
local curr: Instance? = instance
|
|
6
|
-
while curr and curr ~= game do
|
|
7
|
-
table.insert(parts, 1, (curr :: Instance).Name)
|
|
8
|
-
curr = (curr :: Instance).Parent
|
|
9
|
-
end
|
|
10
|
-
return table.concat(parts, "/")
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
local CoreScriptSyncService = {}
|
|
14
|
-
|
|
15
|
-
function CoreScriptSyncService:GetScriptFilePath(script_: Instance): string
|
|
16
|
-
return getInstancePath(script_)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
return CoreScriptSyncService
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
-- Mock FileSystemService: collects snapshot writes in-memory Uses
|
|
2
|
-
-- _G.__snapshotWrites so all module load contexts (standard Luau + Jest
|
|
3
|
-
-- runtime) share the same table. The test-runner creates this before patching.
|
|
4
|
-
local snapshotWrites: { [string]: string } = _G.__snapshotWrites or {}
|
|
5
|
-
|
|
6
|
-
local FileSystemService = {}
|
|
7
|
-
|
|
8
|
-
function FileSystemService:WriteFile(path: string, contents: string)
|
|
9
|
-
-- Rewrite .snap.lua -> .snap.luau for Rojo compatibility
|
|
10
|
-
snapshotWrites[string.gsub(path, "%.snap%.lua$", ".snap.luau")] = contents
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
function FileSystemService:CreateDirectories(_path: string) end
|
|
14
|
-
|
|
15
|
-
function FileSystemService:Exists(path: string): boolean
|
|
16
|
-
return snapshotWrites[string.gsub(path, "%.snap%.lua$", ".snap.luau")] ~= nil
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
function FileSystemService:Remove(path: string)
|
|
20
|
-
snapshotWrites[string.gsub(path, "%.snap%.lua$", ".snap.luau")] = nil
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
function FileSystemService:IsRegularFile(path: string): boolean
|
|
24
|
-
return snapshotWrites[string.gsub(path, "%.snap%.lua$", ".snap.luau")] ~= nil
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
return FileSystemService
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
-- Patched getDataModelService: returns mock services for snapshot support
|
|
2
|
-
local CoreScriptSyncService = require(script.Parent.CoreScriptSyncService)
|
|
3
|
-
local FileSystemService = require(script.Parent.FileSystemService)
|
|
4
|
-
|
|
5
|
-
return function(service: string)
|
|
6
|
-
if service == "FileSystemService" then
|
|
7
|
-
return FileSystemService
|
|
8
|
-
elseif service == "CoreScriptSyncService" then
|
|
9
|
-
return CoreScriptSyncService
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
local success, result = pcall(function()
|
|
13
|
-
local svc = game:GetService(service)
|
|
14
|
-
local _ = svc.Name
|
|
15
|
-
return svc
|
|
16
|
-
end)
|
|
17
|
-
|
|
18
|
-
return success and result or nil
|
|
19
|
-
end
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
local HttpService = game:GetService("HttpService")
|
|
3
|
-
local LogService = game:GetService("LogService")
|
|
4
|
-
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
5
|
-
|
|
6
|
-
local module = {}
|
|
7
|
-
|
|
8
|
-
function module.findInstance(path: string): Instance
|
|
9
|
-
local parts = string.split(path, "/")
|
|
10
|
-
|
|
11
|
-
local result, current: Instance? = pcall(function(): Instance
|
|
12
|
-
return game:FindService(parts[1])
|
|
13
|
-
end)
|
|
14
|
-
assert(result, `Failed to find service {parts[1]}: {current}`)
|
|
15
|
-
|
|
16
|
-
for i = 2, #parts do
|
|
17
|
-
assert(current, `Failed to find instance at {parts[i]}`)
|
|
18
|
-
current = current:FindFirstChild(parts[i])
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
assert(current, `Failed to find instance at path {path}: {current}`)
|
|
22
|
-
|
|
23
|
-
return current
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
function module.getJest(config: { jestPath: string? }): ModuleScript
|
|
27
|
-
local jestPath = config.jestPath
|
|
28
|
-
if jestPath then
|
|
29
|
-
local instance = module.findInstance(jestPath)
|
|
30
|
-
assert(instance, `Failed to find Jest instance at path {jestPath}`)
|
|
31
|
-
assert(instance:IsA("ModuleScript"), `Instance at path {jestPath} is not a ModuleScript`)
|
|
32
|
-
return instance
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
local jestInstance = ReplicatedStorage:FindFirstChild("Jest", true)
|
|
36
|
-
assert(jestInstance, "Failed to find Jest instance in ReplicatedStorage")
|
|
37
|
-
assert(
|
|
38
|
-
jestInstance:IsA("ModuleScript"),
|
|
39
|
-
"Jest instance in ReplicatedStorage is not a ModuleScript"
|
|
40
|
-
)
|
|
41
|
-
return jestInstance
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
-- Find RobloxShared relative to Jest module
|
|
45
|
-
local function findRobloxShared(jestModule: ModuleScript): Instance?
|
|
46
|
-
local parent = jestModule.Parent
|
|
47
|
-
if parent then
|
|
48
|
-
local found = parent:FindFirstChild("RobloxShared")
|
|
49
|
-
or parent:FindFirstChild("jest-roblox-shared")
|
|
50
|
-
if found then
|
|
51
|
-
return found
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
if parent.Parent then
|
|
55
|
-
found = parent.Parent:FindFirstChild("RobloxShared")
|
|
56
|
-
or parent.Parent:FindFirstChild("jest-roblox-shared")
|
|
57
|
-
if found then
|
|
58
|
-
return found
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
return game:FindFirstChild("RobloxShared", true)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
-- Replace a child ModuleScript inside RobloxShared with a patched clone
|
|
67
|
-
local function replaceChild(parent: Instance, name: string, patchSource: ModuleScript)
|
|
68
|
-
local original = parent:FindFirstChild(name)
|
|
69
|
-
if original then
|
|
70
|
-
original:Destroy()
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
local clone = patchSource:Clone()
|
|
74
|
-
clone.Name = name
|
|
75
|
-
clone.Parent = parent
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
-- Patch RobloxShared by replacing ModuleScript children
|
|
79
|
-
local function patchRobloxShared(jestModule: ModuleScript, patchFolder: Instance): boolean
|
|
80
|
-
local robloxShared = findRobloxShared(jestModule)
|
|
81
|
-
if not robloxShared then
|
|
82
|
-
warn("[jest-roblox-cli] Could not find RobloxShared; snapshot support disabled")
|
|
83
|
-
return false
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
local patchGetDataModelService = patchFolder:FindFirstChild("getDataModelService")
|
|
87
|
-
local patchFileSystemService = patchFolder:FindFirstChild("FileSystemService")
|
|
88
|
-
local patchCoreScriptSyncService = patchFolder:FindFirstChild("CoreScriptSyncService")
|
|
89
|
-
|
|
90
|
-
if not (patchGetDataModelService and patchFileSystemService and patchCoreScriptSyncService) then
|
|
91
|
-
warn("[jest-roblox-cli] Missing patch modules; snapshot support disabled")
|
|
92
|
-
return false
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
-- Replace children inside RobloxShared
|
|
96
|
-
replaceChild(robloxShared, "FileSystemService", patchFileSystemService :: ModuleScript)
|
|
97
|
-
replaceChild(robloxShared, "CoreScriptSyncService", patchCoreScriptSyncService :: ModuleScript)
|
|
98
|
-
replaceChild(robloxShared, "getDataModelService", patchGetDataModelService :: ModuleScript)
|
|
99
|
-
|
|
100
|
-
warn("[jest-roblox-cli] Patched RobloxShared at:", robloxShared:GetFullName())
|
|
101
|
-
return true
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
function module.runTestsAync(
|
|
105
|
-
callingScript: LuaSourceContainer,
|
|
106
|
-
config: {
|
|
107
|
-
[string]: any,
|
|
108
|
-
}
|
|
109
|
-
): (string, string)
|
|
110
|
-
warn("Running tests with config:", config)
|
|
111
|
-
local t0 = os.clock()
|
|
112
|
-
local timingEnabled = config._timing
|
|
113
|
-
|
|
114
|
-
local t_findJest0 = os.clock()
|
|
115
|
-
local findSuccess, findValue = pcall(function(): ModuleScript
|
|
116
|
-
return module.getJest(config)
|
|
117
|
-
end)
|
|
118
|
-
local t_findJest = os.clock()
|
|
119
|
-
|
|
120
|
-
if not findSuccess then
|
|
121
|
-
local logSuccess, logHistory = pcall(function(): string
|
|
122
|
-
return HttpService:JSONEncode(LogService:GetLogHistory())
|
|
123
|
-
end)
|
|
124
|
-
|
|
125
|
-
return HttpService:JSONEncode({ success = false, err = findValue }),
|
|
126
|
-
if logSuccess then logHistory else "[]"
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
-- Snapshot support: create shared table BEFORE patching so all module
|
|
130
|
-
-- load contexts (standard Luau + Jest runtime) reference the same table
|
|
131
|
-
_G.__snapshotWrites = {}
|
|
132
|
-
|
|
133
|
-
local t_patchSnapshot0 = os.clock()
|
|
134
|
-
local patchFolder = (callingScript :: any).Parent.patch
|
|
135
|
-
if patchFolder then
|
|
136
|
-
patchRobloxShared(findValue, patchFolder)
|
|
137
|
-
end
|
|
138
|
-
local t_patchSnapshot = os.clock()
|
|
139
|
-
|
|
140
|
-
local t_requireJest0 = os.clock()
|
|
141
|
-
local Jest = (require :: any)(findValue)
|
|
142
|
-
local t_requireJest = os.clock()
|
|
143
|
-
|
|
144
|
-
local function run(): string
|
|
145
|
-
LogService:ClearOutput()
|
|
146
|
-
|
|
147
|
-
local t_resolveProjects0 = os.clock()
|
|
148
|
-
local projects = {}
|
|
149
|
-
|
|
150
|
-
assert(
|
|
151
|
-
config.projects and #config.projects > 0,
|
|
152
|
-
"No projects configured. Set 'projects' in jest.config.ts or pass --projects."
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
for _, projectPath in config.projects do
|
|
156
|
-
table.insert(projects, module.findInstance(projectPath))
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
config.projects = {}
|
|
160
|
-
local t_resolveProjects = os.clock()
|
|
161
|
-
|
|
162
|
-
local t_resolveSetupFiles0 = os.clock()
|
|
163
|
-
if config.setupFiles and #config.setupFiles > 0 then
|
|
164
|
-
local resolved = {}
|
|
165
|
-
|
|
166
|
-
for _, setupPath in config.setupFiles do
|
|
167
|
-
table.insert(resolved, module.findInstance(setupPath))
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
config.setupFiles = resolved
|
|
171
|
-
end
|
|
172
|
-
local t_resolveSetupFiles = os.clock()
|
|
173
|
-
|
|
174
|
-
config._timing = nil
|
|
175
|
-
|
|
176
|
-
local t_jestRunCLI0 = os.clock()
|
|
177
|
-
local jestResult = Jest.runCLI(callingScript, config, projects):expect()
|
|
178
|
-
local t_jestRunCLI = os.clock()
|
|
179
|
-
|
|
180
|
-
local result: { [string]: any } = {
|
|
181
|
-
success = true,
|
|
182
|
-
value = jestResult,
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if timingEnabled then
|
|
186
|
-
result._timing = {
|
|
187
|
-
configDecode = 0,
|
|
188
|
-
findJest = t_findJest - t_findJest0,
|
|
189
|
-
patchSnapshot = t_patchSnapshot - t_patchSnapshot0,
|
|
190
|
-
requireJest = t_requireJest - t_requireJest0,
|
|
191
|
-
resolveProjects = t_resolveProjects - t_resolveProjects0,
|
|
192
|
-
resolveSetupFiles = t_resolveSetupFiles - t_resolveSetupFiles0,
|
|
193
|
-
jestRunCLI = t_jestRunCLI - t_jestRunCLI0,
|
|
194
|
-
total = os.clock() - t0,
|
|
195
|
-
}
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
return HttpService:JSONEncode(result)
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
local runSuccess, runValue = pcall(run)
|
|
202
|
-
|
|
203
|
-
local jestResult: string
|
|
204
|
-
if not runSuccess then
|
|
205
|
-
jestResult = HttpService:JSONEncode({ success = false, err = runValue })
|
|
206
|
-
else
|
|
207
|
-
jestResult = runValue
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
local logSuccess, logHistory = pcall(function(): string
|
|
211
|
-
return HttpService:JSONEncode(LogService:GetLogHistory())
|
|
212
|
-
end)
|
|
213
|
-
|
|
214
|
-
return jestResult, if logSuccess then logHistory else "[]"
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
return module
|