local P local k local p=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 O=p.Set local V=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 Q=V.describe Q("\082\101\097\099\116\080\114\111\102\105\108\101\114 \068\101\118\084\111\111\108\115 \105\110\116\101\103\114\097\116\105\111\110",function()local p=V.expect local Q=V.jest local s=V.it local x=V.beforeEach local J=V.afterEach local U local b local a local G local I local o x(function()I={inject=function() end;onCommitFiberRoot=Q.fn(function(P,k) end),onCommitFiberUnmount=function() end;supportsFiber=true}o=_G.__REACT_DEVTOOLS_GLOBAL_HOOK__ _G.__REACT_DEVTOOLS_GLOBAL_HOOK__=I Q.resetModules()U=(require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\115\104\097\114\101\100")).ReactFeatureFlags U.enableProfilerTimer=true U.enableSchedulerTracing=true k=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")a=k.tracing P=require("\046\046")b=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")G=P.Component:extend("\065\100\118\097\110\099\101\084\105\109\101")G.defaultProps={byAmount=10,shouldComponentUpdate=true}function G.shouldComponentUpdate(k,P)return P.shouldComponentUpdate end function G.render(P)k.unstable_advanceTime(P.props.byAmount)return P.props.children or nil end end)J(function()_G.__REACT_DEVTOOLS_GLOBAL_HOOK__=o end)s("\115\104\111\117\108\100 \097\117\116\111-\080\114\111\102\105\108\101 \097\108\108 \102\105\098\101\114\115 \105\102 \116\104\101 \068\101\118\084\111\111\108\115 \104\111\111\107 \105\115 \100\101\116\101\099\116\101\100",function()local V=Q.fn(function() end)local s=function(p)local O=p.multiplier k.unstable_advanceTime(2)return P.createElement(P.Profiler,{id="\080\114\111\102\105\108\101\114",onRender=V},P.createElement(G,{byAmount=3*O,shouldComponentUpdate=true}),P.createElement(G,{byAmount=7*O;shouldComponentUpdate=false}))end local x=b.create(P.createElement(s,{multiplier=1}));(p(I.onCommitFiberRoot)).toHaveBeenCalledTimes(1);(p(V)).toHaveBeenCalledTimes(1);(p(V)).toHaveBeenCalledWith("\080\114\111\102\105\108\101\114","\109\111\117\110\116",10,10,2,12,O.new())V.mockClear();(p(((x.root:findByType(s)):_currentFiber()).treeBaseDuration)).toBe(12)x.update(P.createElement(s,{multiplier=2}));(p(V)).toHaveBeenCalledTimes(1);(p(V)).toHaveBeenCalledWith("\080\114\111\102\105\108\101\114","\117\112\100\097\116\101",6,13,14,20,O.new());(p(((x.root:findByType(s)):_currentFiber()).treeBaseDuration)).toBe(15)end)s("\115\104\111\117\108\100 \114\101\115\101\116 \116\104\101 \102\105\098\101\114 \115\116\097\099\107 \099\111\114\114\101\099\116\108\121 \097\102\116\101\114 \097\110 \101\114\114\111\114 \119\104\101\110 \112\114\111\102\105\108\105\110\103 \104\111\115\116 \114\111\111\116\115",function()k.unstable_advanceTime(20)local O=b.create(P.createElement("\100\105\118",nil,P.createElement(G,{byAmount=2})))k.unstable_advanceTime(20);(p(function()O.update(P.createElement("\100\105\118",{ref="\116\104\105\115-\119\105\108\108-\099\097\117\115\101-\097\110-\101\114\114\111\114"},P.createElement(G,{byAmount=3})))end)).toThrow()k.unstable_advanceTime(20)O.update(P.createElement("\100\105\118",nil,P.createElement(G,{byAmount=7})));(p(((O.root:findByType("\100\105\118")):_currentFiber()).treeBaseDuration)).toBe(7)end)s("\115\104\111\117\108\100 \115\116\111\114\101 \116\114\097\099\101\100 \105\110\116\101\114\097\099\116\105\111\110\115 \111\110 \116\104\101 \072\111\115\116\078\111\100\101 \115\111 \068\101\118\084\111\111\108\115 \099\097\110 \097\099\099\101\115\115 \116\104\101\109",function()local O=b.create(P.createElement("\100\105\118"))local V=(O.root:_currentFiber()).return_;(p(V.stateNode.memoizedInteractions)).toContainNoInteractions()k.unstable_advanceTime(10)local Q=k.unstable_now()a.unstable_trace("\115\111\109\101 \101\118\101\110\116",Q,function()O.update(P.createElement("\100\105\118"))end);(p(V.stateNode.memoizedInteractions)).toMatchInteractions({{name="\115\111\109\101 \101\118\101\110\116";timestamp=Q}})end)s("\114\101\103\114\101\115\115\105\111\110 \116\101\115\116\058 \035\049\055\049\053\057",function()local function O(P)local p=P.text k.unstable_yieldValue(p)return p end local V=b.create(nil,{unstable_isConcurrent=true})V.update(P.createElement(O,{text="\065"}));(p(k)).toFlushAndYield({"\065"});(p(V)).toMatchRenderedOutput("\065")k.unstable_advanceTime(10000)V.update(P.createElement(O,{text="\066"}));(p(k)).toFlushExpired({});(p(k)).toFlushAndYield({"\066"});(p(V)).toMatchRenderedOutput("\066")end)end)
-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js
local React
local Scheduler
local LuauPolyfill = require("@pkg/@jsdotlua/luau-polyfill")
local Set = LuauPolyfill.Set
local JestGlobals = require("@pkg/@jsdotlua/jest-globals")
local describe = JestGlobals.describe

describe("ReactProfiler DevTools integration", function()
	local jestExpect = JestGlobals.expect
	local jest = JestGlobals.jest
	local it = JestGlobals.it
	local beforeEach = JestGlobals.beforeEach
	local afterEach = JestGlobals.afterEach
	local ReactFeatureFlags
	local ReactTestRenderer
	local SchedulerTracing
	local AdvanceTime
	local hook
	local originalDevtoolsState

	beforeEach(function()
		hook = {
			inject = function() end,
			onCommitFiberRoot = jest.fn(function(rendererId, root) end),
			onCommitFiberUnmount = function() end,
			supportsFiber = true,
		}
		originalDevtoolsState = _G.__REACT_DEVTOOLS_GLOBAL_HOOK__
		_G.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook

		jest.resetModules()

		ReactFeatureFlags = require("@pkg/@jsdotlua/shared").ReactFeatureFlags
		ReactFeatureFlags.enableProfilerTimer = true
		ReactFeatureFlags.enableSchedulerTracing = true
		Scheduler = require("@pkg/@jsdotlua/scheduler")
		-- ROBLOX deviation: import tracing from top-level Scheduler export to avoid direct file access
		SchedulerTracing = Scheduler.tracing
		React = require("..")
		ReactTestRenderer = require("@pkg/@jsdotlua/react-test-renderer")

		AdvanceTime = React.Component:extend("AdvanceTime")
		AdvanceTime.defaultProps = {
			byAmount = 10,
			shouldComponentUpdate = true,
		}
		function AdvanceTime:shouldComponentUpdate(nextProps)
			return nextProps.shouldComponentUpdate
		end
		function AdvanceTime:render()
			-- Simulate time passing when this component is rendered
			Scheduler.unstable_advanceTime(self.props.byAmount)
			return self.props.children or nil
		end
	end)

	afterEach(function()
		_G.__REACT_DEVTOOLS_GLOBAL_HOOK__ = originalDevtoolsState
	end)

	it("should auto-Profile all fibers if the DevTools hook is detected", function()
		-- ROBLOX deviation: hoist declaration so the value is captured correctly
		local onRender = jest.fn(function() end)
		local App = function(props)
			local multiplier = props.multiplier

			Scheduler.unstable_advanceTime(2)

			return React.createElement(
				React.Profiler,
				{
					id = "Profiler",
					onRender = onRender,
				},
				React.createElement(AdvanceTime, {
					byAmount = 3 * multiplier,
					shouldComponentUpdate = true,
				}),
				React.createElement(AdvanceTime, {
					byAmount = 7 * multiplier,
					shouldComponentUpdate = false,
				})
			)
		end

		local rendered =
			ReactTestRenderer.create(React.createElement(App, { multiplier = 1 }))

		jestExpect(hook.onCommitFiberRoot).toHaveBeenCalledTimes(1)

		-- Measure observable timing using the Profiler component.
		-- The time spent in App (above the Profiler) won't be included in the durations,
		-- But needs to be accounted for in the offset times.
		jestExpect(onRender).toHaveBeenCalledTimes(1)
		jestExpect(onRender).toHaveBeenCalledWith(
			"Profiler",
			"mount",
			10,
			10,
			2,
			12,
			Set.new()
		)
		onRender.mockClear()

		-- Measure unobservable timing required by the DevTools profiler.
		-- At this point, the base time should include both:
		-- The time 2ms in the App component itself, and
		-- The 10ms spend in the Profiler sub-tree beneath.
		jestExpect(rendered.root:findByType(App):_currentFiber().treeBaseDuration).toBe(
			12
		)

		rendered.update(React.createElement(App, { multiplier = 2 }))

		-- Measure observable timing using the Profiler component.
		-- The time spent in App (above the Profiler) won't be included in the durations,
		-- But needs to be accounted for in the offset times.
		jestExpect(onRender).toHaveBeenCalledTimes(1)
		jestExpect(onRender).toHaveBeenCalledWith(
			"Profiler",
			"update",
			6,
			13,
			14,
			20,
			Set.new()
		)

		-- Measure unobservable timing required by the DevTools profiler.
		-- At this point, the base time should include both:
		-- The initial 9ms for the components that do not re-render, and
		-- The updated 6ms for the component that does.
		jestExpect(rendered.root:findByType(App):_currentFiber().treeBaseDuration).toBe(
			15
		)
	end)

	it(
		"should reset the fiber stack correctly after an error when profiling host roots",
		function()
			Scheduler.unstable_advanceTime(20)

			local rendered = ReactTestRenderer.create(
				React.createElement(
					"div",
					nil,
					React.createElement(AdvanceTime, { byAmount = 2 })
				)
			)

			Scheduler.unstable_advanceTime(20)

			jestExpect(function()
				rendered.update(React.createElement("div", {
					ref = "this-will-cause-an-error",
				}, React.createElement(AdvanceTime, { byAmount = 3 })))
			end).toThrow()

			Scheduler.unstable_advanceTime(20)

			-- But this should render correctly, if the profiler's fiber stack has been reset.
			rendered.update(
				React.createElement(
					"div",
					nil,
					React.createElement(AdvanceTime, { byAmount = 7 })
				)
			)

			-- Measure unobservable timing required by the DevTools profiler.
			-- At this point, the base time should include only the most recent (not failed) render.
			-- It should not include time spent on the initial render,
			-- Or time that elapsed between any of the above renders.
			jestExpect(rendered.root:findByType("div"):_currentFiber().treeBaseDuration).toBe(
				7
			)
		end
	)

	it(
		"should store traced interactions on the HostNode so DevTools can access them",
		function()
			-- Render without an interaction
			local rendered = ReactTestRenderer.create(React.createElement("div"))

			local root = rendered.root:_currentFiber().return_
			jestExpect(root.stateNode.memoizedInteractions).toContainNoInteractions()

			Scheduler.unstable_advanceTime(10)

			local eventTime = Scheduler.unstable_now()

			-- Render with an interaction
			SchedulerTracing.unstable_trace("some event", eventTime, function()
				rendered.update(React.createElement("div"))
			end)

			jestExpect(root.stateNode.memoizedInteractions).toMatchInteractions({
				{
					name = "some event",
					timestamp = eventTime,
				},
			})
		end
	)

	it("regression test: #17159", function()
		local function Text(props)
			local text = props.text

			Scheduler.unstable_yieldValue(text)

			return text
		end

		local root = ReactTestRenderer.create(nil, { unstable_isConcurrent = true })

		-- Commit something
		root.update(React.createElement(Text, {
			text = "A",
		}))
		jestExpect(Scheduler).toFlushAndYield({
			"A",
		})
		jestExpect(root).toMatchRenderedOutput("A")

		-- Advance time by many seconds, larger than the default expiration time
		-- for updates.
		Scheduler.unstable_advanceTime(10000)
		root.update(React.createElement(Text, {
			text = "B",
		}))

		-- Update B should not instantly expire.
		jestExpect(Scheduler).toFlushExpired({})
		jestExpect(Scheduler).toFlushAndYield({
			"B",
		})
		jestExpect(root).toMatchRenderedOutput("B")
	end)
end)
