Skip to content

Commit 2be01fc

Browse files
author
RoFlection Bot
committed
APT-697 - fix tests for react-testing-library (#62)
* use robloxdev-cli for scripts * upgrade foreman tools * fix some bizarre subtleties of asynchronicity * remove more no-op promise wrappers * Simplify and dodge some issues with promise API details; needs further consideration * upgrade a number of relevant dependencies * fix lint * stylua lint * appease a few luau lints * unfocus test * fix string matching * Introduce targeted wait to insure full cleanup * a whole bunch of CI touchup * clean up bad job definition * one more try, was still referring to redundant job * revert some misguided changes to the async wrapping and unmount logic * introduce workaround to fix hanging tests * Fix some type issues * For now, point to the in-progress DTL changes * fix a skipped test (which was broken) to try to bump coverage * for now, lowering coverage gate. act-compat sees little coverage because we don't actually have to worry about old React 16.x versions floating around * update dependency * Run tests with mock scheduler on and off for added validation * Add short-circuited real delay for use within test logic
1 parent f97ab34 commit 2be01fc

File tree

11 files changed

+71
-50
lines changed

11 files changed

+71
-50
lines changed

bin/ci.sh

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,17 @@ find Packages/_Index -name "*.robloxrc" | xargs rm -f
88

99
echo "Run static analysis"
1010
selene src
11-
roblox-cli analyze analyze.project.json
11+
robloxdev-cli analyze analyze.project.json
1212
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+
robloxdev-cli run --load.place tests.project.json --run bin/spec.lua \
16+
--lua.globals=__DEV__=true \
17+
--fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true \
18+
--fs.read=$PWD --load.asRobloxScript --headlessRenderer 1 --virtualInput 1
19+
20+
echo "Run tests with mock scheduler"
21+
robloxdev-cli run --load.place tests.project.json --run bin/spec.lua \
22+
--lua.globals=__DEV__=true --lua.globals=__ROACT_17_MOCK_SCHEDULER__=true \
23+
--fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true \
24+
--fs.read=$PWD --load.asRobloxScript --headlessRenderer 1 --virtualInput 1

foreman.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[tools]
22
rojo = { source = "Roblox/rojo-rbx-rojo", version = "7.2.1" }
33
rotrieve = { source = "roblox/rotriever", version = "0.5.4" }
4-
selene = { source = "Roblox/Kampfkarren-selene", version = "0.20.0" }
4+
selene = { source = "Roblox/Kampfkarren-selene", version = "=0.26.1" }
55
stylua = { source = "Roblox/JohnnyMorganz-StyLua", version = "=0.14.3" }
6+
rbx-aged-cli = { source = "Roblox/rbx-aged-tool", version = "=5.7.9" }

rotriever.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ files = ["*", "!**/__tests__/**"]
1111
[dependencies]
1212
LuauPolyfill = "github.com/roblox/luau-polyfill@1"
1313
Promise = "github.com/roblox/roblox-lua-promise@3.3.0"
14-
DomTestingLibrary = "github.com/roblox/dom-testing-library-lua@8.14.0"
14+
DomTestingLibrary = "github.com/roblox/dom-testing-library-lua@8.14.2"
1515
React = "github.com/roblox/roact-alignment@17.0.1-rc.16"
1616
ReactRoblox = "github.com/roblox/roact-alignment@17.0.1-rc.16"
1717
Scheduler = "github.com/roblox/roact-alignment@17.0.1-rc.16"

src/__tests__/act.spec.lua

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ local expect = JestGlobals.expect
66
local test = JestGlobals.test
77
local jest = JestGlobals.jest
88

9-
local Promise = require(Packages.Promise)
10-
119
local React = require(Packages.React)
1210
local ParentModule = require(script.Parent.Parent)
1311
local render = ParentModule.render
@@ -25,13 +23,9 @@ test("render calls useEffect immediately", function()
2523
end)
2624

2725
test("findByTestId returns the element", function()
28-
return Promise.resolve()
29-
:andThen(function()
30-
local ref = React.createRef()
31-
render(React.createElement("Frame", { ref = ref, [React.Tag] = "data-testid=foo" }))
32-
expect(screen.findByTestId("foo"):expect()).toBe(ref.current)
33-
end)
34-
:expect()
26+
local ref = React.createRef()
27+
render(React.createElement("Frame", { ref = ref, [React.Tag] = "data-testid=foo" }))
28+
expect(screen.findByTestId("foo"):expect()).toBe(ref.current)
3529
end)
3630

3731
test("fireEvent triggers useEffect calls", function()

src/__tests__/cleanup.spec.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ test("cleans up the document", function()
3636
end
3737

3838
function Test:render()
39+
-- selene: allow(roblox_incorrect_roact_usage)
3940
return React.createElement("Frame", { Name = divId })
4041
end
4142

src/__tests__/end-to-end.spec.lua

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -89,38 +89,26 @@ Array.forEach({
8989
end)
9090

9191
test("waitForElementToBeRemoved", function()
92-
return Promise.resolve()
93-
:andThen(function()
94-
render(React.createElement(ComponentWithLoader, nil))
95-
local function loading()
96-
return screen.getByText("Loading...")
97-
end
98-
waitForElementToBeRemoved(loading):expect()
99-
expect(screen.getByTestId("message")).toHaveTextContent(RegExp("Hello World"))
100-
end)
101-
:expect()
92+
render(React.createElement(ComponentWithLoader, nil))
93+
local function loading()
94+
return screen.getByText("Loading...")
95+
end
96+
waitForElementToBeRemoved(loading):expect()
97+
expect(screen.getByTestId("message")).toHaveTextContent(RegExp("Hello World"))
10298
end)
10399

104100
test("waitFor", function()
105-
return Promise.resolve()
106-
:andThen(function()
107-
render(React.createElement(ComponentWithLoader, nil))
108-
local function message()
109-
return screen.getByText(RegExp("Loaded this message:"))
110-
end
111-
waitFor(message):expect()
112-
expect(screen.getByTestId("message")).toHaveTextContent(RegExp("Hello World"))
113-
end)
114-
:expect()
101+
render(React.createElement(ComponentWithLoader, nil))
102+
local function message()
103+
return screen.getByText(RegExp("Loaded this message:"))
104+
end
105+
waitFor(message):expect()
106+
expect(screen.getByTestId("message")).toHaveTextContent(RegExp("Hello World"))
115107
end)
116108

117109
test("findBy", function()
118-
return Promise.resolve()
119-
:andThen(function()
120-
render(React.createElement(ComponentWithLoader, nil))
121-
expect((screen.findByTestId("message"):expect())).toHaveTextContent(RegExp("Hello World"))
122-
end)
123-
:expect()
110+
render(React.createElement(ComponentWithLoader, nil))
111+
expect((screen.findByTestId("message"):expect())).toHaveTextContent(RegExp("Hello World"))
124112
end)
125113
end)
126114
end)

src/__tests__/render.spec.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,11 @@ test("renders options.wrapper around node", function()
107107
end)
108108

109109
-- ROBLOX FIXME: useEffect is triggered before unmount
110-
test.skip("flushes useEffect cleanup functions sync on unmount()", function()
111-
local spy = jest.fn()
110+
test("flushes useEffect cleanup functions sync on unmount()", function()
111+
local spy, spyFn = jest.fn()
112112
local function Component()
113113
React.useEffect(function()
114-
spy()
114+
return spyFn
115115
end, {})
116116
return nil
117117
end

src/act-compat.lua

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ local function asyncAct(cb)
4848
args[
4949
1 --[[ ROBLOX adaptation: added 1 to array index ]]
5050
],
51-
"Warning: Do not await the result of calling ReactTestUtils.act"
51+
-- ROBLOX deviation: in upstream, this error is accounting for a specific react version
52+
-- (16.8.6); for us, it needs to account for the exact error version in our ported
53+
-- ReactTestUtilsPublicAct
54+
"Do not await the result of calling act(...) with sync logic, it is not a Promise.",
55+
1,
56+
true
5257
)
5358
== 1
5459
then
@@ -58,7 +63,9 @@ local function asyncAct(cb)
5863
firstArgIsString
5964
and string.find(
6065
args[1],
61-
"Warning: The callback passed to ReactTestUtils.act(...) function must not return anything"
66+
"Warning: The callback passed to ReactTestUtils.act(...) function must not return anything",
67+
1,
68+
true
6269
)
6370
== 1
6471
then

src/jsHelpers/react-dom/test-utils/ReactTestUtilsPublicAct.lua

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ local batchedUpdates = ReactRoblox.unstable_batchedUpdates
4141

4242
local IsSomeRendererActing = ReactSharedInternals.IsSomeRendererActing
4343

44+
-- ROBLOX deviation: use realDelay in place of enqueueTask as a fallback to
45+
-- avoid trouble caused by fake timers
46+
local realDelay = require(script.Parent["realTaskDelay.roblox.global"])
47+
4448
-- This is the public version of `ReactTestUtils.act`. It is implemented in
4549
-- "userspace" (i.e. not the reconciler), so that it doesn't add to the
4650
-- production bundle size.
@@ -64,7 +68,24 @@ end
6468
local function flushWorkAndMicroTasks(onDone: (err: any?) -> ())
6569
local ok, err = pcall(function()
6670
flushWork()
67-
enqueueTask(function()
71+
-- ROBLOX deviation START: We need to work around fake timers here in a
72+
-- way that upstream would in `enqueueTask`'s logic. See the upstream
73+
-- implementation here:
74+
-- https://github.com/facebook/react/blob/v17.0.1/packages/shared/enqueueTask.js#L16-L22
75+
local enqueueTaskWithRelevantTimers: (() -> ()) -> () = enqueueTask
76+
if type(task.delay) == "table" then
77+
-- ROBLOX TODO: the upstream implementation referenced above
78+
-- ultimately uses the _real_ implementation of `setImmediate`,
79+
-- which does wait for the next tick in the event loop. In our
80+
-- implementation, fake timers fundamentally block access to
81+
-- `task.delay`, which is our closest equivalent. Instead, it should
82+
-- be sufficient to ignore the engine frame boundary and use
83+
-- `task.defer` instead, which is unaffected by fake timers
84+
enqueueTaskWithRelevantTimers = function(fn)
85+
return realDelay(0, fn)
86+
end
87+
end
88+
enqueueTaskWithRelevantTimers(function()
6889
if flushWork() then
6990
flushWorkAndMicroTasks(onDone)
7091
else
@@ -94,12 +115,10 @@ local function act(callback: (() -> Thenable<any>) | () -> ())
94115
end
95116
end
96117
local previousActingUpdatesScopeDepth = actingUpdatesScopeDepth
97-
local previousIsSomeRendererActing
98-
local previousIsThisRendererActing
99118
actingUpdatesScopeDepth += 1
100119

101-
previousIsSomeRendererActing = IsSomeRendererActing.current
102-
previousIsThisRendererActing = IsThisRendererActing.current
120+
local previousIsSomeRendererActing = IsSomeRendererActing.current
121+
local previousIsThisRendererActing = IsThisRendererActing.current
103122
IsSomeRendererActing.current = true
104123
IsThisRendererActing.current = true
105124

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- Returns a reference to the _real_ task.delay function, to be used by RTL
2+
-- internals where `enqueueTask` isn't quite aligned
3+
return task.delay

0 commit comments

Comments
 (0)