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 L=B.expect local a=B.jest local e=B.it local z=B.beforeEach local A=(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")).Error local W=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 S=W.Object local I local M local O local n z(function()a.resetModules()M=(require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\115\104\097\114\101\100")).ReactFeatureFlags M.replayFailedUnitOfWorkWithInvokeGuardedCallback=false I=require("\046\046")O=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")n=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)e("\115\104\111\117\108\100 \119\111\114\107 \119\105\116\104\111\117\116 \097 \114\101\102 \116\111 \098\101 \102\111\114\119\097\114\100\101\100",function()local B=I.Component:extend("\067\104\105\108\100")function B.render(B)n.unstable_yieldValue(B.props.value)return nil end local function a(L)return I.createElement(B,S.assign({},L,{ref=L.forwardedRef}))end local e=I.forwardRef(function(B,L)return I.createElement(a,S.assign({},B,{forwardedRef=L}))end)O.render(I.createElement(e,{value=123}));(L(n)).toFlushAndYield({123})end)e("\115\104\111\117\108\100 \102\111\114\119\097\114\100 \097 \114\101\102 \102\111\114 \097 \115\105\110\103\108\101 \099\104\105\108\100",function()local B=I.Component:extend("\067\104\105\108\100")function B.render(B)n.unstable_yieldValue(B.props.value)return nil end local function a(L)return I.createElement(B,S.assign({},L,{ref=L.forwardedRef}))end local e=I.forwardRef(function(B,L)return I.createElement(a,S.assign({},B,{forwardedRef=L}))end)local z=I.createRef()O.render(I.createElement(e,{ref=z;value=123}));(L(n)).toFlushAndYield({123});(L((getmetatable(z.current)).__index)).toBe(B)end)e("\115\104\111\117\108\100 \102\111\114\119\097\114\100 \097 \114\101\102 \102\111\114 \109\117\108\116\105\112\108\101 \099\104\105\108\100\114\101\110",function()local B=I.Component:extend("\067\104\105\108\100")function B.render(B)n.unstable_yieldValue(B.props.value)return nil end local function a(L)return I.createElement(B,S.assign({},L,{ref=L.forwardedRef}))end local e=I.forwardRef(function(B,L)return I.createElement(a,S.assign({},B,{forwardedRef=L}))end)local z=I.createRef()O.render(I.createElement("\100\105\118",{},I.createElement("\100\105\118"),I.createElement(e,{ref=z,value=123}),I.createElement("\100\105\118")));(L(n)).toFlushAndYield({123});(L((getmetatable(z.current)).__index)).toBe(B)end)e("\115\104\111\117\108\100 \109\097\105\110\116\097\105\110 \099\104\105\108\100 \105\110\115\116\097\110\099\101 \097\110\100 \114\101\102 \116\104\114\111\117\103\104 \117\112\100\097\116\101\115",function()local B=I.Component:extend("\067\104\105\108\100")function B.render(B)n.unstable_yieldValue(B.props.value)return nil end local function a(L)return I.createElement(B,S.assign({},L,{ref=L.forwardedRef}))end local e=I.forwardRef(function(B,L)return I.createElement(a,S.assign({},B,{forwardedRef=L}))end)local z=0 local A local W=function(B)z+=1 A=B end O.render(I.createElement(e,{ref=W,value=123}));(L(n)).toFlushAndYield({123});(L((getmetatable(A)).__index)).toBe(B);(L(z)).toBe(1)O.render(I.createElement(e,{ref=W,value=456}));(L(n)).toFlushAndYield({456});(L((getmetatable(A)).__index)).toBe(B);(L(z)).toBe(1)end)e("\115\104\111\117\108\100 \110\111\116 \098\114\101\097\107 \108\105\102\101\099\121\099\108\101 \101\114\114\111\114 \104\097\110\100\108\105\110\103",function()local B=I.Component:extend("\069\114\114\111\114\066\111\117\110\100\097\114\121")function B.init(B)B.state={error=nil}end function B.componentDidCatch(L,B)n.unstable_yieldValue("\069\114\114\111\114\066\111\117\110\100\097\114\121\046\099\111\109\112\111\110\101\110\116\068\105\100\067\097\116\099\104")L:setState({error=B})end function B.render(B)if B.state.error then n.unstable_yieldValue("\069\114\114\111\114\066\111\117\110\100\097\114\121\046\114\101\110\100\101\114\058 \099\097\116\099\104")return nil end n.unstable_yieldValue("\069\114\114\111\114\066\111\117\110\100\097\114\121\046\114\101\110\100\101\114\058 \116\114\121")return B.props.children end local a=I.Component:extend("\066\097\100\082\101\110\100\101\114")function a.render(B)n.unstable_yieldValue("\066\097\100\082\101\110\100\101\114 \116\104\114\111\119")error(A.new("\111\111\112\115\033"))end local function e(B)local L=B.forwardedRef n.unstable_yieldValue("\087\114\097\112\112\101\114")return I.createElement(a,S.assign({},B,{ref=L}))end local z=I.forwardRef(function(B,L)return I.createElement(e,S.assign({},B,{forwardedRef=L}))end)local W=I.createRef()O.render(I.createElement(B,nil,I.createElement(z,{ref=W})));(L(n)).toFlushAndYield({"\069\114\114\111\114\066\111\117\110\100\097\114\121\046\114\101\110\100\101\114\058 \116\114\121","\087\114\097\112\112\101\114","\066\097\100\082\101\110\100\101\114 \116\104\114\111\119","\069\114\114\111\114\066\111\117\110\100\097\114\121\046\114\101\110\100\101\114\058 \116\114\121";"\087\114\097\112\112\101\114","\066\097\100\082\101\110\100\101\114 \116\104\114\111\119";"\069\114\114\111\114\066\111\117\110\100\097\114\121\046\099\111\109\112\111\110\101\110\116\068\105\100\067\097\116\099\104","\069\114\114\111\114\066\111\117\110\100\097\114\121\046\114\101\110\100\101\114\058 \099\097\116\099\104"});(L(W.current)).toBe(nil)end)e("\115\104\111\117\108\100 \110\111\116 \114\101-\114\117\110 \116\104\101 \114\101\110\100\101\114 \099\097\108\108\098\097\099\107 \111\110 \097 \100\101\101\112 \115\101\116\083\116\097\116\101",function()local B local a=I.Component:extend("\073\110\110\101\114")function a.render(L)n.unstable_yieldValue("\073\110\110\101\114")B=L return I.createElement("\100\105\118",{ref=L.props.forwardedRef})end local function e(B)n.unstable_yieldValue("\077\105\100\100\108\101")return I.createElement(a,B)end local z=I.forwardRef(function(B,L)n.unstable_yieldValue("\070\111\114\119\097\114\100")return I.createElement(e,S.assign({},B,{forwardedRef=L}))end)local function A()n.unstable_yieldValue("\065\112\112")return I.createElement(z)end O.render(I.createElement(A));(L(n)).toFlushAndYield({"\065\112\112";"\070\111\114\119\097\114\100","\077\105\100\100\108\101","\073\110\110\101\114"})B:setState({});(L(n)).toFlushAndYield({"\073\110\110\101\114"})end)
-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react/src/__tests__/forwardRef-test.internal.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 FIXME Luau: this doesn't play nicely with Object.assign
--!nonstrict

local JestGlobals = require("@pkg/@jsdotlua/jest-globals")
local jestExpect = JestGlobals.expect
local jest = JestGlobals.jest
local it = JestGlobals.it
local beforeEach = JestGlobals.beforeEach
local Error = require("@pkg/@jsdotlua/luau-polyfill").Error
local LuauPolyfill = require("@pkg/@jsdotlua/luau-polyfill")
local Object = LuauPolyfill.Object

local React
local ReactFeatureFlags
local ReactNoop
local Scheduler

beforeEach(function()
	jest.resetModules()
	ReactFeatureFlags = require("@pkg/@jsdotlua/shared").ReactFeatureFlags

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

it("should work without a ref to be forwarded", function()
	local Child = React.Component:extend("Child")
	function Child:render()
		Scheduler.unstable_yieldValue(self.props.value)
		return nil
	end

	local function Wrapper(props)
		return React.createElement(
			Child,
			Object.assign({}, props, { ref = props.forwardedRef })
		)
	end

	local RefForwardingComponent = React.forwardRef(function(props, ref)
		return React.createElement(
			Wrapper,
			Object.assign({}, props, { forwardedRef = ref })
		)
	end)

	ReactNoop.render(React.createElement(RefForwardingComponent, { value = 123 }))
	jestExpect(Scheduler).toFlushAndYield({ 123 })
end)

it("should forward a ref for a single child", function()
	local Child = React.Component:extend("Child")
	function Child:render()
		Scheduler.unstable_yieldValue(self.props.value)
		return nil
	end

	local function Wrapper(props)
		return React.createElement(
			Child,
			Object.assign({}, props, { ref = props.forwardedRef })
		)
	end

	local RefForwardingComponent = React.forwardRef(function(props, ref)
		return React.createElement(
			Wrapper,
			Object.assign({}, props, { forwardedRef = ref })
		)
	end)

	local ref = React.createRef()

	ReactNoop.render(
		React.createElement(RefForwardingComponent, { ref = ref, value = 123 })
	)
	jestExpect(Scheduler).toFlushAndYield({ 123 })
	-- jestExpect(Object.instanceof(ref.current, Child)).toBe(true)
	jestExpect(getmetatable(ref.current).__index).toBe(Child)
end)

it("should forward a ref for multiple children", function()
	local Child = React.Component:extend("Child")
	function Child:render()
		Scheduler.unstable_yieldValue(self.props.value)
		return nil
	end

	local function Wrapper(props)
		return React.createElement(
			Child,
			Object.assign({}, props, { ref = props.forwardedRef })
		)
	end

	local RefForwardingComponent = React.forwardRef(function(props, ref)
		return React.createElement(
			Wrapper,
			Object.assign({}, props, { forwardedRef = ref })
		)
	end)

	local ref = React.createRef()

	ReactNoop.render(
		React.createElement(
			"div",
			{},
			React.createElement("div"),
			React.createElement(RefForwardingComponent, { ref = ref, value = 123 }),
			React.createElement("div")
		)
	)
	jestExpect(Scheduler).toFlushAndYield({ 123 })
	-- ROBLOX FIXME: When instanceof is implemented, use it
	-- jestExpect(Object.instanceof(ref.current, Child)).toBe(true)
	jestExpect(getmetatable(ref.current).__index).toBe(Child)
end)

it("should maintain child instance and ref through updates", function()
	local Child = React.Component:extend("Child")
	function Child:render()
		Scheduler.unstable_yieldValue(self.props.value)
		return nil
	end

	local function Wrapper(props)
		return React.createElement(
			Child,
			Object.assign({}, props, { ref = props.forwardedRef })
		)
	end

	local RefForwardingComponent = React.forwardRef(function(props, ref)
		return React.createElement(
			Wrapper,
			Object.assign({}, props, { forwardedRef = ref })
		)
	end)

	local setRefCount = 0
	local ref

	local setRef = function(r)
		setRefCount += 1
		ref = r
	end

	ReactNoop.render(
		React.createElement(RefForwardingComponent, { ref = setRef, value = 123 })
	)
	jestExpect(Scheduler).toFlushAndYield({ 123 })
	-- ROBLOX FIXME: When instanceof is implemented, use it
	-- jestExpect(Object.instanceof(ref, Child)).toBe(true)
	jestExpect(getmetatable(ref).__index).toBe(Child)
	jestExpect(setRefCount).toBe(1)
	ReactNoop.render(
		React.createElement(RefForwardingComponent, { ref = setRef, value = 456 })
	)
	jestExpect(Scheduler).toFlushAndYield({ 456 })
	-- ROBLOX FIXME: When instanceof is implemented, use it
	-- jestExpect(Object.instanceof(ref, Child)).toBe(true)
	jestExpect(getmetatable(ref).__index).toBe(Child)
	jestExpect(setRefCount).toBe(1)
end)

it("should not break lifecycle error handling", function()
	local ErrorBoundary = React.Component:extend("ErrorBoundary")
	function ErrorBoundary:init()
		self.state = { error = nil }
	end
	function ErrorBoundary:componentDidCatch(error_)
		Scheduler.unstable_yieldValue("ErrorBoundary.componentDidCatch")
		self:setState({ error = error_ })
	end
	function ErrorBoundary:render()
		if self.state.error then
			Scheduler.unstable_yieldValue("ErrorBoundary.render: catch")
			return nil
		end
		Scheduler.unstable_yieldValue("ErrorBoundary.render: try")
		return self.props.children
	end

	local BadRender = React.Component:extend("BadRender")
	function BadRender:render()
		Scheduler.unstable_yieldValue("BadRender throw")
		error(Error.new("oops!"))
	end

	local function Wrapper(props)
		local forwardedRef = props.forwardedRef
		Scheduler.unstable_yieldValue("Wrapper")
		return React.createElement(
			BadRender,
			Object.assign({}, props, { ref = forwardedRef })
		)
	end

	local RefForwardingComponent = React.forwardRef(function(props, ref)
		return React.createElement(
			Wrapper,
			Object.assign({}, props, { forwardedRef = ref })
		)
	end)

	local ref = React.createRef()

	ReactNoop.render(
		React.createElement(
			ErrorBoundary,
			nil,
			React.createElement(RefForwardingComponent, { ref = ref })
		)
	)
	-- ROBLOX Test Noise: jest setup config makes this hide error
	-- boundary warnings in upstream (scripts/jest/setupTests.js:72)
	jestExpect(Scheduler).toFlushAndYield({
		"ErrorBoundary.render: try",
		"Wrapper",
		"BadRender throw",

		-- React retries one more time
		"ErrorBoundary.render: try",
		"Wrapper",
		"BadRender throw",

		-- Errored again on retry. Now handle it.
		"ErrorBoundary.componentDidCatch",
		"ErrorBoundary.render: catch",
	})
	jestExpect(ref.current).toBe(nil)
end)

it("should not re-run the render callback on a deep setState", function()
	local inst

	local Inner = React.Component:extend("Inner")
	function Inner:render()
		Scheduler.unstable_yieldValue("Inner")
		inst = self
		return React.createElement("div", { ref = self.props.forwardedRef })
	end

	local function Middle(props)
		Scheduler.unstable_yieldValue("Middle")
		return React.createElement(Inner, props)
	end

	local Forward = React.forwardRef(function(props, ref)
		Scheduler.unstable_yieldValue("Forward")
		return React.createElement(
			Middle,
			Object.assign({}, props, { forwardedRef = ref })
		)
	end)

	local function App()
		Scheduler.unstable_yieldValue("App")
		return React.createElement(Forward)
	end

	ReactNoop.render(React.createElement(App))
	jestExpect(Scheduler).toFlushAndYield({ "App", "Forward", "Middle", "Inner" })

	inst:setState({})
	jestExpect(Scheduler).toFlushAndYield({ "Inner" })
end)
