local d local i local F local k local b local a local M=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\108\117\097\117-\112\111\108\121\102\105\108\108")local B=M.Error local z=M.setTimeout local H=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\112\114\111\109\105\115\101")local U=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\106\101\115\116-\103\108\111\098\097\108\115")local v=U.expect local S=U.describe local w=U.beforeEach local G=U.it local r=U.xit local W=U.jest local function O(d)if typeof(d)~="\115\116\114\105\110\103"then return d end d=string.gsub(d,"\067\104\101\099\107 \121\111\117\114 \099\111\100\101 \097\116 \046\042\058\037\100\043","\067\104\101\099\107 \121\111\117\114 \099\111\100\101 \097\116 \042\042")return string.gsub(d,"\n    \105\110 \040\091\037\119\037-\037\046\095\093\043\041\091\094\n\093\042","\n    \105\110 \037\049 \040\097\116 \042\042\041")end S("\082\101\097\099\116\076\097\122\121",function()w(function()W.resetModules()k=(require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\115\104\097\114\101\100")).ReactFeatureFlags k.replayFailedUnitOfWorkWithInvokeGuardedCallback=false d=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116")b=d.Suspense a=d.lazy i=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116-\116\101\115\116-\114\101\110\100\101\114\101\114")F=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\115\099\104\101\100\117\108\101\114")end)local M=function(d)return(H.delay(0)):andThen(function()return{default=d}end)end local function U(d)F.unstable_yieldValue(d.text)return d.text end local S=function(d)return H.new(function(i)return z(function()return i()end,d)end)end G("\115\117\115\112\101\110\100\115 \117\110\116\105\108 \109\111\100\117\108\101 \104\097\115 \108\111\097\100\101\100",function()local k=a(function()return M(U)end)local B=i.create(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(k,{text="\072\105"})),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\076\111\097\100\105\110\103\046\046\046"});(v(B)).never.toMatchRenderedOutput("\072\105");(H.delay(0)):await();(v(F)).toFlushAndYield({"\072\105"});(v(B)).toMatchRenderedOutput("\072\105")B.update(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(k,{text="\072\105 \097\103\097\105\110"})));(v(F)).toFlushAndYield({"\072\105 \097\103\097\105\110"});(v(B)).toMatchRenderedOutput("\072\105 \097\103\097\105\110")end)G("\099\097\110 \114\101\115\111\108\118\101 \115\121\110\099\104\114\111\110\111\117\115\108\121 \119\105\116\104\111\117\116 \115\117\115\112\101\110\100\105\110\103",function()local k=a(function()return{andThen=function(d,i)i({default=U})end}end)local M=i.create(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(k,{text="\072\105"})));(v(F)).toHaveYielded({"\072\105"});(v(M)).toMatchRenderedOutput("\072\105")end)G("\099\097\110 \114\101\106\101\099\116 \115\121\110\099\104\114\111\110\111\117\115\108\121 \119\105\116\104\111\117\116 \115\117\115\112\101\110\100\105\110\103",function()local k=a(function()return{andThen=function(d,i,F)F(B("\111\104 \110\111"))end}end)local M=d.Component:extend("\069\114\114\111\114\066\111\117\110\100\097\114\121")function M.init(d)d.state={}end function M.getDerivedStateFromError(d)return{message=d.message}end function M.render(d)if d.state.message then return string.format("\069\114\114\111\114\058 \037\115",d.state.message)end return d.props.children end local z=i.create(d.createElement(M,nil,d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(k,{text="\072\105"}))));(v(F)).toHaveYielded({});(v(z)).toMatchRenderedOutput("\069\114\114\111\114\058 \111\104 \110\111")end)G("\109\117\108\116\105\112\108\101 \108\097\122\121 \099\111\109\112\111\110\101\110\116\115",function()local function k()return d.createElement(U,{text="\070\111\111"})end local function B()return d.createElement(U,{text="\066\097\114"})end local z=(S(100)):andThen(function()return M(k)end)local H=(S(500)):andThen(function()return M(B)end)local w=a(function()return z end)local G=a(function()return H end)local r=i.create(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(w),d.createElement(G)),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\076\111\097\100\105\110\103\046\046\046"});(v(r)).never.toMatchRenderedOutput("\070\111\111\066\097\114")z:await();(v(F)).toFlushAndYield({"\070\111\111"});(v(r)).never.toMatchRenderedOutput("\070\111\111\066\097\114")H:await();(v(F)).toFlushAndYield({"\070\111\111";"\066\097\114"});(v(r)).toMatchRenderedOutput("\070\111\111\066\097\114")end)G("\116\104\114\111\119\115 \105\102 \112\114\111\109\105\115\101 \114\101\106\101\099\116\115",function()local k=function()return(H.delay(0)):andThen(function()error(B("\066\097\100 \110\101\116\119\111\114\107"))end)end local M=a(k)local z=i.create(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(M,{text="\072\105"})),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\076\111\097\100\105\110\103\046\046\046"});(v(z)).never.toMatchRenderedOutput("\072\105")local S,w=pcall(function()(H.delay(0)):await()end);(v(F)).toFlushAndThrow("\066\097\100 \110\101\116\119\111\114\107")end)G("\109\111\117\110\116 \097\110\100 \114\101\111\114\100\101\114",function()local k=d.Component:extend("\067\104\105\108\100")function k.componentDidMount(d)F.unstable_yieldValue("\068\105\100 \109\111\117\110\116\058 "..d.props.label)end function k.componentDidUpdate(d)F.unstable_yieldValue("\068\105\100 \117\112\100\097\116\101\058 "..d.props.label)end function k.render(i)return d.createElement(U,{text=i.props.label})end local B=a(function()return M(k)end)local z=a(function()return M(k)end)local function S(i)local F if i.swap then F={d.createElement(z,{key="\066";label="\066"}),d.createElement(B,{key="\065";label="\065"})}else F={d.createElement(B,{key="\065";label="\065"}),d.createElement(z,{key="\066",label="\066"})}end return d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},F)end local w=i.create(d.createElement(S,{swap=false}),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\076\111\097\100\105\110\103\046\046\046"});(v(w)).never.toMatchRenderedOutput("\065\066");(H.delay(0)):await();(H.delay(0)):await();(v(F)).toFlushAndYield({"\065","\066","\068\105\100 \109\111\117\110\116\058 \065";"\068\105\100 \109\111\117\110\116\058 \066"});(v(w)).toMatchRenderedOutput("\065\066")w.update(d.createElement(S,{swap=true}));(v(F)).toFlushAndYield({"\066","\065";"\068\105\100 \117\112\100\097\116\101\058 \066","\068\105\100 \117\112\100\097\116\101\058 \065"});(v(w)).toMatchRenderedOutput("\066\065")end)G("\116\104\114\111\119\115 \119\105\116\104 \097 \117\115\101\102\117\108 \101\114\114\111\114 \119\104\101\110 \119\114\097\112\112\105\110\103 \105\110\118\097\108\105\100 \116\121\112\101 \119\105\116\104 \108\097\122\121\040\041",function()local k=a(function()return M(42)end)local B=i.create(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(k)),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\076\111\097\100\105\110\103\046\046\046"});(H.delay(0)):await()B.update(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(k)));(v(F)).toFlushAndThrow("\069\108\101\109\101\110\116 \116\121\112\101 \105\115 \105\110\118\097\108\105\100\046 \082\101\099\101\105\118\101\100 \097 \112\114\111\109\105\115\101 \116\104\097\116 \114\101\115\111\108\118\101\115 \116\111\058 \052\050\046 \076\097\122\121 \101\108\101\109\101\110\116 \116\121\112\101 \109\117\115\116 \114\101\115\111\108\118\101 \116\111 \097 \099\108\097\115\115 \111\114 \102\117\110\099\116\105\111\110\046")end)G("\116\104\114\111\119\115 \119\105\116\104 \097 \117\115\101\102\117\108 \101\114\114\111\114 \119\104\101\110 \119\114\097\112\112\105\110\103 \108\097\122\121\040\041 \109\117\108\116\105\112\108\101 \116\105\109\101\115",function()local k=a(function()return M(U)end)local B=a(function()return M(k)end)local z=i.create(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(B,{text="\072\101\108\108\111"})),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\076\111\097\100\105\110\103\046\046\046"});(v(z)).never.toMatchRenderedOutput("\072\101\108\108\111");(H.delay(0)):await()z.update(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(B,{text="\072\101\108\108\111"})))local S=_G.__DEV__ and" \068\105\100 \121\111\117 \119\114\097\112 \097 \099\111\109\112\111\110\101\110\116 \105\110 \082\101\097\099\116\046\108\097\122\121\040\041 \109\111\114\101 \116\104\097\110 \111\110\099\101\063"or"";(v(F)).toFlushAndThrow("\076\097\122\121 \101\108\101\109\101\110\116 \116\121\112\101 \109\117\115\116 \114\101\115\111\108\118\101 \116\111 \097 \099\108\097\115\115 \111\114 \102\117\110\099\116\105\111\110\046"..S)end)G("\119\097\114\110\115 \097\098\111\117\116 \100\101\102\105\110\105\110\103 \112\114\111\112\084\121\112\101\115 \111\110 \116\104\101 \111\117\116\101\114 \119\114\097\112\112\101\114",function()local d=a(function()return M(U)end);(v(function()d.propTypes={hello=function() end}end)).toErrorDev("\082\101\097\099\116\046\108\097\122\121\040\046\046\046\041\058 \073\116 \105\115 \110\111\116 \115\117\112\112\111\114\116\101\100 \116\111 \097\115\115\105\103\110 \096\112\114\111\112\084\121\112\101\115\096 \116\111 \097 \108\097\122\121 \099\111\109\112\111\110\101\110\116 \105\109\112\111\114\116\046 \069\105\116\104\101\114 \115\112\101\099\105\102\121 \116\104\101\109 \119\104\101\114\101 \116\104\101 \099\111\109\112\111\110\101\110\116 \105\115 \100\101\102\105\110\101\100\044 \111\114 \099\114\101\097\116\101 \097 \119\114\097\112\112\105\110\103 \099\111\109\112\111\110\101\110\116 \097\114\111\117\110\100 \105\116\046",{withoutStack=true})end)r("\105\110\099\108\117\100\101\115 \108\097\122\121-\108\111\097\100\101\100 \099\111\109\112\111\110\101\110\116 \105\110 \119\097\114\110\105\110\103 \115\116\097\099\107",function()local k=a(function()F.unstable_yieldValue("\083\116\097\114\116\101\100 \108\111\097\100\105\110\103")local i=function(i)return d.createElement("\100\105\118",nil,{d.createElement(U,{text="\065"}),d.createElement(U,{text="\066"})})end return M(i)end)local B=i.create(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(k)),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\083\116\097\114\116\101\100 \108\111\097\100\105\110\103","\076\111\097\100\105\110\103\046\046\046"});(v(B)).never.toMatchRenderedOutput(d.createElement("\100\105\118",nil,"\065\066"));(H.delay(0)):await();(v(function()(v(F)).toFlushAndYield({"\065","\066"})end)).toErrorDev("    \105\110 \084\101\120\116 \040\097\116 \042\042\041\n    \105\110 \070\111\111 \040\097\116 \042\042\041");(v(B)).toMatchRenderedOutput(d.createElement("\100\105\118",nil,"\065\066"))end)G("\115\117\112\112\111\114\116\115 \099\108\097\115\115 \097\110\100 \102\111\114\119\097\114\100\082\101\102 \099\111\109\112\111\110\101\110\116\115",function()local k=a(function()local i=d.Component:extend("\070\111\111")function i.render(i)return d.createElement(U,{text="\070\111\111"})end return M(i)end)local B=a(function()local i=d.Component:extend("\066\097\114")function i.render(i)return d.createElement(U,{text="\066\097\114"})end return M(d.forwardRef(function(k,b)F.unstable_yieldValue("\102\111\114\119\097\114\100\082\101\102")return d.createElement(i,{ref=b})end))end)local z=d.createRef()local S=i.create(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(k),d.createElement(B,{ref=z})),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\076\111\097\100\105\110\103\046\046\046"});(v(S)).never.toMatchRenderedOutput("\070\111\111\066\097\114");(v(z.current)).toBe(nil);(H.delay(0)):await();(v(F)).toFlushAndYield({"\070\111\111","\102\111\114\119\097\114\100\082\101\102";"\066\097\114"});(v(S)).toMatchRenderedOutput("\070\111\111\066\097\114");(v(z.current)).never.toBe(nil)end)G("\119\097\114\110\115 \097\098\111\117\116 \114\101\102 \111\110 \102\117\110\099\116\105\111\110\115 \102\111\114 \108\097\122\121-\108\111\097\100\101\100 \099\111\109\112\111\110\101\110\116\115",function()local k=a(function()local i=function(i)return d.createElement("\100\105\118")end return M(i)end)local B=d.createRef()i.create(d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(k,{ref=B})),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\076\111\097\100\105\110\103\046\046\046"});(H.delay(0)):await();(v(function()(v(F)).toFlushAndYield({})end)).toErrorDev("\070\117\110\099\116\105\111\110 \099\111\109\112\111\110\101\110\116\115 \099\097\110\110\111\116 \098\101 \103\105\118\101\110 \114\101\102\115")end)r("\115\104\111\117\108\100 \101\114\114\111\114 \119\105\116\104 \097 \099\111\109\112\111\110\101\110\116 \115\116\097\099\107 \110\097\109\105\110\103 \116\104\101 \114\101\115\111\108\118\101\100 \099\111\109\112\111\110\101\110\116",function()local k local z=a(function()return M(function()error(B("\111\104 \110\111"))end)end)local S=d.Component:extend("\069\114\114\111\114\066\111\117\110\100\097\114\121")function S.init(d)d.state={error=nil}end function S.componentDidCatch(F,d,i)k=O(i.componentStack)F:setState({error=d})end function S.render(d)if d.state.error then return nil else return d.props.children end end i.create(d.createElement(S,nil,d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(z,{text="\072\105"}))),{unstable_isConcurrent=true});(v(F)).toFlushAndYield({"\076\111\097\100\105\110\103\046\046\046"})_=pcall(function()(H.delay(0)):await()end);(v(F)).toFlushAndYield({});(v(k)).toContain("\105\110 \082\101\115\111\108\118\101\100\084\101\120\116")end)r("\115\104\111\117\108\100 \101\114\114\111\114 \119\105\116\104 \097 \099\111\109\112\111\110\101\110\116 \115\116\097\099\107 \099\111\110\116\097\105\110\105\110\103 \076\097\122\121 \105\102 \117\110\114\101\115\111\108\118\101\100",function()local k local M=a(function()return{andThen=function(d,i)i(B("\111\104 \110\111"))end}end)local z=d.Component:extend("\069\114\114\111\114\066\111\117\110\100\097\114\121")function z.init(d)d.state={error_=nil}end function z.componentDidCatch(F,d,i)k=O(i.componentStack)F:setState({error_=d})end function z.render(d)return(function()if d.state.error_ then return nil end return d.props.children end)()end i.create(d.createElement(z,nil,d.createElement(b,{fallback=d.createElement(U,{text="\076\111\097\100\105\110\103\046\046\046"})},d.createElement(M,{text="\072\105"}))));(v(F)).toHaveYielded({});(v(k)).toContain("\105\110 \076\097\122\121")end)end)
-- local PropTypes
local React
local ReactTestRenderer
local Scheduler
local ReactFeatureFlags
local Suspense
local lazy

local LuauPolyfill = require("@pkg/@jsdotlua/luau-polyfill")
local Error = LuauPolyfill.Error
local setTimeout = LuauPolyfill.setTimeout
local Promise = require("@pkg/@jsdotlua/promise")
local JestGlobals = require("@pkg/@jsdotlua/jest-globals")
local jestExpect = JestGlobals.expect
local describe = JestGlobals.describe
local beforeEach = JestGlobals.beforeEach
local it = JestGlobals.it
local xit = JestGlobals.xit
local jest = JestGlobals.jest

local function normalizeCodeLocInfo(str)
	if typeof(str) ~= "string" then
		return str
	end

	str = string.gsub(str, "Check your code at .*:%d+", "Check your code at **")
	-- ROBLOX deviation: In roblox/luau, we're using the stack frame from luau,
	-- which looks like:
	--     in Component (at ModulePath.FileName.lua:123)
	return (string.gsub(str, "\n    in ([%w%-%._]+)[^\n]*", "\n    in %1 (at **)"))
end

-- ROBLOX Test Noise: in upstream, jest setup config makes these tests hide
-- the error boundary warnings they trigger (scripts/jest/setupTests.js:72)
describe("ReactLazy", function()
	beforeEach(function()
		jest.resetModules()

		ReactFeatureFlags = require("@pkg/@jsdotlua/shared").ReactFeatureFlags
		ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false
		-- PropTypes = require('prop-types');
		React = require("@pkg/@jsdotlua/react")
		Suspense = React.Suspense
		lazy = React.lazy
		ReactTestRenderer = require("@pkg/@jsdotlua/react-test-renderer")
		Scheduler = require("@pkg/@jsdotlua/scheduler")
	end)

	-- local verifyInnerPropTypesAreChecked = _async(function(Add)
	--     local LazyAdd = lazy(function()
	--         return fakeImport(Add)
	--     end)

	--     jestExpect(function()
	--         LazyAdd.propTypes = {}
	--     end).toErrorDev('React.lazy(...): It is not supported to assign `propTypes` to ' + 'a lazy component import. Either specify them where the component ' + 'is defined, or create a wrapping component around it.', {withoutStack = true})

	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(LazyAdd, {
	--         inner = '2',
	--         outer = '2',
	--     })), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('22')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(function()
	--             Scheduler.unstable_flushAll()
	--         end).toErrorDev({
	--             'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('22')
	--         jestExpect(function()
	--             root.update(React.createElement(Suspense, {
	--                 fallback = React.createElement(Text, {
	--                     text = 'Loading...',
	--                 }),
	--             }, React.createElement(LazyAdd, {
	--                 inner = false,
	--                 outer = false,
	--             })))
	--             jestExpect(Scheduler).toFlushWithoutYielding()
	--         end).toErrorDev('Invalid prop `inner` of type `boolean` supplied to `Add`, expected `number`.')
	--         jestExpect(root).toMatchRenderedOutput('0')
	--     end)
	-- end)

	local fakeImport = function(result)
		-- ROBLOX FIXME: delay(0) because resolved promises are andThen'd on the same tick cycle
		-- remove once addressed in polyfill
		return Promise.delay(0):andThen(function()
			return { default = result }
		end)
	end

	local function Text(props)
		Scheduler.unstable_yieldValue(props.text)

		return props.text
	end

	local delay_ = function(ms)
		return Promise.new(function(resolve)
			return setTimeout(function()
				return resolve()
			end, ms)
		end)
	end

	it("suspends until module has loaded", function()
		local LazyText = lazy(function()
			return fakeImport(Text)
		end)
		local root = ReactTestRenderer.create(
			React.createElement(
				Suspense,
				{
					fallback = React.createElement(Text, {
						text = "Loading...",
					}),
				},
				React.createElement(LazyText, {
					text = "Hi",
				})
			),
			{ unstable_isConcurrent = true }
		)

		jestExpect(Scheduler).toFlushAndYield({
			"Loading...",
		})

		jestExpect(root).never.toMatchRenderedOutput("Hi")

		-- ROBLOX FIXME: delay by one frame is current best translation of `await Promise.resolve()`
		Promise.delay(0):await()

		jestExpect(Scheduler).toFlushAndYield({
			"Hi",
		})
		jestExpect(root).toMatchRenderedOutput("Hi")

		-- Should not suspend on update
		root.update(React.createElement(
			Suspense,
			{
				fallback = React.createElement(Text, {
					text = "Loading...",
				}),
			},
			React.createElement(LazyText, {
				text = "Hi again",
			})
		))
		jestExpect(Scheduler).toFlushAndYield({
			"Hi again",
		})
		jestExpect(root).toMatchRenderedOutput("Hi again")
	end)
	it("can resolve synchronously without suspending", function()
		local LazyText = lazy(function()
			return {
				andThen = function(self, cb)
					cb({ default = Text })
				end,
			}
		end)
		local root = ReactTestRenderer.create(React.createElement(
			Suspense,
			{
				fallback = React.createElement(Text, {
					text = "Loading...",
				}),
			},
			React.createElement(LazyText, {
				text = "Hi",
			})
		))

		jestExpect(Scheduler).toHaveYielded({
			"Hi",
		})
		jestExpect(root).toMatchRenderedOutput("Hi")
	end)
	it("can reject synchronously without suspending", function()
		local LazyText = lazy(function()
			return {
				andThen = function(self, resolve, reject)
					reject(Error("oh no"))
				end,
			}
		end)
		local ErrorBoundary = React.Component:extend("ErrorBoundary")

		function ErrorBoundary:init()
			self.state = {}
		end
		function ErrorBoundary.getDerivedStateFromError(error_)
			return {
				message = error_.message,
			}
		end
		function ErrorBoundary:render()
			if self.state.message then
				return string.format("Error: %s", self.state.message)
			end
			return self.props.children
		end

		local root = ReactTestRenderer.create(
			React.createElement(
				ErrorBoundary,
				nil,
				React.createElement(
					Suspense,
					{
						fallback = React.createElement(Text, {
							text = "Loading...",
						}),
					},
					React.createElement(LazyText, {
						text = "Hi",
					})
				)
			)
		)

		jestExpect(Scheduler).toHaveYielded({})
		jestExpect(root).toMatchRenderedOutput("Error: oh no")
	end)
	it("multiple lazy components", function()
		local function Foo()
			return React.createElement(Text, {
				text = "Foo",
			})
		end
		local function Bar()
			return React.createElement(Text, {
				text = "Bar",
			})
		end

		local promiseForFoo = delay_(100):andThen(function()
			return fakeImport(Foo)
		end)
		local promiseForBar = delay_(500):andThen(function()
			return fakeImport(Bar)
		end)
		local LazyFoo = lazy(function()
			return promiseForFoo
		end)
		local LazyBar = lazy(function()
			return promiseForBar
		end)
		local root = ReactTestRenderer.create(
			React.createElement(Suspense, {
				fallback = React.createElement(Text, {
					text = "Loading...",
				}),
			}, React.createElement(LazyFoo), React.createElement(LazyBar)),
			{ unstable_isConcurrent = true }
		)

		jestExpect(Scheduler).toFlushAndYield({
			"Loading...",
		})
		jestExpect(root).never.toMatchRenderedOutput("FooBar")
		-- ROBLOX deviation START: not needed
		-- jest.advanceTimersByTime(100)
		-- ROBLOX deviation END

		promiseForFoo:await()
		jestExpect(Scheduler).toFlushAndYield({
			"Foo",
		})
		jestExpect(root).never.toMatchRenderedOutput("FooBar")
		-- ROBLOX deviation START: not needed
		-- jest.advanceTimersByTime(500)
		-- ROBLOX deviation END

		promiseForBar:await()

		jestExpect(Scheduler).toFlushAndYield({
			"Foo",
			"Bar",
		})
		jestExpect(root).toMatchRenderedOutput("FooBar")
	end)
	-- it('does not support arbitrary promises, only module objects', _async(function()
	--     spyOnDev(console, 'error')

	--     local LazyText = lazy(function()
	--         return _await(Text)
	--     end)
	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(LazyText, {
	--         text = 'Hi',
	--     })), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('Hi')

	--     return _await(Promise.resolve(), function()
	--         if __DEV__ then
	--             jestExpect(console.error).toHaveBeenCalledTimes(1)
	--             jestExpect(console.error.calls.argsFor(0)[0]).toContain('Expected the result of a dynamic import() call')
	--         end

	--         jestExpect(Scheduler).toFlushAndThrow('Element type is invalid')
	--     end)
	-- end))
	it("throws if promise rejects", function()
		local badImport = function()
			-- ROBLOX FIXME: delay(0) because resolved promises are andThen'd on the same tick cycle
			-- remove once addressed in polyfill
			return Promise.delay(0):andThen(function()
				error(Error("Bad network"))
			end)
		end
		local LazyText = lazy(badImport)
		local root = ReactTestRenderer.create(
			React.createElement(
				Suspense,
				{
					fallback = React.createElement(Text, {
						text = "Loading...",
					}),
				},
				React.createElement(LazyText, {
					text = "Hi",
				})
			),
			{ unstable_isConcurrent = true }
		)

		-- ROBLOX TODO: this gets the 'Bad Network' error, Suspense fallback 'Loading...' not rendered first
		jestExpect(Scheduler).toFlushAndYield({
			"Loading...",
		})
		jestExpect(root).never.toMatchRenderedOutput("Hi")

		local _, _ = pcall(function()
			-- ROBLOX FIXME: delay by one frame is current best translation of `await Promise.resolve()`
			Promise.delay(0):await()
		end)

		jestExpect(Scheduler).toFlushAndThrow("Bad network")
	end)

	it("mount and reorder", function()
		local Child = React.Component:extend("Child")

		function Child:componentDidMount()
			Scheduler.unstable_yieldValue("Did mount: " .. self.props.label)
		end
		function Child:componentDidUpdate()
			Scheduler.unstable_yieldValue("Did update: " .. self.props.label)
		end
		function Child:render()
			return React.createElement(Text, {
				text = self.props.label,
			})
		end

		local LazyChildA = lazy(function()
			return fakeImport(Child)
		end)
		local LazyChildB = lazy(function()
			return fakeImport(Child)
		end)

		local function Parent(props)
			local children
			if props.swap then
				children = {
					React.createElement(LazyChildB, {
						key = "B",
						label = "B",
					}),
					React.createElement(LazyChildA, {
						key = "A",
						label = "A",
					}),
				}
			else
				children = {
					React.createElement(LazyChildA, {
						key = "A",
						label = "A",
					}),
					React.createElement(LazyChildB, {
						key = "B",
						label = "B",
					}),
				}
			end
			return React.createElement(Suspense, {
				fallback = React.createElement(Text, {
					text = "Loading...",
				}),
			}, children)
		end

		local root = ReactTestRenderer.create(
			React.createElement(Parent, { swap = false }),
			{ unstable_isConcurrent = true }
		)

		jestExpect(Scheduler).toFlushAndYield({
			"Loading...",
		})
		jestExpect(root).never.toMatchRenderedOutput("AB")

		-- ROBLOX FIXME: these are not Promises, so I'm not sure what usptream awaiting them means
		-- LazyChildA:await()
		-- LazyChildB:await()
		-- ROBLOX FIXME: these awaits let the test get further, but values in mountLazyComponent() are way off
		Promise.delay(0):await()
		Promise.delay(0):await()

		jestExpect(Scheduler).toFlushAndYield({
			"A",
			"B",
			"Did mount: A",
			"Did mount: B",
		})
		jestExpect(root).toMatchRenderedOutput("AB")

		-- Swap the potsition of A and B
		root.update(React.createElement(Parent, { swap = true }))
		jestExpect(Scheduler).toFlushAndYield({
			"B",
			"A",
			"Did update: B",
			"Did update: A",
		})
		jestExpect(root).toMatchRenderedOutput("BA")
	end)
	-- it('resolves defaultProps, on mount and update', _async(function()
	--     local function T(props)
	--         return React.createElement(Text, props)
	--     end

	--     T.defaultProps = {
	--         text = 'Hi',
	--     }

	--     local LazyText = lazy(function()
	--         return fakeImport(T)
	--     end)
	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(LazyText)), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('Hi')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'Hi',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('Hi')

	--         T.defaultProps = {
	--             text = 'Hi again',
	--         }

	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyText)))
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'Hi again',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('Hi again')
	--     end)
	-- end))
	-- it('resolves defaultProps without breaking memoization', _async(function()
	--     local function LazyImpl(props)
	--         Scheduler.unstable_yieldValue('Lazy')

	--         return React.createElement(React.Fragment, nil, React.createElement(Text, {
	--             text = props.siblingText,
	--         }), props.children)
	--     end

	--     LazyImpl.defaultProps = {
	--         siblingText = 'Sibling',
	--     }

	--     local Lazy = lazy(function()
	--         return fakeImport(LazyImpl)
	--     end)
	--     local Stateful = {}
	--     local StatefulMetatable = {__index = Stateful}

	--     function Stateful.new()
	--         local self = setmetatable({}, StatefulMetatable)
	--         local _temp2

	--         return
	--     end
	--     function Stateful:render()
	--         return React.createElement(Text, {
	--             text = self.state.text,
	--         })
	--     end

	--     local stateful = React.createRef()
	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(Lazy, nil, React.createElement(Stateful, {ref = stateful}))), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('SiblingA')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'Lazy',
	--             'Sibling',
	--             'A',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('SiblingA')
	--         stateful.current.setState({
	--             text = 'B',
	--         })
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'B',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('SiblingB')
	--     end)
	-- end))
	-- it('resolves defaultProps without breaking bailout due to unchanged props and state, #17151', _async(function()
	--     local LazyImpl = {}
	--     local LazyImplMetatable = {__index = LazyImpl}

	--     function LazyImpl:render()
	--         local text = string.format('%s: %s', self.props.label, self.props.value)

	--         return React.createElement(Text, {text = text})
	--     end

	--     LazyImpl.defaultProps = {value = 0}

	--     local Lazy = lazy(function()
	--         return fakeImport(LazyImpl)
	--     end)
	--     local instance1 = React.createRef()
	--     local instance2 = React.createRef()
	--     local root = ReactTestRenderer.create(React.createElement(React.Fragment, nil, React.createElement(LazyImpl, {
	--         ref = instance1,
	--         label = 'Not lazy',
	--     }), React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(Lazy, {
	--         ref = instance2,
	--         label = 'Lazy',
	--     }))), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Not lazy: 0',
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('Not lazy: 0Lazy: 0')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'Lazy: 0',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0')
	--         instance1.current.setState(nil)
	--         jestExpect(Scheduler).toFlushAndYield({})
	--         jestExpect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0')
	--         instance2.current.setState(nil)
	--         jestExpect(Scheduler).toFlushAndYield({})
	--         jestExpect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0')
	--     end)
	-- end))
	-- it('resolves defaultProps without breaking bailout in PureComponent, #17151', _async(function()
	--     local LazyImpl = {}
	--     local LazyImplMetatable = {__index = LazyImpl}

	--     function LazyImpl.new()
	--         local self = setmetatable({}, LazyImplMetatable)
	--         local _temp3

	--         return
	--     end
	--     function LazyImpl:render()
	--         local text = string.format('%s: %s', self.props.label, self.props.value)

	--         return React.createElement(Text, {text = text})
	--     end

	--     LazyImpl.defaultProps = {value = 0}

	--     local Lazy = lazy(function()
	--         return fakeImport(LazyImpl)
	--     end)
	--     local instance1 = React.createRef()
	--     local instance2 = React.createRef()
	--     local root = ReactTestRenderer.create(React.createElement(React.Fragment, nil, React.createElement(LazyImpl, {
	--         ref = instance1,
	--         label = 'Not lazy',
	--     }), React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(Lazy, {
	--         ref = instance2,
	--         label = 'Lazy',
	--     }))), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Not lazy: 0',
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('Not lazy: 0Lazy: 0')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'Lazy: 0',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0')
	--         instance1.current.setState({})
	--         jestExpect(Scheduler).toFlushAndYield({})
	--         jestExpect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0')
	--         instance2.current.setState({})
	--         jestExpect(Scheduler).toFlushAndYield({})
	--         jestExpect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0')
	--     end)
	-- end))
	-- it('sets defaultProps for modern lifecycles', _async(function()
	--     local C = {}
	--     local CMetatable = {__index = C}

	--     function C.getDerivedStateFromProps(props)
	--         Scheduler.unstable_yieldValue(string.format('getDerivedStateFromProps: %s', props.text))

	--         return nil
	--     end
	--     function C.new(props)
	--         local self = setmetatable({}, CMetatable)

	--         self.state = {}

	--         Scheduler.unstable_yieldValue(string.format('constructor: %s', self.props.text))
	--     end
	--     function C:componentDidMount()
	--         Scheduler.unstable_yieldValue(string.format('componentDidMount: %s', self.props.text))
	--     end
	--     function C:componentDidUpdate(prevProps)
	--         Scheduler.unstable_yieldValue(string.format('componentDidUpdate: %s -> %s', prevProps.text, self.props.text))
	--     end
	--     function C:componentWillUnmount()
	--         Scheduler.unstable_yieldValue(string.format('componentWillUnmount: %s', self.props.text))
	--     end
	--     function C:shouldComponentUpdate(nextProps)
	--         Scheduler.unstable_yieldValue(string.format('shouldComponentUpdate: %s -> %s', self.props.text, nextProps.text))

	--         return true
	--     end
	--     function C:getSnapshotBeforeUpdate(prevProps)
	--         Scheduler.unstable_yieldValue(string.format('getSnapshotBeforeUpdate: %s -> %s', prevProps.text, self.props.text))

	--         return nil
	--     end
	--     function C:render()
	--         return React.createElement(Text, {
	--             text = self.props.text + self.props.num,
	--         })
	--     end

	--     C.defaultProps = {
	--         text = 'A',
	--     }

	--     local LazyClass = lazy(function()
	--         return fakeImport(C)
	--     end)
	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(LazyClass, {num = 1})), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('A1')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'constructor: A',
	--             'getDerivedStateFromProps: A',
	--             'A1',
	--             'componentDidMount: A',
	--         })
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyClass, {num = 2})))
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'getDerivedStateFromProps: A',
	--             'shouldComponentUpdate: A -> A',
	--             'A2',
	--             'getSnapshotBeforeUpdate: A -> A',
	--             'componentDidUpdate: A -> A',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('A2')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyClass, {num = 3})))
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'getDerivedStateFromProps: A',
	--             'shouldComponentUpdate: A -> A',
	--             'A3',
	--             'getSnapshotBeforeUpdate: A -> A',
	--             'componentDidUpdate: A -> A',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('A3')
	--     end)
	-- end))
	-- it('sets defaultProps for legacy lifecycles', _async(function()
	--     local C = {}
	--     local CMetatable = {__index = C}

	--     function C.new()
	--         local self = setmetatable({}, CMetatable)
	--         local _temp4

	--         return
	--     end
	--     function C:UNSAFE_componentWillMount()
	--         Scheduler.unstable_yieldValue(string.format('UNSAFE_componentWillMount: %s', self.props.text))
	--     end
	--     function C:UNSAFE_componentWillUpdate(nextProps)
	--         Scheduler.unstable_yieldValue(string.format('UNSAFE_componentWillUpdate: %s -> %s', self.props.text, nextProps.text))
	--     end
	--     function C:UNSAFE_componentWillReceiveProps(nextProps)
	--         Scheduler.unstable_yieldValue(string.format('UNSAFE_componentWillReceiveProps: %s -> %s', self.props.text, nextProps.text))
	--     end
	--     function C:render()
	--         return React.createElement(Text, {
	--             text = self.props.text + self.props.num,
	--         })
	--     end

	--     C.defaultProps = {
	--         text = 'A',
	--     }

	--     local LazyClass = lazy(function()
	--         return fakeImport(C)
	--     end)
	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(LazyClass, {num = 1})))

	--     jestExpect(Scheduler).toHaveYielded({
	--         'Loading...',
	--     })
	--     jestExpect(Scheduler).toFlushAndYield({})
	--     jestExpect(root).toMatchRenderedOutput('Loading...')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(Scheduler).toHaveYielded({})
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyClass, {num = 2})))
	--         jestExpect(Scheduler).toHaveYielded({
	--             'UNSAFE_componentWillMount: A',
	--             'A2',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('A2')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyClass, {num = 3})))
	--         jestExpect(Scheduler).toHaveYielded({
	--             'UNSAFE_componentWillReceiveProps: A -> A',
	--             'UNSAFE_componentWillUpdate: A -> A',
	--             'A3',
	--         })
	--         jestExpect(Scheduler).toFlushAndYield({})
	--         jestExpect(root).toMatchRenderedOutput('A3')
	--     end)
	-- end))
	-- it('resolves defaultProps on the outer wrapper but warns', _async(function()
	--     local function T(props)
	--         Scheduler.unstable_yieldValue(props.inner + ' ' + props.outer)

	--         return props.inner + ' ' + props.outer
	--     end

	--     T.defaultProps = {
	--         inner = 'Hi',
	--     }

	--     local LazyText = lazy(function()
	--         return fakeImport(T)
	--     end)

	--     jestExpect(function()
	--         LazyText.defaultProps = {
	--             outer = 'Bye',
	--         }
	--     end).toErrorDev('React.lazy(...): It is not supported to assign `defaultProps` to ' + 'a lazy component import. Either specify them where the component ' + 'is defined, or create a wrapping component around it.', {withoutStack = true})

	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(LazyText)), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('Hi Bye')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'Hi Bye',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('Hi Bye')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyText, {
	--             outer = 'World',
	--         })))
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'Hi World',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('Hi World')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyText, {
	--             inner = 'Friends',
	--         })))
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'Friends Bye',
	--         })
	--         jestExpect(root).toMatchRenderedOutput('Friends Bye')
	--     end)
	-- end))
	it("throws with a useful error when wrapping invalid type with lazy()", function()
		local BadLazy = lazy(function()
			return fakeImport(42)
		end)
		local root = ReactTestRenderer.create(
			React.createElement(Suspense, {
				fallback = React.createElement(Text, {
					text = "Loading...",
				}),
			}, React.createElement(BadLazy)),
			{ unstable_isConcurrent = true }
		)

		jestExpect(Scheduler).toFlushAndYield({
			"Loading...",
		})

		Promise.delay(0):await()

		root.update(React.createElement(Suspense, {
			fallback = React.createElement(Text, {
				text = "Loading...",
			}),
		}, React.createElement(BadLazy)))
		jestExpect(Scheduler).toFlushAndThrow(
			"Element type is invalid. Received a promise that resolves to: 42. "
				.. "Lazy element type must resolve to a class or function."
		)
	end)
	it("throws with a useful error when wrapping lazy() multiple times", function()
		local Lazy1 = lazy(function()
			return fakeImport(Text)
		end)
		local Lazy2 = lazy(function()
			return fakeImport(Lazy1)
		end)
		local root = ReactTestRenderer.create(
			React.createElement(
				Suspense,
				{
					fallback = React.createElement(Text, {
						text = "Loading...",
					}),
				},
				React.createElement(Lazy2, {
					text = "Hello",
				})
			),
			{ unstable_isConcurrent = true }
		)

		jestExpect(Scheduler).toFlushAndYield({
			"Loading...",
		})
		jestExpect(root).never.toMatchRenderedOutput("Hello")

		-- ROBLOX FIXME: delay by one frame is current best translation of `await Promise.resolve()`
		Promise.delay(0):await()

		root.update(React.createElement(
			Suspense,
			{
				fallback = React.createElement(Text, {
					text = "Loading...",
				}),
			},
			React.createElement(Lazy2, {
				text = "Hello",
			})
		))

		local moreThanOnce = _G.__DEV__
				and " Did you wrap a component in React.lazy() more than once?"
			or ""

		-- ROBLOX FIXME: Using substring of error, not RegExp because currently missing from LuauPolyfill.
		-- Still tests optional dev error and substring of main error, but missing the first sentence below
		-- 'Element type is invalid. Received a promise that resolves to: table: '
		jestExpect(Scheduler).toFlushAndThrow(
			"Lazy element type must resolve to a class or function." .. moreThanOnce
		)
	end)
	it("warns about defining propTypes on the outer wrapper", function()
		local LazyText = lazy(function()
			return fakeImport(Text)
		end)

		jestExpect(function()
			LazyText.propTypes = {
				hello = function() end,
			}
		end).toErrorDev(
			"React.lazy(...): It is not supported to assign `propTypes` to "
				.. "a lazy component import. Either specify them where the component "
				.. "is defined, or create a wrapping component around it.",
			{ withoutStack = true }
		)
	end)
	-- it('respects propTypes on function component with defaultProps', _async(function()
	--     local function Add(props)
	--         jestExpect(props.innerWithDefault).toBe(42)

	--         return props.inner + props.outer
	--     end

	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--         innerWithDefault = PropTypes.number.isRequired,
	--     }
	--     Add.defaultProps = {innerWithDefault = 42}

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(Add))
	-- end))
	-- it('respects propTypes on function component without defaultProps', _async(function()
	--     local function Add(props)
	--         return props.inner + props.outer
	--     end

	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--     }

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(Add))
	-- end))
	-- it('respects propTypes on class component with defaultProps', _async(function()
	--     local Add = {}
	--     local AddMetatable = {__index = Add}

	--     function Add:render()
	--         jestExpect(self.props.innerWithDefault).toBe(42)

	--         return self.props.inner + self.props.outer
	--     end

	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--         innerWithDefault = PropTypes.number.isRequired,
	--     }
	--     Add.defaultProps = {innerWithDefault = 42}

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(Add))
	-- end))
	-- it('respects propTypes on class component without defaultProps', _async(function()
	--     local Add = {}
	--     local AddMetatable = {__index = Add}

	--     function Add:render()
	--         return self.props.inner + self.props.outer
	--     end

	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--     }

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(Add))
	-- end))
	-- it('respects propTypes on forwardRef component with defaultProps', _async(function()
	--     local Add = React.forwardRef(function(props, ref)
	--         jestExpect(props.innerWithDefault).toBe(42)

	--         return props.inner + props.outer
	--     end)

	--     Add.displayName = 'Add'
	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--         innerWithDefault = PropTypes.number.isRequired,
	--     }
	--     Add.defaultProps = {innerWithDefault = 42}

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(Add))
	-- end))
	-- it('respects propTypes on forwardRef component without defaultProps', _async(function()
	--     local Add = React.forwardRef(function(props, ref)
	--         return props.inner + props.outer
	--     end)

	--     Add.displayName = 'Add'
	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--     }

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(Add))
	-- end))
	-- it('respects propTypes on outer memo component with defaultProps', _async(function()
	--     local Add = function(props)
	--         jestExpect(props.innerWithDefault).toBe(42)

	--         return props.inner + props.outer
	--     end

	--     Add = React.memo(Add)
	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--         innerWithDefault = PropTypes.number.isRequired,
	--     }
	--     Add.defaultProps = {innerWithDefault = 42}

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(Add))
	-- end))
	-- it('respects propTypes on outer memo component without defaultProps', _async(function()
	--     local Add = function(props)
	--         return props.inner + props.outer
	--     end

	--     Add = React.memo(Add)
	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--     }

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(Add))
	-- end))
	-- it('respects propTypes on inner memo component with defaultProps', _async(function()
	--     local Add = function(props)
	--         jestExpect(props.innerWithDefault).toBe(42)

	--         return props.inner + props.outer
	--     end

	--     Add.displayName = 'Add'
	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--         innerWithDefault = PropTypes.number.isRequired,
	--     }
	--     Add.defaultProps = {innerWithDefault = 42}

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(React.memo(Add)))
	-- end))
	-- it('respects propTypes on inner memo component without defaultProps', _async(function()
	--     local Add = function(props)
	--         return props.inner + props.outer
	--     end

	--     Add.displayName = 'Add'
	--     Add.propTypes = {
	--         inner = PropTypes.number.isRequired,
	--     }

	--     return _awaitIgnored(verifyInnerPropTypesAreChecked(React.memo(Add)))
	-- end))
	-- it('uses outer resolved props for validating propTypes on memo', _async(function()
	--     local T = function(props)
	--         return React.createElement(Text, {
	--             text = props.text,
	--         })
	--     end

	--     T.defaultProps = {
	--         text = 'Inner default text',
	--     }
	--     T = React.memo(T)
	--     T.propTypes = {
	--         text = PropTypes.string.isRequired,
	--     }

	--     local LazyText = lazy(function()
	--         return fakeImport(T)
	--     end)
	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(LazyText)), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('Inner default text')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(function()
	--             jestExpect(Scheduler).toFlushAndYield({
	--                 'Inner default text',
	--             })
	--         end).toErrorDev('The prop `text` is marked as required in `T`, but its value is `undefined`')
	--         jestExpect(root).toMatchRenderedOutput('Inner default text')
	--         jestExpect(function()
	--             root.update(React.createElement(Suspense, {
	--                 fallback = React.createElement(Text, {
	--                     text = 'Loading...',
	--                 }),
	--             }, React.createElement(LazyText, {text = nil})))
	--             jestExpect(Scheduler).toFlushAndYield({nil})
	--         end).toErrorDev('The prop `text` is marked as required in `T`, but its value is `null`')
	--         jestExpect(root).toMatchRenderedOutput(nil)
	--     end)
	-- end))

	-- ROBLOX TODO: wrong component stack, not resolving past Suspense/Lazy wrappers
	xit("includes lazy-loaded component in warning stack", function()
		local LazyFoo = lazy(function()
			Scheduler.unstable_yieldValue("Started loading")

			local Foo = function(props)
				return React.createElement("div", nil, {
					React.createElement(Text, {
						text = "A",
					}),
					React.createElement(Text, {
						text = "B",
					}),
				})
			end

			return fakeImport(Foo)
		end)
		local root = ReactTestRenderer.create(
			React.createElement(Suspense, {
				fallback = React.createElement(Text, {
					text = "Loading...",
				}),
			}, React.createElement(LazyFoo)),
			{ unstable_isConcurrent = true }
		)

		jestExpect(Scheduler).toFlushAndYield({
			"Started loading",
			"Loading...",
		})
		jestExpect(root).never.toMatchRenderedOutput(
			React.createElement("div", nil, "AB")
		)

		Promise.delay(0):await()

		jestExpect(function()
			jestExpect(Scheduler).toFlushAndYield({
				"A",
				"B",
			})
		end).toErrorDev("    in Text (at **)\n" .. "    in Foo (at **)")
		jestExpect(root).toMatchRenderedOutput(React.createElement("div", nil, "AB"))
	end)

	it("supports class and forwardRef components", function()
		local LazyClass = lazy(function()
			local Foo = React.Component:extend("Foo")

			function Foo:render()
				return React.createElement(Text, {
					text = "Foo",
				})
			end

			return fakeImport(Foo)
		end)

		local LazyForwardRef = lazy(function()
			local Bar = React.Component:extend("Bar")

			function Bar:render()
				return React.createElement(Text, {
					text = "Bar",
				})
			end

			return fakeImport(React.forwardRef(function(props, ref)
				Scheduler.unstable_yieldValue("forwardRef")

				return React.createElement(Bar, { ref = ref })
			end))
		end)

		local ref = React.createRef()
		local root = ReactTestRenderer.create(
			React.createElement(
				Suspense,
				{
					fallback = React.createElement(Text, {
						text = "Loading...",
					}),
				},
				React.createElement(LazyClass),
				React.createElement(LazyForwardRef, { ref = ref })
			),
			{ unstable_isConcurrent = true }
		)

		jestExpect(Scheduler).toFlushAndYield({
			"Loading...",
		})
		jestExpect(root).never.toMatchRenderedOutput("FooBar")
		jestExpect(ref.current).toBe(nil)

		-- ROBLOX FIXME: delay by one frame is current best translation of `await Promise.resolve()`
		Promise.delay(0):await()

		jestExpect(Scheduler).toFlushAndYield({
			"Foo",
			"forwardRef",
			"Bar",
		})
		jestExpect(root).toMatchRenderedOutput("FooBar")
		jestExpect(ref.current).never.toBe(nil)
	end)
	-- it('supports defaultProps defined on the memo() return value', _async(function()
	--     local Add = React.memo(function(props)
	--         return props.inner + props.outer
	--     end)

	--     Add.defaultProps = {inner = 2}

	--     local LazyAdd = lazy(function()
	--         return fakeImport(Add)
	--     end)
	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(LazyAdd, {outer = 2})), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('4')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('4')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyAdd, {outer = 2})))
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('4')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyAdd, {outer = 3})))
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('5')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyAdd, {outer = 3})))
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('5')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyAdd, {
	--             outer = 1,
	--             inner = 1,
	--         })))
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('2')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyAdd, {
	--             outer = 1,
	--             inner = 1,
	--         })))
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('2')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyAdd, {outer = 1})))
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('3')
	--     end)
	-- end))
	-- it('merges defaultProps in the correct order', _async(function()
	--     local Add = React.memo(function(props)
	--         return props.inner + props.outer
	--     end)

	--     Add.defaultProps = {inner = 100}
	--     Add = React.memo(Add)
	--     Add.defaultProps = {
	--         inner = 2,
	--         outer = 0,
	--     }

	--     local LazyAdd = lazy(function()
	--         return fakeImport(Add)
	--     end)
	--     local root = ReactTestRenderer.create(React.createElement(Suspense, {
	--         fallback = React.createElement(Text, {
	--             text = 'Loading...',
	--         }),
	--     }, React.createElement(LazyAdd, {outer = 2})), {unstable_isConcurrent = true})

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Loading...',
	--     })
	--     jestExpect(root).not.toMatchRenderedOutput('4')

	--     return _await(Promise.resolve(), function()
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('4')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyAdd, {outer = 3})))
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('5')
	--         root.update(React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             }),
	--         }, React.createElement(LazyAdd)))
	--         jestExpect(Scheduler).toFlushWithoutYielding()
	--         jestExpect(root).toMatchRenderedOutput('2')
	--     end)
	-- end))
	it("warns about ref on functions for lazy-loaded components", function()
		local LazyFoo = lazy(function()
			local Foo = function(props)
				return React.createElement("div")
			end
			return fakeImport(Foo)
		end)
		local ref = React.createRef()

		ReactTestRenderer.create(
			React.createElement(Suspense, {
				fallback = React.createElement(Text, {
					text = "Loading...",
				}),
			}, React.createElement(LazyFoo, { ref = ref })),
			{ unstable_isConcurrent = true }
		)
		jestExpect(Scheduler).toFlushAndYield({
			"Loading...",
		})

		-- ROBLOX FIXME: delay by one frame is current best translation of `await Promise.resolve()`
		Promise.delay(0):await()

		jestExpect(function()
			jestExpect(Scheduler).toFlushAndYield({})
		end).toErrorDev("Function components cannot be given refs")
	end)
	-- ROBLOX TODO: normalizeCodeLocInfo needs regex
	xit("should error with a component stack naming the resolved component", function()
		local componentStackMessage
		local LazyText = lazy(function()
			return fakeImport(function()
				error(Error("oh no"))
			end)
		end)
		local ErrorBoundary = React.Component:extend("ErrorBoundary")

		function ErrorBoundary:init()
			self.state = { error = nil }
		end
		function ErrorBoundary:componentDidCatch(error_, errMessage)
			componentStackMessage = normalizeCodeLocInfo(errMessage.componentStack)

			self:setState({ error = error_ })
		end
		function ErrorBoundary:render()
			if self.state.error then
				return nil
			else
				return self.props.children
			end
		end

		ReactTestRenderer.create(
			React.createElement(
				ErrorBoundary,
				nil,
				React.createElement(
					Suspense,
					{
						fallback = React.createElement(Text, {
							text = "Loading...",
						}),
					},
					React.createElement(LazyText, {
						text = "Hi",
					})
				)
			),
			{ unstable_isConcurrent = true }
		)
		jestExpect(Scheduler).toFlushAndYield({
			"Loading...",
		})

		_ = pcall(function()
			Promise.delay(0):await()
		end)

		jestExpect(Scheduler).toFlushAndYield({})
		jestExpect(componentStackMessage).toContain("in ResolvedText")
	end)
	-- ROBLOX FIXME: missing stack frame that contains Lazy
	xit("should error with a component stack containing Lazy if unresolved", function()
		local componentStackMessage
		local LazyText = lazy(function()
			return {
				andThen = function(resolve, reject)
					reject(Error("oh no"))
				end,
			}
		end)
		local ErrorBoundary = React.Component:extend("ErrorBoundary")

		function ErrorBoundary:init()
			self.state = {
				error_ = nil,
			}
		end
		function ErrorBoundary:componentDidCatch(error_, errMessage)
			-- ROBLOX FIXME: componentStack is missing LazyText even before normalize
			componentStackMessage = normalizeCodeLocInfo(errMessage.componentStack)

			self:setState({ error_ = error_ })
		end
		function ErrorBoundary:render()
			return (function()
				if self.state.error_ then
					return nil
				end

				return self.props.children
			end)()
		end

		ReactTestRenderer.create(
			React.createElement(
				ErrorBoundary,
				nil,
				React.createElement(
					Suspense,
					{
						fallback = React.createElement(Text, {
							text = "Loading...",
						}),
					},
					React.createElement(LazyText, {
						text = "Hi",
					})
				)
			)
		)
		jestExpect(Scheduler).toHaveYielded({})
		-- ROBLOX TODO: only this final assert fails
		jestExpect(componentStackMessage).toContain("in Lazy")
	end)

	-- @gate enableLazyElements
	-- xit('mount and reorder lazy elements', function()
	--     local Child = React.Component:extend("Child")

	--     function Child:componentDidMount()
	--         Scheduler.unstable_yieldValue('Did mount: ' .. self.props.label)
	--     end
	--     function Child:componentDidUpdate()
	--         Scheduler.unstable_yieldValue('Did update: ' .. self.props.label)
	--     end
	--     function Child:render()
	--         return React.createElement(Text, {
	--             text = self.props.label,
	--         })
	--     end

	--     local lazyChildA = lazy(function()
	--         Scheduler.unstable_yieldValue('Init A')

	--         return fakeImport(React.createElement(Child, {
	--             key = 'A',
	--             label = 'A',
	--         }))
	--     end)
	--     local lazyChildB = lazy(function()
	--         Scheduler.unstable_yieldValue('Init B')

	--         return fakeImport(React.createElement(Child, {
	--             key = 'B',
	--             label = 'B',
	--         }))
	--     end)
	--     local lazyChildA2 = lazy(function()
	--         Scheduler.unstable_yieldValue('Init A2')

	--         return fakeImport(React.createElement(Child, {
	--             key = 'A',
	--             label = 'a',
	--         }))
	--     end)

	--     local lazyChildB2 = lazy(function()
	--         Scheduler.unstable_yieldValue('Init B2')

	--         return fakeImport(React.createElement(Child, {
	--             key = 'B',
	--             label = 'b',
	--         }))
	--     end)

	--     local function Parent(props)
	--         local swap = props.swap

	--         return React.createElement(Suspense, {
	--             fallback = React.createElement(Text, {
	--                 text = 'Loading...',
	--             })},
	--             (function()
	--             if swap then
	--                 return{lazyChildB2, lazyChildA2}
	--             end

	--             return{lazyChildA, lazyChildB}
	--         end)())
	--     end

	--     local root = ReactTestRenderer.create(React.createElement(Parent, {swap = false}), {
	--         unstable_isConcurrent = true
	--     })

	--     -- ROBLOX FIXME: delay by one frame is current best translation of `await Promise.resolve()`
	--     Promise.delay(0):await()

	--     jestExpect(Scheduler).toFlushAndYield({
	--         'Init A',
	--         'Loading...',
	--     })
	--     jestExpect(root).never.toMatchRenderedOutput('AB')

	--     return _await(lazyChildA, function()
	--         jestExpect(Scheduler).toFlushAndYield({
	--             'Init B',
	--         })

	--         return _await(lazyChildB, function()
	--             jestExpect(Scheduler).toFlushAndYield({
	--                 'A',
	--                 'B',
	--                 'Did mount: A',
	--                 'Did mount: B',
	--             })
	--             jestExpect(root).toMatchRenderedOutput('AB')
	--             root.update(React.createElement(Parent, {swap = true}))
	--             jestExpect(Scheduler).toFlushAndYield({
	--                 'Init B2',
	--                 'Loading...',
	--             })

	--             return _await(lazyChildB2, function()
	--                 jestExpect(Scheduler).toFlushAndYield({
	--                     'Init A2',
	--                     'Loading...',
	--                 })

	--                 return _await(lazyChildA2, function()
	--                     jestExpect(Scheduler).toFlushAndYield({
	--                         'b',
	--                         'a',
	--                         'Did update: b',
	--                         'Did update: a',
	--                     })
	--                     jestExpect(root).toMatchRenderedOutput('ba')
	--                 end)
	--             end)
	--         end)
	--     end)
	-- end)
end)
