local x=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 s=x.beforeEach local F=x.expect local z=x.describe local W=x.it local M=x.jest local S=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 p=S.Object local r local Q local J local B="\082\067\084\086\105\101\119"local c z("\082\101\097\099\116\084\101\115\116\082\101\110\100\101\114\101\114\084\114\097\118\101\114\115\097\108",function()local x local z local S local l local O s(function()M.resetModules()r=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116")c=function(x)return r.createElement(B,x)end Q=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")S=r.Component:extend("\069\120\097\109\112\108\101\083\112\114\101\097\100")J=r.createContext(nil)O=r.Component:extend("\069\120\097\109\112\108\101")function O.render(s)return r.createElement(c,nil,r.createElement(c,{foo="\102\111\111"},r.createElement(c,{bar="\098\097\114"}),r.createElement(c,{bar="\098\097\114",baz="\098\097\122";itself="\105\116\115\101\108\102"}),r.createElement(c),r.createElement(S,{bar="\098\097\114"}),r.createElement(x,{bar="\098\097\114",bing="\098\105\110\103"}),r.createElement(z,{bar="\098\097\114"}),r.createElement(z,{null="\110\117\108\108"},r.createElement(c,{void="\118\111\105\100"}),r.createElement(c,{void="\118\111\105\100"})),r.createElement(r.Profiler,{id="\116\101\115\116",onRender=function()return end},r.createElement(l,{qux="\113\117\120"})),r.createElement(r.Fragment,nil,r.createElement(r.Fragment,nil,r.createElement(J.Provider,{value=p.None},r.createElement(J.Consumer,nil,function()return r.createElement(c,{nested=true})end))),r.createElement(c,{nested=true}),r.createElement(c,{nested=true}))))end function S.render(x)return r.createElement(c,x.props)end x=function(x)return r.createElement(c,{baz="\098\097\122"})end z=function(x)return nil end l=r.forwardRef(function(x,s)return r.createElement(c,{qux=x.qux,ref=s})end)end)W("\105\110\105\116\105\097\108\105\122\101\115",function()local x=Q.create(r.createElement(O))local s=function(x)return rawget(x.props,"\102\111\111")~=nil end local z=x.root:find(s);(F(z.props.children)).toHaveLength(9);(F(z.type)).toBe(c);(F(x.root.parent)).toBe(nil);(F(z.children[1].parent._fiber)).toBe(z._fiber)end)W("\115\101\097\114\099\104\101\115 \118\105\097 \046\102\105\110\100\040\041 \047 \046\102\105\110\100\065\108\108\040\041",function()local x=Q.create(r.createElement(O))local s=function(x)return rawget(x.props,"\102\111\111")~=nil end local z=function(x)return rawget(x.props,"\098\097\114")~=nil end local W=function(x)return rawget(x.props,"\098\097\122")~=nil end local M=function(x)return rawget(x.props,"\098\105\110\103")~=nil end local S=function(x)return rawget(x.props,"\110\117\108\108")~=nil end local p=function(x)return rawget(x.props,"\118\111\105\100")~=nil end local J=function(x)return rawget(x.props,"\105\116\115\101\108\102")~=nil end local B=function(x)return rawget(x.props,"\110\101\115\116\101\100")~=nil end;(F(function()return x.root:find(s)end)).never.toThrow();(F(function()return x.root:find(z)end)).toThrow();(F(function()return x.root:find(W)end)).toThrow();(F(function()return x.root:find(M)end)).never.toThrow();(F(function()return x.root:find(S)end)).never.toThrow();(F(function()return x.root:find(p)end)).toThrow();(F(function()return x.root:find(B)end)).toThrow();(F(x.root:findAll(s,{deep=false}))).toHaveLength(1);(F(x.root:findAll(z,{deep=false}))).toHaveLength(5);(F(x.root:findAll(W,{deep=false}))).toHaveLength(2);(F(x.root:findAll(M,{deep=false}))).toHaveLength(1);(F(x.root:findAll(S,{deep=false}))).toHaveLength(1);(F(x.root:findAll(p,{deep=false}))).toHaveLength(0);(F(x.root:findAll(B,{deep=false}))).toHaveLength(3);(F(x.root:findAll(s))).toHaveLength(2);(F(x.root:findAll(z))).toHaveLength(9);(F(x.root:findAll(W))).toHaveLength(4);(F(x.root:findAll(M))).toHaveLength(1);(F(x.root:findAll(S))).toHaveLength(1);(F(x.root:findAll(p))).toHaveLength(0);(F(x.root:findAll(B,{deep=false}))).toHaveLength(3)local c=x.root:find(M);(F(c:find(z))).toBe(c);(F(c:find(M))).toBe(c);(F(c:findAll(W,{deep=false}))).toHaveLength(1);(F(c:findAll(W))).toHaveLength(2)local l=x.root:find(s);(F(l:findAll(s,{deep=false}))).toHaveLength(1);(F(l:findAll(s))).toHaveLength(2)local L=l:find(J);(F(L:find(z))).toBe(L);(F(L:find(W))).toBe(L);(F(L:findAll(W,{deep=false}))).toHaveLength(1);(F(L:findAll(W))).toHaveLength(2)end)W("\115\101\097\114\099\104\101\115 \118\105\097 \046\102\105\110\100\066\121\084\121\112\101\040\041 \047 \046\102\105\110\100\065\108\108\066\121\084\121\112\101\040\041",function()local s=Q.create(r.createElement(O));(F(function()return s.root:findByType(x)end)).never.toThrow();(F(function()return s.root:findByType(c)end)).never.toThrow();(F(function()return s.root:findByType(l)end)).never.toThrow();(F(function()return s.root:findByType(z)end)).toThrow();(F(#s.root:findAllByType(x))).toEqual(1);(F(#s.root:findAllByType(c,{deep=false}))).toEqual(1);(F(#s.root:findAllByType(c))).toEqual(11);(F(#s.root:findAllByType(z))).toEqual(2);(F(#s.root:findAllByType(l))).toEqual(1)local W=s.root:findAllByType(z);(F(#W[1]:findAllByType(c))).toEqual(0);(F(#W[2]:findAllByType(c))).toEqual(0)local M=s.root:findAllByType(x);(F(#M[1]:findAllByType(c))).toEqual(1)end)W("\115\101\097\114\099\104\101\115 \118\105\097 \046\102\105\110\100\066\121\080\114\111\112\115\040\041 \047 \046\102\105\110\100\065\108\108\066\121\080\114\111\112\115\040\041",function()local x=Q.create(r.createElement(O))local s="\102\111\111"local z="\098\097\114"local W="\098\097\122"local M="\113\117\120";(F(function()return x.root:findByProps({foo=s})end)).never.toThrow();(F(function()return x.root:findByProps({bar=z})end)).toThrow();(F(function()return x.root:findByProps({baz=W})end)).toThrow();(F(function()return x.root:findByProps({qux=M})end)).never.toThrow();(F(x.root:findAllByProps({foo=s},{deep=false}))).toHaveLength(1);(F(x.root:findAllByProps({bar=z},{deep=false}))).toHaveLength(5);(F(x.root:findAllByProps({baz=W},{deep=false}))).toHaveLength(2);(F(x.root:findAllByProps({qux=M},{deep=false}))).toHaveLength(1);(F(x.root:findAllByProps({foo=s}))).toHaveLength(2);(F(x.root:findAllByProps({bar=z}))).toHaveLength(9);(F(x.root:findAllByProps({baz=W}))).toHaveLength(4);(F(x.root:findAllByProps({qux=M}))).toHaveLength(3)end)W("\115\107\105\112\115 \115\112\101\099\105\097\108 \110\111\100\101\115",function()local x=Q.create(r.createElement(O));(F(x.root:findAllByType(r.Fragment))).toHaveLength(0);(F(x.root:findAllByType(J.Consumer))).toHaveLength(0);(F(x.root:findAllByType(J.Provider))).toHaveLength(0)local s=(x.root:findByProps({foo="\102\111\111"},{deep=false})).children[1]local z=x.root:findAllByProps({nested=true},{deep=false});(F(#z)).toEqual(3);(F(z[1].parent._fiber)).toBe(s._fiber);(F(z[2].parent._fiber)).toBe(s._fiber);(F(z[3].parent._fiber)).toBe(s._fiber)end)W("\099\097\110 \104\097\118\101 \115\112\101\099\105\097\108 \110\111\100\101\115 \097\115 \114\111\111\116\115",function()local x=r.forwardRef(function(x,s)return r.createElement("\115\101\099\116\105\111\110",x)end);(F(#(Q.create(r.createElement(x,nil,r.createElement("\100\105\118"),r.createElement("\100\105\118")))).root:findAllByType("\100\105\118"))).toEqual(2);(F(#(Q.create(r.createElement(r.Fragment,nil,r.createElement("\100\105\118"),r.createElement("\100\105\118")))).root:findAllByType("\100\105\118"))).toEqual(2);(F(#(Q.create(r.createElement(r.Fragment,{key="\102\111\111"},r.createElement("\100\105\118",nil),r.createElement("\100\105\118",nil)))).root:findAllByType("\100\105\118"))).toEqual(2);(F(#(Q.create(r.createElement(r.StrictMode,nil,r.createElement("\100\105\118"),r.createElement("\100\105\118")))).root:findAllByType("\100\105\118"))).toEqual(2);(F(#(Q.create(r.createElement(J.Provider,{value=p.None},r.createElement("\100\105\118"),r.createElement("\100\105\118")))).root:findAllByType("\100\105\118"))).toEqual(2)end)end)
-- Upstream: https://github.com/facebook/react/blob/16654436039dd8f16a63928e71081c7745872e8f/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js
--!strict
-- /**
--  * 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 JestGlobals = require("@pkg/@jsdotlua/jest-globals")
local beforeEach = JestGlobals.beforeEach
local jestExpect = JestGlobals.expect
local describe = JestGlobals.describe
local it = JestGlobals.it
local jest = JestGlobals.jest

local LuauPolyfill = require("@pkg/@jsdotlua/luau-polyfill")
local Object = LuauPolyfill.Object

local React
local ReactTestRenderer
local Context
local RCTView = "RCTView"
local View

describe("ReactTestRendererTraversal", function()
	local ExampleFn
	local ExampleNull
	local ExampleSpread
	local ExampleForwardRef

	local Example

	beforeEach(function()
		jest.resetModules()
		React = require("@pkg/@jsdotlua/react")
		View = function(props)
			return React.createElement(RCTView, props)
		end
		ReactTestRenderer = require("@pkg/@jsdotlua/react-test-renderer")
		ExampleSpread = React.Component:extend("ExampleSpread")
		Context = React.createContext(nil)

		Example = React.Component:extend("Example")
		function Example:render()
			return React.createElement(
				View,
				nil,
				React.createElement(
					View,
					{
						foo = "foo",
					},
					React.createElement(View, {
						bar = "bar",
					}),
					React.createElement(View, {
						bar = "bar",
						baz = "baz",
						itself = "itself",
					}),
					React.createElement(View),
					React.createElement(ExampleSpread, {
						bar = "bar",
					}),
					React.createElement(ExampleFn, {
						bar = "bar",
						bing = "bing",
					}),
					React.createElement(ExampleNull, {
						bar = "bar",
					}),
					React.createElement(
						ExampleNull,
						{
							null = "null",
						},
						React.createElement(View, {
							void = "void",
						}),
						React.createElement(View, {
							void = "void",
						})
					),
					React.createElement(
						React.Profiler,
						{
							id = "test",
							onRender = function()
								return
							end,
						},
						React.createElement(ExampleForwardRef, {
							qux = "qux",
						})
					),
					React.createElement(
						React.Fragment,
						nil,
						React.createElement(
							React.Fragment,
							nil,
							React.createElement(
								Context.Provider,
								{ value = Object.None },
								React.createElement(Context.Consumer, nil, function()
									return React.createElement(View, { nested = true })
								end)
							)
						),
						React.createElement(View, { nested = true }),
						React.createElement(View, { nested = true })
					)
				)
			)
		end

		function ExampleSpread:render()
			return React.createElement(View, self.props)
		end

		ExampleFn = function(props)
			return React.createElement(View, {
				baz = "baz",
			})
		end
		ExampleNull = function(props)
			return nil
		end
		ExampleForwardRef = React.forwardRef(function(props, ref)
			-- ROBLOX deviation: no easy spread operator, and tests don't demand a generic solution
			return React.createElement(View, { qux = props.qux, ref = ref })
		end)
	end)

	it("initializes", function()
		local render = ReactTestRenderer.create(React.createElement(Example))
		local hasFooProp = function(node)
			-- ROBLOX deviation: workaround for hasOwnProperty
			return rawget(node.props, "foo") ~= nil
		end

		-- assert .props, .type and .parent attributes
		local foo = render.root:find(hasFooProp)

		jestExpect(foo.props.children).toHaveLength(9)
		jestExpect(foo.type).toBe(View)
		jestExpect(render.root.parent).toBe(nil)
		-- ROBLOX FIXME: when this matches upstream, jestExpect takes an hour to complete
		jestExpect(foo.children[1].parent._fiber).toBe(foo._fiber)
	end)
	it("searches via .find() / .findAll()", function()
		local render = ReactTestRenderer.create(React.createElement(Example))
		local hasFooProp = function(node)
			-- ROBLOX deviation: workaround for hasOwnProperty
			return rawget(node.props, "foo") ~= nil
		end
		local hasBarProp = function(node)
			-- ROBLOX deviation: workaround for hasOwnProperty
			return rawget(node.props, "bar") ~= nil
		end
		local hasBazProp = function(node)
			-- ROBLOX deviation: workaround for hasOwnProperty
			return rawget(node.props, "baz") ~= nil
		end
		local hasBingProp = function(node)
			-- ROBLOX deviation: workaround for hasOwnProperty
			return rawget(node.props, "bing") ~= nil
		end
		local hasNullProp = function(node)
			-- ROBLOX deviation: workaround for hasOwnProperty
			return rawget(node.props, "null") ~= nil
		end
		local hasVoidProp = function(node)
			-- ROBLOX deviation: workaround for hasOwnProperty
			return rawget(node.props, "void") ~= nil
		end
		local hasItselfProp = function(node)
			-- ROBLOX deviation: workaround for hasOwnProperty
			return rawget(node.props, "itself") ~= nil
		end
		local hasNestedProp = function(node)
			-- ROBLOX deviation: workaround for hasOwnProperty
			return rawget(node.props, "nested") ~= nil
		end

		jestExpect(function()
			return render.root:find(hasFooProp)
		end).never.toThrow() -- 1 match
		jestExpect(function()
			return render.root:find(hasBarProp)
		end).toThrow() -- >1 matches
		jestExpect(function()
			return render.root:find(hasBazProp)
		end).toThrow() -- >1 matches
		jestExpect(function()
			return render.root:find(hasBingProp)
		end).never.toThrow() -- 1 match
		jestExpect(function()
			return render.root:find(hasNullProp)
		end).never.toThrow() -- 1 match
		jestExpect(function()
			return render.root:find(hasVoidProp)
		end).toThrow() -- 0 matches
		jestExpect(function()
			return render.root:find(hasNestedProp)
		end).toThrow() -- >1 matches

		-- same assertion as :find(), but confirm length
		jestExpect(render.root:findAll(hasFooProp, { deep = false })).toHaveLength(1)
		jestExpect(render.root:findAll(hasBarProp, { deep = false })).toHaveLength(5)
		jestExpect(render.root:findAll(hasBazProp, { deep = false })).toHaveLength(2)
		jestExpect(render.root:findAll(hasBingProp, { deep = false })).toHaveLength(1)
		jestExpect(render.root:findAll(hasNullProp, { deep = false })).toHaveLength(1)
		jestExpect(render.root:findAll(hasVoidProp, { deep = false })).toHaveLength(0)
		jestExpect(render.root:findAll(hasNestedProp, { deep = false })).toHaveLength(3)

		-- note: with {deep: true}, :findAll() will continue to
		--       search children, even after finding a match
		jestExpect(render.root:findAll(hasFooProp)).toHaveLength(2)
		jestExpect(render.root:findAll(hasBarProp)).toHaveLength(9)
		jestExpect(render.root:findAll(hasBazProp)).toHaveLength(4)
		jestExpect(render.root:findAll(hasBingProp)).toHaveLength(1) -- no spread
		jestExpect(render.root:findAll(hasNullProp)).toHaveLength(1) -- no spread
		jestExpect(render.root:findAll(hasVoidProp)).toHaveLength(0)
		jestExpect(render.root:findAll(hasNestedProp, { deep = false })).toHaveLength(3)

		local bing = render.root:find(hasBingProp)

		jestExpect(bing:find(hasBarProp)).toBe(bing)
		jestExpect(bing:find(hasBingProp)).toBe(bing)
		jestExpect(bing:findAll(hasBazProp, { deep = false })).toHaveLength(1)
		jestExpect(bing:findAll(hasBazProp)).toHaveLength(2)

		local foo = render.root:find(hasFooProp)
		jestExpect(foo:findAll(hasFooProp, { deep = false })).toHaveLength(1)
		jestExpect(foo:findAll(hasFooProp)).toHaveLength(2)

		local itself = foo:find(hasItselfProp)

		jestExpect(itself:find(hasBarProp)).toBe(itself)
		jestExpect(itself:find(hasBazProp)).toBe(itself)
		jestExpect(itself:findAll(hasBazProp, { deep = false })).toHaveLength(1)
		jestExpect(itself:findAll(hasBazProp)).toHaveLength(2)
	end)
	it("searches via .findByType() / .findAllByType()", function()
		local render = ReactTestRenderer.create(React.createElement(Example))
		jestExpect(function()
			return render.root:findByType(ExampleFn)
		end).never.toThrow() -- 1 match
		jestExpect(function()
			return render.root:findByType(View)
		end).never.toThrow() -- 1 match

		jestExpect(function()
			return render.root:findByType(ExampleForwardRef)
		end).never.toThrow() -- 1 match

		-- note: there are clearly multiple <View /> in general, but there
		--       is only one being rendered at root node level
		jestExpect(function()
			return render.root:findByType(ExampleNull)
		end).toThrow() -- 2 matches
		jestExpect(#render.root:findAllByType(ExampleFn)).toEqual(1)
		jestExpect(#render.root:findAllByType(View, { deep = false })).toEqual(1)

		jestExpect(#render.root:findAllByType(View)).toEqual(11)

		jestExpect(#render.root:findAllByType(ExampleNull)).toEqual(2)

		jestExpect(#render.root:findAllByType(ExampleForwardRef)).toEqual(1)

		local nulls = render.root:findAllByType(ExampleNull)

		jestExpect(#nulls[1]:findAllByType(View)).toEqual(0)
		jestExpect(#nulls[2]:findAllByType(View)).toEqual(0)

		local fn = render.root:findAllByType(ExampleFn)

		jestExpect(#fn[1]:findAllByType(View)).toEqual(1)
	end)
	it("searches via .findByProps() / .findAllByProps()", function()
		local render = ReactTestRenderer.create(React.createElement(Example))
		local foo = "foo"
		local bar = "bar"
		local baz = "baz"
		local qux = "qux"

		jestExpect(function()
			return render.root:findByProps({ foo = foo })
		end).never.toThrow() -- 1 match
		jestExpect(function()
			return render.root:findByProps({ bar = bar })
		end).toThrow() -- >1 matches
		jestExpect(function()
			return render.root:findByProps({ baz = baz })
		end).toThrow() -- >1 matches

		jestExpect(function()
			return render.root:findByProps({ qux = qux })
		end).never.toThrow() -- 1 match

		jestExpect(render.root:findAllByProps({ foo = foo }, { deep = false })).toHaveLength(
			1
		)
		jestExpect(render.root:findAllByProps({ bar = bar }, { deep = false })).toHaveLength(
			5
		)
		jestExpect(render.root:findAllByProps({ baz = baz }, { deep = false })).toHaveLength(
			2
		)
		jestExpect(render.root:findAllByProps({ qux = qux }, { deep = false })).toHaveLength(
			1
		)

		jestExpect(render.root:findAllByProps({ foo = foo })).toHaveLength(2)
		jestExpect(render.root:findAllByProps({ bar = bar })).toHaveLength(9)
		jestExpect(render.root:findAllByProps({ baz = baz })).toHaveLength(4)
		-- ROBLOX FIXME: this assert currently fails, only gets 1
		jestExpect(render.root:findAllByProps({ qux = qux })).toHaveLength(3)
	end)
	it("skips special nodes", function()
		local render = ReactTestRenderer.create(React.createElement(Example))

		jestExpect(render.root:findAllByType(React.Fragment)).toHaveLength(0)
		jestExpect(render.root:findAllByType(Context.Consumer)).toHaveLength(0)
		jestExpect(render.root:findAllByType(Context.Provider)).toHaveLength(0)

		local expectedParent = render.root:findByProps({
			foo = "foo",
		}, {
			deep = false,
		}).children[1]
		local nestedViews = render.root:findAllByProps(
			{ nested = true },
			{ deep = false }
		)

		jestExpect(#nestedViews).toEqual(3)

		-- ROBLOX FIXME: jest expect take a long time to complete, it needs to be fixed
		jestExpect(nestedViews[1].parent._fiber).toBe(expectedParent._fiber)
		jestExpect(nestedViews[2].parent._fiber).toBe(expectedParent._fiber)
		jestExpect(nestedViews[3].parent._fiber).toBe(expectedParent._fiber)
	end)
	it("can have special nodes as roots", function()
		local FR = React.forwardRef(function(props, ref)
			return React.createElement("section", props)
		end)

		jestExpect(
			#ReactTestRenderer.create(
				React.createElement(
					FR,
					nil,
					React.createElement("div"),
					React.createElement("div")
				)
			).root
				:findAllByType("div")
		).toEqual(2)
		jestExpect(
			#ReactTestRenderer.create(
				React.createElement(
					React.Fragment,
					nil,
					React.createElement("div"),
					React.createElement("div")
				)
			).root
				:findAllByType("div")
		).toEqual(2)
		jestExpect(#ReactTestRenderer.create(React.createElement(React.Fragment, {
			key = "foo",
		}, React.createElement("div", nil), React.createElement("div", nil))).root
			:findAllByType("div")).toEqual(2)
		jestExpect(
			#ReactTestRenderer.create(
				React.createElement(
					React.StrictMode,
					nil,
					React.createElement("div"),
					React.createElement("div")
				)
			).root
				:findAllByType("div")
		).toEqual(2)
		jestExpect(
			#ReactTestRenderer.create(
				React.createElement(
					Context.Provider,
					{ value = Object.None },
					React.createElement("div"),
					React.createElement("div")
				)
			).root
				:findAllByType("div")
		).toEqual(2)
	end)
end)
