Skip to content

Commit 1e8b68b

Browse files
author
RoFlection Bot
committed
Port pure, act-compat, fire-event- index, and some react-dom utilities (#21)
1 parent 8bb2854 commit 1e8b68b

17 files changed

+845
-15
lines changed

analyze.project.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66
"$path": "Packages",
77
"ReactTestingLibrary": {
88
"$path": "src"
9-
},
10-
"Types": {
11-
"$path": "types"
129
}
1310
}
1411
}
15-
}
12+
}

bin/ci.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ find Packages/Dev -name "*.robloxrc" | xargs rm -f
77
find Packages/_Index -name "*.robloxrc" | xargs rm -f
88

99
echo "Run static analysis"
10-
selene src types
10+
selene src
1111
roblox-cli analyze analyze.project.json
12-
stylua -c src types
12+
stylua -c src
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 --load.asRobloxScript --headlessRenderer 1 --virtualInput 1
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

default.project.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
"$path": "Packages",
77
"ReactTestingLibrary": {
88
"$path": "src"
9-
},
10-
"Types": {
11-
"$path": "types"
129
}
1310
}
1411
}

foreman.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[tools]
22
rotrieve = { source = "roblox/rotriever", version = "0.5.4" }
33
selene = { source = "Kampfkarren/selene", version = "0.18.2" }
4-
stylua = { source = "JohnnyMorganz/StyLua", version = "=0.13.1" }
4+
stylua = { source = "JohnnyMorganz/StyLua", version = "=0.14.2" }

rotriever.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ authors = [
77
content_root = "src"
88

99
[dependencies]
10-
LuauPolyfill = "github.com/roblox/luau-polyfill@0.3.4"
10+
LuauPolyfill = "github.com/roblox/luau-polyfill@0.4.0"
1111
Promise = "github.com/evaera/roblox-lua-promise@4.0.0"
1212
DomTestingLibrary = { git = "https://github.com/roblox/dom-testing-library-lua.git", rev="main" }
13+
React = { git = "https://github.com/roblox/roact-alignment.git", package = "React" }
14+
ReactRoblox = { git = "https://github.com/roblox/roact-alignment.git", package = "ReactRoblox" }
15+
Scheduler = { git = "https://github.com/roblox/roact-alignment.git", package = "Scheduler" }
16+
Shared = { git = "https://github.com/roblox/roact-alignment.git", package = "Shared" }
1317

1418
[dev_dependencies]
1519
JestGlobals = { git = "https://github.com/roblox/jest-roblox.git", rev="master" }

src/.robloxrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"language": {
3+
"mode": "strict"
4+
},
5+
"lint": {
6+
"*": "enabled"
7+
}
8+
}

src/__tests__/act.spec.lua

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
-- ROBLOX upstream: https://github.com/testing-library/react-testing-library/blob/v12.1.5/src/__tests__/act.js
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 Promise = require(Packages.Promise)
10+
11+
local React = require(Packages.React)
12+
local ParentModule = require(script.Parent.Parent)(afterEach)
13+
local render = ParentModule.render
14+
local fireEvent = ParentModule.fireEvent
15+
local screen = ParentModule.screen
16+
17+
it("render calls useEffect immediately", function()
18+
local effectCb = jest.fn()
19+
local function MyUselessComponent()
20+
React.useEffect(effectCb)
21+
return nil
22+
end
23+
render(React.createElement(MyUselessComponent, nil))
24+
jestExpect(effectCb).toHaveBeenCalledTimes(1)
25+
end)
26+
27+
it("findByTestId returns the element", function()
28+
return Promise.resolve()
29+
:andThen(function()
30+
local ref = React.createRef()
31+
render(React.createElement("Frame", { ref = ref }))
32+
ref.current:SetAttribute("data-testid", "foo")
33+
jestExpect(screen.findByTestId("foo"):expect()).toBe(ref.current)
34+
end)
35+
:expect()
36+
end)
37+
38+
it("fireEvent triggers useEffect calls", function()
39+
local effectCb = jest.fn()
40+
local function Counter()
41+
React.useEffect(effectCb)
42+
local count, setCount = React.useState(0)
43+
return React.createElement("TextButton", {
44+
Size = UDim2.new(0, 100, 0, 100),
45+
[React.Event.Activated] = function()
46+
return setCount(count + 1)
47+
end,
48+
Text = count,
49+
})
50+
end
51+
local buttonNode = render(React.createElement(Counter, nil)).container:GetChildren()[1]
52+
task.wait()
53+
effectCb:mockClear()
54+
fireEvent.click(buttonNode)
55+
-- ROBLOX deviation START: replace toHaveTextContext
56+
jestExpect(buttonNode.Text).toBe("1")
57+
-- ROBLOX deviation END
58+
jestExpect(effectCb).toHaveBeenCalledTimes(1)
59+
end)
60+
61+
-- ROBLOX deviation START: hydrate is not supported
62+
-- it("calls to hydrate will run useEffects", function()
63+
-- local effectCb = jest.fn()
64+
-- local function MyUselessComponent()
65+
-- React.useEffect(effectCb)
66+
-- return nil
67+
-- end
68+
-- render(React.createElement(MyUselessComponent, nil), { hydrate = true })
69+
-- jestExpect(effectCb).toHaveBeenCalledTimes(1)
70+
-- end)
71+
-- ROBLOX deviation END
72+
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- ROBLOX upstream: https://github.com/testing-library/react-testing-library/blob/v12.1.5/src/__tests__/auto-cleanup-skip.js
2+
return function()
3+
local Packages = script.Parent.Parent.Parent
4+
5+
local JestGlobals = require(Packages.Dev.JestGlobals)
6+
local jestExpect = JestGlobals.expect
7+
8+
local document = require(Packages.DomTestingLibrary).document
9+
10+
local React = require(Packages.React)
11+
local render
12+
beforeAll(function()
13+
_G.RTL_SKIP_AUTO_CLEANUP = "true"
14+
local rtl = require(script.Parent.Parent)(afterEach)
15+
render = rtl.render
16+
end)
17+
18+
-- This one verifies that if RTL_SKIP_AUTO_CLEANUP is set
19+
-- then we DON'T auto-wire up the afterEach for folks
20+
it("first", function()
21+
render(React.createElement("TextLabel", { Text = "hi" }))
22+
end)
23+
24+
it("second", function()
25+
-- ROBLOX deviation START: restore so it cleans up after this test
26+
_G.RTL_SKIP_AUTO_CLEANUP = nil
27+
-- ROBLOX deviation END
28+
jestExpect(document:GetChildren()[1]:GetChildren()[1]:IsA("TextLabel")).toBe(true)
29+
jestExpect(document:GetChildren()[1]:GetChildren()[1].Text).toBe("hi")
30+
end)
31+
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- ROBLOX upstream: https://github.com/testing-library/react-testing-library/blob/v12.1.5/src/__tests__/auto-cleanup.js
2+
return function()
3+
local Packages = script.Parent.Parent.Parent
4+
5+
local JestGlobals = require(Packages.Dev.JestGlobals)
6+
local jestExpect = JestGlobals.expect
7+
8+
local document = require(Packages.DomTestingLibrary).document
9+
10+
local React = require(Packages.React)
11+
local render = require(script.Parent.Parent)(afterEach).render -- This just verifies that by importing RTL in an
12+
-- environment which supports afterEach (like jest)
13+
-- we'll get automatic cleanup between tests.
14+
it("first", function()
15+
render(React.createElement("TextLabel", { Text = "hi" }))
16+
end)
17+
18+
it("second", function()
19+
-- ROBLOX deviation START: not using toBeEmptyDOMElement
20+
jestExpect(#document:GetChildren()).toBe(0)
21+
-- ROBLOX deviation END
22+
end)
23+
end

src/act-compat.lua

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
-- ROBLOX upstream: https://github.com/testing-library/react-testing-library/blob/v12.1.5/src/act-compat.js
2+
local Packages = script.Parent.Parent
3+
4+
local LuauPolyfill = require(Packages.LuauPolyfill)
5+
local console = LuauPolyfill.console
6+
type Promise<T> = LuauPolyfill.Promise<T>
7+
8+
local Promise = require(Packages.Promise)
9+
10+
local exports = {}
11+
12+
-- local React = require(Packages.React)
13+
-- local ReactDOM = require(Packages["react-dom"]).default
14+
local testUtils = require(script.Parent.jsHelpers["react-dom"]["test-utils"])
15+
local reactAct = testUtils.act
16+
local actSupported = reactAct ~= nil
17+
18+
-- act is supported react-dom@16.8.0
19+
-- so for versions that don't have act from test utils
20+
-- we do this little polyfill. No warnings, but it's
21+
-- better than nothing.
22+
-- ROBLOX deviation START: don't polyfill
23+
-- local function actPolyfill(cb)
24+
-- ReactDOM:unstable_batchedUpdates(cb)
25+
-- ReactDOM:render(React.createElement("Frame", nil), Instance.new("Frame"))
26+
-- end
27+
28+
local act = reactAct
29+
-- ROBLOX deviation END
30+
31+
local youHaveBeenWarned = false
32+
local isAsyncActSupported = nil
33+
34+
local function asyncAct(cb)
35+
if actSupported == true then
36+
if isAsyncActSupported == nil then
37+
return Promise.new(function(resolve, reject)
38+
-- patch console.error here
39+
local originalConsoleError = console.error
40+
console.error = function(...: any)
41+
local args = table.pack(...)
42+
--[[ if console.error fired *with that specific message* ]]
43+
--[[ istanbul ignore next ]]
44+
local firstArgIsString = typeof(args[1]) == "string"
45+
if
46+
firstArgIsString
47+
and string.find(
48+
args[
49+
1 --[[ ROBLOX adaptation: added 1 to array index ]]
50+
],
51+
"Warning: Do not await the result of calling ReactTestUtils.act"
52+
)
53+
== 1
54+
then
55+
-- v16.8.6
56+
isAsyncActSupported = false
57+
elseif
58+
firstArgIsString
59+
and string.find(
60+
args[1],
61+
"Warning: The callback passed to ReactTestUtils.act(...) function must not return anything"
62+
)
63+
== 1
64+
then
65+
-- no-op
66+
else
67+
originalConsoleError(...)
68+
end
69+
end
70+
71+
local cbReturn, result
72+
local ok, err = pcall(function()
73+
result = reactAct(function()
74+
cbReturn = cb()
75+
return cbReturn
76+
end)
77+
end)
78+
79+
if not ok then
80+
console.error = originalConsoleError
81+
reject(err)
82+
return
83+
end
84+
85+
result:andThen(function()
86+
console.error = originalConsoleError
87+
-- if it got here, it means async act is supported
88+
isAsyncActSupported = true
89+
resolve()
90+
end, function(err)
91+
console.error = originalConsoleError
92+
isAsyncActSupported = true
93+
reject(err)
94+
end)
95+
96+
-- 16.8.6's act().then() doesn't call a resolve handler, so we need to manually flush here, sigh
97+
98+
if isAsyncActSupported == false then
99+
console.error = originalConsoleError
100+
--[[ istanbul ignore next ]]
101+
if not youHaveBeenWarned then
102+
-- if act is supported and async act isn't and they're trying to use async
103+
-- act, then they need to upgrade from 16.8 to 16.9.
104+
-- This is a seamless upgrade, so we'll add a warning
105+
console.error(
106+
'It looks like you\'re using a version of react-dom that supports the "act" function, but not an awaitable version of "act" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.'
107+
)
108+
youHaveBeenWarned = true
109+
end
110+
111+
cbReturn:andThen(function()
112+
-- a faux-version.
113+
-- todo - copy https://github.com/facebook/react/blob/master/packages/shared/enqueueTask.js
114+
Promise.resolve():andThen(function()
115+
-- use sync act to flush effects
116+
act(function() end)
117+
resolve()
118+
end)
119+
end, reject)
120+
end
121+
end)
122+
elseif isAsyncActSupported == false then
123+
-- use the polyfill directly
124+
local result: Promise<any>
125+
act(function()
126+
result = cb() :: any
127+
end)
128+
return result:andThen(function()
129+
return (Promise.resolve() :: Promise<any>):andThen(function()
130+
-- use sync act to flush effects
131+
act(function() end)
132+
end)
133+
end) :: any
134+
end
135+
-- all good! regular act
136+
return act(cb)
137+
end
138+
139+
-- use the polyfill
140+
local result: Promise<any>
141+
act(function()
142+
result = cb() :: any
143+
end)
144+
return result:andThen(function()
145+
return Promise.resolve():andThen(function()
146+
-- use sync act to flush effects
147+
act(function() end)
148+
end)
149+
end)
150+
end
151+
152+
exports.default = act
153+
exports.asyncAct = asyncAct
154+
155+
--[[ eslint no-console:0 ]]
156+
return exports

0 commit comments

Comments
 (0)