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

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

	local RobloxRenderer = require(script.Parent.RobloxRenderer)

	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("RobloxRenderer%.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("RobloxRenderer%.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 pairs(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 pairs(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
