local d,i local F,k 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.it local z=b.xit local H=b.jest M(function()H.resetModules()H.useFakeTimers()d=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116")F=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116-\114\111\098\108\111\120")i=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")local b=Instance.new("\070\111\108\100\101\114")k=F.createRoot(b)end)B("\115\104\111\117\108\100 \097\108\108\111\119 \107\101\121 \112\114\111\112\101\114\116\121 \116\111 \101\120\112\114\101\115\115 \105\100\101\110\116\105\116\121",function()local F=d.createRef()local function b(i)local k={}for F=1,500,1 do local b=i.invert and tostring(51-F)or tostring(F)k[b]=d.createElement("\084\101\120\116\076\097\098\101\108",{Text=F})end return d.createElement("\070\114\097\109\101",{ref=F},unpack(k))end local function M(d)local i={}for d,F in d do i[F.Text]=F end return i end k:render(d.createElement(b))i.unstable_flushAllWithoutAsserting()local B=M(F.current:GetChildren())k:render(d.createElement(b,{invert=true}))i.unstable_flushAllWithoutAsserting()local z=M(F.current:GetChildren())for d=1,500,1 do(a(B[d])).toBe(z[51-d]);(a(B[51-d])).toBe(z[d])end end)B("\115\104\111\117\108\100 \097\108\108\111\119 \116\097\098\108\101 \107\101\121 \116\111 \101\120\112\114\101\115\115 \105\100\101\110\116\105\116\121",function()local F=d.createRef()local function b(i)local k={}for F=1,50,1 do local b=i.invert and tostring(51-F)or tostring(F)k[b]=d.createElement("\084\101\120\116\076\097\098\101\108",{Text=F})end return d.createElement("\070\114\097\109\101",{ref=F},k)end local function M(d)local i={}for d,F in d do i[F.Text]=F end return i end k:render(d.createElement(b))i.unstable_flushAllWithoutAsserting()local B=M(F.current:GetChildren())k:render(d.createElement(b,{invert=true}))i.unstable_flushAllWithoutAsserting()local z=M(F.current:GetChildren())for d=1,50,1 do(a(B[d])).toBe(z[51-d]);(a(B[51-d])).toBe(z[d])end end)B("\115\104\111\117\108\100 \117\115\101 \116\097\098\108\101 \107\101\121 \116\111 \101\120\112\114\101\115\115 \105\100\101\110\116\105\116\121 \119\104\101\110 \117\112\100\097\116\105\110\103 \099\104\105\108\100\114\101\110 \116\121\112\101",function()local F=d.createRef()local function b(i)local k={}for i=1,i.count,1 do k[tostring(i)]=d.createElement("\084\101\120\116\076\097\098\101\108",{Text=tostring(i)})end if i.count==0 then k.Test=d.createElement("\070\114\097\109\101")end return d.createElement("\070\114\097\109\101",{ref=F},k)end k:render(d.createElement(b,{count=0}))i.unstable_flushAllWithoutAsserting();(a(F.current:FindFirstChild(tostring("\084\101\115\116")))).never.toBe(nil)k:render(d.createElement(b,{count=15;complexComponents=false}))i.unstable_flushAllWithoutAsserting()for d=1,15,1 do(a(F.current:FindFirstChild(tostring(d)))).never.toBe(nil)end end)B("\115\104\111\117\108\100 \100\101\102\101\114 \116\111 \112\114\111\118\105\100\101\100 \107\101\121 \105\102 \098\111\116\104 \097\114\101 \112\114\101\115\101\110\116",function()local F=d.createRef()local function b(i)local k={}for F=1,50,1 do local b=i.invert and tostring(51-F)or tostring(F)k[tostring(F)]=d.createElement("\084\101\120\116\076\097\098\101\108",{key=b;Text=F})end return d.createElement("\070\114\097\109\101",{ref=F},k)end local function M(d)local i={}for d,F in d do i[F.Text]=F end return i end;(a(function()k:render(d.createElement(b))i.unstable_flushAllWithoutAsserting()end)).toErrorDev({"\080\108\101\097\115\101 \112\114\111\118\105\100\101 \111\110\108\121 \111\110\101 \107\101\121 \100\101\102\105\110\105\116\105\111\110\046 \087\104\101\110 \098\111\116\104 \097\114\101 \112\114\101\115\101\110\116\044 \116\104\101 \"\107\101\121\" \112\114\111\112 \119\105\108\108 \116\097\107\101 \112\114\101\099\101\100\101\110\099\101\046"})local B=M(F.current:GetChildren())k:render(d.createElement(b,{invert=true}))i.unstable_flushAllWithoutAsserting()local z=M(F.current:GetChildren())for d=1,50,1 do(a(B[d])).toBe(z[51-d]);(a(B[51-d])).toBe(z[d])end end)B("\115\104\111\117\108\100 \117\115\101 \099\111\109\112\111\115\105\116\101 \105\100\101\110\116\105\116\121",function()local F=d.Component:extend("\087\114\097\112\112\101\114")function F.render(i)return d.createElement("\070\114\097\109\101",nil,i.props.children)end local b=d.createRef()local M=d.createRef()k:render(d.createElement(F,{key="\119\114\097\112\049"},d.createElement("\070\114\097\109\101",{ref=b})))i.unstable_flushAllWithoutAsserting()k:render(d.createElement(F,{key="\119\114\097\112\050"},d.createElement("\070\114\097\109\101",{ref=M})))i.unstable_flushAllWithoutAsserting();(a(b.current)).never.toBe(M.current)end)local function U(F,b)local M=d.createRef()local B=d.Component:extend("\087\114\097\112\112\101\114")function B.render(i)return d.createElement("\070\114\097\109\101",nil,d.createElement("\070\114\097\109\101",{ref=M;key=F}))end k:render(d.createElement(B),b)i.unstable_flushAllWithoutAsserting()local z=M.current;(a(z)).never.toBe(nil)end B("\115\104\111\117\108\100 \097\108\108\111\119 \097\110\121 \099\104\097\114\097\099\116\101\114 \097\115 \097 \107\101\121\044 \105\110 \097 \100\101\116\097\099\104\101\100 \112\097\114\101\110\116",function()local i=d.createElement("\070\114\097\109\101")U("\060\'\087\069\073\082\068\047\038\092\107\101\121\'\062",i)end)B("\115\104\111\117\108\100 \097\108\108\111\119 \097\110\121 \099\104\097\114\097\099\116\101\114 \097\115 \097 \107\101\121\044 \105\110 \097\110 \097\116\116\097\099\104\101\100 \112\097\114\101\110\116",function()local F=d.createElement("\070\114\097\109\101")k:render(F)i.unstable_flushAllWithoutAsserting()U("\060\'\087\069\073\082\068\047\038\092\107\101\121\'\062",F)end)B("\115\104\111\117\108\100 \108\101\116 \114\101\115\116\114\117\099\116\117\114\101\100 \099\111\109\112\111\110\101\110\116\115 \114\101\116\097\105\110 \116\104\101\105\114 \117\110\105\113\117\101\110\101\115\115",function()local F=d.createElement("\070\114\097\109\101")local b=d.createElement("\070\114\097\109\101")local M=d.createElement("\070\114\097\109\101")local B=d.Component:extend("\084\101\115\116\067\111\109\112\111\110\101\110\116")function B.render(i)return d.createElement("\070\114\097\109\101",nil,M,i.props.children[1],i.props.children[2])end local z=d.Component:extend("\084\101\115\116\067\111\110\116\097\105\110\101\114")function z.render(i)return d.createElement(B,nil,F,b)end;(a(function()k:render(d.createElement(z))i.unstable_flushAllWithoutAsserting()end)).never.toThrow()end)B("\115\104\111\117\108\100 \108\101\116 \110\101\115\116\101\100 \114\101\115\116\114\117\099\116\117\114\101\115 \114\101\116\097\105\110 \116\104\101\105\114 \117\110\105\113\117\101\110\101\115\115",function()local F=d.createElement("\070\114\097\109\101")local b=d.createElement("\070\114\097\109\101")local M=d.createElement("\070\114\097\109\101")local B=d.Component:extend("\084\101\115\116\067\111\109\112\111\110\101\110\116")function B.render(i)return d.createElement("\070\114\097\109\101",nil,M,i.props.children[1],i.props.children[2])end local z=d.Component:extend("\084\101\115\116\067\111\110\116\097\105\110\101\114")function z.render(i)return d.createElement("\070\114\097\109\101",nil,d.createElement(B,nil,F,b))end;(a(function()k:render(d.createElement(z))i.unstable_flushAllWithoutAsserting()end)).never.toThrow()end)z("\115\104\111\117\108\100 \108\101\116 \116\101\120\116 \110\111\100\101\115 \114\101\116\097\105\110 \116\104\101\105\114 \117\110\105\113\117\101\110\101\115\115",function()local F=d.Component:extend("\084\101\115\116\067\111\109\112\111\110\101\110\116")function F.render(i)return d.createElement("\070\114\097\109\101",nil,i.props.children,d.createElement("\070\114\097\109\101"))end local b=d.Component:extend("\084\101\115\116\067\111\110\116\097\105\110\101\114")function b.render(i)return d.createElement(F,nil,d.createElement("\070\114\097\109\101"),nil,{"\115\101\099\111\110\100"})end;(a(function()k:render(d.createElement(b))i.unstable_flushAllWithoutAsserting()end)).never.toThrow()end)B("\115\104\111\117\108\100 \114\101\116\097\105\110 \107\101\121 \100\117\114\105\110\103 \117\112\100\097\116\101\115 \105\110 \099\111\109\112\111\115\105\116\101 \099\111\109\112\111\110\101\110\116\115",function()local F=d.createRef()local b local M=d.Component:extend("\084\101\115\116\067\111\109\112\111\110\101\110\116")function M.render(i)return d.createElement("\070\114\097\109\101",{ref=F},i.props.children)end local B=d.Component:extend("\084\101\115\116\067\111\110\116\097\105\110\101\114")function B.init(d)d.state={swapped=false}b=function()d:setState({swapped=true})end end function B.render(i)return d.createElement(M,nil,i.state.swapped and i.props.second or i.props.first,i.state.swapped and i.props.first or i.props.second)end local z=d.createElement("\084\101\120\116\076\097\098\101\108",{key="\065",Text="\072\101\108\108\111"})local H=d.createElement("\084\101\120\116\076\097\098\101\108",{key="\066",Text="\087\111\114\108\100"})local function U(d)local i={}for d,F in d do i[F.Text]=F end return i end k:render(d.createElement(B,{first=z,second=H}))i.unstable_flushAllWithoutAsserting()local v=U(F.current:GetChildren())b()local S=U(F.current:GetChildren());(a(v.Hello)).toBe(S.Hello);(a(v.World)).toBe(S.World)end)B("\115\104\111\117\108\100 \110\111\116 \097\108\108\111\119 \105\109\112\108\105\099\105\116 \097\110\100 \101\120\112\108\105\099\105\116 \107\101\121\115 \116\111 \099\111\108\108\105\100\101",function()local F=function(i)return d.createElement("\070\114\097\109\101",nil,d.createElement("\070\114\097\109\101"),d.createElement("\070\114\097\109\101",{key="\049"}))end;(a(function()k:render(d.createElement(F))i.unstable_flushAllWithoutAsserting()end)).never.toThrow()end)
-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/__tests__/ReactIdentity-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
]]

-- ROBLOX deviation: This test file was adapted from `react-dom` and ported to roblox-renderer
local React, Scheduler
local ReactRoblox, reactRobloxRoot

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

beforeEach(function()
	jest.resetModules()
	jest.useFakeTimers()
	React = require("@pkg/@jsdotlua/react")
	ReactRoblox = require("@pkg/@jsdotlua/react-roblox")
	Scheduler = require("@pkg/@jsdotlua/scheduler")
	local parent = Instance.new("Folder")
	reactRobloxRoot = ReactRoblox.createRoot(parent)
end)

-- ROBLOX deviation: This test can hit succeed erroneously in luau, since
-- table iteration order is unpredictable; with only two elements and no
-- guaranteed order, react might happen to assign the right values even if key
-- behavior isn't working.
-- it("should allow key property to express identity", function()
--   local ref = React.createRef()
--   local function Component(props)
--     return React.createElement("Frame", {ref=ref},
--       React.createElement("Frame", {key=props.swap and "banana" or "apple", prop="Hello"}),
--       React.createElement("Frame", {key=props.swap and "apple" or "banana", prop="World"})
--     )
--   end

--   local function childrenByProp(children)
--     local byProp = {}
--     for _, instance in children do
--       byProp[instance.prop] = instance
--     end
--     return byProp
--   end

--   -- ROBLOX deviation: Use react-noop + act instead of rendering into document
--   ReactNoop.act(function()
--     ReactNoop.render(React.createElement(Component))
--   end)
--   local origChildren = childrenByProp(ref.current.children)
--   ReactNoop.act(function()
--     ReactNoop.render(React.createElement(Component, {swap=true}))
--   end)
--   local newChildren = childrenByProp(ref.current.children)

--   -- After rendering with `swap=true`, the keys will have switched and the
--   -- prop values will correspond to the opposite children
--   jestExpect(origChildren["Hello"]).toBe(newChildren["World"])
--   jestExpect(origChildren["World"]).toBe(newChildren["Hello"])
-- end)

-- ROBLOX deviation START: Replaces the above test. This new test verifies the
-- behavior expected above, but uses enough table keys to greatly reduce the
-- likelihood of coincidental success.
it("should allow key property to express identity", function()
	local ref = React.createRef()
	local function Component(props)
		local children = {}
		for i = 1, 500 do
			local key = props.invert and tostring(51 - i) or tostring(i)
			children[key] = React.createElement("TextLabel", { Text = i })
		end

		return React.createElement("Frame", { ref = ref }, unpack(children))
	end

	local function childrenByProp(children)
		local byProp = {}
		for _, instance in children do
			byProp[instance.Text] = instance
		end
		return byProp
	end

	reactRobloxRoot:render(React.createElement(Component))
	Scheduler.unstable_flushAllWithoutAsserting()

	local origChildren = childrenByProp(ref.current:GetChildren())

	reactRobloxRoot:render(React.createElement(Component, { invert = true }))
	Scheduler.unstable_flushAllWithoutAsserting()

	local newChildren = childrenByProp(ref.current:GetChildren())

	-- After rendering with `invert=true`, the keys will have switched and the
	-- prop values will correspond to the opposite children
	for i = 1, 500 do
		jestExpect(origChildren[i]).toBe(newChildren[51 - i])
		jestExpect(origChildren[51 - i]).toBe(newChildren[i])
	end
end)
-- ROBLOX deviation END

-- ROBLOX deviation: Verify equivalent behavior with table keys, an adaptation
-- to be compatible with currently-released Roact
it("should allow table key to express identity", function()
	local ref = React.createRef()
	local function Component(props)
		local children = {}
		for i = 1, 50 do
			local key = props.invert and tostring(51 - i) or tostring(i)
			children[key] = React.createElement("TextLabel", { Text = i })
		end

		return React.createElement("Frame", { ref = ref }, children)
	end

	local function childrenByProp(children)
		local byProp = {}
		for _, instance in children do
			byProp[instance.Text] = instance
		end
		return byProp
	end

	reactRobloxRoot:render(React.createElement(Component))
	Scheduler.unstable_flushAllWithoutAsserting()

	local origChildren = childrenByProp(ref.current:GetChildren())

	reactRobloxRoot:render(React.createElement(Component, { invert = true }))
	Scheduler.unstable_flushAllWithoutAsserting()

	local newChildren = childrenByProp(ref.current:GetChildren())

	-- After rendering with `invert=true`, the keys will have switched and the
	-- prop values will correspond to the opposite children
	for i = 1, 50 do
		jestExpect(origChildren[i]).toBe(newChildren[51 - i])
		jestExpect(origChildren[51 - i]).toBe(newChildren[i])
	end
end)

-- ROBLOX deviation: Verify equivalent behavior with table keys, an adaptation
-- to be compatible with currently-released Roact
it("should use table key to express identity when updating children type", function()
	local ref = React.createRef()

	local function Component(props)
		local children = {}
		for i = 1, props.count do
			children[tostring(i)] =
				React.createElement("TextLabel", { Text = tostring(i) })
		end

		if props.count == 0 then
			children["Test"] = React.createElement("Frame")
		end

		return React.createElement("Frame", { ref = ref }, children)
	end

	reactRobloxRoot:render(React.createElement(Component, {
		count = 0,
	}))
	Scheduler.unstable_flushAllWithoutAsserting()

	jestExpect(ref.current:FindFirstChild(tostring("Test"))).never.toBe(nil)

	reactRobloxRoot:render(React.createElement(Component, {
		count = 15,
		complexComponents = false,
	}))
	Scheduler.unstable_flushAllWithoutAsserting()

	for i = 1, 15 do
		jestExpect(ref.current:FindFirstChild(tostring(i))).never.toBe(nil)
	end
end)

it("should defer to provided key if both are present", function()
	local ref = React.createRef()
	local function Component(props)
		local children = {}
		for i = 1, 50 do
			local key = props.invert and tostring(51 - i) or tostring(i)
			-- provide both explicit key and table key, where table-key does not
			-- obey the `invert` prop and should not be the one that's used.
			children[tostring(i)] =
				React.createElement("TextLabel", { key = key, Text = i })
		end

		return React.createElement("Frame", { ref = ref }, children)
	end

	local function childrenByProp(children)
		local byProp = {}
		for _, instance in children do
			byProp[instance.Text] = instance
		end
		return byProp
	end

	jestExpect(function()
		reactRobloxRoot:render(React.createElement(Component))
		Scheduler.unstable_flushAllWithoutAsserting()
	end).toErrorDev({
		-- We expect to see warnings caused by using both kinds of key
		'Please provide only one key definition. When both are present, the "key" prop will take precedence.',
	})

	local origChildren = childrenByProp(ref.current:GetChildren())

	reactRobloxRoot:render(React.createElement(Component, { invert = true }))
	Scheduler.unstable_flushAllWithoutAsserting()

	local newChildren = childrenByProp(ref.current:GetChildren())

	-- After rendering with `invert=true`, the keys will have switched and the
	-- prop values will correspond to the opposite children
	for i = 1, 50 do
		jestExpect(origChildren[i]).toBe(newChildren[51 - i])
		jestExpect(origChildren[51 - i]).toBe(newChildren[i])
	end
end)

it("should use composite identity", function()
	local Wrapper = React.Component:extend("Wrapper")
	function Wrapper:render()
		return React.createElement("Frame", nil, self.props.children)
	end

	local ref1 = React.createRef()
	local ref2 = React.createRef()

	reactRobloxRoot:render(
		React.createElement(
			Wrapper,
			{ key = "wrap1" },
			React.createElement("Frame", { ref = ref1 })
		)
	)
	Scheduler.unstable_flushAllWithoutAsserting()

	reactRobloxRoot:render(
		React.createElement(
			Wrapper,
			{ key = "wrap2" },
			React.createElement("Frame", { ref = ref2 })
		)
	)
	Scheduler.unstable_flushAllWithoutAsserting()

	jestExpect(ref1.current).never.toBe(ref2.current)
end)

local function renderAComponentWithKeyIntoContainer(key, container)
	local ref = React.createRef()

	local Wrapper = React.Component:extend("Wrapper")
	function Wrapper:render()
		return React.createElement(
			"Frame",
			nil,
			React.createElement("Frame", { ref = ref, key = key })
		)
	end

	reactRobloxRoot:render(React.createElement(Wrapper), container)
	Scheduler.unstable_flushAllWithoutAsserting()

	local span = ref.current
	jestExpect(span).never.toBe(nil)
end

it("should allow any character as a key, in a detached parent", function()
	local detachedContainer = React.createElement("Frame")
	renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", detachedContainer)
end)

it("should allow any character as a key, in an attached parent", function()
	-- This test exists to protect against implementation details that
	-- incorrectly query escaped IDs using DOM tools like getElementById.
	local attachedContainer = React.createElement("Frame")
	reactRobloxRoot:render(attachedContainer)
	Scheduler.unstable_flushAllWithoutAsserting()

	renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", attachedContainer)
end)

-- ROBLOX deviation: this test not relevant to Roblox or reconciler, since
-- script execution doesn't work this way to begin with
-- it('should not allow scripts in keys to execute', function()
--   local h4x0rKey =
--     '"><script>window[\'YOUVEBEENH4X0RED\']=true;</script><div id="'

--   local attachedContainer = document.createElement('div')
--   document.body.appendChild(attachedContainer)

--   renderAComponentWithKeyIntoContainer(h4x0rKey, attachedContainer)

--   document.body.removeChild(attachedContainer)

--   -- If we get this far, make sure we haven't executed the code
--   jestExpect(window.YOUVEBEENH4X0RED).toBe(undefined)
-- end)

it("should let restructured components retain their uniqueness", function()
	local instance0 = React.createElement("Frame")
	local instance1 = React.createElement("Frame")
	local instance2 = React.createElement("Frame")

	local TestComponent = React.Component:extend("TestComponent")
	function TestComponent:render()
		return React.createElement(
			"Frame",
			nil,
			instance2,
			self.props.children[1],
			self.props.children[2]
		)
	end

	local TestContainer = React.Component:extend("TestContainer")
	function TestContainer:render()
		return React.createElement(TestComponent, nil, instance0, instance1)
	end

	jestExpect(function()
		reactRobloxRoot:render(React.createElement(TestContainer))
		Scheduler.unstable_flushAllWithoutAsserting()
	end).never.toThrow()
end)

it("should let nested restructures retain their uniqueness", function()
	local instance0 = React.createElement("Frame")
	local instance1 = React.createElement("Frame")
	local instance2 = React.createElement("Frame")

	local TestComponent = React.Component:extend("TestComponent")
	function TestComponent:render()
		return React.createElement(
			"Frame",
			nil,
			instance2,
			self.props.children[1],
			self.props.children[2]
		)
	end

	local TestContainer = React.Component:extend("TestContainer")
	function TestContainer:render()
		return React.createElement(
			"Frame",
			nil,
			React.createElement(TestComponent, nil, instance0, instance1)
		)
	end

	jestExpect(function()
		reactRobloxRoot:render(React.createElement(TestContainer))
		Scheduler.unstable_flushAllWithoutAsserting()
	end).never.toThrow()
end)

-- ROBLOX deviaton: Roblox game engine doesn't support raw text nodes
xit("should let text nodes retain their uniqueness", function()
	local TestComponent = React.Component:extend("TestComponent")
	function TestComponent:render()
		return React.createElement(
			"Frame",
			nil,
			self.props.children,
			React.createElement("Frame")
		)
	end

	local TestContainer = React.Component:extend("TestContainer")
	function TestContainer:render()
		return React.createElement(
			TestComponent,
			nil,
			React.createElement("Frame"),
			nil,
			{ "second" }
		)
	end

	jestExpect(function()
		reactRobloxRoot:render(React.createElement(TestContainer))
		Scheduler.unstable_flushAllWithoutAsserting()
	end).never.toThrow()
end)

it("should retain key during updates in composite components", function()
	local ref = React.createRef()
	local swap

	local TestComponent = React.Component:extend("TestComponent")
	function TestComponent:render()
		return React.createElement("Frame", { ref = ref }, self.props.children)
	end

	local TestContainer = React.Component:extend("TestContainer")
	function TestContainer:init()
		self.state = { swapped = false }

		swap = function()
			self:setState({ swapped = true })
		end
	end

	function TestContainer:render()
		return React.createElement(
			TestComponent,
			nil,
			self.state.swapped and self.props.second or self.props.first,
			self.state.swapped and self.props.first or self.props.second
		)
	end

	local instance0 = React.createElement("TextLabel", { key = "A", Text = "Hello" })
	local instance1 = React.createElement("TextLabel", { key = "B", Text = "World" })

	local function childrenByProp(children)
		local byProp = {}
		for _, instance in children do
			byProp[instance.Text] = instance
		end
		return byProp
	end

	reactRobloxRoot:render(
		React.createElement(TestContainer, { first = instance0, second = instance1 })
	)
	Scheduler.unstable_flushAllWithoutAsserting()

	local originalChildren = childrenByProp(ref.current:GetChildren())
	swap()
	local newChildren = childrenByProp(ref.current:GetChildren())

	jestExpect(originalChildren["Hello"]).toBe(newChildren["Hello"])
	jestExpect(originalChildren["World"]).toBe(newChildren["World"])
end)

it("should not allow implicit and explicit keys to collide", function()
	local component = function(_props)
		return React.createElement(
			"Frame",
			nil,
			React.createElement("Frame"),
			React.createElement("Frame", { key = "1" })
		)
	end

	jestExpect(function()
		reactRobloxRoot:render(React.createElement(component))
		Scheduler.unstable_flushAllWithoutAsserting()
	end).never.toThrow()
end)
