local d local i local F local k=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 b=k.expect local a=k.jest local M=k.describe local B=k.beforeEach local z=k.it M("\082\101\097\099\116\073\110\099\114\101\109\101\110\116\097\108\083\099\104\101\100\117\108\105\110\103",function()B(function()a.resetModules()d=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116")i=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116-\110\111\111\112-\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 function k(d)return{type="\115\112\097\110",children={};prop=d,hidden=false}end z("\115\099\104\101\100\117\108\101\115 \097\110\100 \102\108\117\115\104\101\115 \100\101\102\101\114\114\101\100 \119\111\114\107",function()i.render(d.createElement("\115\112\097\110",{prop="\049"}));(b(i.getChildren())).toEqual({});(b(F)).toFlushWithoutYielding();(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop="\049"}))end)z("\115\101\097\114\099\104\101\115 \102\111\114 \119\111\114\107 \111\110 \111\116\104\101\114 \114\111\111\116\115 \111\110\099\101 \116\104\101 \099\117\114\114\101\110\116 \114\111\111\116 \099\111\109\112\108\101\116\101\115",function()i.renderToRootWithID(d.createElement("\115\112\097\110",{prop="\097\058\049"}),"\097")i.renderToRootWithID(d.createElement("\115\112\097\110",{prop="\098\058\049"}),"\098")i.renderToRootWithID(d.createElement("\115\112\097\110",{prop="\099\058\049"}),"\099");(b(F)).toFlushWithoutYielding();(b(i.getChildren("\097"))).toEqual({k("\097\058\049")});(b(i.getChildren("\098"))).toEqual({k("\098\058\049")});(b(i.getChildren("\099"))).toEqual({k("\099\058\049")})end)z("\115\099\104\101\100\117\108\101\115 \116\111\112-\108\101\118\101\108 \117\112\100\097\116\101\115 \105\110 \111\114\100\101\114 \111\102 \112\114\105\111\114\105\116\121",function()i.render(d.createElement("\115\112\097\110",{prop=1}));(b(F)).toFlushWithoutYielding();(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=1}))i.batchedUpdates(function()i.render(d.createElement("\115\112\097\110",{prop=5}))i.flushSync(function()i.render(d.createElement("\115\112\097\110",{prop=2}))i.render(d.createElement("\115\112\097\110",{prop=3}))i.render(d.createElement("\115\112\097\110",{prop=4}))end)end);(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=4}));(b(F)).toFlushWithoutYielding();(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=4}))end)z("\115\099\104\101\100\117\108\101\115 \116\111\112-\108\101\118\101\108 \117\112\100\097\116\101\115 \119\105\116\104 \115\097\109\101 \112\114\105\111\114\105\116\121 \105\110 \111\114\100\101\114 \111\102 \105\110\115\101\114\116\105\111\110",function()i.render(d.createElement("\115\112\097\110",{prop=1}));(b(F)).toFlushWithoutYielding();(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=1}))i.render(d.createElement("\115\112\097\110",{prop=2}))i.render(d.createElement("\115\112\097\110",{prop=3}))i.render(d.createElement("\115\112\097\110",{prop=4}))i.render(d.createElement("\115\112\097\110",{prop=5}));(b(F)).toFlushWithoutYielding();(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=5}))end)z("\119\111\114\107\115 \111\110 \100\101\102\101\114\114\101\100 \114\111\111\116\115 \105\110 \116\104\101 \111\114\100\101\114 \116\104\101\121 \119\101\114\101 \115\099\104\101\100\117\108\101\100",function()local k=d.useEffect local function a(d)local i=d.text k(function()F.unstable_yieldValue(i)end,{i})return i end i.act(function()i.renderToRootWithID(d.createElement(a,{text="\097\058\049"}),"\097")i.renderToRootWithID(d.createElement(a,{text="\098\058\049"}),"\098")i.renderToRootWithID(d.createElement(a,{text="\099\058\049"}),"\099")end);(b(F)).toHaveYielded({"\097\058\049";"\098\058\049";"\099\058\049"});(b((i.getChildren("\097"))[1].text)).toEqual("\097\058\049");(b((i.getChildren("\098"))[1].text)).toEqual("\098\058\049");(b((i.getChildren("\099"))[1].text)).toEqual("\099\058\049");(b(#i.getChildren("\097"))).toEqual(1);(b(#i.getChildren("\098"))).toEqual(1);(b(#i.getChildren("\099"))).toEqual(1)i.act(function()i.renderToRootWithID(d.createElement(a,{text="\099\058\050"}),"\099")i.renderToRootWithID(d.createElement(a,{text="\098\058\050"}),"\098");(b(F)).toFlushAndYieldThrough({"\099\058\050"});(b((i.getChildren("\097"))[1].text)).toEqual("\097\058\049");(b((i.getChildren("\098"))[1].text)).toEqual("\098\058\049");(b((i.getChildren("\099"))[1].text)).toEqual("\099\058\050");(b(#i.getChildren("\097"))).toEqual(1);(b(#i.getChildren("\098"))).toEqual(1);(b(#i.getChildren("\099"))).toEqual(1)i.renderToRootWithID(d.createElement(a,{text="\097\058\050"}),"\097");(b(F)).toFlushAndYieldThrough({"\098\058\050"});(b((i.getChildren("\097"))[1].text)).toEqual("\097\058\049");(b((i.getChildren("\098"))[1].text)).toEqual("\098\058\050");(b((i.getChildren("\099"))[1].text)).toEqual("\099\058\050");(b(#i.getChildren("\097"))).toEqual(1);(b(#i.getChildren("\098"))).toEqual(1);(b(#i.getChildren("\099"))).toEqual(1);(b(F)).toFlushAndYieldThrough({"\097\058\050"});(b((i.getChildren("\097"))[1].text)).toEqual("\097\058\050");(b((i.getChildren("\098"))[1].text)).toEqual("\098\058\050");(b((i.getChildren("\099"))[1].text)).toEqual("\099\058\050");(b(#i.getChildren("\097"))).toEqual(1);(b(#i.getChildren("\098"))).toEqual(1);(b(#i.getChildren("\099"))).toEqual(1)end)end)z("\115\099\104\101\100\117\108\101\115 \115\121\110\099 \117\112\100\097\116\101\115 \119\104\101\110 \105\110\115\105\100\101 \099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116\047\085\112\100\097\116\101",function()local k local a=d.Component:extend("\070\111\111")function a.init(d)d.state={tick=0}end function a.componentDidMount(d)F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116 \040\098\101\102\111\114\101 \115\101\116\083\116\097\116\101\041\058 "..d.state.tick)d:setState({tick=1})F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116 \040\097\102\116\101\114 \115\101\116\083\116\097\116\101\041\058 "..d.state.tick)end function a.componentDidUpdate(d)F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 "..d.state.tick)if d.state.tick==2 then F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101 \040\098\101\102\111\114\101 \115\101\116\083\116\097\116\101\041\058 "..d.state.tick)d:setState({tick=3})F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101 \040\097\102\116\101\114 \115\101\116\083\116\097\116\101\041\058 "..d.state.tick)end end function a.render(i)F.unstable_yieldValue("\114\101\110\100\101\114\058 "..i.state.tick)k=i return d.createElement("\115\112\097\110",{prop=i.state.tick})end i.render(d.createElement(a));(b(F)).toFlushAndYieldThrough({"\114\101\110\100\101\114\058 \048"});(b(i.flushNextYield())).toEqual({"\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116 \040\098\101\102\111\114\101 \115\101\116\083\116\097\116\101\041\058 \048","\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116 \040\097\102\116\101\114 \115\101\116\083\116\097\116\101\041\058 \048";"\114\101\110\100\101\114\058 \049","\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 \049"})k:setState({tick=2});(b(F)).toFlushAndYieldThrough({"\114\101\110\100\101\114\058 \050"});(b(i.flushNextYield())).toEqual({"\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 \050","\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101 \040\098\101\102\111\114\101 \115\101\116\083\116\097\116\101\041\058 \050","\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101 \040\097\102\116\101\114 \115\101\116\083\116\097\116\101\041\058 \050","\114\101\110\100\101\114\058 \051","\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 \051"})end)z("\099\097\110 \111\112\116-\105\110 \116\111 \097\115\121\110\099 \115\099\104\101\100\117\108\105\110\103 \105\110\115\105\100\101 \099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116\047\085\112\100\097\116\101",function()local k local a=d.Component:extend("\070\111\111")function a.init(d)d.state={tick=0}end function a.componentDidMount(d)local k=d i.deferredUpdates(function()F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116 \040\098\101\102\111\114\101 \115\101\116\083\116\097\116\101\041\058 "..k.state.tick)k:setState({tick=1})F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116 \040\097\102\116\101\114 \115\101\116\083\116\097\116\101\041\058 "..k.state.tick)end)end function a.componentDidUpdate(d)local k=d i.deferredUpdates(function()F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 "..k.state.tick)if k.state.tick==2 then F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101 \040\098\101\102\111\114\101 \115\101\116\083\116\097\116\101\041\058 "..k.state.tick)k:setState({tick=3})F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101 \040\097\102\116\101\114 \115\101\116\083\116\097\116\101\041\058 "..k.state.tick)end end)end function a.render(i)F.unstable_yieldValue("\114\101\110\100\101\114\058 "..i.state.tick)k=i return d.createElement("\115\112\097\110",{prop=i.state.tick})end i.flushSync(function()i.render(d.createElement(a))end);(b(F)).toHaveYielded({"\114\101\110\100\101\114\058 \048";"\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116 \040\098\101\102\111\114\101 \115\101\116\083\116\097\116\101\041\058 \048","\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116 \040\097\102\116\101\114 \115\101\116\083\116\097\116\101\041\058 \048"});(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=0}));(b(F)).toFlushAndYield({"\114\101\110\100\101\114\058 \049","\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 \049"});(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=1}))k:setState({tick=2});(b(F)).toFlushAndYieldThrough({"\114\101\110\100\101\114\058 \050";"\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 \050";"\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101 \040\098\101\102\111\114\101 \115\101\116\083\116\097\116\101\041\058 \050","\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101 \040\097\102\116\101\114 \115\101\116\083\116\097\116\101\041\058 \050"});(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=2}));(b(F)).toFlushAndYield({"\114\101\110\100\101\114\058 \051","\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 \051"});(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=3}))end)z("\112\101\114\102\111\114\109\115 \084\097\115\107 \119\111\114\107 \101\118\101\110 \097\102\116\101\114 \116\105\109\101 \114\117\110\115 \111\117\116",function()local k=d.Component:extend("\070\111\111")function k.init(d)d.state={step=1}end function k.componentDidMount(d)local i=d d:setState({step=2},function()i:setState({step=3},function()i:setState({step=4},function()i:setState({step=5})end)end)end)end function k.render(i)F.unstable_yieldValue("\070\111\111")return d.createElement("\115\112\097\110",{prop=i.state.step})end i.render(d.createElement(k));(b(F)).toFlushAndYieldThrough({"\070\111\111"});(b(i)).toMatchRenderedOutput(nil)i.flushNextYield();(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=5}))end)z("\099\097\110 \111\112\116-\111\117\116 \111\102 \098\097\116\099\104\105\110\103 \117\115\105\110\103 \117\110\098\097\116\099\104\101\100\085\112\100\097\116\101\115",function()i.flushSync(function()i.render(d.createElement("\115\112\097\110",{prop=0}));(b(i.getChildren())).toEqual({})i.unbatchedUpdates(function()i.render(d.createElement("\115\112\097\110",{prop=1}));(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=1}))i.render(d.createElement("\115\112\097\110",{prop=2}));(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=2}))end)i.render(d.createElement("\115\112\097\110",{prop=3}));(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=2}))end);(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=3}))end)z("\110\101\115\116\101\100 \117\112\100\097\116\101\115 \097\114\101 \097\108\119\097\121\115 \100\101\102\101\114\114\101\100\044 \101\118\101\110 \105\110\115\105\100\101 \117\110\098\097\116\099\104\101\100\085\112\100\097\116\101\115",function()local k local a=d.Component:extend("\070\111\111")function a.init(d)d.state={step=0}end function a.componentDidUpdate(k)local a=k F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 "..k.state.step)if k.state.step==1 then i.unbatchedUpdates(function()a:setState({step=2})end);(b(F)).toHaveYielded({"\114\101\110\100\101\114\058 \049","\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 \049"});(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=1}))end end function a.render(i)F.unstable_yieldValue("\114\101\110\100\101\114\058 "..i.state.step)k=i return d.createElement("\115\112\097\110",{prop=i.state.step})end i.render(d.createElement(a));(b(F)).toFlushAndYield({"\114\101\110\100\101\114\058 \048"});(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=0}))i.flushSync(function()k:setState({step=1})end);(b(F)).toHaveYielded({"\114\101\110\100\101\114\058 \050";"\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101\058 \050"});(b(i)).toMatchRenderedOutput(d.createElement("\115\112\097\110",{prop=2}))end)end)
--[[*
 * Upstream: https://github.com/facebook/react/blob/d13f5b9538e48f74f7c571ef3cde652ca887cca0/packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.js
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @emails react-core
 * @jest-environment node
 --]]
--!strict
local React
local ReactNoop
local Scheduler
local JestGlobals = require("@pkg/@jsdotlua/jest-globals")
local jestExpect = JestGlobals.expect
local jest = JestGlobals.jest
local describe = JestGlobals.describe
local beforeEach = JestGlobals.beforeEach
local it = JestGlobals.it

describe("ReactIncrementalScheduling", function()
	beforeEach(function()
		jest.resetModules()

		React = require("@pkg/@jsdotlua/react")
		ReactNoop = require("@pkg/@jsdotlua/react-noop-renderer")
		Scheduler = require("@pkg/@jsdotlua/scheduler")
	end)

	local function span(prop)
		return {
			type = "span",
			children = {},
			prop = prop,
			hidden = false,
		}
	end

	it("schedules and flushes deferred work", function()
		ReactNoop.render(React.createElement("span", {
			prop = "1",
		}))
		jestExpect(ReactNoop.getChildren()).toEqual({})
		jestExpect(Scheduler).toFlushWithoutYielding()

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = "1" })
		)
	end)
	it("searches for work on other roots once the current root completes", function()
		ReactNoop.renderToRootWithID(
			React.createElement("span", {
				prop = "a:1",
			}),
			"a"
		)
		ReactNoop.renderToRootWithID(
			React.createElement("span", {
				prop = "b:1",
			}),
			"b"
		)
		ReactNoop.renderToRootWithID(
			React.createElement("span", {
				prop = "c:1",
			}),
			"c"
		)
		jestExpect(Scheduler).toFlushWithoutYielding()
		jestExpect(ReactNoop.getChildren("a")).toEqual({
			span("a:1"),
		})
		jestExpect(ReactNoop.getChildren("b")).toEqual({
			span("b:1"),
		})
		jestExpect(ReactNoop.getChildren("c")).toEqual({
			span("c:1"),
		})
	end)
	it("schedules top-level updates in order of priority", function()
		ReactNoop.render(React.createElement("span", { prop = 1 }))
		jestExpect(Scheduler).toFlushWithoutYielding()

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 1 })
		)

		ReactNoop.batchedUpdates(function()
			ReactNoop.render(React.createElement("span", { prop = 5 }))
			ReactNoop.flushSync(function()
				ReactNoop.render(React.createElement("span", { prop = 2 }))
				ReactNoop.render(React.createElement("span", { prop = 3 }))
				ReactNoop.render(React.createElement("span", { prop = 4 }))
			end)
		end)

		-- The sync updates flush first.

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 4 })
		)

		-- The terminal value should be the last update that was scheduled,
		-- regardless of priority. In this case, that's the last sync update.
		jestExpect(Scheduler).toFlushWithoutYielding()

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 4 })
		)
	end)
	it("schedules top-level updates with same priority in order of insertion", function()
		-- Initial render.
		ReactNoop.render(React.createElement("span", { prop = 1 }))
		jestExpect(Scheduler).toFlushWithoutYielding()

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 1 })
		)

		ReactNoop.render(React.createElement("span", { prop = 2 }))
		ReactNoop.render(React.createElement("span", { prop = 3 }))
		ReactNoop.render(React.createElement("span", { prop = 4 }))
		ReactNoop.render(React.createElement("span", { prop = 5 }))
		jestExpect(Scheduler).toFlushWithoutYielding()

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 5 })
		)
	end)
	it("works on deferred roots in the order they were scheduled", function()
		local useEffect = React.useEffect

		local function Text(props)
			local text = props.text

			useEffect(function()
				Scheduler.unstable_yieldValue(text)
			end, { text })

			return text
		end

		ReactNoop.act(function()
			ReactNoop.renderToRootWithID(
				React.createElement(Text, {
					text = "a:1",
				}),
				"a"
			)
			ReactNoop.renderToRootWithID(
				React.createElement(Text, {
					text = "b:1",
				}),
				"b"
			)
			ReactNoop.renderToRootWithID(
				React.createElement(Text, {
					text = "c:1",
				}),
				"c"
			)
		end)
		jestExpect(Scheduler).toHaveYielded({
			"a:1",
			"b:1",
			"c:1",
		})

		-- deviation: getChildrenAsJSX() method which is used in upstream replaced with
		-- equivalent getChildren() evaluations because Roact doesn't support JSX.

		jestExpect(ReactNoop.getChildren("a")[1].text).toEqual("a:1")
		jestExpect(ReactNoop.getChildren("b")[1].text).toEqual("b:1")
		jestExpect(ReactNoop.getChildren("c")[1].text).toEqual("c:1")
		jestExpect(#ReactNoop.getChildren("a")).toEqual(1)
		jestExpect(#ReactNoop.getChildren("b")).toEqual(1)
		jestExpect(#ReactNoop.getChildren("c")).toEqual(1)

		-- Schedule deferred work in the reverse order
		ReactNoop.act(function()
			ReactNoop.renderToRootWithID(
				React.createElement(Text, {
					text = "c:2",
				}),
				"c"
			)
			ReactNoop.renderToRootWithID(
				React.createElement(Text, {
					text = "b:2",
				}),
				"b"
			)
			jestExpect(Scheduler).toFlushAndYieldThrough({
				"c:2",
			})
			jestExpect(ReactNoop.getChildren("a")[1].text).toEqual("a:1")
			jestExpect(ReactNoop.getChildren("b")[1].text).toEqual("b:1")
			jestExpect(ReactNoop.getChildren("c")[1].text).toEqual("c:2")
			jestExpect(#ReactNoop.getChildren("a")).toEqual(1)
			jestExpect(#ReactNoop.getChildren("b")).toEqual(1)
			jestExpect(#ReactNoop.getChildren("c")).toEqual(1)
			-- Schedule last bit of work, it will get processed the last

			ReactNoop.renderToRootWithID(
				React.createElement(Text, {
					text = "a:2",
				}),
				"a"
			)

			-- Keep performing work in the order it was scheduled
			jestExpect(Scheduler).toFlushAndYieldThrough({
				"b:2",
			})
			jestExpect(ReactNoop.getChildren("a")[1].text).toEqual("a:1")
			jestExpect(ReactNoop.getChildren("b")[1].text).toEqual("b:2")
			jestExpect(ReactNoop.getChildren("c")[1].text).toEqual("c:2")
			jestExpect(#ReactNoop.getChildren("a")).toEqual(1)
			jestExpect(#ReactNoop.getChildren("b")).toEqual(1)
			jestExpect(#ReactNoop.getChildren("c")).toEqual(1)
			jestExpect(Scheduler).toFlushAndYieldThrough({
				"a:2",
			})
			jestExpect(ReactNoop.getChildren("a")[1].text).toEqual("a:2")
			jestExpect(ReactNoop.getChildren("b")[1].text).toEqual("b:2")
			jestExpect(ReactNoop.getChildren("c")[1].text).toEqual("c:2")
			jestExpect(#ReactNoop.getChildren("a")).toEqual(1)
			jestExpect(#ReactNoop.getChildren("b")).toEqual(1)
			jestExpect(#ReactNoop.getChildren("c")).toEqual(1)
		end)
	end)
	it("schedules sync updates when inside componentDidMount/Update", function()
		local instance

		local Foo = React.Component:extend("Foo")
		function Foo:init()
			self.state = { tick = 0 }
		end

		function Foo:componentDidMount()
			Scheduler.unstable_yieldValue(
				"componentDidMount (before setState): " .. self.state.tick
			)
			self:setState({
				tick = 1,
			})
			-- We're in a batch. Update hasn't flushed yet.

			Scheduler.unstable_yieldValue(
				"componentDidMount (after setState): " .. self.state.tick
			)
		end

		function Foo:componentDidUpdate()
			Scheduler.unstable_yieldValue("componentDidUpdate: " .. self.state.tick)

			if self.state.tick == 2 then
				Scheduler.unstable_yieldValue(
					"componentDidUpdate (before setState): " .. self.state.tick
				)
				self:setState({
					tick = 3,
				})
				Scheduler.unstable_yieldValue(
					"componentDidUpdate (after setState): " .. self.state.tick
				) -- We're in a batch. Update hasn't flushed yet.
				-- We're in a batch. Update hasn't flushed yet.
			end
		end

		function Foo:render()
			Scheduler.unstable_yieldValue("render: " .. self.state.tick)
			instance = self
			return React.createElement("span", {
				prop = self.state.tick,
			})
		end

		ReactNoop.render(React.createElement(Foo))
		-- Render without committing
		jestExpect(Scheduler).toFlushAndYieldThrough({
			"render: 0",
		})

		-- Do one more unit of work to commit
		jestExpect(ReactNoop.flushNextYield()).toEqual({
			"componentDidMount (before setState): 0",
			"componentDidMount (after setState): 0",
			-- If the setState inside componentDidMount were deferred, there would be
			-- no more ops. Because it has Task priority, we get these ops, too:
			"render: 1",
			"componentDidUpdate: 1",
		})
		instance:setState({ tick = 2 })
		jestExpect(Scheduler).toFlushAndYieldThrough({
			"render: 2",
		})
		jestExpect(ReactNoop.flushNextYield()).toEqual({
			"componentDidUpdate: 2",
			"componentDidUpdate (before setState): 2",
			"componentDidUpdate (after setState): 2",
			-- If the setState inside componentDidUpdate were deferred, there would be
			-- no more ops. Because it has Task priority, we get these ops, too:
			"render: 3",
			"componentDidUpdate: 3",
		})
	end)
	it("can opt-in to async scheduling inside componentDidMount/Update", function()
		local instance

		local Foo = React.Component:extend("Foo")
		function Foo:init()
			self.state = { tick = 0 }
		end
		function Foo:componentDidMount()
			local _this = self
			ReactNoop.deferredUpdates(function()
				Scheduler.unstable_yieldValue(
					"componentDidMount (before setState): " .. _this.state.tick
				)

				_this:setState({
					tick = 1,
				})

				Scheduler.unstable_yieldValue(
					"componentDidMount (after setState): " .. _this.state.tick
				)
			end)
		end

		function Foo:componentDidUpdate()
			local _this2 = self

			ReactNoop.deferredUpdates(function()
				Scheduler.unstable_yieldValue("componentDidUpdate: " .. _this2.state.tick)

				if _this2.state.tick == 2 then
					Scheduler.unstable_yieldValue(
						"componentDidUpdate (before setState): " .. _this2.state.tick
					)

					_this2:setState({
						tick = 3,
					})

					Scheduler.unstable_yieldValue(
						"componentDidUpdate (after setState): " .. _this2.state.tick
					)
				end
			end)
		end

		function Foo:render()
			Scheduler.unstable_yieldValue("render: " .. self.state.tick)
			instance = self
			return React.createElement("span", {
				prop = self.state.tick,
			})
		end
		ReactNoop.flushSync(function()
			ReactNoop.render(React.createElement(Foo))
		end)
		-- The cDM update should not have flushed yet because it has async priority.
		jestExpect(Scheduler).toHaveYielded({
			"render: 0",
			"componentDidMount (before setState): 0",
			"componentDidMount (after setState): 0",
		})

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 0 })
		)

		-- Now flush the cDM update.
		jestExpect(Scheduler).toFlushAndYield({
			"render: 1",
			"componentDidUpdate: 1",
		})

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 1 })
		)

		-- Increment the tick to 2. This will trigger an update inside cDU. Flush
		-- the first update without flushing the second one.
		instance:setState({ tick = 2 })
		jestExpect(Scheduler).toFlushAndYieldThrough({
			"render: 2",
			"componentDidUpdate: 2",
			"componentDidUpdate (before setState): 2",
			"componentDidUpdate (after setState): 2",
		})

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 2 })
		)

		-- Now flush the cDU update.
		jestExpect(Scheduler).toFlushAndYield({
			"render: 3",
			"componentDidUpdate: 3",
		})

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 3 })
		)
	end)
	it("performs Task work even after time runs out", function()
		local Foo = React.Component:extend("Foo")
		function Foo:init()
			self.state = { step = 1 }
		end

		function Foo:componentDidMount()
			local _this3 = self
			self:setState({
				step = 2,
			}, function()
				_this3:setState({
					step = 3,
				}, function()
					_this3:setState({
						step = 4,
					}, function()
						_this3:setState({
							step = 5,
						})
					end)
				end)
			end)
		end

		function Foo:render()
			Scheduler.unstable_yieldValue("Foo")
			return React.createElement("span", {
				prop = self.state.step,
			})
		end
		ReactNoop.render(React.createElement(Foo))
		-- This should be just enough to complete all the work, but not enough to
		-- commit it.
		jestExpect(Scheduler).toFlushAndYieldThrough({
			"Foo",
		})

		jestExpect(ReactNoop).toMatchRenderedOutput(nil)

		-- Do one more unit of work.
		ReactNoop.flushNextYield()

		-- The updates should all be flushed with Task priority
		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 5 })
		)
	end)
	it("can opt-out of batching using unbatchedUpdates", function()
		ReactNoop.flushSync(function()
			ReactNoop.render(React.createElement("span", { prop = 0 }))
			jestExpect(ReactNoop.getChildren()).toEqual({})
			-- Should not have flushed yet because we're still batching

			-- unbatchedUpdates reverses the effect of batchedUpdates, so sync
			-- updates are not batched
			ReactNoop.unbatchedUpdates(function()
				ReactNoop.render(React.createElement("span", { prop = 1 }))

				jestExpect(ReactNoop).toMatchRenderedOutput(
					React.createElement("span", { prop = 1 })
				)

				ReactNoop.render(React.createElement("span", { prop = 2 }))

				jestExpect(ReactNoop).toMatchRenderedOutput(
					React.createElement("span", { prop = 2 })
				)
			end)
			ReactNoop.render(React.createElement("span", { prop = 3 }))
			-- Remaining update is now flushed

			jestExpect(ReactNoop).toMatchRenderedOutput(
				React.createElement("span", { prop = 2 })
			)
		end)
		-- Remaining update is now flushed

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 3 })
		)
	end)
	it("nested updates are always deferred, even inside unbatchedUpdates", function()
		local instance
		local Foo = React.Component:extend("Foo")
		function Foo:init()
			self.state = {
				step = 0,
			}
		end
		function Foo:componentDidUpdate()
			local _this4 = self
			Scheduler.unstable_yieldValue("componentDidUpdate: " .. self.state.step)

			if self.state.step == 1 then
				ReactNoop.unbatchedUpdates(function()
					-- This is a nested state update, so it should not be
					-- flushed synchronously, even though we wrapped it
					-- in unbatchedUpdates.
					_this4:setState({
						step = 2,
					})
				end)
				jestExpect(Scheduler).toHaveYielded({
					"render: 1",
					"componentDidUpdate: 1",
				})

				jestExpect(ReactNoop).toMatchRenderedOutput(
					React.createElement("span", { prop = 1 })
				)
			end
		end

		function Foo:render()
			Scheduler.unstable_yieldValue("render: " .. self.state.step)
			instance = self
			return React.createElement("span", {
				prop = self.state.step,
			})
		end

		ReactNoop.render(React.createElement(Foo))
		jestExpect(Scheduler).toFlushAndYield({
			"render: 0",
		})

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 0 })
		)

		ReactNoop.flushSync(function()
			instance:setState({ step = 1 })
		end)
		jestExpect(Scheduler).toHaveYielded({
			"render: 2",
			"componentDidUpdate: 2",
		})

		jestExpect(ReactNoop).toMatchRenderedOutput(
			React.createElement("span", { prop = 2 })
		)
	end)
end)
