return function()local p=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local G=require(script.Parent.Parent.Parent.assertDeepEqual)local g=require(script.Parent.Parent.Binding)local t=require(script.Parent.Parent.PropMarkers.Children)local a=require(script.Parent.Parent.Component)local m=require(script.Parent.Parent.createElement)local Z=require(script.Parent.Parent.createFragment)local J=require(script.Parent.Parent.createReconciler)local d=require(script.Parent.Parent.createRef)local K=require(script.Parent.Parent.createSpy)local S=require(script.Parent.Parent.GlobalConfig)local T=require(script.Parent.Parent.Portal)local C=require(script.Parent.Parent.PropMarkers.Ref)local e=require(script.Parent.Parent.PropMarkers.Event)local B=require(script.Parent)local M=J(B)describe("\109\111\117\110\116\072\111\115\116\078\111\100\101",function()it("\115\104\111\117\108\100 \099\114\101\097\116\101 \105\110\115\116\097\110\099\101\115 \119\105\116\104 \099\111\114\114\101\099\116 \112\114\111\112\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\072\101\108\108\111\033"local g="\083\111\109\101 \075\101\121"local t=m("\083\116\114\105\110\103\086\097\108\117\101",{Value=G})local a=M.createVirtualNode(t,p,g)B.mountHostNode(M,a);(expect(#p:GetChildren())).to.equal(1)local Z=(p:GetChildren())[1];(expect(Z.ClassName)).to.equal("\083\116\114\105\110\103\086\097\108\117\101");(expect(Z.Value)).to.equal(G);(expect(Z.Name)).to.equal(g)end)it("\115\104\111\117\108\100 \099\114\101\097\116\101 \099\104\105\108\100\114\101\110 \119\105\116\104 \099\111\114\114\101\099\116 \110\097\109\101\115 \097\110\100 \112\114\111\112\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\072\101\121 \116\104\101\114\101\033"local g=173 local t="\083\111\109\101 \075\101\121"local a=m("\083\116\114\105\110\103\086\097\108\117\101",{Value=G},{ChildA=m("\073\110\116\086\097\108\117\101",{Value=g}),ChildB=m("\070\111\108\100\101\114")})local Z=M.createVirtualNode(a,p,t)B.mountHostNode(M,Z);(expect(#p:GetChildren())).to.equal(1)local J=(p:GetChildren())[1];(expect(J.ClassName)).to.equal("\083\116\114\105\110\103\086\097\108\117\101");(expect(J.Value)).to.equal(G);(expect(J.Name)).to.equal(t);(expect(#J:GetChildren())).to.equal(2)local d=J.ChildA local K=J.ChildB;(expect(d)).to.be.ok();(expect(K)).to.be.ok();(expect(d.ClassName)).to.equal("\073\110\116\086\097\108\117\101");(expect(d.Value)).to.equal(g);(expect(K.ClassName)).to.equal("\070\111\108\100\101\114")end)it("\115\104\111\117\108\100 \097\116\116\097\099\104 \066\105\110\100\105\110\103\115 \116\111 \082\111\098\108\111\120 \112\114\111\112\101\114\116\105\101\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local t,a=g.create(10)local Z=m("\073\110\116\086\097\108\117\101",{Value=t})local J=M.createVirtualNode(Z,p,G)B.mountHostNode(M,J);(expect(#p:GetChildren())).to.equal(1)local d=(p:GetChildren())[1];(expect(d.ClassName)).to.equal("\073\110\116\086\097\108\117\101");(expect(d.Value)).to.equal(10)a(20);(expect(d.Value)).to.equal(20)B.unmountHostNode(M,J)end)it("\115\104\111\117\108\100 \099\111\110\110\101\099\116 \066\105\110\100\105\110\103 \114\101\102\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=d()local t=m("\070\114\097\109\101",{[C]=g})local a=M.createVirtualNode(t,p,G)B.mountHostNode(M,a);(expect(#p:GetChildren())).to.equal(1)local Z=(p:GetChildren())[1];(expect(g.current)).to.be.ok();(expect(g.current)).to.equal(Z)B.unmountHostNode(M,a)end)it("\115\104\111\117\108\100 \099\097\108\108 \102\117\110\099\116\105\111\110 \114\101\102\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=K()local t=m("\070\114\097\109\101",{[C]=g.value})local a=M.createVirtualNode(t,p,G)B.mountHostNode(M,a);(expect(#p:GetChildren())).to.equal(1)local Z=(p:GetChildren())[1];(expect(g.callCount)).to.equal(1)g:assertCalledWith(Z)B.unmountHostNode(M,a)end)it("\115\104\111\117\108\100 \116\104\114\111\119 \105\102 \115\101\116\116\105\110\103 \105\110\118\097\108\105\100 \105\110\115\116\097\110\099\101 \112\114\111\112\101\114\116\105\101\115",function()local p={elementTracing=true}S.scoped(p,function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=m("\070\114\097\109\101",{Frob=6})local t=M.createVirtualNode(g,p,G)local a,Z=pcall(B.mountHostNode,M,t)assert(not a,"\069\120\112\101\099\116\101\100 \099\097\108\108 \116\111 \102\097\105\108");(expect(Z:find("\070\114\111\098"))).to.be.ok();(expect(Z:find("\070\114\097\109\101"))).to.be.ok();(expect(Z:find("\105\110\105\116\037\046\115\112\101\099"))).to.be.ok()end)end)end)describe("\117\112\100\097\116\101\072\111\115\116\078\111\100\101",function()it("\115\104\111\117\108\100 \117\112\100\097\116\101 \110\111\100\101 \112\114\111\112\115 \097\110\100 \099\104\105\108\100\114\101\110",function()local p=Instance.new("\070\111\108\100\101\114")local G="\117\112\100\097\116\101\072\111\115\116\078\111\100\101\084\101\115\116"local g="\102\111\111"local t="\098\097\114"local a=(Instance.new("\083\116\114\105\110\103\086\097\108\117\101")).Value local Z=m("\083\116\114\105\110\103\086\097\108\117\101",{Value=g},{ChildA=m("\073\110\116\086\097\108\117\101",{Value=1});ChildB=m("\066\111\111\108\086\097\108\117\101",{Value=true}),ChildC=m("\083\116\114\105\110\103\086\097\108\117\101",{Value="\116\101\115\116"}),ChildD=m("\083\116\114\105\110\103\086\097\108\117\101",{Value="\116\101\115\116"})})local J=M.createVirtualNode(Z,p,G)B.mountHostNode(M,J)local d=m("\083\116\114\105\110\103\086\097\108\117\101",{Value=t},{ChildA=m("\083\116\114\105\110\103\086\097\108\117\101",{Value="\116\101\115\116"});ChildB=m("\066\111\111\108\086\097\108\117\101",{Value=false}),ChildC=m("\083\116\114\105\110\103\086\097\108\117\101",{}),ChildE=m("\070\111\108\100\101\114",{})})B.updateHostNode(M,J,d)local K=p[G];(expect(K.ClassName)).to.equal("\083\116\114\105\110\103\086\097\108\117\101");(expect(K.Value)).to.equal(t);(expect(#K:GetChildren())).to.equal(4)local S=K.ChildA;(expect(S.ClassName)).to.equal("\083\116\114\105\110\103\086\097\108\117\101");(expect(S.Value)).to.equal("\116\101\115\116")local T=K.ChildB;(expect(T.ClassName)).to.equal("\066\111\111\108\086\097\108\117\101");(expect(T.Value)).to.equal(false)local C=K.ChildC;(expect(C.ClassName)).to.equal("\083\116\114\105\110\103\086\097\108\117\101");(expect(C.Value)).to.equal(a)local e=K.ChildE;(expect(e.ClassName)).to.equal("\070\111\108\100\101\114")end)it("\115\104\111\117\108\100 \117\112\100\097\116\101 \066\105\110\100\105\110\103\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local t,a=g.create(10)local Z=m("\073\110\116\086\097\108\117\101",{Value=t})local J=M.createVirtualNode(Z,p,G)B.mountHostNode(M,J)local d=(p:GetChildren())[1];(expect(d.Value)).to.equal(10)local K,S=g.create(99)local T=m("\073\110\116\086\097\108\117\101",{Value=K})B.updateHostNode(M,J,T);(expect(d.Value)).to.equal(99)a(123);(expect(d.Value)).to.equal(99)S(123);(expect(d.Value)).to.equal(123)B.unmountHostNode(M,J)end)it("\115\104\111\117\108\100 \117\112\100\097\116\101 \066\105\110\100\105\110\103 \114\101\102\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=d()local t=d()local a=m("\070\114\097\109\101",{[C]=g})local Z=M.createVirtualNode(a,p,G)B.mountHostNode(M,Z);(expect(#p:GetChildren())).to.equal(1)local J=(p:GetChildren())[1];(expect(g.current)).to.equal(J);(expect(t.current)).never.to.be.ok()local K=m("\070\114\097\109\101",{[C]=t})B.updateHostNode(M,Z,K);(expect(g.current)).never.to.be.ok();(expect(t.current)).to.equal(J)B.unmountHostNode(M,Z)end)it("\115\104\111\117\108\100 \099\097\108\108 \111\108\100 \102\117\110\099\116\105\111\110 \114\101\102\115 \119\105\116\104 \110\105\108 \097\110\100 \110\101\119 \102\117\110\099\116\105\111\110 \114\101\102\115 \119\105\116\104 \097 \118\097\108\105\100 \114\098\120",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=K()local t=K()local a=m("\070\114\097\109\101",{[C]=g.value})local Z=M.createVirtualNode(a,p,G)B.mountHostNode(M,Z);(expect(#p:GetChildren())).to.equal(1)local J=(p:GetChildren())[1];(expect(g.callCount)).to.equal(1)g:assertCalledWith(J);(expect(t.callCount)).to.equal(0)local d=m("\070\114\097\109\101",{[C]=t.value})B.updateHostNode(M,Z,d);(expect(g.callCount)).to.equal(2)g:assertCalledWith(nil);(expect(t.callCount)).to.equal(1)t:assertCalledWith(J)B.unmountHostNode(M,Z)end)it("\115\104\111\117\108\100 \110\111\116 \099\097\108\108 \102\117\110\099\116\105\111\110 \114\101\102\115 \097\103\097\105\110 \105\102 \116\104\101\121 \100\105\100\110\'\116 \099\104\097\110\103\101",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=K()local t=m("\070\114\097\109\101",{Size=UDim2.new(1,0,1,0),[C]=g.value})local a=M.createVirtualNode(t,p,G)B.mountHostNode(M,a);(expect(#p:GetChildren())).to.equal(1)local Z=(p:GetChildren())[1];(expect(g.callCount)).to.equal(1)g:assertCalledWith(Z)local J=m("\070\114\097\109\101",{Size=UDim2.new(.5,0,.5,0),[C]=g.value})B.updateHostNode(M,a,J);(expect(g.callCount)).to.equal(1)end)it("\115\104\111\117\108\100 \116\104\114\111\119 \105\102 \115\101\116\116\105\110\103 \105\110\118\097\108\105\100 \105\110\115\116\097\110\099\101 \112\114\111\112\101\114\116\105\101\115",function()local p={elementTracing=true}S.scoped(p,function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=m("\070\114\097\109\101")local t=m("\070\114\097\109\101",{Frob=6})local a=M.createVirtualNode(g,p,G)B.mountHostNode(M,a)local Z,J=pcall(B.updateHostNode,M,a,t)assert(not Z,"\069\120\112\101\099\116\101\100 \099\097\108\108 \116\111 \102\097\105\108");(expect(J:find("\070\114\111\098"))).to.be.ok();(expect(J:find("\070\114\097\109\101"))).to.be.ok();(expect(J:find("\105\110\105\116\037\046\115\112\101\099"))).to.be.ok()end)end)it("\115\104\111\117\108\100 \100\101\108\101\116\101 \105\110\115\116\097\110\099\101\115 \119\104\101\110 \114\101\099\111\110\099\105\108\105\110\103 \116\111 \110\105\108 \099\104\105\108\100\114\101\110",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=m("\070\114\097\109\101",{Size=UDim2.new(1,0,1,0)},{child=m("\070\114\097\109\101")})local t=M.createVirtualNode(g,p,G)B.mountHostNode(M,t);(expect(#p:GetChildren())).to.equal(1)local a=(p:GetChildren())[1];(expect(#a:GetChildren())).to.equal(1)local Z=m("\070\114\097\109\101",{Size=UDim2.new(.5,0,.5,0)})B.updateHostNode(M,t,Z);(expect(#a:GetChildren())).to.equal(0)end)end)describe("\117\110\109\111\117\110\116\072\111\115\116\078\111\100\101",function()it("\115\104\111\117\108\100 \100\101\108\101\116\101 \105\110\115\116\097\110\099\101\115 \102\114\111\109 \116\104\101 \105\110\115\105\100\101-\111\117\116",function()local p=Instance.new("\070\111\108\100\101\114")local G="\082\111\111\116"local g=m("\070\111\108\100\101\114",nil,{Child=m("\070\111\108\100\101\114",nil,{Grandchild=m("\070\111\108\100\101\114")})})local t=M.mountVirtualNode(g,p,G);(expect(#p:GetChildren())).to.equal(1)local a=(p:GetChildren())[1];(expect(#a:GetChildren())).to.equal(1)local Z=(a:GetChildren())[1];(expect(#Z:GetChildren())).to.equal(1)local J=(Z:GetChildren())[1]B.unmountHostNode(M,t);(expect(J.Parent)).to.equal(nil);(expect(Z.Parent)).to.equal(nil);(expect(a.Parent)).to.equal(nil)end)it("\115\104\111\117\108\100 \117\110\115\117\098\115\099\114\105\098\101 \102\114\111\109 \097\110\121 \066\105\110\100\105\110\103\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local t,a=g.create(10)local Z=m("\073\110\116\086\097\108\117\101",{Value=t})local J=M.createVirtualNode(Z,p,G)B.mountHostNode(M,J)local d=(p:GetChildren())[1];(expect(d.Value)).to.equal(10)B.unmountHostNode(M,J)a(56);(expect(d.Value)).to.equal(10)end)it("\115\104\111\117\108\100 \099\108\101\097\114 \066\105\110\100\105\110\103 \114\101\102\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=d()local t=m("\070\114\097\109\101",{[C]=g})local a=M.createVirtualNode(t,p,G)B.mountHostNode(M,a);(expect(g.current)).to.be.ok()B.unmountHostNode(M,a);(expect(g.current)).never.to.be.ok()end)it("\115\104\111\117\108\100 \099\097\108\108 \102\117\110\099\116\105\111\110 \114\101\102\115 \119\105\116\104 \110\105\108",function()local p=Instance.new("\070\111\108\100\101\114")local G="\083\111\109\101 \075\101\121"local g=K()local t=m("\070\114\097\109\101",{[C]=g.value})local a=M.createVirtualNode(t,p,G)B.mountHostNode(M,a);(expect(g.callCount)).to.equal(1)B.unmountHostNode(M,a);(expect(g.callCount)).to.equal(2)g:assertCalledWith(nil)end)end)describe("\080\111\114\116\097\108\115",function()it("\115\104\111\117\108\100 \099\114\101\097\116\101 \097\110\100 \100\101\115\116\114\111\121 \105\110\115\116\097\110\099\101\115 \097\115 \099\104\105\108\100\114\101\110 \111\102 \096\116\097\114\103\101\116\096",function()local p=Instance.new("\070\111\108\100\101\114")local function G(p)return m("\073\110\116\086\097\108\117\101",{Value=p.value})end local g=m(T,{target=p},{folderOne=m("\070\111\108\100\101\114"),folderTwo=m("\070\111\108\100\101\114");intValueOne=m(G,{value=42})})local t=nil local a="\083\111\109\101 \075\101\121"local Z=M.mountVirtualNode(g,t,a);(expect(#p:GetChildren())).to.equal(3);(expect(p:FindFirstChild("\102\111\108\100\101\114\079\110\101"))).to.be.ok();(expect(p:FindFirstChild("\102\111\108\100\101\114\084\119\111"))).to.be.ok();(expect(p:FindFirstChild("\105\110\116\086\097\108\117\101\079\110\101"))).to.be.ok();(expect((p:FindFirstChild("\105\110\116\086\097\108\117\101\079\110\101")).Value)).to.equal(42)M.unmountVirtualNode(Z);(expect(#p:GetChildren())).to.equal(0)end)it("\115\104\111\117\108\100 \112\097\115\115 \112\114\111\112 \117\112\100\097\116\101\115 \116\104\114\111\117\103\104 \116\111 \099\104\105\108\100\114\101\110",function()local p=Instance.new("\070\111\108\100\101\114")local G=m(T,{target=p},{ChildValue=m("\073\110\116\086\097\108\117\101",{Value=1})})local g=m(T,{target=p},{ChildValue=m("\073\110\116\086\097\108\117\101",{Value=2})})local t=nil local a="\065 \072\111\115\116 \075\101\121"local Z=M.mountVirtualNode(G,t,a);(expect(#p:GetChildren())).to.equal(1)local J=p.ChildValue;(expect(J.Value)).to.equal(1)Z=M.updateVirtualNode(Z,g);(expect(#p:GetChildren())).to.equal(1)local d=p.ChildValue;(expect(J)).to.equal(d);(expect(d.Value)).to.equal(2)M.unmountVirtualNode(Z);(expect(#p:GetChildren())).to.equal(0)end)it("\115\104\111\117\108\100 \116\104\114\111\119 \105\102 \096\116\097\114\103\101\116\096 \105\115 \110\105\108",function()local p=m(T)local G=nil local g="\075\101\121\115 \102\111\114 \069\118\101\114\121\111\110\101";(expect(function()M.mountVirtualNode(p,G,g)end)).to.throw()end)it("\115\104\111\117\108\100 \116\104\114\111\119 \105\102 \096\116\097\114\103\101\116\096 \105\115 \110\111\116 \097 \082\111\098\108\111\120 \105\110\115\116\097\110\099\101",function()local p=m(T,{target={}})local G=nil local g="\085\110\108\101\097\115\104 \116\104\101 \107\101\121\115\033";(expect(function()M.mountVirtualNode(p,G,g)end)).to.throw()end)it("\115\104\111\117\108\100 \114\101\099\114\101\097\116\101 \105\110\115\116\097\110\099\101\115 \105\102 \096\116\097\114\103\101\116\096 \099\104\097\110\103\101\115 \105\110 \097\110 \117\112\100\097\116\101",function()local p=Instance.new("\070\111\108\100\101\114")local G=Instance.new("\070\111\108\100\101\114")local g=m(T,{target=p},{ChildValue=m("\073\110\116\086\097\108\117\101",{Value=1})})local t=m(T,{target=G},{ChildValue=m("\073\110\116\086\097\108\117\101",{Value=2})})local a=nil local Z="\083\111\109\101 \075\101\121"local J=M.mountVirtualNode(g,a,Z);(expect(#p:GetChildren())).to.equal(1);(expect(#G:GetChildren())).to.equal(0)local d=p.ChildValue;(expect(d.Value)).to.equal(1)J=M.updateVirtualNode(J,t);(expect(#p:GetChildren())).to.equal(0);(expect(#G:GetChildren())).to.equal(1)local K=G.ChildValue;(expect(K.Value)).to.equal(2)M.unmountVirtualNode(J);(expect(#p:GetChildren())).to.equal(0);(expect(#G:GetChildren())).to.equal(0)end)end)describe("\070\114\097\103\109\101\110\116\115",function()it("\115\104\111\117\108\100 \112\097\114\101\110\116 \116\104\101 \102\114\097\103\109\101\110\116\'\115 \101\108\101\109\101\110\116\115 \105\110\116\111 \116\104\101 \102\114\097\103\109\101\110\116\'\115 \112\097\114\101\110\116",function()local p=Instance.new("\070\111\108\100\101\114")local G=Z({key=m("\073\110\116\086\097\108\117\101",{Value=1}),key2=m("\073\110\116\086\097\108\117\101",{Value=2})})local g=M.mountVirtualNode(G,p,"\116\101\115\116");(expect(p:FindFirstChild("\107\101\121"))).to.be.ok();(expect(p.key.ClassName)).to.equal("\073\110\116\086\097\108\117\101");(expect(p.key.Value)).to.equal(1);(expect(p:FindFirstChild("\107\101\121\050"))).to.be.ok();(expect(p.key2.ClassName)).to.equal("\073\110\116\086\097\108\117\101");(expect(p.key2.Value)).to.equal(2)M.unmountVirtualNode(g);(expect(#p:GetChildren())).to.equal(0)end)it("\115\104\111\117\108\100 \097\108\108\111\119 \115\105\098\108\105\110\103 \102\114\097\103\109\101\110\116 \116\111 \104\097\118\101 \099\111\109\109\111\110 \107\101\121\115",function()local p=Instance.new("\070\111\108\100\101\114")local G="\084\101\115\116"local function g(p)return m("\073\110\116\086\097\108\117\101",{},{fragmentA=Z({key=m("\083\116\114\105\110\103\086\097\108\117\101",{Value="\065"}),key2=m("\083\116\114\105\110\103\086\097\108\117\101",{Value="\066"})}),fragmentB=Z({key=m("\083\116\114\105\110\103\086\097\108\117\101",{Value="\067"});key2=m("\083\116\114\105\110\103\086\097\108\117\101",{Value="\068"})})})end local t=M.mountVirtualNode(m(g),p,G)local a=p[G]:GetChildren();(expect(#a)).to.equal(4)local J={}for p,G in a do(expect(G.ClassName)).to.equal("\083\116\114\105\110\103\086\097\108\117\101")J[G.Value]=1+(J[G.Value]or 0)end;(expect(J.A)).to.equal(1);(expect(J.B)).to.equal(1);(expect(J.C)).to.equal(1);(expect(J.D)).to.equal(1)M.unmountVirtualNode(t);(expect(#p:GetChildren())).to.equal(0)end)it("\115\104\111\117\108\100 \114\101\110\100\101\114 \110\101\115\116\101\100 \102\114\097\103\109\101\110\116\115",function()local p=Instance.new("\070\111\108\100\101\114")local G=Z({key=Z({TheValue=m("\073\110\116\086\097\108\117\101",{Value=1});TheOtherValue=m("\073\110\116\086\097\108\117\101",{Value=2})})})local g=M.mountVirtualNode(G,p,"\084\101\115\116");(expect(p:FindFirstChild("\084\104\101\086\097\108\117\101"))).to.be.ok();(expect(p.TheValue.ClassName)).to.equal("\073\110\116\086\097\108\117\101");(expect(p.TheValue.Value)).to.equal(1);(expect(p:FindFirstChild("\084\104\101\079\116\104\101\114\086\097\108\117\101"))).to.be.ok();(expect(p.TheOtherValue.ClassName)).to.equal("\073\110\116\086\097\108\117\101");(expect(p.TheOtherValue.Value)).to.equal(2)M.unmountVirtualNode(g);(expect(#p:GetChildren())).to.equal(0)end)it("\115\104\111\117\108\100 \110\111\116 \097\100\100 \097\110\121 \105\110\115\116\097\110\099\101\115 \105\102 \116\104\101 \102\114\097\103\109\101\110\116 \105\115 \101\109\112\116\121",function()local p=Instance.new("\070\111\108\100\101\114")local G=M.mountVirtualNode(Z({}),p,"\116\101\115\116");(expect(#p:GetChildren())).to.equal(0)M.unmountVirtualNode(G);(expect(#p:GetChildren())).to.equal(0)end)end)describe("\067\111\110\116\101\120\116",function()it("\115\104\111\117\108\100 \112\097\115\115 \099\111\110\116\101\120\116 \118\097\108\117\101\115 \116\104\114\111\117\103\104 \082\111\098\108\111\120 \104\111\115\116 \110\111\100\101\115",function()local p=a:extend("\067\111\110\115\117\109\101\114")local g function p.init(p)g={hello=p:__getContext("\104\101\108\108\111")}end function p.render(p) end local t=m("\070\111\108\100\101\114",nil,{Consumer=m(p)})local Z=nil local J="\067\111\110\116\101\120\116 \084\101\115\116"local d={hello="\119\111\114\108\100"}local K=M.mountVirtualNode(t,Z,J,d);(expect(g)).never.to.equal(d)G(g,d)M.unmountVirtualNode(K)end)it("\115\104\111\117\108\100 \112\097\115\115 \099\111\110\116\101\120\116 \118\097\108\117\101\115 \116\104\114\111\117\103\104 \112\111\114\116\097\108 \110\111\100\101\115",function()local p=Instance.new("\070\111\108\100\101\114")local g=a:extend("\080\114\111\118\105\100\101\114")function g.init(p)p:__addContext("\102\111\111","\098\097\114")end function g.render(p)return m("\070\111\108\100\101\114",nil,p.props[t])end local Z=a:extend("\067\111\110\115\117\109\101\114")local J function Z.init(p)J={foo=p:__getContext("\102\111\111")}end function Z.render(p)return nil end local d=m(g,nil,{Portal=m(T,{target=p},{Consumer=m(Z)})})local K=nil local S="\083\111\109\101 \075\101\121"M.mountVirtualNode(d,K,S)G(J,{foo="\098\097\114"})end)end)describe("\076\101\103\097\099\121 \099\111\110\116\101\120\116",function()it("\115\104\111\117\108\100 \112\097\115\115 \099\111\110\116\101\120\116 \118\097\108\117\101\115 \116\104\114\111\117\103\104 \082\111\098\108\111\120 \104\111\115\116 \110\111\100\101\115",function()local p=a:extend("\067\111\110\115\117\109\101\114")local g function p.init(p)g=p._context end function p.render(p) end local t=m("\070\111\108\100\101\114",nil,{Consumer=m(p)})local Z=nil local J="\067\111\110\116\101\120\116 \084\101\115\116"local d={hello="\119\111\114\108\100"}local K=M.mountVirtualNode(t,Z,J,nil,d);(expect(g)).never.to.equal(d)G(g,d)M.unmountVirtualNode(K)end)it("\115\104\111\117\108\100 \112\097\115\115 \099\111\110\116\101\120\116 \118\097\108\117\101\115 \116\104\114\111\117\103\104 \112\111\114\116\097\108 \110\111\100\101\115",function()local p=Instance.new("\070\111\108\100\101\114")local g=a:extend("\080\114\111\118\105\100\101\114")function g.init(p)p._context.foo="\098\097\114"end function g.render(p)return m("\070\111\108\100\101\114",nil,p.props[t])end local Z=a:extend("\067\111\110\115\117\109\101\114")local J function Z.init(p)J=p._context end function Z.render(p)return nil end local d=m(g,nil,{Portal=m(T,{target=p},{Consumer=m(Z)})})local K=nil local S="\083\111\109\101 \075\101\121"M.mountVirtualNode(d,K,S)G(J,{foo="\098\097\114"})end)end)describe("\073\110\116\101\103\114\097\116\105\111\110 \084\101\115\116\115",function()local G=nil beforeEach(function()G=Instance.new("\070\111\108\100\101\114")G.Parent=p end)afterEach(function()G:Destroy()G=nil end)it("\115\104\111\117\108\100 \110\111\116 \097\108\108\111\119 \114\101-\101\110\116\114\097\110\099\121 \105\110 \117\112\100\097\116\101\067\104\105\108\100\114\101\110",function()local p=a:extend("\067\104\105\108\100\067\111\109\112\111\110\101\110\116")function p.init(p)p:setState({firstTime=true})end local g function p.render(p)if p.state.firstTime then return m("\070\114\097\109\101")end return m("\084\101\120\116\076\097\098\101\108")end function p.didMount(p)g=coroutine.create(function()p:setState({firstTime=false})end)end local t=a:extend("\080\097\114\101\110\116\067\111\109\112\111\110\101\110\116")function t.init(p)p:setState({count=1})p.childAdded=function()p:setState({count=p.state.count+1})end end function t.render(G)return m("\070\114\097\109\101",{[e.ChildAdded]=G.childAdded},{ChildComponent=m(p,{count=G.state.count})})end local Z=Instance.new("\083\099\114\101\101\110\071\117\105")Z.Parent=G local J=m(t)local d="\083\111\109\101 \075\101\121"local K=M.mountVirtualNode(J,Z,d)coroutine.resume(g);(expect(#Z:GetChildren())).to.equal(1)local S=(Z:GetChildren())[1];(expect(#S:GetChildren())).to.equal(1)M.unmountVirtualNode(K)end)it("\115\104\111\117\108\100 \110\111\116 \097\108\108\111\119 \114\101-\101\110\116\114\097\110\099\121 \105\110 \117\112\100\097\116\101\067\104\105\108\100\114\101\110 \101\118\101\110 \119\105\116\104 \099\097\108\108\098\097\099\107\115",function()local p=a:extend("\076\111\119\101\115\116\067\111\109\112\111\110\101\110\116")function p.render(p)return m("\070\114\097\109\101")end function p.didMount(p)p.props.onDidMountCallback()end local g=a:extend("\067\104\105\108\100\067\111\109\112\111\110\101\110\116")function g.init(p)p:setState({firstTime=true})end local t function g.render(G)if G.state.firstTime then return m("\070\114\097\109\101")end return m(p,{onDidMountCallback=G.props.onDidMountCallback})end function g.didMount(p)t=coroutine.create(function()p:setState({firstTime=false})end)end local Z=a:extend("\080\097\114\101\110\116\067\111\109\112\111\110\101\110\116")local J=0 function Z.init(p)p:setState({count=1})p.onDidMountCallback=function()J=J+1 if p.state.count<5 then p:setState({count=p.state.count+1})end end end function Z.render(p)return m("\070\114\097\109\101",{},{ChildComponent=m(g,{count=p.state.count,onDidMountCallback=p.onDidMountCallback})})end local d=Instance.new("\083\099\114\101\101\110\071\117\105")d.Parent=G local K=m(Z)local S="\083\111\109\101 \075\101\121"local T=M.mountVirtualNode(K,d,S)coroutine.resume(t);(expect(#d:GetChildren())).to.equal(1)local C=(d:GetChildren())[1];(expect(#C:GetChildren())).to.equal(1);(expect(J<=2)).to.equal(true)M.unmountVirtualNode(T)end)it("\115\104\111\117\108\100 \110\101\118\101\114 \099\097\108\108 \117\110\109\111\117\110\116 \116\119\105\099\101 \105\110 \116\104\101 \099\097\115\101 \111\102 \117\112\100\097\116\101 \099\104\105\108\100\114\101\110 \114\101-\114\101\110\116\114\097\110\099\121",function()local p={}local function g(G)p[G]=p[G]+1 end local function t(G)p[G]=0 end local Z=a:extend("\076\111\119\101\115\116\067\111\109\112\111\110\101\110\116")function Z.init(p)t(tostring(p))end function Z.render(p)return m("\070\114\097\109\101")end function Z.didMount(p)p.props.onDidMountCallback()end function Z.willUnmount(p)g(tostring(p))end local J=a:extend("\070\105\114\115\116\067\111\109\112\111\110\101\110\116")function J.init(p)t(tostring(p))end function J.render(p)return m("\084\101\120\116\076\097\098\101\108")end function J.willUnmount(p)g(tostring(p))end local d=a:extend("\067\104\105\108\100\067\111\109\112\111\110\101\110\116")function d.init(p)t(tostring(p))p:setState({firstTime=true})end local K function d.render(p)if p.state.firstTime then return m(J)end return m(Z,{onDidMountCallback=p.props.onDidMountCallback})end function d.didMount(p)K=coroutine.create(function()p:setState({firstTime=false})end)end function d.willUnmount(p)g(tostring(p))end local S=a:extend("\080\097\114\101\110\116\067\111\109\112\111\110\101\110\116")local T=0 function S.init(p)p:setState({count=1})p.onDidMountCallback=function()T=T+1 if p.state.count<5 then p:setState({count=p.state.count+1})end end end function S.render(p)return m("\070\114\097\109\101",{},{ChildComponent=m(d,{count=p.state.count;onDidMountCallback=p.onDidMountCallback})})end local C=Instance.new("\083\099\114\101\101\110\071\117\105")C.Parent=G local e=m(S)local B="\083\111\109\101 \075\101\121"local q=M.mountVirtualNode(e,C,B)coroutine.resume(K);(expect(#C:GetChildren())).to.equal(1)local z=(C:GetChildren())[1];(expect(#z:GetChildren())).to.equal(1);(expect(T<=2)).to.equal(true)M.unmountVirtualNode(q)for p,G in p do(expect(G)).to.equal(1)end end)it("\115\104\111\117\108\100 \110\101\118\101\114 \117\110\109\111\117\110\116 \097 \110\111\100\101 \117\110\110\101\099\101\115\097\114\105\108\121 \105\110 \116\104\101 \099\097\115\101 \111\102 \114\101-\114\101\110\116\114\121",function()local p=a:extend("\076\111\119\101\115\116\067\111\109\112\111\110\101\110\116")function p.render(p)return m("\070\114\097\109\101")end function p.didUpdate(g,p,G)if p.firstTime and not g.props.firstTime then g.props.onChangedCallback()end end local g=a:extend("\067\104\105\108\100\067\111\109\112\111\110\101\110\116")function g.init(p)p:setState({firstTime=true})end local t function g.render(G)return m(p,{firstTime=G.state.firstTime,onChangedCallback=G.props.onChangedCallback})end function g.didMount(p)t=coroutine.create(function()p:setState({firstTime=false})end)end local Z=a:extend("\080\097\114\101\110\116\067\111\109\112\111\110\101\110\116")local J=0 function Z.init(p)p:setState({count=1})p.onChangedCallback=function()J=J+1 if p.state.count<5 then p:setState({count=p.state.count+1})end end end function Z.render(p)return m("\070\114\097\109\101",{},{ChildComponent=m(g,{count=p.state.count,onChangedCallback=p.onChangedCallback})})end local d=Instance.new("\083\099\114\101\101\110\071\117\105")d.Parent=G local K=m(Z)local S="\083\111\109\101 \075\101\121"local T=M.mountVirtualNode(K,d,S)coroutine.resume(t);(expect(#d:GetChildren())).to.equal(1)local C=(d:GetChildren())[1];(expect(#C:GetChildren())).to.equal(1);(expect(J)).to.equal(1)M.unmountVirtualNode(T)end)end)end
return function()
	local ReplicatedStorage = game:GetService("ReplicatedStorage")

	local assertDeepEqual = require(script.Parent.Parent.Parent.assertDeepEqual)

	local Binding = require(script.Parent.Parent.Binding)
	local Children = require(script.Parent.Parent.PropMarkers.Children)
	local Component = require(script.Parent.Parent.Component)
	local createElement = require(script.Parent.Parent.createElement)
	local createFragment = require(script.Parent.Parent.createFragment)
	local createReconciler = require(script.Parent.Parent.createReconciler)
	local createRef = require(script.Parent.Parent.createRef)
	local createSpy = require(script.Parent.Parent.createSpy)
	local GlobalConfig = require(script.Parent.Parent.GlobalConfig)
	local Portal = require(script.Parent.Parent.Portal)
	local Ref = require(script.Parent.Parent.PropMarkers.Ref)
	local Event = require(script.Parent.Parent.PropMarkers.Event)

	local RobloxRenderer = require(script.Parent)

	local reconciler = createReconciler(RobloxRenderer)

	describe("mountHostNode", function()
		it("should create instances with correct props", function()
			local parent = Instance.new("Folder")
			local value = "Hello!"
			local key = "Some Key"

			local element = createElement("StringValue", {
				Value = value,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(#parent:GetChildren()).to.equal(1)

			local root = parent:GetChildren()[1]

			expect(root.ClassName).to.equal("StringValue")
			expect(root.Value).to.equal(value)
			expect(root.Name).to.equal(key)
		end)

		it("should create children with correct names and props", function()
			local parent = Instance.new("Folder")
			local rootValue = "Hey there!"
			local childValue = 173
			local key = "Some Key"

			local element = createElement("StringValue", {
				Value = rootValue,
			}, {
				ChildA = createElement("IntValue", {
					Value = childValue,
				}),

				ChildB = createElement("Folder"),
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(#parent:GetChildren()).to.equal(1)

			local root = parent:GetChildren()[1]

			expect(root.ClassName).to.equal("StringValue")
			expect(root.Value).to.equal(rootValue)
			expect(root.Name).to.equal(key)

			expect(#root:GetChildren()).to.equal(2)

			local childA = root.ChildA
			local childB = root.ChildB

			expect(childA).to.be.ok()
			expect(childB).to.be.ok()

			expect(childA.ClassName).to.equal("IntValue")
			expect(childA.Value).to.equal(childValue)

			expect(childB.ClassName).to.equal("Folder")
		end)

		it("should attach Bindings to Roblox properties", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local binding, update = Binding.create(10)
			local element = createElement("IntValue", {
				Value = binding,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(#parent:GetChildren()).to.equal(1)

			local instance = parent:GetChildren()[1]

			expect(instance.ClassName).to.equal("IntValue")
			expect(instance.Value).to.equal(10)

			update(20)

			expect(instance.Value).to.equal(20)

			RobloxRenderer.unmountHostNode(reconciler, node)
		end)

		it("should connect Binding refs", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local ref = createRef()
			local element = createElement("Frame", {
				[Ref] = ref,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(#parent:GetChildren()).to.equal(1)

			local instance = parent:GetChildren()[1]

			expect(ref.current).to.be.ok()
			expect(ref.current).to.equal(instance)

			RobloxRenderer.unmountHostNode(reconciler, node)
		end)

		it("should call function refs", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local spyRef = createSpy()
			local element = createElement("Frame", {
				[Ref] = spyRef.value,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(#parent:GetChildren()).to.equal(1)

			local instance = parent:GetChildren()[1]

			expect(spyRef.callCount).to.equal(1)
			spyRef:assertCalledWith(instance)

			RobloxRenderer.unmountHostNode(reconciler, node)
		end)

		it("should throw if setting invalid instance properties", function()
			local configValues = {
				elementTracing = true,
			}

			GlobalConfig.scoped(configValues, function()
				local parent = Instance.new("Folder")
				local key = "Some Key"

				local element = createElement("Frame", {
					Frob = 6,
				})

				local node = reconciler.createVirtualNode(element, parent, key)

				local success, message = pcall(RobloxRenderer.mountHostNode, reconciler, node)
				assert(not success, "Expected call to fail")

				expect(message:find("Frob")).to.be.ok()
				expect(message:find("Frame")).to.be.ok()
				expect(message:find("init%.spec")).to.be.ok()
			end)
		end)
	end)

	describe("updateHostNode", function()
		it("should update node props and children", function()
			-- TODO: Break up test

			local parent = Instance.new("Folder")
			local key = "updateHostNodeTest"
			local firstValue = "foo"
			local newValue = "bar"

			local defaultStringValue = Instance.new("StringValue").Value

			local element = createElement("StringValue", {
				Value = firstValue,
			}, {
				ChildA = createElement("IntValue", {
					Value = 1,
				}),
				ChildB = createElement("BoolValue", {
					Value = true,
				}),
				ChildC = createElement("StringValue", {
					Value = "test",
				}),
				ChildD = createElement("StringValue", {
					Value = "test",
				}),
			})

			local node = reconciler.createVirtualNode(element, parent, key)
			RobloxRenderer.mountHostNode(reconciler, node)

			-- Not testing mountHostNode's work here, only testing that the
			-- node is properly updated.

			local newElement = createElement("StringValue", {
				Value = newValue,
			}, {
				-- ChildA changes element type.
				ChildA = createElement("StringValue", {
					Value = "test",
				}),
				-- ChildB changes child properties.
				ChildB = createElement("BoolValue", {
					Value = false,
				}),
				-- ChildC should reset its Value property back to the default.
				ChildC = createElement("StringValue", {}),
				-- ChildD is deleted.
				-- ChildE is added.
				ChildE = createElement("Folder", {}),
			})

			RobloxRenderer.updateHostNode(reconciler, node, newElement)

			local root = parent[key]
			expect(root.ClassName).to.equal("StringValue")
			expect(root.Value).to.equal(newValue)
			expect(#root:GetChildren()).to.equal(4)

			local childA = root.ChildA
			expect(childA.ClassName).to.equal("StringValue")
			expect(childA.Value).to.equal("test")

			local childB = root.ChildB
			expect(childB.ClassName).to.equal("BoolValue")
			expect(childB.Value).to.equal(false)

			local childC = root.ChildC
			expect(childC.ClassName).to.equal("StringValue")
			expect(childC.Value).to.equal(defaultStringValue)

			local childE = root.ChildE
			expect(childE.ClassName).to.equal("Folder")
		end)

		it("should update Bindings", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local bindingA, updateA = Binding.create(10)
			local element = createElement("IntValue", {
				Value = bindingA,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			local instance = parent:GetChildren()[1]

			expect(instance.Value).to.equal(10)

			local bindingB, updateB = Binding.create(99)
			local newElement = createElement("IntValue", {
				Value = bindingB,
			})

			RobloxRenderer.updateHostNode(reconciler, node, newElement)

			expect(instance.Value).to.equal(99)

			updateA(123)

			expect(instance.Value).to.equal(99)

			updateB(123)

			expect(instance.Value).to.equal(123)

			RobloxRenderer.unmountHostNode(reconciler, node)
		end)

		it("should update Binding refs", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local refA = createRef()
			local refB = createRef()

			local element = createElement("Frame", {
				[Ref] = refA,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(#parent:GetChildren()).to.equal(1)

			local instance = parent:GetChildren()[1]

			expect(refA.current).to.equal(instance)
			expect(refB.current).never.to.be.ok()

			local newElement = createElement("Frame", {
				[Ref] = refB,
			})

			RobloxRenderer.updateHostNode(reconciler, node, newElement)

			expect(refA.current).never.to.be.ok()
			expect(refB.current).to.equal(instance)

			RobloxRenderer.unmountHostNode(reconciler, node)
		end)

		it("should call old function refs with nil and new function refs with a valid rbx", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local spyRefA = createSpy()
			local spyRefB = createSpy()

			local element = createElement("Frame", {
				[Ref] = spyRefA.value,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(#parent:GetChildren()).to.equal(1)

			local instance = parent:GetChildren()[1]

			expect(spyRefA.callCount).to.equal(1)
			spyRefA:assertCalledWith(instance)
			expect(spyRefB.callCount).to.equal(0)

			local newElement = createElement("Frame", {
				[Ref] = spyRefB.value,
			})

			RobloxRenderer.updateHostNode(reconciler, node, newElement)

			expect(spyRefA.callCount).to.equal(2)
			spyRefA:assertCalledWith(nil)
			expect(spyRefB.callCount).to.equal(1)
			spyRefB:assertCalledWith(instance)

			RobloxRenderer.unmountHostNode(reconciler, node)
		end)

		it("should not call function refs again if they didn't change", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local spyRef = createSpy()

			local element = createElement("Frame", {
				Size = UDim2.new(1, 0, 1, 0),
				[Ref] = spyRef.value,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(#parent:GetChildren()).to.equal(1)

			local instance = parent:GetChildren()[1]

			expect(spyRef.callCount).to.equal(1)
			spyRef:assertCalledWith(instance)

			local newElement = createElement("Frame", {
				Size = UDim2.new(0.5, 0, 0.5, 0),
				[Ref] = spyRef.value,
			})

			RobloxRenderer.updateHostNode(reconciler, node, newElement)

			-- Not called again
			expect(spyRef.callCount).to.equal(1)
		end)

		it("should throw if setting invalid instance properties", function()
			local configValues = {
				elementTracing = true,
			}

			GlobalConfig.scoped(configValues, function()
				local parent = Instance.new("Folder")
				local key = "Some Key"

				local firstElement = createElement("Frame")
				local secondElement = createElement("Frame", {
					Frob = 6,
				})

				local node = reconciler.createVirtualNode(firstElement, parent, key)
				RobloxRenderer.mountHostNode(reconciler, node)

				local success, message = pcall(RobloxRenderer.updateHostNode, reconciler, node, secondElement)
				assert(not success, "Expected call to fail")

				expect(message:find("Frob")).to.be.ok()
				expect(message:find("Frame")).to.be.ok()
				expect(message:find("init%.spec")).to.be.ok()
			end)
		end)

		it("should delete instances when reconciling to nil children", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local element = createElement("Frame", {
				Size = UDim2.new(1, 0, 1, 0),
			}, {
				child = createElement("Frame"),
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(#parent:GetChildren()).to.equal(1)

			local instance = parent:GetChildren()[1]
			expect(#instance:GetChildren()).to.equal(1)

			local newElement = createElement("Frame", {
				Size = UDim2.new(0.5, 0, 0.5, 0),
			})

			RobloxRenderer.updateHostNode(reconciler, node, newElement)
			expect(#instance:GetChildren()).to.equal(0)
		end)
	end)

	describe("unmountHostNode", function()
		it("should delete instances from the inside-out", function()
			local parent = Instance.new("Folder")
			local key = "Root"
			local element = createElement("Folder", nil, {
				Child = createElement("Folder", nil, {
					Grandchild = createElement("Folder"),
				}),
			})

			local node = reconciler.mountVirtualNode(element, parent, key)

			expect(#parent:GetChildren()).to.equal(1)

			local root = parent:GetChildren()[1]
			expect(#root:GetChildren()).to.equal(1)

			local child = root:GetChildren()[1]
			expect(#child:GetChildren()).to.equal(1)

			local grandchild = child:GetChildren()[1]

			RobloxRenderer.unmountHostNode(reconciler, node)

			expect(grandchild.Parent).to.equal(nil)
			expect(child.Parent).to.equal(nil)
			expect(root.Parent).to.equal(nil)
		end)

		it("should unsubscribe from any Bindings", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local binding, update = Binding.create(10)
			local element = createElement("IntValue", {
				Value = binding,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			local instance = parent:GetChildren()[1]

			expect(instance.Value).to.equal(10)

			RobloxRenderer.unmountHostNode(reconciler, node)
			update(56)

			expect(instance.Value).to.equal(10)
		end)

		it("should clear Binding refs", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local ref = createRef()
			local element = createElement("Frame", {
				[Ref] = ref,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

			expect(ref.current).to.be.ok()

			RobloxRenderer.unmountHostNode(reconciler, node)

			expect(ref.current).never.to.be.ok()
		end)

		it("should call function refs with nil", function()
			local parent = Instance.new("Folder")
			local key = "Some Key"

			local spyRef = createSpy()
			local element = createElement("Frame", {
				[Ref] = spyRef.value,
			})

			local node = reconciler.createVirtualNode(element, parent, key)

			RobloxRenderer.mountHostNode(reconciler, node)

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

			RobloxRenderer.unmountHostNode(reconciler, node)

			expect(spyRef.callCount).to.equal(2)
			spyRef:assertCalledWith(nil)
		end)
	end)

	describe("Portals", function()
		it("should create and destroy instances as children of `target`", function()
			local target = Instance.new("Folder")

			local function FunctionComponent(props)
				return createElement("IntValue", {
					Value = props.value,
				})
			end

			local element = createElement(Portal, {
				target = target,
			}, {
				folderOne = createElement("Folder"),
				folderTwo = createElement("Folder"),
				intValueOne = createElement(FunctionComponent, {
					value = 42,
				}),
			})
			local hostParent = nil
			local hostKey = "Some Key"
			local node = reconciler.mountVirtualNode(element, hostParent, hostKey)

			expect(#target:GetChildren()).to.equal(3)

			expect(target:FindFirstChild("folderOne")).to.be.ok()
			expect(target:FindFirstChild("folderTwo")).to.be.ok()
			expect(target:FindFirstChild("intValueOne")).to.be.ok()
			expect(target:FindFirstChild("intValueOne").Value).to.equal(42)

			reconciler.unmountVirtualNode(node)

			expect(#target:GetChildren()).to.equal(0)
		end)

		it("should pass prop updates through to children", function()
			local target = Instance.new("Folder")

			local firstElement = createElement(Portal, {
				target = target,
			}, {
				ChildValue = createElement("IntValue", {
					Value = 1,
				}),
			})

			local secondElement = createElement(Portal, {
				target = target,
			}, {
				ChildValue = createElement("IntValue", {
					Value = 2,
				}),
			})

			local hostParent = nil
			local hostKey = "A Host Key"
			local node = reconciler.mountVirtualNode(firstElement, hostParent, hostKey)

			expect(#target:GetChildren()).to.equal(1)

			local firstValue = target.ChildValue
			expect(firstValue.Value).to.equal(1)

			node = reconciler.updateVirtualNode(node, secondElement)

			expect(#target:GetChildren()).to.equal(1)

			local secondValue = target.ChildValue
			expect(firstValue).to.equal(secondValue)
			expect(secondValue.Value).to.equal(2)

			reconciler.unmountVirtualNode(node)

			expect(#target:GetChildren()).to.equal(0)
		end)

		it("should throw if `target` is nil", function()
			-- TODO: Relax this restriction?
			local element = createElement(Portal)
			local hostParent = nil
			local hostKey = "Keys for Everyone"

			expect(function()
				reconciler.mountVirtualNode(element, hostParent, hostKey)
			end).to.throw()
		end)

		it("should throw if `target` is not a Roblox instance", function()
			local element = createElement(Portal, {
				target = {},
			})
			local hostParent = nil
			local hostKey = "Unleash the keys!"

			expect(function()
				reconciler.mountVirtualNode(element, hostParent, hostKey)
			end).to.throw()
		end)

		it("should recreate instances if `target` changes in an update", function()
			local firstTarget = Instance.new("Folder")
			local secondTarget = Instance.new("Folder")

			local firstElement = createElement(Portal, {
				target = firstTarget,
			}, {
				ChildValue = createElement("IntValue", {
					Value = 1,
				}),
			})

			local secondElement = createElement(Portal, {
				target = secondTarget,
			}, {
				ChildValue = createElement("IntValue", {
					Value = 2,
				}),
			})

			local hostParent = nil
			local hostKey = "Some Key"
			local node = reconciler.mountVirtualNode(firstElement, hostParent, hostKey)

			expect(#firstTarget:GetChildren()).to.equal(1)
			expect(#secondTarget:GetChildren()).to.equal(0)

			local firstChild = firstTarget.ChildValue
			expect(firstChild.Value).to.equal(1)

			node = reconciler.updateVirtualNode(node, secondElement)

			expect(#firstTarget:GetChildren()).to.equal(0)
			expect(#secondTarget:GetChildren()).to.equal(1)

			local secondChild = secondTarget.ChildValue
			expect(secondChild.Value).to.equal(2)

			reconciler.unmountVirtualNode(node)

			expect(#firstTarget:GetChildren()).to.equal(0)
			expect(#secondTarget:GetChildren()).to.equal(0)
		end)
	end)

	describe("Fragments", function()
		it("should parent the fragment's elements into the fragment's parent", function()
			local hostParent = Instance.new("Folder")

			local fragment = createFragment({
				key = createElement("IntValue", {
					Value = 1,
				}),
				key2 = createElement("IntValue", {
					Value = 2,
				}),
			})

			local node = reconciler.mountVirtualNode(fragment, hostParent, "test")

			expect(hostParent:FindFirstChild("key")).to.be.ok()
			expect(hostParent.key.ClassName).to.equal("IntValue")
			expect(hostParent.key.Value).to.equal(1)

			expect(hostParent:FindFirstChild("key2")).to.be.ok()
			expect(hostParent.key2.ClassName).to.equal("IntValue")
			expect(hostParent.key2.Value).to.equal(2)

			reconciler.unmountVirtualNode(node)

			expect(#hostParent:GetChildren()).to.equal(0)
		end)

		it("should allow sibling fragment to have common keys", function()
			local hostParent = Instance.new("Folder")
			local hostKey = "Test"

			local function parent(_props)
				return createElement("IntValue", {}, {
					fragmentA = createFragment({
						key = createElement("StringValue", {
							Value = "A",
						}),
						key2 = createElement("StringValue", {
							Value = "B",
						}),
					}),
					fragmentB = createFragment({
						key = createElement("StringValue", {
							Value = "C",
						}),
						key2 = createElement("StringValue", {
							Value = "D",
						}),
					}),
				})
			end

			local node = reconciler.mountVirtualNode(createElement(parent), hostParent, hostKey)
			local parentChildren = hostParent[hostKey]:GetChildren()

			expect(#parentChildren).to.equal(4)

			local childValues = {}

			for _, child in parentChildren do
				expect(child.ClassName).to.equal("StringValue")
				childValues[child.Value] = 1 + (childValues[child.Value] or 0)
			end

			-- check if the StringValues have not collided
			expect(childValues.A).to.equal(1)
			expect(childValues.B).to.equal(1)
			expect(childValues.C).to.equal(1)
			expect(childValues.D).to.equal(1)

			reconciler.unmountVirtualNode(node)

			expect(#hostParent:GetChildren()).to.equal(0)
		end)

		it("should render nested fragments", function()
			local hostParent = Instance.new("Folder")

			local fragment = createFragment({
				key = createFragment({
					TheValue = createElement("IntValue", {
						Value = 1,
					}),
					TheOtherValue = createElement("IntValue", {
						Value = 2,
					}),
				}),
			})

			local node = reconciler.mountVirtualNode(fragment, hostParent, "Test")

			expect(hostParent:FindFirstChild("TheValue")).to.be.ok()
			expect(hostParent.TheValue.ClassName).to.equal("IntValue")
			expect(hostParent.TheValue.Value).to.equal(1)

			expect(hostParent:FindFirstChild("TheOtherValue")).to.be.ok()
			expect(hostParent.TheOtherValue.ClassName).to.equal("IntValue")
			expect(hostParent.TheOtherValue.Value).to.equal(2)

			reconciler.unmountVirtualNode(node)

			expect(#hostParent:GetChildren()).to.equal(0)
		end)

		it("should not add any instances if the fragment is empty", function()
			local hostParent = Instance.new("Folder")

			local node = reconciler.mountVirtualNode(createFragment({}), hostParent, "test")

			expect(#hostParent:GetChildren()).to.equal(0)

			reconciler.unmountVirtualNode(node)

			expect(#hostParent:GetChildren()).to.equal(0)
		end)
	end)

	describe("Context", function()
		it("should pass context values through Roblox host nodes", function()
			local Consumer = Component:extend("Consumer")

			local capturedContext
			function Consumer:init()
				capturedContext = {
					hello = self:__getContext("hello"),
				}
			end

			function Consumer:render() end

			local element = createElement("Folder", nil, {
				Consumer = createElement(Consumer),
			})
			local hostParent = nil
			local hostKey = "Context Test"
			local context = {
				hello = "world",
			}
			local node = reconciler.mountVirtualNode(element, hostParent, hostKey, context)

			expect(capturedContext).never.to.equal(context)
			assertDeepEqual(capturedContext, context)

			reconciler.unmountVirtualNode(node)
		end)

		it("should pass context values through portal nodes", function()
			local target = Instance.new("Folder")

			local Provider = Component:extend("Provider")

			function Provider:init()
				self:__addContext("foo", "bar")
			end

			function Provider:render()
				return createElement("Folder", nil, self.props[Children])
			end

			local Consumer = Component:extend("Consumer")

			local capturedContext
			function Consumer:init()
				capturedContext = {
					foo = self:__getContext("foo"),
				}
			end

			function Consumer:render()
				return nil
			end

			local element = createElement(Provider, nil, {
				Portal = createElement(Portal, {
					target = target,
				}, {
					Consumer = createElement(Consumer),
				}),
			})
			local hostParent = nil
			local hostKey = "Some Key"
			reconciler.mountVirtualNode(element, hostParent, hostKey)

			assertDeepEqual(capturedContext, {
				foo = "bar",
			})
		end)
	end)

	describe("Legacy context", function()
		it("should pass context values through Roblox host nodes", function()
			local Consumer = Component:extend("Consumer")

			local capturedContext
			function Consumer:init()
				capturedContext = self._context
			end

			function Consumer:render() end

			local element = createElement("Folder", nil, {
				Consumer = createElement(Consumer),
			})
			local hostParent = nil
			local hostKey = "Context Test"
			local context = {
				hello = "world",
			}
			local node = reconciler.mountVirtualNode(element, hostParent, hostKey, nil, context)

			expect(capturedContext).never.to.equal(context)
			assertDeepEqual(capturedContext, context)

			reconciler.unmountVirtualNode(node)
		end)

		it("should pass context values through portal nodes", function()
			local target = Instance.new("Folder")

			local Provider = Component:extend("Provider")

			function Provider:init()
				self._context.foo = "bar"
			end

			function Provider:render()
				return createElement("Folder", nil, self.props[Children])
			end

			local Consumer = Component:extend("Consumer")

			local capturedContext
			function Consumer:init()
				capturedContext = self._context
			end

			function Consumer:render()
				return nil
			end

			local element = createElement(Provider, nil, {
				Portal = createElement(Portal, {
					target = target,
				}, {
					Consumer = createElement(Consumer),
				}),
			})
			local hostParent = nil
			local hostKey = "Some Key"
			reconciler.mountVirtualNode(element, hostParent, hostKey)

			assertDeepEqual(capturedContext, {
				foo = "bar",
			})
		end)
	end)

	describe("Integration Tests", function()
		local temporaryParent = nil
		beforeEach(function()
			temporaryParent = Instance.new("Folder")
			temporaryParent.Parent = ReplicatedStorage
		end)

		afterEach(function()
			temporaryParent:Destroy()
			temporaryParent = nil
		end)

		it("should not allow re-entrancy in updateChildren", function()
			local ChildComponent = Component:extend("ChildComponent")

			function ChildComponent:init()
				self:setState({
					firstTime = true,
				})
			end

			local childCoroutine

			function ChildComponent:render()
				if self.state.firstTime then
					return createElement("Frame")
				end

				return createElement("TextLabel")
			end

			function ChildComponent:didMount()
				childCoroutine = coroutine.create(function()
					self:setState({
						firstTime = false,
					})
				end)
			end

			local ParentComponent = Component:extend("ParentComponent")

			function ParentComponent:init()
				self:setState({
					count = 1,
				})

				self.childAdded = function()
					self:setState({
						count = self.state.count + 1,
					})
				end
			end

			function ParentComponent:render()
				return createElement("Frame", {
					[Event.ChildAdded] = self.childAdded,
				}, {
					ChildComponent = createElement(ChildComponent, {
						count = self.state.count,
					}),
				})
			end

			local parent = Instance.new("ScreenGui")
			parent.Parent = temporaryParent

			local tree = createElement(ParentComponent)

			local hostKey = "Some Key"
			local instance = reconciler.mountVirtualNode(tree, parent, hostKey)

			coroutine.resume(childCoroutine)

			expect(#parent:GetChildren()).to.equal(1)

			local frame = parent:GetChildren()[1]

			expect(#frame:GetChildren()).to.equal(1)

			reconciler.unmountVirtualNode(instance)
		end)

		it("should not allow re-entrancy in updateChildren even with callbacks", function()
			local LowestComponent = Component:extend("LowestComponent")

			function LowestComponent:render()
				return createElement("Frame")
			end

			function LowestComponent:didMount()
				self.props.onDidMountCallback()
			end

			local ChildComponent = Component:extend("ChildComponent")

			function ChildComponent:init()
				self:setState({
					firstTime = true,
				})
			end

			local childCoroutine

			function ChildComponent:render()
				if self.state.firstTime then
					return createElement("Frame")
				end

				return createElement(LowestComponent, {
					onDidMountCallback = self.props.onDidMountCallback,
				})
			end

			function ChildComponent:didMount()
				childCoroutine = coroutine.create(function()
					self:setState({
						firstTime = false,
					})
				end)
			end

			local ParentComponent = Component:extend("ParentComponent")

			local didMountCallbackCalled = 0

			function ParentComponent:init()
				self:setState({
					count = 1,
				})

				self.onDidMountCallback = function()
					didMountCallbackCalled = didMountCallbackCalled + 1
					if self.state.count < 5 then
						self:setState({
							count = self.state.count + 1,
						})
					end
				end
			end

			function ParentComponent:render()
				return createElement("Frame", {}, {
					ChildComponent = createElement(ChildComponent, {
						count = self.state.count,
						onDidMountCallback = self.onDidMountCallback,
					}),
				})
			end

			local parent = Instance.new("ScreenGui")
			parent.Parent = temporaryParent

			local tree = createElement(ParentComponent)

			local hostKey = "Some Key"
			local instance = reconciler.mountVirtualNode(tree, parent, hostKey)

			coroutine.resume(childCoroutine)

			expect(#parent:GetChildren()).to.equal(1)

			local frame = parent:GetChildren()[1]

			expect(#frame:GetChildren()).to.equal(1)

			-- In an ideal world, the didMount callback would probably be called only once. Since it is called by two different
			-- LowestComponent instantiations 2 is also acceptable though.
			expect(didMountCallbackCalled <= 2).to.equal(true)

			reconciler.unmountVirtualNode(instance)
		end)

		it("should never call unmount twice in the case of update children re-rentrancy", function()
			local unmountCounts = {}

			local function addUnmount(id)
				unmountCounts[id] = unmountCounts[id] + 1
			end

			local function addInit(id)
				unmountCounts[id] = 0
			end

			local LowestComponent = Component:extend("LowestComponent")
			function LowestComponent:init()
				addInit(tostring(self))
			end

			function LowestComponent:render()
				return createElement("Frame")
			end

			function LowestComponent:didMount()
				self.props.onDidMountCallback()
			end

			function LowestComponent:willUnmount()
				addUnmount(tostring(self))
			end

			local FirstComponent = Component:extend("FirstComponent")
			function FirstComponent:init()
				addInit(tostring(self))
			end

			function FirstComponent:render()
				return createElement("TextLabel")
			end

			function FirstComponent:willUnmount()
				addUnmount(tostring(self))
			end

			local ChildComponent = Component:extend("ChildComponent")

			function ChildComponent:init()
				addInit(tostring(self))

				self:setState({
					firstTime = true,
				})
			end

			local childCoroutine

			function ChildComponent:render()
				if self.state.firstTime then
					return createElement(FirstComponent)
				end

				return createElement(LowestComponent, {
					onDidMountCallback = self.props.onDidMountCallback,
				})
			end

			function ChildComponent:didMount()
				childCoroutine = coroutine.create(function()
					self:setState({
						firstTime = false,
					})
				end)
			end

			function ChildComponent:willUnmount()
				addUnmount(tostring(self))
			end

			local ParentComponent = Component:extend("ParentComponent")

			local didMountCallbackCalled = 0

			function ParentComponent:init()
				self:setState({
					count = 1,
				})

				self.onDidMountCallback = function()
					didMountCallbackCalled = didMountCallbackCalled + 1
					if self.state.count < 5 then
						self:setState({
							count = self.state.count + 1,
						})
					end
				end
			end

			function ParentComponent:render()
				return createElement("Frame", {}, {
					ChildComponent = createElement(ChildComponent, {
						count = self.state.count,
						onDidMountCallback = self.onDidMountCallback,
					}),
				})
			end

			local parent = Instance.new("ScreenGui")
			parent.Parent = temporaryParent

			local tree = createElement(ParentComponent)

			local hostKey = "Some Key"
			local instance = reconciler.mountVirtualNode(tree, parent, hostKey)

			coroutine.resume(childCoroutine)

			expect(#parent:GetChildren()).to.equal(1)

			local frame = parent:GetChildren()[1]

			expect(#frame:GetChildren()).to.equal(1)

			-- In an ideal world, the didMount callback would probably be called only once. Since it is called by two different
			-- LowestComponent instantiations 2 is also acceptable though.
			expect(didMountCallbackCalled <= 2).to.equal(true)

			reconciler.unmountVirtualNode(instance)

			for _, value in unmountCounts do
				expect(value).to.equal(1)
			end
		end)

		it("should never unmount a node unnecesarily in the case of re-rentry", function()
			local LowestComponent = Component:extend("LowestComponent")
			function LowestComponent:render()
				return createElement("Frame")
			end

			function LowestComponent:didUpdate(prevProps, _prevState)
				if prevProps.firstTime and not self.props.firstTime then
					self.props.onChangedCallback()
				end
			end

			local ChildComponent = Component:extend("ChildComponent")

			function ChildComponent:init()
				self:setState({
					firstTime = true,
				})
			end

			local childCoroutine

			function ChildComponent:render()
				return createElement(LowestComponent, {
					firstTime = self.state.firstTime,
					onChangedCallback = self.props.onChangedCallback,
				})
			end

			function ChildComponent:didMount()
				childCoroutine = coroutine.create(function()
					self:setState({
						firstTime = false,
					})
				end)
			end

			local ParentComponent = Component:extend("ParentComponent")

			local onChangedCallbackCalled = 0

			function ParentComponent:init()
				self:setState({
					count = 1,
				})

				self.onChangedCallback = function()
					onChangedCallbackCalled = onChangedCallbackCalled + 1
					if self.state.count < 5 then
						self:setState({
							count = self.state.count + 1,
						})
					end
				end
			end

			function ParentComponent:render()
				return createElement("Frame", {}, {
					ChildComponent = createElement(ChildComponent, {
						count = self.state.count,
						onChangedCallback = self.onChangedCallback,
					}),
				})
			end

			local parent = Instance.new("ScreenGui")
			parent.Parent = temporaryParent

			local tree = createElement(ParentComponent)

			local hostKey = "Some Key"
			local instance = reconciler.mountVirtualNode(tree, parent, hostKey)

			coroutine.resume(childCoroutine)

			expect(#parent:GetChildren()).to.equal(1)

			local frame = parent:GetChildren()[1]

			expect(#frame:GetChildren()).to.equal(1)

			expect(onChangedCallbackCalled).to.equal(1)

			reconciler.unmountVirtualNode(instance)
		end)
	end)
end
