return function()local Q=require(script.Parent.assign)local R=require(script.Parent.createElement)local w=require(script.Parent.createFragment)local S=require(script.Parent.createSpy)local u=require(script.Parent.NoopRenderer)local l=require(script.Parent.Type)local h=require(script.Parent.ElementKind)local o=require(script.Parent.createReconciler)local V=o(u)describe("\116\114\101\101 \111\112\101\114\097\116\105\111\110\115",function()it("\115\104\111\117\108\100 \109\111\117\110\116 \097\110\100 \117\110\109\111\117\110\116",function()local Q=V.mountVirtualTree(R("\083\116\114\105\110\103\086\097\108\117\101"));(expect(Q)).to.be.ok()V.unmountVirtualTree(Q)end)it("\115\104\111\117\108\100 \109\111\117\110\116\044 \117\112\100\097\116\101\044 \097\110\100 \117\110\109\111\117\110\116",function()local Q=V.mountVirtualTree(R("\083\116\114\105\110\103\086\097\108\117\101"));(expect(Q)).to.be.ok()V.updateVirtualTree(Q,R("\083\116\114\105\110\103\086\097\108\117\101"))V.unmountVirtualTree(Q)end)end)describe("\098\111\111\108\101\097\110\115",function()it("\115\104\111\117\108\100 \109\111\117\110\116 \098\111\111\108\101\097\110\115 \097\115 \110\105\108",function()local Q=V.mountVirtualNode(false,nil,"\116\101\115\116");(expect(Q)).to.equal(nil)end)it("\115\104\111\117\108\100 \117\110\109\111\117\110\116 \110\111\100\101\115 \105\102 \116\104\101\121 \097\114\101 \117\112\100\097\116\101\100 \116\111 \097 \098\111\111\108\101\097\110 \118\097\108\117\101",function()local Q=V.mountVirtualNode(R("\083\116\114\105\110\103\086\097\108\117\101"),nil,"\116\101\115\116");(expect(Q)).to.be.ok()Q=V.updateVirtualNode(Q,true);(expect(Q)).to.equal(nil)end)end)describe("\105\110\118\097\108\105\100 \101\108\101\109\101\110\116\115",function()it("\115\104\111\117\108\100 \116\104\114\111\119 \101\114\114\111\114\115 \119\104\101\110 \097\116\116\101\109\112\116\105\110\103 \116\111 \109\111\117\110\116 \105\110\118\097\108\105\100 \101\108\101\109\101\110\116\115",function()local Q=function()return"\072\101\108\108\111"end local w=function()return 1 end local S=function()return function() end end local u=function()return{}end local l=nil local h="\083\111\109\101 \075\101\121";(expect(function()V.mountVirtualNode(R(Q),l,h)end)).to.throw();(expect(function()V.mountVirtualNode(R(w),l,h)end)).to.throw();(expect(function()V.mountVirtualNode(R(S),l,h)end)).to.throw();(expect(function()V.mountVirtualNode(R(u),l,h)end)).to.throw()end)end)describe("\072\111\115\116 \099\111\109\112\111\110\101\110\116\115",function()it("\115\104\111\117\108\100 \105\110\118\111\107\101 \116\104\101 \114\101\110\100\101\114\101\114 \116\111 \109\111\117\110\116 \104\111\115\116 \110\111\100\101\115",function()local w=S(u.mountHostNode)local h=Q({},u,{mountHostNode=w.value})local V=o(h)local Z=R("\083\116\114\105\110\103\086\097\108\117\101")local s=nil local B="\083\111\109\101 \075\101\121"local y=V.mountVirtualNode(Z,s,B);(expect(l.of(y))).to.equal(l.VirtualNode);(expect(w.callCount)).to.equal(1)local W=w:captureValues("\114\101\099\111\110\099\105\108\101\114","\110\111\100\101");(expect(W.reconciler)).to.equal(V);(expect(W.node)).to.equal(y)end)it("\115\104\111\117\108\100 \105\110\118\111\107\101 \116\104\101 \114\101\110\100\101\114\101\114 \116\111 \117\112\100\097\116\101 \104\111\115\116 \110\111\100\101\115",function()local w=S(u.updateHostNode)local h=Q({},u,{mountHostNode=u.mountHostNode;updateHostNode=w.value})local V=o(h)local Z=R("\083\116\114\105\110\103\086\097\108\117\101")local s=nil local B="\075\101\121"local y=V.mountVirtualNode(Z,s,B);(expect(l.of(y))).to.equal(l.VirtualNode)local W=R("\083\116\114\105\110\103\086\097\108\117\101")local L=V.updateVirtualNode(y,W);(expect(L)).to.equal(y);(expect(w.callCount)).to.equal(1)local j=w:captureValues("\114\101\099\111\110\099\105\108\101\114","\110\111\100\101","\110\101\119\069\108\101\109\101\110\116");(expect(j.reconciler)).to.equal(V);(expect(j.node)).to.equal(y);(expect(j.newElement)).to.equal(W)end)it("\115\104\111\117\108\100 \105\110\118\111\107\101 \116\104\101 \114\101\110\100\101\114\101\114 \116\111 \117\110\109\111\117\110\116 \104\111\115\116 \110\111\100\101\115",function()local w=S(u.unmountHostNode)local h=Q({},u,{mountHostNode=u.mountHostNode;unmountHostNode=w.value})local V=o(h)local Z=R("\083\116\114\105\110\103\086\097\108\117\101")local s=nil local B="\075\101\121"local y=V.mountVirtualNode(Z,s,B);(expect(l.of(y))).to.equal(l.VirtualNode)V.unmountVirtualNode(y);(expect(w.callCount)).to.equal(1)local W=w:captureValues("\114\101\099\111\110\099\105\108\101\114","\110\111\100\101");(expect(W.reconciler)).to.equal(V);(expect(W.node)).to.equal(y)end)end)describe("\070\117\110\099\116\105\111\110 \099\111\109\112\111\110\101\110\116\115",function()it("\115\104\111\117\108\100 \109\111\117\110\116 \097\110\100 \117\110\109\111\117\110\116 \102\117\110\099\116\105\111\110 \099\111\109\112\111\110\101\110\116\115",function()local Q=S(function(Q)return nil end)local w=R(Q.value,{someValue=5})local u=nil local h="\065 \075\101\121"local o=V.mountVirtualNode(w,u,h);(expect(l.of(o))).to.equal(l.VirtualNode);(expect(Q.callCount)).to.equal(1)local Z=Q:captureValues("\112\114\111\112\115");(expect(Z.props)).to.be.a("\116\097\098\108\101");(expect(Z.props.someValue)).to.equal(5)V.unmountVirtualNode(o);(expect(Q.callCount)).to.equal(1)end)it("\115\104\111\117\108\100 \109\111\117\110\116 \115\105\110\103\108\101 \099\104\105\108\100\114\101\110 \111\102 \102\117\110\099\116\105\111\110 \099\111\109\112\111\110\101\110\116\115",function()local Q=S(function(Q)return nil end)local w=S(function(w)return R(Q.value,{value=w.value+1})end)local u=R(w.value,{value=13})local h=nil local o="\065 \075\101\121"local Z=V.mountVirtualNode(u,h,o);(expect(l.of(Z))).to.equal(l.VirtualNode);(expect(w.callCount)).to.equal(1);(expect(Q.callCount)).to.equal(1)local s=w:captureValues("\112\114\111\112\115")local B=Q:captureValues("\112\114\111\112\115");(expect(s.props)).to.be.a("\116\097\098\108\101");(expect(s.props.value)).to.equal(13);(expect(B.props)).to.be.a("\116\097\098\108\101");(expect(B.props.value)).to.equal(14)V.unmountVirtualNode(Z);(expect(w.callCount)).to.equal(1);(expect(Q.callCount)).to.equal(1)end)it("\115\104\111\117\108\100 \109\111\117\110\116 \102\114\097\103\109\101\110\116\115 \114\101\116\117\114\110\101\100 \098\121 \102\117\110\099\116\105\111\110 \099\111\109\112\111\110\101\110\116\115",function()local Q=S(function(Q)return nil end)local u=S(function(Q)return nil end)local h=S(function(S)return w({A=R(Q.value,{value=S.value+1}),B=R(u.value,{value=S.value+5})})end)local o=R(h.value,{value=17})local Z=nil local s="\065 \075\101\121"local B=V.mountVirtualNode(o,Z,s);(expect(l.of(B))).to.equal(l.VirtualNode);(expect(h.callCount)).to.equal(1);(expect(Q.callCount)).to.equal(1);(expect(u.callCount)).to.equal(1)local y=h:captureValues("\112\114\111\112\115")local W=Q:captureValues("\112\114\111\112\115")local L=u:captureValues("\112\114\111\112\115");(expect(y.props)).to.be.a("\116\097\098\108\101");(expect(y.props.value)).to.equal(17);(expect(W.props)).to.be.a("\116\097\098\108\101");(expect(W.props.value)).to.equal(18);(expect(L.props)).to.be.a("\116\097\098\108\101");(expect(L.props.value)).to.equal(22)V.unmountVirtualNode(B);(expect(h.callCount)).to.equal(1);(expect(Q.callCount)).to.equal(1);(expect(u.callCount)).to.equal(1)end)end)describe("\070\114\097\103\109\101\110\116\115",function()it("\115\104\111\117\108\100 \109\111\117\110\116 \102\114\097\103\109\101\110\116\115",function()local Q=w({})local R=V.mountVirtualNode(Q,nil,"\116\101\115\116");(expect(R)).to.be.ok();(expect(h.of(R.currentElement))).to.equal(h.Fragment)end)it("\115\104\111\117\108\100 \109\111\117\110\116 \097\110 \101\109\112\116\121 \102\114\097\103\109\101\110\116",function()local Q=w({})local R=V.mountVirtualNode(Q,nil,"\116\101\115\116");(expect(R)).to.be.ok()local S=next(R.children);(expect(S)).to.never.be.ok()end)it("\115\104\111\117\108\100 \109\111\117\110\116 \097\108\108 \102\114\097\103\109\101\110\116\'\115 \099\104\105\108\100\114\101\110",function()local Q=S(function(Q)return nil end)local u={}local l=5 for w=1,l,1 do u["\107\101\121"..tostring(w)]=R(Q.value,{})end local h=w(u)local o=V.mountVirtualNode(h,nil,"\116\101\115\116");(expect(o)).to.be.ok();(expect(Q.callCount)).to.equal(l)end)end)end
return function()
	local assign = require(script.Parent.assign)
	local createElement = require(script.Parent.createElement)
	local createFragment = require(script.Parent.createFragment)
	local createSpy = require(script.Parent.createSpy)
	local NoopRenderer = require(script.Parent.NoopRenderer)
	local Type = require(script.Parent.Type)
	local ElementKind = require(script.Parent.ElementKind)

	local createReconciler = require(script.Parent.createReconciler)

	local noopReconciler = createReconciler(NoopRenderer)

	describe("tree operations", function()
		it("should mount and unmount", function()
			local tree = noopReconciler.mountVirtualTree(createElement("StringValue"))

			expect(tree).to.be.ok()

			noopReconciler.unmountVirtualTree(tree)
		end)

		it("should mount, update, and unmount", function()
			local tree = noopReconciler.mountVirtualTree(createElement("StringValue"))

			expect(tree).to.be.ok()

			noopReconciler.updateVirtualTree(tree, createElement("StringValue"))

			noopReconciler.unmountVirtualTree(tree)
		end)
	end)

	describe("booleans", function()
		it("should mount booleans as nil", function()
			local node = noopReconciler.mountVirtualNode(false, nil, "test")
			expect(node).to.equal(nil)
		end)

		it("should unmount nodes if they are updated to a boolean value", function()
			local node = noopReconciler.mountVirtualNode(createElement("StringValue"), nil, "test")

			expect(node).to.be.ok()

			node = noopReconciler.updateVirtualNode(node, true)

			expect(node).to.equal(nil)
		end)
	end)

	describe("invalid elements", function()
		it("should throw errors when attempting to mount invalid elements", function()
			-- These function components return values with incorrect types
			local returnsString = function()
				return "Hello"
			end
			local returnsNumber = function()
				return 1
			end
			local returnsFunction = function()
				return function() end
			end
			local returnsTable = function()
				return {}
			end

			local hostParent = nil
			local key = "Some Key"

			expect(function()
				noopReconciler.mountVirtualNode(createElement(returnsString), hostParent, key)
			end).to.throw()

			expect(function()
				noopReconciler.mountVirtualNode(createElement(returnsNumber), hostParent, key)
			end).to.throw()

			expect(function()
				noopReconciler.mountVirtualNode(createElement(returnsFunction), hostParent, key)
			end).to.throw()

			expect(function()
				noopReconciler.mountVirtualNode(createElement(returnsTable), hostParent, key)
			end).to.throw()
		end)
	end)

	describe("Host components", function()
		it("should invoke the renderer to mount host nodes", function()
			local mountHostNode = createSpy(NoopRenderer.mountHostNode)

			local renderer = assign({}, NoopRenderer, {
				mountHostNode = mountHostNode.value,
			})

			local reconciler = createReconciler(renderer)

			local element = createElement("StringValue")
			local hostParent = nil
			local key = "Some Key"
			local node = reconciler.mountVirtualNode(element, hostParent, key)

			expect(Type.of(node)).to.equal(Type.VirtualNode)

			expect(mountHostNode.callCount).to.equal(1)

			local values = mountHostNode:captureValues("reconciler", "node")

			expect(values.reconciler).to.equal(reconciler)
			expect(values.node).to.equal(node)
		end)

		it("should invoke the renderer to update host nodes", function()
			local updateHostNode = createSpy(NoopRenderer.updateHostNode)

			local renderer = assign({}, NoopRenderer, {
				mountHostNode = NoopRenderer.mountHostNode,
				updateHostNode = updateHostNode.value,
			})

			local reconciler = createReconciler(renderer)

			local element = createElement("StringValue")
			local hostParent = nil
			local key = "Key"
			local node = reconciler.mountVirtualNode(element, hostParent, key)

			expect(Type.of(node)).to.equal(Type.VirtualNode)

			local newElement = createElement("StringValue")
			local newNode = reconciler.updateVirtualNode(node, newElement)

			expect(newNode).to.equal(node)

			expect(updateHostNode.callCount).to.equal(1)

			local values = updateHostNode:captureValues("reconciler", "node", "newElement")

			expect(values.reconciler).to.equal(reconciler)
			expect(values.node).to.equal(node)
			expect(values.newElement).to.equal(newElement)
		end)

		it("should invoke the renderer to unmount host nodes", function()
			local unmountHostNode = createSpy(NoopRenderer.unmountHostNode)

			local renderer = assign({}, NoopRenderer, {
				mountHostNode = NoopRenderer.mountHostNode,
				unmountHostNode = unmountHostNode.value,
			})

			local reconciler = createReconciler(renderer)

			local element = createElement("StringValue")
			local hostParent = nil
			local key = "Key"
			local node = reconciler.mountVirtualNode(element, hostParent, key)

			expect(Type.of(node)).to.equal(Type.VirtualNode)

			reconciler.unmountVirtualNode(node)

			expect(unmountHostNode.callCount).to.equal(1)

			local values = unmountHostNode:captureValues("reconciler", "node")

			expect(values.reconciler).to.equal(reconciler)
			expect(values.node).to.equal(node)
		end)
	end)

	describe("Function components", function()
		it("should mount and unmount function components", function()
			local componentSpy = createSpy(function(_props)
				return nil
			end)

			local element = createElement(componentSpy.value, {
				someValue = 5,
			})
			local hostParent = nil
			local key = "A Key"
			local node = noopReconciler.mountVirtualNode(element, hostParent, key)

			expect(Type.of(node)).to.equal(Type.VirtualNode)

			expect(componentSpy.callCount).to.equal(1)

			local calledWith = componentSpy:captureValues("props")

			expect(calledWith.props).to.be.a("table")
			expect(calledWith.props.someValue).to.equal(5)

			noopReconciler.unmountVirtualNode(node)

			expect(componentSpy.callCount).to.equal(1)
		end)

		it("should mount single children of function components", function()
			local childComponentSpy = createSpy(function(_props)
				return nil
			end)

			local parentComponentSpy = createSpy(function(props)
				return createElement(childComponentSpy.value, {
					value = props.value + 1,
				})
			end)

			local element = createElement(parentComponentSpy.value, {
				value = 13,
			})
			local hostParent = nil
			local key = "A Key"
			local node = noopReconciler.mountVirtualNode(element, hostParent, key)

			expect(Type.of(node)).to.equal(Type.VirtualNode)

			expect(parentComponentSpy.callCount).to.equal(1)
			expect(childComponentSpy.callCount).to.equal(1)

			local parentCalledWith = parentComponentSpy:captureValues("props")
			local childCalledWith = childComponentSpy:captureValues("props")

			expect(parentCalledWith.props).to.be.a("table")
			expect(parentCalledWith.props.value).to.equal(13)

			expect(childCalledWith.props).to.be.a("table")
			expect(childCalledWith.props.value).to.equal(14)

			noopReconciler.unmountVirtualNode(node)

			expect(parentComponentSpy.callCount).to.equal(1)
			expect(childComponentSpy.callCount).to.equal(1)
		end)

		it("should mount fragments returned by function components", function()
			local childAComponentSpy = createSpy(function(_props)
				return nil
			end)

			local childBComponentSpy = createSpy(function(_props)
				return nil
			end)

			local parentComponentSpy = createSpy(function(props)
				return createFragment({
					A = createElement(childAComponentSpy.value, {
						value = props.value + 1,
					}),
					B = createElement(childBComponentSpy.value, {
						value = props.value + 5,
					}),
				})
			end)

			local element = createElement(parentComponentSpy.value, {
				value = 17,
			})
			local hostParent = nil
			local key = "A Key"
			local node = noopReconciler.mountVirtualNode(element, hostParent, key)

			expect(Type.of(node)).to.equal(Type.VirtualNode)

			expect(parentComponentSpy.callCount).to.equal(1)
			expect(childAComponentSpy.callCount).to.equal(1)
			expect(childBComponentSpy.callCount).to.equal(1)

			local parentCalledWith = parentComponentSpy:captureValues("props")
			local childACalledWith = childAComponentSpy:captureValues("props")
			local childBCalledWith = childBComponentSpy:captureValues("props")

			expect(parentCalledWith.props).to.be.a("table")
			expect(parentCalledWith.props.value).to.equal(17)

			expect(childACalledWith.props).to.be.a("table")
			expect(childACalledWith.props.value).to.equal(18)

			expect(childBCalledWith.props).to.be.a("table")
			expect(childBCalledWith.props.value).to.equal(22)

			noopReconciler.unmountVirtualNode(node)

			expect(parentComponentSpy.callCount).to.equal(1)
			expect(childAComponentSpy.callCount).to.equal(1)
			expect(childBComponentSpy.callCount).to.equal(1)
		end)
	end)

	describe("Fragments", function()
		it("should mount fragments", function()
			local fragment = createFragment({})
			local node = noopReconciler.mountVirtualNode(fragment, nil, "test")

			expect(node).to.be.ok()
			expect(ElementKind.of(node.currentElement)).to.equal(ElementKind.Fragment)
		end)

		it("should mount an empty fragment", function()
			local emptyFragment = createFragment({})
			local node = noopReconciler.mountVirtualNode(emptyFragment, nil, "test")

			expect(node).to.be.ok()

			local nextNode = next(node.children)
			expect(nextNode).to.never.be.ok()
		end)

		it("should mount all fragment's children", function()
			local childComponentSpy = createSpy(function(_props)
				return nil
			end)
			local elements = {}
			local totalElements = 5

			for i = 1, totalElements do
				elements["key" .. tostring(i)] = createElement(childComponentSpy.value, {})
			end

			local fragments = createFragment(elements)
			local node = noopReconciler.mountVirtualNode(fragments, nil, "test")

			expect(node).to.be.ok()
			expect(childComponentSpy.callCount).to.equal(totalElements)
		end)
	end)
end
