local d local i local F local k=(require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\115\104\097\114\101\100")).console local b=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 a=b.expect local M=b.beforeEach local B=b.describe local z=b.it local H=b.xit local U=b.jest B("\082\101\097\099\116\073\110\099\114\101\109\101\110\116\097\108\082\101\102\108\101\099\116\105\111\110",function()M(function()U.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 b(...)local d,i,F=...return{type="\100\105\118",children=F;prop=nil,hidden=false}end local function B(d)return{type="\115\112\097\110",children={};prop=d,hidden=false}end z("\104\097\110\100\108\101\115 \105\115\077\111\117\110\116\101\100 \101\118\101\110 \119\104\101\110 \116\104\101 \105\110\105\116\105\097\108 \114\101\110\100\101\114 \105\115 \100\101\102\101\114\114\101\100",function()local k={}local b=d.Component:extend("\067\111\109\112\111\110\101\110\116")function b._isMounted(d)return d.__updater.isMounted(d)end function b.UNSAFE_componentWillMount(d)table.insert(k,d)F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\087\105\108\108\077\111\117\110\116\058 "..tostring(d:_isMounted()))end function b.componentDidMount(d)F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116\058 "..tostring(d:_isMounted()))end function b.render(i)return d.createElement("\115\112\097\110")end local function M()return d.createElement(b)end i.render(d.createElement(M));(a(F)).toFlushAndYieldThrough({"\099\111\109\112\111\110\101\110\116\087\105\108\108\077\111\117\110\116\058 \102\097\108\115\101"});(a(k[1]:_isMounted())).toBe(false);(a(function()return(a(F)).toFlushAndYield({"\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116\058 \116\114\117\101"})end)).toErrorDev("\085\115\105\110\103 \085\078\083\065\070\069\095\099\111\109\112\111\110\101\110\116\087\105\108\108\077\111\117\110\116 \105\110 \115\116\114\105\099\116 \109\111\100\101 \105\115 \110\111\116 \114\101\099\111\109\109\101\110\100\101\100",{withoutStack=true});(a(k[1]:_isMounted())).toBe(true)end)z("\104\097\110\100\108\101\115 \105\115\077\111\117\110\116\101\100 \119\104\101\110 \097\110 \117\110\109\111\117\110\116 \105\115 \100\101\102\101\114\114\101\100",function()local k={}local b=d.Component:extend("\067\111\109\112\111\110\101\110\116")function b.init(d)d.state={}end function b._isMounted(d)return d.__updater.isMounted(d)end function b.UNSAFE_componentWillMount(d)table.insert(k,d)end function b.componentWillUnmount(d)F.unstable_yieldValue("\099\111\109\112\111\110\101\110\116\087\105\108\108\085\110\109\111\117\110\116\058 "..tostring(d:_isMounted()))end function b.render(i)F.unstable_yieldValue("\067\111\109\112\111\110\101\110\116")return d.createElement("\115\112\097\110")end local function M()F.unstable_yieldValue("\079\116\104\101\114")return d.createElement("\115\112\097\110")end local function B(i)if i.mount then return d.createElement(b)end return d.createElement(M)end i.render(d.createElement(B,{mount=true}));(a(function()return(a(F)).toFlushAndYield({"\067\111\109\112\111\110\101\110\116"})end)).toErrorDev("\085\115\105\110\103 \085\078\083\065\070\069\095\099\111\109\112\111\110\101\110\116\087\105\108\108\077\111\117\110\116 \105\110 \115\116\114\105\099\116 \109\111\100\101 \105\115 \110\111\116 \114\101\099\111\109\109\101\110\100\101\100",{withoutStack=true});(a(k[1]:_isMounted())).toBe(true)i.render(d.createElement(B,{mount=false}));(a(F)).toFlushAndYieldThrough({"\079\116\104\101\114"});(a(k[1]:_isMounted())).toBe(true);(a(F)).toFlushAndYield({"\099\111\109\112\111\110\101\110\116\087\105\108\108\085\110\109\111\117\110\116\058 \116\114\117\101"});(a(k[1]:_isMounted())).toBe(false)end)H("\102\105\110\100\115 \110\111 \110\111\100\101 \098\101\102\111\114\101 \105\110\115\101\114\116\105\111\110 \097\110\100 \099\111\114\114\101\099\116 \110\111\100\101 \098\101\102\111\114\101 \100\101\108\101\116\105\111\110",function()local M=nil local function z(d)local F=k.error k.error=nil local b,a=pcall(function()return i.findInstance(d)end)k.error=F if b then return a else error(a)end end local H=d.Component:extend("\067\111\109\112\111\110\101\110\116")function H.UNSAFE_componentWillMount(d)M=d F.unstable_yieldValue({"\099\111\109\112\111\110\101\110\116\087\105\108\108\077\111\117\110\116",z(d)})end function H.componentDidMount(d)F.unstable_yieldValue({"\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116",z(d)})end function H.UNSAFE_componentWillUpdate(d)F.unstable_yieldValue({"\099\111\109\112\111\110\101\110\116\087\105\108\108\085\112\100\097\116\101",z(d)})end function H.componentDidUpdate(d)F.unstable_yieldValue({"\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101";z(d)})end function H.componentWillUnmount(d)F.unstable_yieldValue({"\099\111\109\112\111\110\101\110\116\087\105\108\108\085\110\109\111\117\110\116";z(d)})end function H.render(i)F.unstable_yieldValue("\114\101\110\100\101\114")return function()if i.props.step<2 then return d.createElement(B,{ref=function(d)i.span=d return d end})elseif i.props.step==2 then return d.createElement(b,{ref=function(d)i.div=d return d end})elseif i.props.step==3 then return nil elseif i.props.step==4 then return d.createElement(b,{ref=function(d)i.span=d return d end})else return nil end end end local function U()F.unstable_yieldValue("\114\101\110\100\101\114 \115\105\098\108\105\110\103")return d.createElement(B)end local function v(i)return{d.createElement(H,{key="\097";step=i.step});d.createElement(U,{key="\098"})}end i.render(d.createElement(v,{step=0}));(a(F)).toFlushAndYieldThrough({{"\099\111\109\112\111\110\101\110\116\087\105\108\108\077\111\117\110\116",nil};"\114\101\110\100\101\114","\114\101\110\100\101\114 \115\105\098\108\105\110\103"});(a(M)).toBeDefined();(a(z(M))).toBe(nil);(a(function()return(a(F)).toFlushAndYield({{"\099\111\109\112\111\110\101\110\116\068\105\100\077\111\117\110\116";B()}})end)).toErrorDev({"\085\115\105\110\103 \085\078\083\065\070\069\095\099\111\109\112\111\110\101\110\116\087\105\108\108\077\111\117\110\116 \105\110 \115\116\114\105\099\116 \109\111\100\101 \105\115 \110\111\116 \114\101\099\111\109\109\101\110\100\101\100";"\085\115\105\110\103 \085\078\083\065\070\069\095\099\111\109\112\111\110\101\110\116\087\105\108\108\085\112\100\097\116\101 \105\110 \115\116\114\105\099\116 \109\111\100\101 \105\115 \110\111\116 \114\101\099\111\109\109\101\110\100\101\100"},{withoutStack=true})local S=M.span;(a(S)).toBeDefined();(a(z(M))).toBe(S)i.render(d.createElement(v,{step=1}));(a(F)).toFlushAndYield({{"\099\111\109\112\111\110\101\110\116\087\105\108\108\085\112\100\097\116\101",S},"\114\101\110\100\101\114","\114\101\110\100\101\114 \115\105\098\108\105\110\103",{"\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101",S}});(a(i.findInstance(M))).toBe(S)i.render(d.createElement(v,{step=2}));(a(F)).toFlushAndYieldThrough({{"\099\111\109\112\111\110\101\110\116\087\105\108\108\085\112\100\097\116\101",S},"\114\101\110\100\101\114";"\114\101\110\100\101\114 \115\105\098\108\105\110\103"});(a(i.findInstance(M))).toBe(S);(a(F)).toFlushAndYield({{"\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101",b()}})local w=M.div;(a(w)).toBeDefined();(a(S)).never.toBe(w);(a(i.findInstance(M))).toBe(w)i.render(d.createElement(v,{step=3}));(a(F)).toFlushAndYieldThrough({{"\099\111\109\112\111\110\101\110\116\087\105\108\108\085\112\100\097\116\101",w};"\114\101\110\100\101\114";"\114\101\110\100\101\114 \115\105\098\108\105\110\103"});(a(i.findInstance(M))).toBe(w);(a(F)).toFlushAndYield({{"\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101",nil}});(a(i.findInstance(M))).toBe(nil)i.render(d.createElement(v,{step=4}));(a(F)).toFlushAndYield({{"\099\111\109\112\111\110\101\110\116\087\105\108\108\085\112\100\097\116\101",nil},"\114\101\110\100\101\114","\114\101\110\100\101\114 \115\105\098\108\105\110\103";{"\099\111\109\112\111\110\101\110\116\068\105\100\085\112\100\097\116\101";b()}})i.render({});(a(F)).toFlushAndYield({{"\099\111\109\112\111\110\101\110\116\087\105\108\108\085\110\109\111\117\110\116";w}})end)end)
-- ROBLOX upstream: https://github.com/facebook/react/blob/d13f5b9538e48f74f7c571ef3cde652ca887cca0/packages/react-reconciler/src/__tests__/ReactIncrementalReflection-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
--

local React
local ReactNoop
local Scheduler
local console = require("@pkg/@jsdotlua/shared").console

local JestGlobals = require("@pkg/@jsdotlua/jest-globals")
local jestExpect = JestGlobals.expect
local beforeEach = JestGlobals.beforeEach
local describe = JestGlobals.describe
local it = JestGlobals.it
local xit = JestGlobals.xit
local jest = JestGlobals.jest

describe("ReactIncrementalReflection", 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 div(...)
		local _, _, children = ...
		-- deviation: commented out unused children processing logic
		-- children = children.map(function(c)
		--     return(function()
		--         if typeof(c) == 'string' then
		--             return{
		--                 text = c,
		--                 hidden = false,
		--             }
		--         end

		--         return c
		--     end)()
		-- end)

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

	it("handles isMounted even when the initial render is deferred", function()
		local instances = {}
		local Component = React.Component:extend("Component")

		function Component:_isMounted()
			-- No longer a public API, but we can test that it works internally by
			-- reaching into the updater.
			return self.__updater.isMounted(self)
		end
		function Component:UNSAFE_componentWillMount()
			table.insert(instances, self)
			Scheduler.unstable_yieldValue(
				"componentWillMount: " .. tostring(self:_isMounted())
			)
		end
		function Component:componentDidMount()
			Scheduler.unstable_yieldValue(
				"componentDidMount: " .. tostring(self:_isMounted())
			)
		end
		function Component:render()
			return React.createElement("span")
		end

		local function Foo()
			return React.createElement(Component)
		end

		ReactNoop.render(React.createElement(Foo))

		-- Render part way through but don't yet commit the updates.
		jestExpect(Scheduler).toFlushAndYieldThrough({
			"componentWillMount: false",
		})
		jestExpect(instances[1]:_isMounted()).toBe(false)

		-- Render the rest and commit the updates.
		jestExpect(function()
			return jestExpect(Scheduler).toFlushAndYield({
				"componentDidMount: true",
			})
		end).toErrorDev(
			"Using UNSAFE_componentWillMount in strict mode is not recommended",
			{ withoutStack = true }
		)
		jestExpect(instances[1]:_isMounted()).toBe(true)
	end)

	it("handles isMounted when an unmount is deferred", function()
		local instances = {}
		local Component = React.Component:extend("Component")

		function Component:init()
			self.state = {}
		end

		function Component:_isMounted()
			return self.__updater.isMounted(self)
		end
		function Component:UNSAFE_componentWillMount()
			table.insert(instances, self)
		end
		function Component:componentWillUnmount()
			Scheduler.unstable_yieldValue(
				"componentWillUnmount: " .. tostring(self:_isMounted())
			)
		end
		function Component:render()
			Scheduler.unstable_yieldValue("Component")
			return React.createElement("span")
		end

		local function Other()
			Scheduler.unstable_yieldValue("Other")
			return React.createElement("span")
		end

		local function Foo(props)
			if props.mount then
				return React.createElement(Component)
			end
			return React.createElement(Other)
		end

		ReactNoop.render(React.createElement(Foo, { mount = true }))
		jestExpect(function()
			return jestExpect(Scheduler).toFlushAndYield({
				"Component",
			})
		end).toErrorDev(
			"Using UNSAFE_componentWillMount in strict mode is not recommended",
			{ withoutStack = true }
		)

		jestExpect(instances[1]:_isMounted()).toBe(true)

		ReactNoop.render(React.createElement(Foo, { mount = false }))
		-- Render part way through but don't yet commit the updates so it is not
		-- fully unmounted yet.
		jestExpect(Scheduler).toFlushAndYieldThrough({
			"Other",
		})

		jestExpect(instances[1]:_isMounted()).toBe(true)

		-- Finish flushing the unmount.
		jestExpect(Scheduler).toFlushAndYield({
			"componentWillUnmount: true",
		})
		jestExpect(instances[1]:_isMounted()).toBe(false)
	end)
	-- ROBLOX TODO: 292: gets "componentDidMount", but not the inner span. maybe ReactFeatureFlags need alignment for tests?
	xit("finds no node before insertion and correct node before deletion", function()
		local classInstance = nil
		local function findInstance(inst)
			-- We ignore warnings fired by findInstance because we are testing
			-- that the actual behavior still works as expected even though it
			-- is deprecated.
			local oldConsoleError = console.error
			console.error = nil
			local ok, result = pcall(function()
				-- ROBLOX FIXME: always returns nil because subtreeFlags don't match upstream while finding fiber in getNearestMountedFiber
				return ReactNoop.findInstance(inst)
			end)
			console.error = oldConsoleError
			if ok then
				return result
			else
				error(result)
			end
		end

		local Component = React.Component:extend("Component")

		function Component:UNSAFE_componentWillMount()
			classInstance = self
			Scheduler.unstable_yieldValue({
				"componentWillMount",
				findInstance(self),
			})
		end
		function Component:componentDidMount()
			Scheduler.unstable_yieldValue({
				"componentDidMount",
				findInstance(self),
			})
		end
		function Component:UNSAFE_componentWillUpdate()
			Scheduler.unstable_yieldValue({
				"componentWillUpdate",
				findInstance(self),
			})
		end
		function Component:componentDidUpdate()
			Scheduler.unstable_yieldValue({
				"componentDidUpdate",
				findInstance(self),
			})
		end
		function Component:componentWillUnmount()
			Scheduler.unstable_yieldValue({
				"componentWillUnmount",
				findInstance(self),
			})
		end
		function Component:render()
			Scheduler.unstable_yieldValue("render")
			return function()
				if self.props.step < 2 then
					return React.createElement(span, {
						ref = function(ref)
							self.span = ref
							return ref
						end,
					})
				elseif self.props.step == 2 then
					return React.createElement(div, {
						ref = function(ref)
							self.div = ref
							return ref
						end,
					})
				elseif self.props.step == 3 then
					return nil
				elseif self.props.step == 4 then
					return React.createElement(div, {
						ref = function(ref)
							self.span = ref
							return ref
						end,
					})
				else
					return nil
				end
			end
		end

		local function Sibling()
			-- Sibling is used to assert that we've rendered past the first component.
			Scheduler.unstable_yieldValue("render sibling")
			return React.createElement(span)
		end

		local function Foo(props)
			return {
				React.createElement(Component, {
					key = "a",
					step = props.step,
				}),
				React.createElement(Sibling, {
					key = "b",
				}),
			}
		end

		ReactNoop.render(React.createElement(Foo, { step = 0 }))
		-- Flush past Component but don't complete rendering everything yet.
		jestExpect(Scheduler).toFlushAndYieldThrough({
			{
				"componentWillMount",
				nil,
			},
			"render",
			"render sibling",
		})

		jestExpect(classInstance).toBeDefined()
		-- The instance has been complete but is still not committed so it should
		-- not find any host nodes in it.
		jestExpect(findInstance(classInstance)).toBe(nil)
		jestExpect(function()
			-- ROBLOX FIXME: toFlushAndYield gets an empty array
			return jestExpect(Scheduler).toFlushAndYield({
				{
					"componentDidMount",
					span(),
				},
			})
		end).toErrorDev({
			"Using UNSAFE_componentWillMount in strict mode is not recommended",
			"Using UNSAFE_componentWillUpdate in strict mode is not recommended",
		}, { withoutStack = true })

		local hostSpan = classInstance.span
		jestExpect(hostSpan).toBeDefined()

		jestExpect(findInstance(classInstance)).toBe(hostSpan)

		-- Flush next step which will cause an update but not yet render a new host
		-- node.
		ReactNoop.render(React.createElement(Foo, { step = 1 }))
		jestExpect(Scheduler).toFlushAndYield({
			{
				"componentWillUpdate",
				hostSpan,
			},
			"render",
			"render sibling",
			{
				"componentDidUpdate",
				hostSpan,
			},
		})

		jestExpect(ReactNoop.findInstance(classInstance)).toBe(hostSpan)

		-- The next step will render a new host node but won't get committed yet.
		-- We expect this to mutate the original Fiber.
		ReactNoop.render(React.createElement(Foo, { step = 2 }))
		jestExpect(Scheduler).toFlushAndYieldThrough({
			{
				"componentWillUpdate",
				hostSpan,
			},
			"render",
			"render sibling",
		})

		-- This should still be the host span.
		jestExpect(ReactNoop.findInstance(classInstance)).toBe(hostSpan)

		-- When we finally flush the tree it will get committed.
		jestExpect(Scheduler).toFlushAndYield({
			{
				"componentDidUpdate",
				div(),
			},
		})

		local hostDiv = classInstance.div
		jestExpect(hostDiv).toBeDefined()
		jestExpect(hostSpan).never.toBe(hostDiv)

		-- We should now find the new host node.
		jestExpect(ReactNoop.findInstance(classInstance)).toBe(hostDiv)

		-- Render to null but don't commit it yet.
		ReactNoop.render(React.createElement(Foo, { step = 3 }))
		jestExpect(Scheduler).toFlushAndYieldThrough({
			{
				"componentWillUpdate",
				hostDiv,
			},
			"render",
			"render sibling",
		})

		-- This should still be the host div since the deletion is not committed.
		jestExpect(ReactNoop.findInstance(classInstance)).toBe(hostDiv)

		jestExpect(Scheduler).toFlushAndYield({
			{
				"componentDidUpdate",
				nil,
			},
		})

		-- This should still be the host div since the deletion is not committed.
		jestExpect(ReactNoop.findInstance(classInstance)).toBe(nil)

		-- Render a div again
		ReactNoop.render(React.createElement(Foo, { step = 4 }))
		jestExpect(Scheduler).toFlushAndYield({
			{
				"componentWillUpdate",
				nil,
			},
			"render",
			"render sibling",
			{
				"componentDidUpdate",
				div(),
			},
		})

		-- Unmount the component.
		ReactNoop.render({})
		jestExpect(Scheduler).toFlushAndYield({
			{
				"componentWillUnmount",
				hostDiv,
			},
		})
	end)
end)
