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 local p local r={UNSTABLE=true,reify=true,reconcile=true,teardown=true}s(function()M.resetModules()S=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\111\097\099\116")p=require("\046\046")end)W("\104\097\115 \097\108\108 \105\110\116\101\114\102\097\099\101 \109\101\109\098\101\114\115 \116\104\097\116 \111\108\100 \082\111\097\099\116 \101\120\112\111\115\101\115",function()for x,s in S do if r[x]then continue end;(F(p)).toHaveProperty(x)local z=typeof(s)if x=="\082\101\102"or x=="\067\104\105\108\100\114\101\110"then z="\115\116\114\105\110\103"end if x=="\080\111\114\116\097\108"then z="\102\117\110\099\116\105\111\110"end;(F(typeof(p[x]))).toBe(z)end end)z("\119\097\114\110\115 \097\098\111\117\116 \100\101\112\114\101\099\097\116\101\100 \082\111\097\099\116 \065\080\073 \102\101\097\116\117\114\101\115",function()W("\119\097\114\110\115 \097\098\111\117\116 \099\114\101\097\116\101\070\114\097\103\109\101\110\116",function()(F(function()p.createFragment({div=p.createElement("\100\105\118")})end)).toWarnDev("\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\099\114\101\097\116\101\070\114\097\103\109\101\110\116\' \105\115 \100\101\112\114\101\099\097\116\101\100",{withoutStack=true})end)W("\119\097\114\110\115 \097\098\111\117\116 \067\111\109\112\111\110\101\110\116\058\101\120\116\101\110\100\040\041 \119\105\116\104 \110\111 \097\114\103\115",function()(F(function()p.Component:extend()end)).toWarnDev("\067\111\109\112\111\110\101\110\116\058\101\120\116\101\110\100\040\041 \097\099\099\101\112\116\105\110\103 \110\111 \097\114\103\117\109\101\110\116\115 \105\115 \100\101\112\114\101\099\097\116\101\100",{withoutStack=true})end)W("\119\097\114\110\115 \097\098\111\117\116 \111\110\101\067\104\105\108\100",function()(F(function()p.oneChild({p.createElement("\100\105\118")})end)).toWarnDev("\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\111\110\101\067\104\105\108\100\' \105\115 \100\101\112\114\101\099\097\116\101\100",{withoutStack=true})end)W("\119\097\114\110\115 \097\098\111\117\116 \115\101\116\071\108\111\098\097\108\067\111\110\102\105\103",function()(F(function()p.setGlobalConfig({propValidation=true})end)).toWarnDev("\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\115\101\116\071\108\111\098\097\108\067\111\110\102\105\103\' \105\115 \100\101\112\114\101\099\097\116\101\100",{withoutStack=true})end)W("\119\097\114\110\115 \097\098\111\117\116 \082\111\097\099\116\046\080\111\114\116\097\108",function()local x=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116-\114\111\098\108\111\120")local s=Instance.new("\070\111\108\100\101\114")local function z(x)return p.createElement(p.Portal,{target=s},p.createElement("\070\114\097\109\101"))end;(F(function()local s=x.createLegacyRoot(Instance.new("\083\099\114\101\101\110\071\117\105"))s:render(p.createElement(z))end)).toWarnDev("\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\082\111\097\099\116\046\080\111\114\116\097\108\' \105\115 \100\101\112\114\101\099\097\116\101\100")end)W("\119\097\114\110\115 \097\098\111\117\116 \109\111\117\110\116",function()(F(function()p.mount(p.createElement("\084\101\120\116\076\097\098\101\108",{Text="\070\111\111"}))end)).toWarnDev({"\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\109\111\117\110\116\' \105\115 \100\101\112\114\101\099\097\116\101\100"},{withoutStack=true})end)W("\119\097\114\110\115 \097\098\111\117\116 \109\111\117\110\116 \119\105\116\104 \105\110\118\097\108\105\100 \105\110\115\116\097\110\099\101",function()(F(function()(F(function()p.mount(p.createElement("\084\101\120\116\076\097\098\101\108",{Text="\070\111\111"}),"\073\'\109 \110\111\116 \097\110 \105\110\115\116\097\110\099\101\033")end)).toWarnDev({"\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\109\111\117\110\116\' \105\115 \100\101\112\114\101\099\097\116\101\100"},{withoutStack=true})end)).toThrow("\067\097\110\110\111\116 \109\111\117\110\116 \101\108\101\109\101\110\116 \040\096\084\101\120\116\076\097\098\101\108\096\041 \105\110\116\111 \097 \112\097\114\101\110\116 \116\104\097\116 \105\115 \110\111\116 \097 \082\111\098\108\111\120 \073\110\115\116\097\110\099\101 \040\103\111\116 \116\121\112\101 \096\115\116\114\105\110\103\096\041",{withoutStack=true});(F(function()(F(function()p.mount(p.createElement("\070\114\097\109\101"),{bogusParent=true})end)).toWarnDev({"\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\109\111\117\110\116\' \105\115 \100\101\112\114\101\099\097\116\101\100"},{withoutStack=true})end)).toThrow("\067\097\110\110\111\116 \109\111\117\110\116 \101\108\101\109\101\110\116 \040\096\070\114\097\109\101\096\041 \105\110\116\111 \097 \112\097\114\101\110\116 \116\104\097\116 \105\115 \110\111\116 \097 \082\111\098\108\111\120 \073\110\115\116\097\110\099\101 \040\103\111\116 \116\121\112\101 \096\116\097\098\108\101\096\041 \n\123 \098\111\103\117\115\080\097\114\101\110\116",{withoutStack=true})end)W("\119\097\114\110\115 \097\098\111\117\116 \117\112\100\097\116\101",function()local x;(F(function()x=p.mount(p.createElement("\084\101\120\116\076\097\098\101\108",{Text="\070\111\111"}))end)).toWarnDev("\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\109\111\117\110\116\' \105\115 \100\101\112\114\101\099\097\116\101\100",{withoutStack=true});(F(function()p.update(x,p.createElement("\084\101\120\116\076\097\098\101\108",{Text="\066\097\114"}))end)).toWarnDev("\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\117\112\100\097\116\101\' \105\115 \100\101\112\114\101\099\097\116\101\100",{withoutStack=true})end)W("\119\097\114\110\115 \097\098\111\117\116 \117\110\109\111\117\110\116",function()local x;(F(function()x=p.mount(p.createElement("\084\101\120\116\076\097\098\101\108",{Text="\070\111\111"}))end)).toWarnDev("\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\109\111\117\110\116\' \105\115 \100\101\112\114\101\099\097\116\101\100",{withoutStack=true});(F(function()p.unmount(x)end)).toWarnDev("\087\097\114\110\105\110\103\058 \084\104\101 \108\101\103\097\099\121 \082\111\097\099\116 \065\080\073 \'\117\110\109\111\117\110\116\' \105\115 \100\101\112\114\101\099\097\116\101\100",{withoutStack=true})end)end)z("\104\097\110\100\108\101\115 \117\110\105\110\105\116\105\097\108\105\122\101\100 \115\116\097\116\101",function()W("\101\114\114\111\114\115 \105\102 \117\110\105\110\105\116\105\097\108\105\122\101\100 \115\116\097\116\101 \105\115 \097\115\115\105\103\110\101\100",function()local x=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116-\114\111\098\108\111\120")local s=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")local z=Instance.new("\070\111\108\100\101\114")local W=p.Component:extend("\067\111\109\112\111\110\101\110\116")function W.render(x)x.state.foo="\098\097\114"end local M=p.createElement(W)local S=x.createRoot(z);(F(function()S:render(M)s.unstable_flushAllWithoutAsserting()end)).toErrorDev("\065\116\116\101\109\112\116\101\100 \116\111 \100\105\114\101\099\116\108\121 \109\117\116\097\116\101 \115\116\097\116\101\046 \085\115\101 \115\101\116\083\116\097\116\101 \116\111 \097\115\115\105\103\110 \110\101\119 \118\097\108\117\101\115 \116\111 \115\116\097\116\101\046")end)W("\119\097\114\110\115 \105\102 \117\110\105\110\105\116\105\097\108\105\122\101\100 \115\116\097\116\101 \105\115 \097\099\099\101\115\115\101\100",function()local x=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116-\114\111\098\108\111\120")local s=Instance.new("\070\111\108\100\101\114")local z=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")local W=p.Component:extend("\067\111\109\112\111\110\101\110\116")local M=false function W.render(x)if x.state.foo==nil then M=true end end local S=p.createElement(W)local r=x.createRoot(s);(F(function()r:render(S)z.unstable_flushAllWithoutAsserting()end)).toWarnDev("\065\116\116\101\109\112\116\101\100 \116\111 \097\099\099\101\115\115 \117\110\105\110\105\116\105\097\108\105\122\101\100 \115\116\097\116\101\046 \085\115\101 \115\101\116\083\116\097\116\101 \116\111 \105\110\105\116\105\097\108\105\122\101 \115\116\097\116\101");(F(M)).toBe(true)end)end)z("\067\104\105\108\100\065\114\114\097\121 \075\101\121\115",function()W("\083\104\111\122\117\108\100 \097\115\115\105\103\110 \107\101\121\115 \116\111 \099\104\105\108\100\114\101\110 \105\110 \097\110 \097\114\114\097\121",function()local x=require("\064\112\107\103\047\064\106\115\100\111\116\108\117\097\047\114\101\097\099\116-\114\111\098\108\111\120")local s=Instance.new("\070\111\108\100\101\114")local z=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")local W=p.Component:extend("\067\111\109\112\111\110\101\110\116")function W.render(x)return p.createElement("\070\114\097\109\101",{},{p.createElement("\084\101\120\116\076\097\098\101\108",{Text="\111\110\101"});p.createElement("\084\101\120\116\076\097\098\101\108",{Text="\116\119\111"});p.createElement("\084\101\120\116\076\097\098\101\108",{Text="\116\104\114\101\101"})})end local M=p.createElement(W)local S=x.createRoot(s);(F(function()S:render(M)z.unstable_flushAllWithoutAsserting()end)).toErrorDev("\087\097\114\110\105\110\103\058 \069\097\099\104 \099\104\105\108\100 \105\110 \097 \108\105\115\116 \115\104\111\117\108\100 \104\097\118\101 \097 \117\110\105\113\117\101 \"\107\101\121\" \112\114\111\112\046")local r=s:FindFirstChild(1,true);(F(r)).toBeDefined();(F(r.Text)).toEqual("\111\110\101")local Q=s:FindFirstChild(2,true);(F(Q)).toBeDefined();(F(Q.Text)).toEqual("\116\119\111")local J=s:FindFirstChild(3,true);(F(J)).toBeDefined();(F(J.Text)).toEqual("\116\104\114\101\101")end)end)
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]

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 Roact
local RoactCompat

local UNSUPPORTED = {
	-- Container for features that are unstable in current Roact
	UNSTABLE = true,
	-- Very old aliases for the top-level Roact tree management API
	reify = true,
	reconcile = true,
	teardown = true,
}
beforeEach(function()
	jest.resetModules()
	Roact = require("@pkg/@jsdotlua/roact")
	RoactCompat = require("..")
end)

it("has all interface members that old Roact exposes", function()
	for k, v in Roact do
		if UNSUPPORTED[k] then
			-- Skip any API members that are well and truly unsupported
			continue
		end

		jestExpect(RoactCompat).toHaveProperty(k)
		local memberType = typeof(v)
		-- Exports common Roact symbol keys as the reserved key names used
		-- by RoactCompat ("ref" and "children", respectively)
		if k == "Ref" or k == "Children" then
			memberType = "string"
		end
		-- Roact.Portal is replaced by ReactRoblox.createPortal. The
		-- latter is a function that returns a portal object, while the
		-- former is a special component type. By implementing Roact.Portal
		-- as a function component that returns `createPortal`'s result, we
		-- can get similar behavior, but with `Roact.Portal` having a
		-- different type.
		if k == "Portal" then
			memberType = "function"
		end
		jestExpect(typeof(RoactCompat[k])).toBe(memberType)
	end
end)

describe("warns about deprecated Roact API features", function()
	it("warns about createFragment", function()
		jestExpect(function()
			RoactCompat.createFragment({ div = RoactCompat.createElement("div") })
		end).toWarnDev(
			"Warning: The legacy Roact API 'createFragment' is deprecated",
			{ withoutStack = true }
		)
	end)

	it("warns about Component:extend() with no args", function()
		jestExpect(function()
			RoactCompat.Component:extend()
		end).toWarnDev(
			"Component:extend() accepting no arguments is deprecated",
			{ withoutStack = true }
		)
	end)

	it("warns about oneChild", function()
		jestExpect(function()
			RoactCompat.oneChild({ RoactCompat.createElement("div") })
		end).toWarnDev(
			"Warning: The legacy Roact API 'oneChild' is deprecated",
			{ withoutStack = true }
		)
	end)

	it("warns about setGlobalConfig", function()
		jestExpect(function()
			RoactCompat.setGlobalConfig({ propValidation = true })
		end).toWarnDev(
			"Warning: The legacy Roact API 'setGlobalConfig' is deprecated",
			{ withoutStack = true }
		)
	end)

	it("warns about Roact.Portal", function()
		local ReactRoblox = require("@pkg/@jsdotlua/react-roblox")
		local target = Instance.new("Folder")
		local function withPortal(_props)
			return RoactCompat.createElement(RoactCompat.Portal, {
				target = target,
			}, RoactCompat.createElement("Frame"))
		end
		jestExpect(function()
			local root = ReactRoblox.createLegacyRoot(Instance.new("ScreenGui"))
			root:render(RoactCompat.createElement(withPortal))
		end).toWarnDev("Warning: The legacy Roact API 'Roact.Portal' is deprecated")
	end)

	it("warns about mount", function()
		jestExpect(function()
			RoactCompat.mount(RoactCompat.createElement("TextLabel", { Text = "Foo" }))
		end).toWarnDev({
			"Warning: The legacy Roact API 'mount' is deprecated",
		}, { withoutStack = true })
	end)

	it("warns about mount with invalid instance", function()
		jestExpect(function()
			jestExpect(function()
				RoactCompat.mount(
					RoactCompat.createElement("TextLabel", { Text = "Foo" }),
					"I'm not an instance!"
				)
			end).toWarnDev({
				"Warning: The legacy Roact API 'mount' is deprecated",
			}, { withoutStack = true })
		end).toThrow(
			"Cannot mount element (`TextLabel`) into a parent that is not a Roblox Instance (got type `string`)",
			{ withoutStack = true }
		)

		jestExpect(function()
			jestExpect(function()
				RoactCompat.mount(
					RoactCompat.createElement("Frame"),
					{ bogusParent = true }
				)
			end).toWarnDev({
				"Warning: The legacy Roact API 'mount' is deprecated",
			}, { withoutStack = true })
		end).toThrow(
			"Cannot mount element (`Frame`) into a parent that is not a Roblox Instance (got type `table`) \n{ bogusParent",
			{ withoutStack = true }
		)
	end)

	it("warns about update", function()
		local tree
		jestExpect(function()
			tree = RoactCompat.mount(
				RoactCompat.createElement("TextLabel", { Text = "Foo" })
			)
		end).toWarnDev(
			"Warning: The legacy Roact API 'mount' is deprecated",
			{ withoutStack = true }
		)

		jestExpect(function()
			RoactCompat.update(
				tree,
				RoactCompat.createElement("TextLabel", { Text = "Bar" })
			)
		end).toWarnDev(
			"Warning: The legacy Roact API 'update' is deprecated",
			{ withoutStack = true }
		)
	end)

	it("warns about unmount", function()
		local tree
		jestExpect(function()
			tree = RoactCompat.mount(
				RoactCompat.createElement("TextLabel", { Text = "Foo" })
			)
		end).toWarnDev(
			"Warning: The legacy Roact API 'mount' is deprecated",
			{ withoutStack = true }
		)

		jestExpect(function()
			RoactCompat.unmount(tree)
		end).toWarnDev(
			"Warning: The legacy Roact API 'unmount' is deprecated",
			{ withoutStack = true }
		)
	end)
end)

describe("handles uninitialized state", function()
	it("errors if uninitialized state is assigned", function()
		local ReactRoblox = require("@pkg/@jsdotlua/react-roblox")
		local Scheduler = require("@pkg/@jsdotlua/scheduler")
		local parent = Instance.new("Folder")
		local Component = RoactCompat.Component:extend("Component")

		function Component:render()
			self.state.foo = "bar"
		end

		local componentInstance = RoactCompat.createElement(Component)
		local root = ReactRoblox.createRoot(parent)

		jestExpect(function()
			root:render(componentInstance)
			Scheduler.unstable_flushAllWithoutAsserting()
		end).toErrorDev(
			"Attempted to directly mutate state. Use setState to assign new values to state."
		)
	end)

	it("warns if uninitialized state is accessed", function()
		local ReactRoblox = require("@pkg/@jsdotlua/react-roblox")
		local parent = Instance.new("Folder")
		local Scheduler = require("@pkg/@jsdotlua/scheduler")
		local Component = RoactCompat.Component:extend("Component")

		local capturedBool = false

		function Component:render()
			if self.state.foo == nil then
				capturedBool = true
			end
		end

		local componentInstance = RoactCompat.createElement(Component)
		local root = ReactRoblox.createRoot(parent)

		jestExpect(function()
			root:render(componentInstance)
			Scheduler.unstable_flushAllWithoutAsserting()
		end).toWarnDev(
			"Attempted to access uninitialized state. Use setState to initialize state"
		)

		jestExpect(capturedBool).toBe(true)
	end)
end)

describe("ChildArray Keys", function()
	it("Shozuld assign keys to children in an array", function()
		local ReactRoblox = require("@pkg/@jsdotlua/react-roblox")
		local parent = Instance.new("Folder")
		local Scheduler = require("@pkg/@jsdotlua/scheduler")
		local Component = RoactCompat.Component:extend("Component")

		function Component:render()
			return RoactCompat.createElement("Frame", {}, {
				RoactCompat.createElement("TextLabel", { Text = "one" }),
				RoactCompat.createElement("TextLabel", { Text = "two" }),
				RoactCompat.createElement("TextLabel", { Text = "three" }),
			})
		end

		local componentInstance = RoactCompat.createElement(Component)

		local root = ReactRoblox.createRoot(parent)

		-- We expect this to warn us about the implicit keys even though
		-- it's assigning them to maintain ordering
		jestExpect(function()
			root:render(componentInstance)
			Scheduler.unstable_flushAllWithoutAsserting()
		end).toErrorDev(
			'Warning: Each child in a list should have a unique "key" prop.'
		)

		local firstChild = parent:FindFirstChild(1, true)
		jestExpect(firstChild).toBeDefined()
		jestExpect(firstChild.Text).toEqual("one")

		local secondChild = parent:FindFirstChild(2, true)
		jestExpect(secondChild).toBeDefined()
		jestExpect(secondChild.Text).toEqual("two")

		local thirdChild = parent:FindFirstChild(3, true)
		jestExpect(thirdChild).toBeDefined()
		jestExpect(thirdChild.Text).toEqual("three")
	end)
end)
