Skip to content

Commit 06973f4

Browse files
author
RoFlection Bot
committed
Port Events + VirtualInputManager (#53)
* Port Events * Port related tests * dispatchEvent
1 parent 19507c0 commit 06973f4

File tree

9 files changed

+1042
-3
lines changed

9 files changed

+1042
-3
lines changed

bin/ci.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ roblox-cli analyze analyze.project.json
1212
stylua -c src types jsHelpers
1313

1414
echo "Run tests"
15-
roblox-cli run --load.place tests.project.json --run bin/spec.lua --lua.globals=__DEV__=true --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true --fs.read=$PWD
15+
roblox-cli run --load.place tests.project.json --run bin/spec.lua --lua.globals=__DEV__=true --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true --fs.read=$PWD --load.asRobloxScript --headlessRenderer 1 --virtualInput 1
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
-- ROBLOX upstream: No upstream
2+
return function()
3+
local Packages = script.Parent.Parent.Parent
4+
5+
local JestGlobals = require(Packages.Dev.JestGlobals)
6+
local jestExpect = JestGlobals.expect
7+
local jest = JestGlobals.jest
8+
9+
local dispatchEvent = require(script.Parent.Parent.dispatchEvent)
10+
describe("dispatchEvent", function()
11+
it("should trigger click event", function()
12+
local element = Instance.new("TextButton")
13+
element.Size = UDim2.new(0, 100, 0, 100)
14+
element.Text = "Click Me"
15+
16+
local callbackFn = jest.fn()
17+
18+
element.Activated:Connect(function(...)
19+
callbackFn(...)
20+
end)
21+
22+
jestExpect(callbackFn).toHaveBeenCalledTimes(0)
23+
dispatchEvent(element, "click")
24+
jestExpect(callbackFn).toHaveBeenCalledTimes(1)
25+
end)
26+
27+
it("should trigger keyDown event", function()
28+
local element = Instance.new("Frame")
29+
element.Size = UDim2.new(0, 100, 0, 100)
30+
31+
local callbackFn = jest.fn()
32+
33+
element.InputBegan:Connect(function(input)
34+
if input.UserInputType == Enum.UserInputType.Keyboard and input.KeyCode == Enum.KeyCode.Escape then
35+
callbackFn()
36+
end
37+
end)
38+
39+
jestExpect(callbackFn).toHaveBeenCalledTimes(0)
40+
dispatchEvent(element, "keyDown", { key = Enum.KeyCode.Escape })
41+
jestExpect(callbackFn).toHaveBeenCalledTimes(1)
42+
end)
43+
44+
it("should trigger keyUp event", function()
45+
local element = Instance.new("Frame")
46+
element.Size = UDim2.new(0, 100, 0, 100)
47+
48+
local callbackFn = jest.fn()
49+
50+
element.InputEnded:Connect(function(input)
51+
if input.UserInputType == Enum.UserInputType.Keyboard and input.KeyCode == Enum.KeyCode.Escape then
52+
callbackFn()
53+
end
54+
end)
55+
56+
jestExpect(callbackFn).toHaveBeenCalledTimes(0)
57+
dispatchEvent(element, "keyUp", { key = Enum.KeyCode.Escape })
58+
jestExpect(callbackFn).toHaveBeenCalledTimes(1)
59+
end)
60+
61+
it("should trigger change event", function()
62+
local element = Instance.new("TextBox")
63+
element.Size = UDim2.new(0, 100, 0, 100)
64+
element.Text = ""
65+
66+
local callbackFn = jest.fn()
67+
68+
element.Changed:Connect(function(property: string)
69+
if property == "Text" then
70+
callbackFn()
71+
end
72+
end)
73+
74+
jestExpect(callbackFn).toHaveBeenCalledTimes(0)
75+
dispatchEvent(element, "change", { target = { Text = "Hello" } })
76+
jestExpect(callbackFn).toHaveBeenCalledTimes(1)
77+
end)
78+
79+
it("should trigger resize event", function()
80+
local element = Instance.new("Frame")
81+
element.Size = UDim2.new(0, 100, 0, 100)
82+
83+
local callbackFn = jest.fn()
84+
85+
element.Changed:Connect(function(property: string)
86+
if property == "Size" then
87+
callbackFn()
88+
end
89+
end)
90+
91+
jestExpect(callbackFn).toHaveBeenCalledTimes(0)
92+
dispatchEvent(element, "resize", { value = UDim2.new(0, 200, 0, 200) })
93+
jestExpect(callbackFn).toHaveBeenCalledTimes(1)
94+
end)
95+
end)
96+
end

jsHelpers/dispatchEvent.lua

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
-- ROBLOX upstream: no upstream
2+
local Packages = script.Parent.Parent
3+
4+
local LuauPolyfill = require(Packages.LuauPolyfill)
5+
local Error = LuauPolyfill.Error
6+
7+
local virtualInput = game:GetService("VirtualInputManager")
8+
9+
local function getRoot(child): Instance
10+
local parent = child.Parent :: Instance
11+
if parent == nil then
12+
return child
13+
end
14+
15+
while parent.Parent ~= nil and (parent.Parent :: Instance).Parent ~= game do
16+
parent = parent.Parent
17+
end
18+
return parent
19+
end
20+
21+
local function makeInteractable(fn: any)
22+
return function(element, data)
23+
local coreGui_ = element:FindFirstAncestorOfClass("CoreGui")
24+
local screenGui_ = element:FindFirstAncestorOfClass("ScreenGui")
25+
local coreGui = if coreGui_ then coreGui_ else game:GetService("CoreGui")
26+
local screenGui = (if screenGui_ then screenGui_ else Instance.new("ScreenGui")) :: ScreenGui
27+
local root
28+
local cleanupCore = false
29+
30+
if not screenGui:FindFirstAncestorOfClass("CoreGui") and screenGui.Parent == nil then
31+
screenGui.Parent = coreGui
32+
cleanupCore = true
33+
end
34+
35+
if not element:FindFirstAncestorOfClass("ScreenGui") then
36+
root = getRoot(element)
37+
root.Parent = screenGui
38+
end
39+
40+
fn(element, data)
41+
42+
if screenGui_ ~= screenGui then
43+
root.Parent = nil
44+
screenGui:Destroy()
45+
end
46+
47+
if screenGui and cleanupCore then
48+
screenGui.Parent = nil
49+
end
50+
end
51+
end
52+
53+
local function getCenter(element: Instance): (number, number)
54+
local position = (element :: any).AbsolutePosition
55+
local size = (element :: any).AbsoluteSize
56+
57+
return position.X + size.X / 2, position.Y + size.Y / 2
58+
end
59+
60+
local function click(element: Instance)
61+
local x, y = getCenter(element)
62+
local mouseButton = 0
63+
local layerCollector = nil
64+
local repeatCount = 1
65+
66+
virtualInput:SendMouseButtonEvent(x, y, mouseButton, true, layerCollector, repeatCount)
67+
virtualInput:SendMouseButtonEvent(x, y, mouseButton, false, layerCollector, repeatCount)
68+
virtualInput:WaitForInputEventsProcessed()
69+
end
70+
71+
local function keyDown(_element: Instance, data: { key: Enum.KeyCode })
72+
if not data or not data.key then
73+
error("No key set for event")
74+
end
75+
virtualInput:SendKeyEvent(true, data.key, false, nil)
76+
virtualInput:WaitForInputEventsProcessed()
77+
end
78+
79+
local function keyUp(_element: Instance, data: { key: Enum.KeyCode })
80+
if not data or not data.key then
81+
error("No key set for event")
82+
end
83+
virtualInput:SendKeyEvent(false, data.key, false, nil)
84+
virtualInput:WaitForInputEventsProcessed()
85+
end
86+
87+
local function change(element: Instance, data: { target: { [string]: any } })
88+
if element:IsA("TextBox") then
89+
if data and data.target then
90+
for k, v in pairs(data.target) do
91+
(element :: any)[k] = v
92+
end
93+
end
94+
else
95+
error(Error.new("The change event must be fired in a TextBox Instance"))
96+
end
97+
end
98+
99+
local function resize(element: TextBox, data: { value: string })
100+
local value = if data and data.value then data.value else (element :: any).Size
101+
element.Size = value
102+
end
103+
104+
local events = {
105+
click = makeInteractable(click),
106+
keyDown = makeInteractable(keyDown),
107+
keyUp = makeInteractable(keyUp),
108+
change = makeInteractable(change),
109+
resize = makeInteractable(resize),
110+
}
111+
112+
local function dispatchEvent(element: Instance, eventName: string, data: { [string]: any }?)
113+
local eventFn = events[eventName]
114+
if not eventFn then
115+
error("Event not found")
116+
end
117+
eventFn(element, data)
118+
end
119+
120+
return dispatchEvent

0 commit comments

Comments
 (0)