local c=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 H=c.expect local e=c.describe local n=c.it local v=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 Q=v.Error local R=v.util.inspect local b=require("\046\046\047\069\114\114\111\114\072\097\110\100\108\105\110\103\046\114\111\098\108\111\120\046\108\117\097\117")local f=b.describeError local U=b.errorToString local j=b.parseReactError e("\100\101\115\099\114\105\098\101\069\114\114\111\114",function()n("\112\114\101\115\101\114\118\101\115 \111\114\105\103\105\110\097\108 \115\116\097\099\107 \102\114\111\109 \115\116\114\105\110\103 \101\114\114\111\114 \119\104\101\110 \114\101\116\104\114\111\119\110",function()local c="\112\114\101\115\101\114\118\101 \115\116\097\099\107 \102\114\111\109 \115\116\114\105\110\103 \101\114\114\111\114"local function e()error(c)end local n,v=xpcall(e,f);(H(n)).toBe(false);(H(v.message)).toBe(c)local Q=v.stack local R,b=xpcall(function()error(v)end,f);(H(R)).toBe(false);(H(b.message)).toBe(c);(H(b.stack)).toBe(Q)end)n("\112\114\101\115\101\114\118\101\115 \111\114\105\103\105\110\097\108 \115\116\097\099\107 \102\114\111\109 \069\114\114\111\114 \119\104\101\110 \114\101\116\104\114\111\119\110",function()local c="\112\114\101\115\101\114\118\101 \115\116\097\099\107 \102\114\111\109 \069\114\114\111\114"local function e()error(Q.new(c))end local n,v=xpcall(e,f);(H(n)).toBe(false);(H(v.message)).toBe(c)local R=v.stack local b,U=xpcall(function()error(v)end,f);(H(b)).toBe(false);(H(U.message)).toBe(c);(H(U.stack)).toBe(R)end)n("\116\114\097\110\115\102\111\114\109\115 \115\116\114\105\110\103 \101\114\114\111\114\115 \105\110\116\111 \069\114\114\111\114 \111\098\106\101\099\116\115",function()local c="\116\114\097\110\115\102\111\114\109 \115\116\114\105\110\103 \105\110\116\111 \069\114\114\111\114"local function e()error(c)end local n,R=xpcall(e,f);(H(n)).toBe(false);(H(v.instanceof(R,Q))).toBe(true);(H(R.message)).toBe(c);(H(R.stack)).toContain(script:GetFullName())end)n("\114\101\116\104\114\111\119\115 \069\114\114\111\114 \111\098\106\101\099\116\115 \119\105\116\104\111\117\116 \099\104\097\110\103\105\110\103 \116\104\101\109",function()local c=Q.new("\114\101\116\104\114\111\119 \069\114\114\111\114 \119\105\116\104\111\117\116 \099\104\097\110\103\101\115")local function e()error(c)end local n,v=xpcall(e,f);(H(n)).toBe(false);(H(v)).toBe(c)end)end)e("\101\114\114\111\114\084\111\083\116\114\105\110\103",function()n("\103\105\118\101\115 \115\116\097\099\107 \116\114\097\099\101 \102\111\114 \069\114\114\111\114",function()local c=U(Q.new("\104\048\119\100\121"));(H(c)).toContain(script.Name);(H(c)).toContain("\104\048\119\100\121")end)n("\112\114\105\110\116\115 \114\097\110\100\111\109 \116\097\098\108\101\115",function()local c=U({["\036\036\104\048\119\100\121\n"]=31337});(H(c)).toContain("\036\036\104\048\119\100\121");(H(c)).toContain("\051\049\051\051\055")end)n("\112\114\105\110\116\115 \097\114\114\097\121\115",function()local c=U({foo=1;2,3});(H(c)).toContain("\102\111\111\058 \049")end)end)e("\112\097\114\115\101\082\101\097\099\116\069\114\114\111\114",function()n("\114\101\116\117\114\110\115 \116\104\101 \119\104\111\108\101 \109\101\115\115\097\103\101 \105\102 \110\111\116 \102\111\114\109\097\116\116\101\100 \097\115 \101\120\112\101\099\116\101\100",function()local c=R(Q.new("\110\111\116 \102\111\114\109\097\116\116\101\100 \102\111\114 \115\112\108\105\116"))local e,n=j(c);(H(e.message)).toBe(c);(H(e.stack)).toBeNil();(H(n)).toBe("")end)n("\100\111\101\115 \110\111\116 \115\112\108\105\116 \101\114\114\111\114\115 \119\105\116\104 \116\104\101 \119\114\111\110\103 \110\117\109\098\101\114 \111\102 \115\101\099\116\105\111\110\115",function()local c=table.concat({"\097";"\098","\099";"\100"},b.__ERROR_DIVIDER)local e,n=j(c);(H(e.message)).toBe(c);(H(e.stack)).toBeNil();(H(n)).toBe("")end)n("\112\097\114\115\101\115 \101\114\114\111\114\115 \099\114\101\097\116\101\100 \098\121 \101\114\114\111\114\084\111\083\116\114\105\110\103",function()local c=U(Q.new("\102\111\111"))local e=debug.info(1,"\108")-1 local n=string.format("\037\115\058\037\100",debug.info(1,"\115"),e)local v,R=j(c);(H(v.message)).toBe("\102\111\111");(H(v.stack)).toContain(n);(H(R)).toBe("")end)n("\115\101\112\097\114\097\116\101\115 \116\104\101 \115\116\097\099\107 \102\114\097\109\101 \102\114\111\109 \116\104\101 \114\101\116\104\114\111\119",function()local c=Q.new("\098\097\114")local e=debug.info(1,"\108")-1 local n=string.format("\037\115\058\037\100",debug.info(1,"\115"),e)local v,R=xpcall(function()error(c)end,U);(H(v)).toBe(false)local b,f=pcall(function()error(R)end)e=debug.info(1,"\108")-2 local V=string.format("\037\115\058\037\100",debug.info(1,"\115"),e);(H(b)).toBe(false)local K,y=j(f);(H(K.message)).toBe("\098\097\114");(H(K.stack)).toContain(n);(H(y)).toContain(V)end)end)
local JestGlobals = require("@pkg/@jsdotlua/jest-globals")
local jestExpect = JestGlobals.expect
local describe = JestGlobals.describe
local it = JestGlobals.it

local LuauPolyfill = require("@pkg/@jsdotlua/luau-polyfill")
local Error = LuauPolyfill.Error
local inspect = LuauPolyfill.util.inspect
local ErrorHandling = require("../ErrorHandling.roblox.luau")
local describeError = ErrorHandling.describeError
local errorToString = ErrorHandling.errorToString
local parseReactError = ErrorHandling.parseReactError

describe("describeError", function()
	it("preserves original stack from string error when rethrown", function()
		local message = "preserve stack from string error"
		local function throws()
			error(message)
		end

		local ok, e = xpcall(throws, describeError)

		jestExpect(ok).toBe(false)
		jestExpect(e.message).toBe(message)
		local originalStack = e.stack

		local ok2, e2 = xpcall(function()
			error(e)
		end, describeError)

		jestExpect(ok2).toBe(false)
		jestExpect(e2.message).toBe(message)
		jestExpect(e2.stack).toBe(originalStack)
	end)

	it("preserves original stack from Error when rethrown", function()
		local message = "preserve stack from Error"
		local function throws()
			error(Error.new(message))
		end

		local ok, e = xpcall(throws, describeError)

		jestExpect(ok).toBe(false)
		jestExpect(e.message).toBe(message)
		local originalStack = e.stack

		local ok2, e2 = xpcall(function()
			error(e)
		end, describeError)

		jestExpect(ok2).toBe(false)
		jestExpect(e2.message).toBe(message)
		jestExpect(e2.stack).toBe(originalStack)
	end)

	it("transforms string errors into Error objects", function()
		local message = "transform string into Error"
		local function throws()
			error(message)
		end

		local ok, e = xpcall(throws, describeError)

		jestExpect(ok).toBe(false)
		jestExpect(LuauPolyfill.instanceof(e, Error)).toBe(true)
		jestExpect(e.message).toBe(message)
		jestExpect(e.stack).toContain(script:GetFullName())
	end)

	it("rethrows Error objects without changing them", function()
		local errorObject = Error.new("rethrow Error without changes")
		local function throws()
			error(errorObject)
		end

		local ok, e = xpcall(throws, describeError)

		jestExpect(ok).toBe(false)
		jestExpect(e).toBe(errorObject)
	end)
end)

describe("errorToString", function()
	it("gives stack trace for Error", function()
		local errorString = errorToString(Error.new("h0wdy"))

		jestExpect(errorString).toContain(script.Name)
		jestExpect(errorString).toContain("h0wdy")
	end)
	it("prints random tables", function()
		local errorString = errorToString({ ["$$h0wdy\n"] = 31337 })

		jestExpect(errorString).toContain("$$h0wdy")
		jestExpect(errorString).toContain("31337")
	end)
	it("prints arrays", function()
		-- selene: allow(mixed_table)
		local errorString = errorToString({ foo = 1, 2, 3 })

		jestExpect(errorString).toContain("foo: 1")
	end)
end)

describe("parseReactError", function()
	it("returns the whole message if not formatted as expected", function()
		local errorString = inspect(Error.new("not formatted for split"))

		local parsed, rethrow = parseReactError(errorString)
		jestExpect(parsed.message).toBe(errorString)
		-- Stack is nil because it's presumed to be included in the message
		-- and we wouldn't be able to generate a useful stack at parse time
		jestExpect(parsed.stack).toBeNil()
		-- The error was not rethrown, so rethrow will be an empty string
		jestExpect(rethrow).toBe("")
	end)

	it("does not split errors with the wrong number of sections", function()
		local errorString =
			table.concat({ "a", "b", "c", "d" }, ErrorHandling.__ERROR_DIVIDER)

		local parsed, rethrow = parseReactError(errorString)
		jestExpect(parsed.message).toBe(errorString)
		-- Stack is nil because it's presumed to be included in the message
		-- and we wouldn't be able to generate a useful stack at parse time
		jestExpect(parsed.stack).toBeNil()
		-- The error was not rethrown, so rethrow will be an empty string
		jestExpect(rethrow).toBe("")
	end)

	it("parses errors created by errorToString", function()
		local errorString = errorToString(Error.new("foo"))
		local stackIndex = debug.info(1, "l") - 1
		local throwFrame = string.format("%s:%d", debug.info(1, "s"), stackIndex)

		local parsed, rethrow = parseReactError(errorString)

		jestExpect(parsed.message).toBe("foo")
		jestExpect(parsed.stack).toContain(throwFrame)
		-- The error was not rethrown, so rethrow will be an empty string
		jestExpect(rethrow).toBe("")
	end)

	it("separates the stack frame from the rethrow", function()
		local errorObject = Error.new("bar")
		local stackIndex = debug.info(1, "l") - 1
		local throwFrame = string.format("%s:%d", debug.info(1, "s"), stackIndex)

		local ok, caughtString = xpcall(function()
			error(errorObject)
		end, errorToString)

		jestExpect(ok).toBe(false)
		-- Simluate rethrowing the stringified error (like Scheduler does in
		-- `performWorkUntilDeadline`) and catching it elsewhere
		local ok2, errorString = pcall(function()
			error(caughtString)
		end)
		stackIndex = debug.info(1, "l") - 2
		local rethrowFrame = string.format("%s:%d", debug.info(1, "s"), stackIndex)

		jestExpect(ok2).toBe(false)

		local parsed, rethrow = parseReactError(errorString)
		jestExpect(parsed.message).toBe("bar")
		jestExpect(parsed.stack).toContain(throwFrame)
		jestExpect(rethrow).toContain(rethrowFrame)
	end)
end)
