local D=script.Parent.Parent.Parent.TestRunner local B=require(D.Dev.JestDiff)local function c(D,B)if _G.__DEV__ then if typeof(D)=="\115\116\114\105\110\103"then if string.find(D,"\069\114\114\111\114\058 \085\110\099\097\117\103\104\116 \037\091")==1 then return true end if string.find(D,"\087\097\114\110\105\110\103\058 \084\104\101 \097\098\111\118\101 \101\114\114\111\114 \111\099\099\117\114\114\101\100")==1 then return true end end else if D~=nil and(typeof(D.message)=="\115\116\114\105\110\103"and(typeof(D.stack)=="\115\116\114\105\110\103"and#B==0))then return true end if string.find(D,"\097\099\116\040\046\046\046\041 \105\115 \110\111\116 \115\117\112\112\111\114\116\101\100 \105\110 \112\114\111\100\117\099\116\105\111\110 \098\117\105\108\100\115 \111\102 \082\101\097\099\116")==0 then return true end end return false end local function z(D)if typeof(D)~="\115\116\114\105\110\103"then return D end D=string.gsub(D,"\067\104\101\099\107 \121\111\117\114 \099\111\100\101 \097\116 \046\042\058\037\100\043","\067\104\101\099\107 \121\111\117\114 \099\111\100\101 \097\116 \042\042")return string.gsub(D,"\n    \105\110 \040\091\037\119\037-\037\046\095\093\043\041\091\094\n\093\042","\n    \105\110 \037\049 \040\097\116 \042\042\041")end return function(G,H)return function(V,u,p,Y,...)local t=require(D.Dev.LuauPolyfill)local x=t.Array local b=t.console if Y==nil then Y={}end if _G.__DEV__ then if typeof(p)=="\115\116\114\105\110\103"then p={p}elseif not x.isArray(p)then error(string.format("\037\115\040\041 \114\101\113\117\105\114\101\115 \097 \112\097\114\097\109\101\116\101\114 \111\102 \116\121\112\101 \115\116\114\105\110\103 \111\114 \097\110 \097\114\114\097\121 \111\102 \115\116\114\105\110\103\115 ",H)..string.format("\098\117\116 \119\097\115 \103\105\118\101\110 \037\115\046",typeof(p)))end if typeof(Y)~="\116\097\098\108\101"or x.isArray(Y)and next(Y)~=nil then error(string.format("\037\115\040\041 \115\101\099\111\110\100 \097\114\103\117\109\101\110\116\044 \119\104\101\110 \112\114\101\115\101\110\116\044 \115\104\111\117\108\100 \098\101 \097\110 \111\098\106\101\099\116\046 ",H).."\068\105\100 \121\111\117 \102\111\114\103\101\116 \116\111 \119\114\097\112 \116\104\101 \109\101\115\115\097\103\101\115 \105\110\116\111 \097\110 \097\114\114\097\121\063")end if select("\035",...)>0 then error(string.format("\037\115\040\041 \114\101\099\101\105\118\101\100 \109\111\114\101 \116\104\097\110 \116\119\111 \097\114\103\117\109\101\110\116\115\046 ",H).."\068\105\100 \121\111\117 \102\111\114\103\101\116 \116\111 \119\114\097\112 \116\104\101 \109\101\115\115\097\103\101\115 \105\110\116\111 \097\110 \097\114\114\097\121\063")end local D=Y.withoutStack local V=Y.logAllErrors local t={}local J={}local m={}local h=nil local a=nil local s local function N(D)return typeof(D)=="\115\116\114\105\110\103"and string.match(D,"\n    \105\110 ")~=nil end local function S(D,...)local H={...}if not V and(G=="\101\114\114\111\114"and c(D,H))then return end local u=D local Y,b=pcall(string.format,D,unpack(H))if Y then u=b end local s=z(u)local S=0 local Q=string.gsub(D,"\037\037\115",function()S=S+1 return S-1 end)if not Y or S~=#H then h={format=D;args=H;expectedArgCount=S}end if#H>=2 and(N(H[#H])and N(H[#H-1]))then a={format=D}end for D=1,#p,1 do local B=p[D]if s==B or string.find(s,B,1,true)~=nil then if N(s)then table.insert(J,s)else table.insert(t,s)end x.splice(p,D,1)return end end local q if#p==0 then q="\085\110\101\120\112\101\099\116\101\100 \119\097\114\110\105\110\103 \114\101\099\111\114\100\101\100\058 "..s elseif#p==1 then q="\085\110\101\120\112\101\099\116\101\100 \119\097\114\110\105\110\103 \114\101\099\111\114\100\101\100\058 "..tostring(B.diff(p[1],s))else q="\085\110\101\120\112\101\099\116\101\100 \119\097\114\110\105\110\103 \114\101\099\111\114\100\101\100\058 "..tostring(B.diff(p,{s}))end table.insert(m,q)end local Q=b[G]b[G]=S local q,y=pcall(u)if not q then s=y end b[G]=Q if s then error(s,4)end if#m>0 then return{message=function()return m[1]end,pass=false}end if#p>0 then return{message=function()return string.format("\069\120\112\101\099\116\101\100 \119\097\114\110\105\110\103 \119\097\115 \110\111\116 \114\101\099\111\114\100\101\100\058 \037\115\n  ",p[1])end,pass=false}end if typeof(D)=="\110\117\109\098\101\114"then if D~=#t then local B=t return{message=function()return("\069\120\112\101\099\116\101\100 \037\100 \119\097\114\110\105\110\103\115 \119\105\116\104\111\117\116 \097 \099\111\109\112\111\110\101\110\116 \115\116\097\099\107 \098\117\116 \114\101\099\101\105\118\101\100 \037\100\058\n"):format(D,#t)..table.concat(B,"\n")end;pass=false}end elseif D==true then if#J>0 then return{message=function()return"\082\101\099\101\105\118\101\100 \119\097\114\110\105\110\103 \117\110\101\120\112\101\099\116\101\100\108\121 \105\110\099\108\117\100\101\115 \097 \099\111\109\112\111\110\101\110\116 \115\116\097\099\107\058\n"..(("  \037\115\n\073\102 \116\104\105\115 \119\097\114\110\105\110\103 \105\110\116\101\110\116\105\111\110\097\108\108\121 \105\110\099\108\117\100\101\115 \116\104\101 \099\111\109\112\111\110\101\110\116 \115\116\097\099\107\044 \114\101\109\111\118\101 "):format(J[1])..(("\123\119\105\116\104\111\117\116\083\116\097\099\107\058 \116\114\117\101\125 \102\114\111\109 \116\104\101 \037\115\040\041 \099\097\108\108\046 \073\102 \121\111\117 \104\097\118\101 \097 \109\105\120 \111\102 "):format(H)..(("\119\097\114\110\105\110\103\115 \119\105\116\104 \097\110\100 \119\105\116\104\111\117\116 \115\116\097\099\107 \105\110 \111\110\101 \037\115\040\041 \099\097\108\108\044 \112\097\115\115 "):format(H).."\123\119\105\116\104\111\117\116\083\116\097\099\107\058 \078\125 \119\104\101\114\101 \078 \105\115 \116\104\101 \110\117\109\098\101\114 \111\102 \119\097\114\110\105\110\103\115 \119\105\116\104\111\117\116 \115\116\097\099\107\115\046")))end,pass=false}end elseif D==false or D==nil then if#t>0 then return{message=function()return"\082\101\099\101\105\118\101\100 \119\097\114\110\105\110\103 \117\110\101\120\112\101\099\116\101\100\108\121 \100\111\101\115 \110\111\116 \105\110\099\108\117\100\101 \097 \099\111\109\112\111\110\101\110\116 \115\116\097\099\107\058\n"..(("  \037\115\n\073\102 \116\104\105\115 \119\097\114\110\105\110\103 \105\110\116\101\110\116\105\111\110\097\108\108\121 \111\109\105\116\115 \116\104\101 \099\111\109\112\111\110\101\110\116 \115\116\097\099\107\044 \097\100\100 "):format(t[1])..string.format("\123\119\105\116\104\111\117\116\083\116\097\099\107\058 \116\114\117\101\125 \116\111 \116\104\101 \037\115 \099\097\108\108\046",H))end,pass=false}end else error(("\084\104\101 \115\101\099\111\110\100 \097\114\103\117\109\101\110\116 \102\111\114 \037\115\040\041\044 \119\104\101\110 \115\112\101\099\105\102\105\101\100\044 \109\117\115\116 \098\101 \097\110 \111\098\106\101\099\116\046 \073\116 \109\097\121 \104\097\118\101 \097 "):format(H)..("\112\114\111\112\101\114\116\121 \099\097\108\108\101\100 \"\119\105\116\104\111\117\116\083\116\097\099\107\" \119\104\111\115\101 \118\097\108\117\101 \109\097\121 \098\101 \117\110\100\101\102\105\110\101\100\044 \098\111\111\108\101\097\110\044 \111\114 \097 \110\117\109\098\101\114\046 "..string.format("\073\110\115\116\101\097\100 \114\101\099\101\105\118\101\100 \037\115\046",typeof(D))))end if h~=nil then return{message=function()return("\082\101\099\101\105\118\101\100 \037\100 \097\114\103\117\109\101\110\116\115 \102\111\114 \097 \109\101\115\115\097\103\101 \119\105\116\104 \037\115 \112\108\097\099\101\104\111\108\100\101\114\115\058\n  \037\115"):format(#h.args,h.expectedArgCount,h.format)end,pass=false}end if a~=nil then return{message=function()return"\082\101\099\101\105\118\101\100 \109\111\114\101 \116\104\097\110 \111\110\101 \099\111\109\112\111\110\101\110\116 \115\116\097\099\107 \102\111\114 \097 \119\097\114\110\105\110\103\058\n"..(("  \037\115\n\068\105\100 \121\111\117 \097\099\099\105\100\101\110\116\097\108\108\121 \112\097\115\115 \097 \115\116\097\099\107 \116\111 \119\097\114\110\105\110\103\040\041 \097\115 \116\104\101 \108\097\115\116 \097\114\103\117\109\101\110\116\063 "):format(a.format).."\068\111\110\'\116 \102\111\114\103\101\116 \119\097\114\110\105\110\103\040\041 \097\108\114\101\097\100\121 \105\110\106\101\099\116\115 \116\104\101 \099\111\109\112\111\110\101\110\116 \115\116\097\099\107 \097\117\116\111\109\097\116\105\099\097\108\108\121\046")end;pass=false}end return{pass=true}else u()return{pass=true}end end end
--!nonstrict
-- ROBLOX upstream: https://github.com/facebook/react/blob/6d50a9d090a2a672fc3dea5ce77a3a05332a6caa/fixtures/legacy-jsx-runtimes/setupTests.js
local Packages = script.Parent.Parent.Parent.TestRunner
local JestDiff = require(Packages.Dev.JestDiff)

local function shouldIgnoreConsoleError(format, args)
	-- deviation: instead of checking if `process.env.NODE_ENV ~= "production"`
	-- we use the __DEV__ global
	if _G.__DEV__ then
		if typeof(format) == "string" then
			if string.find(format, "Error: Uncaught %[") == 1 then
				-- // This looks like an uncaught error from invokeGuardedCallback() wrapper
				-- // in development that is reported by jsdom. Ignore because it's noisy.
				return true
			end
			-- ROBLOX FIXME: The "Warning: " prefix is applied before the string
			-- reaches this function, which appears to not be the case upstream
			if string.find(format, "Warning: The above error occurred") == 1 then
				-- // This looks like an error addendum from ReactFiberErrorLogger.
				-- // Ignore it too.
				return true
			end
		end
	else
		if
			format ~= nil
			and typeof(format.message) == "string"
			and typeof(format.stack) == "string"
			and #args == 0
		then
			-- // In production, ReactFiberErrorLogger logs error objects directly.
			-- // They are noisy too so we'll try to ignore them.
			return true
		end
		if
			string.find(format, "act(...) is not supported in production builds of React")
			== 0
		then
			-- // We don't yet support act() for prod builds, and warn for it.
			-- // But we'd like to use act() ourselves for prod builds.
			-- // Let's ignore the warning and #yolo.
			return true
		end
	end

	return false
end

local function normalizeCodeLocInfo(str)
	if typeof(str) ~= "string" then
		return str
	end

	-- // This special case exists only for the special source location in
	-- // ReactElementValidator. That will go away if we remove source locations.
	str = string.gsub(str, "Check your code at .*:%d+", "Check your code at **")
	-- // V8 format:
	-- //  at Component (/path/filename.js:123:45)
	-- // React format:
	-- //    in Component (at filename.js:123)

	-- ROBLOX deviation: In roblox/luau, we're using the stack frame from luau,
	-- which looks like:
	--     in Component (at ModulePath.FileName.lua:123)
	return (string.gsub(str, "\n    in ([%w%-%._]+)[^\n]*", "\n    in %1 (at **)"))
end

return function(consoleMethod, matcherName)
	return function(_matcherContext, callback, expectedMessages, options, ...)
		local LuauPolyfill = require(Packages.Dev.LuauPolyfill)
		local Array = LuauPolyfill.Array
		local console = LuauPolyfill.console

		if options == nil then
			options = {}
		end
		-- deviation: instead of checking if `process.env.NODE_ENV ~= "production"`
		-- we use the __DEV__ global
		if _G.__DEV__ then
			-- // Warn about incorrect usage of matcher.
			if typeof(expectedMessages) == "string" then
				expectedMessages = { expectedMessages }
			elseif not Array.isArray(expectedMessages) then
				error(
					string.format(
						"%s() requires a parameter of type string or an array of strings ",
						matcherName
					)
						.. string.format("but was given %s.", typeof(expectedMessages))
				)
			end
			-- deviation: since an empty table will return true for
			-- `Array.isArray(options)`, check if the table is not empty
			if
				typeof(options) ~= "table"
				or (Array.isArray(options) and next(options) ~= nil)
			then
				error(
					string.format(
						"%s() second argument, when present, should be an object. ",
						matcherName
					)
						.. "Did you forget to wrap the messages into an array?"
				)
			end
			if select("#", ...) > 0 then
				error(
					string.format("%s() received more than two arguments. ", matcherName)
						.. "Did you forget to wrap the messages into an array?"
				)
			end

			local withoutStack = options.withoutStack
			local logAllErrors = options.logAllErrors
			local warningsWithoutComponentStack = {}
			local warningsWithComponentStack = {}
			local unexpectedWarnings = {}

			local lastWarningWithMismatchingFormat = nil
			local lastWarningWithExtraComponentStack = nil

			-- // Catch errors thrown by the callback,
			-- // But only rethrow them if all test expectations have been satisfied.
			-- // Otherwise an Error in the callback can mask a failed expectation,
			-- // and result in a test that passes when it shouldn't.
			local caughtError

			local function isLikelyAComponentStack(message)
				return typeof(message) == "string"
					and string.match(message, "\n    in ") ~= nil
			end

			local function consoleSpy(format, ...)
				-- // Ignore uncaught errors reported by jsdom
				-- // and React addendums because they're too noisy.
				local args = { ... }
				if
					not logAllErrors
					and consoleMethod == "error"
					and shouldIgnoreConsoleError(format, args)
				then
					return
				end

				local message = format
				local formattedOk, formattedOrError =
					pcall(string.format, format, unpack(args))
				if formattedOk then
					message = formattedOrError
				end
				local normalizedMessage = normalizeCodeLocInfo(message)

				-- Remember if the number of %s interpolations
				-- doesn't match the number of arguments.
				-- We'll fail the test if it happens.
				local argIndex = 0
				-- ROBLOX FIXME selene: remove _ assignment when bug in selene is fixed https://github.com/Kampfkarren/selene/issues/406
				local _ = string.gsub(format, "%%s", function()
					argIndex = argIndex + 1
					return argIndex - 1
				end)

				if not formattedOk or argIndex ~= #args then
					lastWarningWithMismatchingFormat = {
						format = format,
						args = args,
						expectedArgCount = argIndex,
					}
				end

				-- // Protect against accidentally passing a component stack
				-- // to warning() which already injects the component stack.
				if
					#args >= 2
					and isLikelyAComponentStack(args[#args])
					and isLikelyAComponentStack(args[#args - 1])
				then
					lastWarningWithExtraComponentStack = { format = format }
				end

				for index = 1, #expectedMessages do
					local expectedMessage = expectedMessages[index]
					if
						normalizedMessage == expectedMessage
						or string.find(normalizedMessage, expectedMessage, 1, true)
							~= nil
					then
						if isLikelyAComponentStack(normalizedMessage) then
							table.insert(warningsWithComponentStack, normalizedMessage)
						else
							table.insert(warningsWithoutComponentStack, normalizedMessage)
						end
						Array.splice(expectedMessages, index, 1)
						return
					end
				end

				local errorMessage
				if #expectedMessages == 0 then
					errorMessage = "Unexpected warning recorded: " .. normalizedMessage
				elseif #expectedMessages == 1 then
					errorMessage = "Unexpected warning recorded: "
						.. tostring(JestDiff.diff(expectedMessages[1], normalizedMessage))
				else
					errorMessage = "Unexpected warning recorded: "
						.. tostring(
							JestDiff.diff(expectedMessages, { normalizedMessage })
						)
				end

				-- // Record the call stack for unexpected warnings.
				-- // We don't throw an Error here though,
				-- // Because it might be suppressed by ReactFiberScheduler.
				table.insert(unexpectedWarnings, errorMessage)
			end

			-- // TODO Decide whether we need to support nested toWarn* expectations.
			-- // If we don't need it, add a check here to see if this is already our spy,
			-- // And throw an error.
			local originalMethod = console[consoleMethod]

			-- // Avoid using Jest's built-in spy since it can't be removed.
			console[consoleMethod] = consoleSpy

			local ok, errorMessage = pcall(callback)
			if not ok then
				caughtError = errorMessage
			end

			-- finally block
			-- // Restore the unspied method so that unexpected errors fail tests.
			console[consoleMethod] = originalMethod

			-- // Any unexpected Errors thrown by the callback should fail the test.
			-- // This should take precedence since unexpected errors could block warnings.
			if caughtError then
				error(caughtError, 4)
			end

			-- // Any unexpected warnings should be treated as a failure.
			if #unexpectedWarnings > 0 then
				return {
					message = function()
						return unexpectedWarnings[1]
					end,
					pass = false,
				}
			end

			-- // Any remaining messages indicate a failed expectations.
			if #expectedMessages > 0 then
				return {
					message = function()
						return string.format(
							"Expected warning was not recorded: %s\n  ",
							expectedMessages[1]
						)
					end,
					pass = false,
				}
			end

			if typeof(withoutStack) == "number" then
				-- // We're expecting a particular number of warnings without stacks.
				if withoutStack ~= #warningsWithoutComponentStack then
					local warnings = warningsWithoutComponentStack
					return {
						message = function()
							return ("Expected %d warnings without a component stack but received %d:\n"):format(
								withoutStack,
								#warningsWithoutComponentStack
							) .. table.concat(warnings, "\n")
						end,
						pass = false,
					}
				end
			elseif withoutStack == true then
				if #warningsWithComponentStack > 0 then
					return {
						message = function()
							return "Received warning unexpectedly includes a component stack:\n"
								.. ("  %s\nIf this warning intentionally includes the component stack, remove "):format(
									warningsWithComponentStack[1]
								)
								.. ("{withoutStack: true} from the %s() call. If you have a mix of "):format(
									matcherName
								)
								.. ("warnings with and without stack in one %s() call, pass "):format(
									matcherName
								)
								.. "{withoutStack: N} where N is the number of warnings without stacks."
						end,
						pass = false,
					}
				end
			elseif withoutStack == false or withoutStack == nil then
				-- // We're expecting that all warnings *do* have the stack (default).
				-- // If some warnings don't have it, it's an error.
				if #warningsWithoutComponentStack > 0 then
					return {
						message = function()
							return "Received warning unexpectedly does not include a component stack:\n"
								.. ("  %s\nIf this warning intentionally omits the component stack, add "):format(
									warningsWithoutComponentStack[1]
								)
								.. string.format(
									"{withoutStack: true} to the %s call.",
									matcherName
								)
						end,
						pass = false,
					}
				end
			else
				error(
					("The second argument for %s(), when specified, must be an object. It may have a "):format(
						matcherName
					)
						.. 'property called "withoutStack" whose value may be undefined, boolean, or a number. '
						.. string.format("Instead received %s.", typeof(withoutStack))
				)
			end

			if lastWarningWithMismatchingFormat ~= nil then
				return {
					message = function()
						return ("Received %d arguments for a message with %s placeholders:\n  %s"):format(
							#lastWarningWithMismatchingFormat.args,
							lastWarningWithMismatchingFormat.expectedArgCount,
							lastWarningWithMismatchingFormat.format
						)
					end,
					pass = false,
				}
			end
			if lastWarningWithExtraComponentStack ~= nil then
				return {
					message = function()
						return "Received more than one component stack for a warning:\n"
							.. ("  %s\nDid you accidentally pass a stack to warning() as the last argument? "):format(
								lastWarningWithExtraComponentStack.format
							)
							.. "Don't forget warning() already injects the component stack automatically."
					end,
					pass = false,
				}
			end

			return { pass = true }
		else
			-- // Any uncaught errors or warnings should fail tests in production mode.
			callback()

			return { pass = true }
		end
	end
end
