return function()local i=require(script.Parent.createSpy)local W=require(script.Parent.Type)local K=require(script.Parent.GlobalConfig)local D=require(script.Parent.Binding)describe("\066\105\110\100\105\110\103\046\099\114\101\097\116\101",function()it("\115\104\111\117\108\100 \114\101\116\117\114\110 \097 \066\105\110\100\105\110\103 \111\098\106\101\099\116 \097\110\100 \097\110 \117\112\100\097\116\101 \102\117\110\099\116\105\111\110",function()local i,K=D.create(1);(expect(W.of(i))).to.equal(W.Binding);(expect(typeof(K))).to.equal("\102\117\110\099\116\105\111\110")end)it("\115\104\111\117\108\100 \115\117\112\112\111\114\116 \116\111\115\116\114\105\110\103 \111\110 \098\105\110\100\105\110\103\115",function()local i,W=D.create(1);(expect(tostring(i))).to.equal("\082\111\097\099\116\066\105\110\100\105\110\103\040\049\041")W("\102\111\111");(expect(tostring(i))).to.equal("\082\111\097\099\116\066\105\110\100\105\110\103\040\102\111\111\041")end)end)describe("\066\105\110\100\105\110\103 \111\098\106\101\099\116",function()it("\115\104\111\117\108\100 \112\114\111\118\105\100\101 \097 \103\101\116\116\101\114 \097\110\100 \115\101\116\116\101\114",function()local i,W=D.create(1);(expect(i:getValue())).to.equal(1)W(3);(expect(i:getValue())).to.equal(3)end)it("\115\104\111\117\108\100 \108\101\116 \117\115\101\114\115 \115\117\098\115\099\114\105\098\101 \097\110\100 \117\110\115\117\098\115\099\114\105\098\101 \116\111 \105\116\115 \117\112\100\097\116\101\115",function()local W,K=D.create(1)local u=i()local J=D.subscribe(W,u.value);(expect(u.callCount)).to.equal(0)K(2);(expect(u.callCount)).to.equal(1)u:assertCalledWith(2)J()K(3);(expect(u.callCount)).to.equal(1)end)end)describe("\077\097\112\112\101\100 \098\105\110\100\105\110\103\115",function()it("\115\104\111\117\108\100 \098\101 \099\111\109\112\111\115\097\098\108\101",function()local i,W=D.create("\104\105")local K=i:map(string.len)local u=K:map(function(i)return i%2==0 end);(expect(i:getValue())).to.equal("\104\105");(expect(K:getValue())).to.equal(2);(expect(u:getValue())).to.equal(true)W("\115\117\112");(expect(i:getValue())).to.equal("\115\117\112");(expect(K:getValue())).to.equal(3);(expect(u:getValue())).to.equal(false)end)it("\115\104\111\117\108\100 \099\097\115\099\097\100\101 \117\112\100\097\116\101\115 \119\104\101\110 \115\117\098\115\099\114\105\098\101\100",function()local W,K=D.create("\104\105")local u=i()local J=D.subscribe(W,u.value)local G=W:map(string.len)local e=i()local d=D.subscribe(G,e.value)local F=G:map(function(i)return i%2==0 end)local A=i()local c=D.subscribe(F,A.value);(expect(u.callCount)).to.equal(0);(expect(e.callCount)).to.equal(0);(expect(A.callCount)).to.equal(0)K("\110\105\099\101");(expect(u.callCount)).to.equal(1)u:assertCalledWith("\110\105\099\101");(expect(e.callCount)).to.equal(1)e:assertCalledWith(4);(expect(A.callCount)).to.equal(1)A:assertCalledWith(true)J()d()c()K("\103\111\111\100\098\121\101");(expect(u.callCount)).to.equal(1);(expect(A.callCount)).to.equal(1);(expect(e.callCount)).to.equal(1)end)it("\115\104\111\117\108\100 \116\104\114\111\119 \119\104\101\110 \117\112\100\097\116\101\100 \100\105\114\101\099\116\108\121",function()local i=D.create(1)local W=i:map(function(i)return i end);(expect(function()D.update(W,5)end)).to.throw()end)end)describe("\066\105\110\100\105\110\103\046\106\111\105\110",function()it("\115\104\111\117\108\100 \104\097\118\101 \103\101\116\086\097\108\117\101",function()local i=D.create(1)local W=D.create(2)local K=D.create(3)local u=D.join({i,W,foo=K})local J=u:getValue();(expect(J)).to.be.a("\116\097\098\108\101");(expect(J[1])).to.equal(1);(expect(J[2])).to.equal(2);(expect(J.foo)).to.equal(3)end)it("\115\104\111\117\108\100 \117\112\100\097\116\101 \119\104\101\110 \097\110\121 \111\110\101 \111\102 \116\104\101 \115\117\098\115\099\114\105\098\101\100 \098\105\110\100\105\110\103\115 \117\112\100\097\116\101\115",function()local W,K=D.create(1)local u,J=D.create(2)local G,e=D.create(3)local d=D.join({W,u,foo=G})local F=i()D.subscribe(d,F.value);(expect(F.callCount)).to.equal(0)K(3);(expect(F.callCount)).to.equal(1)local A=F:captureValues("\118\097\108\117\101");(expect(A.value)).to.be.a("\116\097\098\108\101");(expect(A.value[1])).to.equal(3);(expect(A.value[2])).to.equal(2);(expect(A.value.foo)).to.equal(3)J(4);(expect(F.callCount)).to.equal(2)A=F:captureValues("\118\097\108\117\101");(expect(A.value)).to.be.a("\116\097\098\108\101");(expect(A.value[1])).to.equal(3);(expect(A.value[2])).to.equal(4);(expect(A.value.foo)).to.equal(3)e(8);(expect(F.callCount)).to.equal(3)A=F:captureValues("\118\097\108\117\101");(expect(A.value)).to.be.a("\116\097\098\108\101");(expect(A.value[1])).to.equal(3);(expect(A.value[2])).to.equal(4);(expect(A.value.foo)).to.equal(8)end)it("\115\104\111\117\108\100 \100\105\115\099\111\110\110\101\099\116 \102\114\111\109 \097\108\108 \117\112\115\116\114\101\097\109 \098\105\110\100\105\110\103\115",function()local W,K=D.create(1)local u,J=D.create(2)local G=D.join({W,u})local e=i()local d=D.subscribe(G,e.value);(expect(e.callCount)).to.equal(0)K(3);(expect(e.callCount)).to.equal(1)J(3);(expect(e.callCount)).to.equal(2)d()K(4);(expect(e.callCount)).to.equal(2)J(2);(expect(e.callCount)).to.equal(2)local F=G:getValue();(expect(F[1])).to.equal(4);(expect(F[2])).to.equal(2)end)it("\115\104\111\117\108\100 \098\101 \111\107\097\121 \119\105\116\104 \099\097\108\108\105\110\103 \100\105\115\099\111\110\110\101\099\116 \109\117\108\116\105\112\108\101 \116\105\109\101\115",function()local i=D.join({})local W=D.subscribe(i,function() end)W()W()end)it("\115\104\111\117\108\100 \116\104\114\111\119 \105\102 \117\112\100\097\116\101\100 \100\105\114\101\099\116\108\121",function()local i=D.join({})expect(function()D.update(i,0)end)end)it("\115\104\111\117\108\100 \116\104\114\111\119 \119\104\101\110 \097 \110\111\110-\116\097\098\108\101 \118\097\108\117\101 \105\115 \112\097\115\115\101\100",function()K.scoped({typeChecks=true},function()(expect(function()D.join("\104\105")end)).to.throw()end)end)it("\115\104\111\117\108\100 \116\104\114\111\119 \119\104\101\110 \097 \110\111\110-\098\105\110\100\105\110\103 \118\097\108\117\101 \105\115 \112\097\115\115\101\100 \118\105\097 \116\097\098\108\101",function()K.scoped({typeChecks=true},function()(expect(function()local i=D.create(123)D.join({i,"\097\098\099\100\101"})end)).to.throw()end)end)end)end
return function()
	local createSpy = require(script.Parent.createSpy)
	local Type = require(script.Parent.Type)
	local GlobalConfig = require(script.Parent.GlobalConfig)

	local Binding = require(script.Parent.Binding)

	describe("Binding.create", function()
		it("should return a Binding object and an update function", function()
			local binding, update = Binding.create(1)

			expect(Type.of(binding)).to.equal(Type.Binding)
			expect(typeof(update)).to.equal("function")
		end)

		it("should support tostring on bindings", function()
			local binding, update = Binding.create(1)
			expect(tostring(binding)).to.equal("RoactBinding(1)")

			update("foo")
			expect(tostring(binding)).to.equal("RoactBinding(foo)")
		end)
	end)

	describe("Binding object", function()
		it("should provide a getter and setter", function()
			local binding, update = Binding.create(1)

			expect(binding:getValue()).to.equal(1)

			update(3)

			expect(binding:getValue()).to.equal(3)
		end)

		it("should let users subscribe and unsubscribe to its updates", function()
			local binding, update = Binding.create(1)

			local spy = createSpy()
			local disconnect = Binding.subscribe(binding, spy.value)

			expect(spy.callCount).to.equal(0)

			update(2)

			expect(spy.callCount).to.equal(1)
			spy:assertCalledWith(2)

			disconnect()
			update(3)

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

	describe("Mapped bindings", function()
		it("should be composable", function()
			local word, updateWord = Binding.create("hi")

			local wordLength = word:map(string.len)
			local isEvenLength = wordLength:map(function(value)
				return value % 2 == 0
			end)

			expect(word:getValue()).to.equal("hi")
			expect(wordLength:getValue()).to.equal(2)
			expect(isEvenLength:getValue()).to.equal(true)

			updateWord("sup")

			expect(word:getValue()).to.equal("sup")
			expect(wordLength:getValue()).to.equal(3)
			expect(isEvenLength:getValue()).to.equal(false)
		end)

		it("should cascade updates when subscribed", function()
			-- base binding
			local word, updateWord = Binding.create("hi")

			local wordSpy = createSpy()
			local disconnectWord = Binding.subscribe(word, wordSpy.value)

			-- binding -> base binding
			local length = word:map(string.len)

			local lengthSpy = createSpy()
			local disconnectLength = Binding.subscribe(length, lengthSpy.value)

			-- binding -> binding -> base binding
			local isEvenLength = length:map(function(value)
				return value % 2 == 0
			end)

			local isEvenLengthSpy = createSpy()
			local disconnectIsEvenLength = Binding.subscribe(isEvenLength, isEvenLengthSpy.value)

			expect(wordSpy.callCount).to.equal(0)
			expect(lengthSpy.callCount).to.equal(0)
			expect(isEvenLengthSpy.callCount).to.equal(0)

			updateWord("nice")

			expect(wordSpy.callCount).to.equal(1)
			wordSpy:assertCalledWith("nice")

			expect(lengthSpy.callCount).to.equal(1)
			lengthSpy:assertCalledWith(4)

			expect(isEvenLengthSpy.callCount).to.equal(1)
			isEvenLengthSpy:assertCalledWith(true)

			disconnectWord()
			disconnectLength()
			disconnectIsEvenLength()

			updateWord("goodbye")

			expect(wordSpy.callCount).to.equal(1)
			expect(isEvenLengthSpy.callCount).to.equal(1)
			expect(lengthSpy.callCount).to.equal(1)
		end)

		it("should throw when updated directly", function()
			local source = Binding.create(1)
			local mapped = source:map(function(v)
				return v
			end)

			expect(function()
				Binding.update(mapped, 5)
			end).to.throw()
		end)
	end)

	describe("Binding.join", function()
		it("should have getValue", function()
			local binding1 = Binding.create(1)
			local binding2 = Binding.create(2)
			local binding3 = Binding.create(3)

			local joinedBinding = Binding.join({
				binding1,
				binding2,
				foo = binding3,
			})

			local bindingValue = joinedBinding:getValue()
			expect(bindingValue).to.be.a("table")
			expect(bindingValue[1]).to.equal(1)
			expect(bindingValue[2]).to.equal(2)
			expect(bindingValue.foo).to.equal(3)
		end)

		it("should update when any one of the subscribed bindings updates", function()
			local binding1, update1 = Binding.create(1)
			local binding2, update2 = Binding.create(2)
			local binding3, update3 = Binding.create(3)

			local joinedBinding = Binding.join({
				binding1,
				binding2,
				foo = binding3,
			})

			local spy = createSpy()
			Binding.subscribe(joinedBinding, spy.value)

			expect(spy.callCount).to.equal(0)

			update1(3)
			expect(spy.callCount).to.equal(1)

			local args = spy:captureValues("value")
			expect(args.value).to.be.a("table")
			expect(args.value[1]).to.equal(3)
			expect(args.value[2]).to.equal(2)
			expect(args.value["foo"]).to.equal(3)

			update2(4)
			expect(spy.callCount).to.equal(2)

			args = spy:captureValues("value")
			expect(args.value).to.be.a("table")
			expect(args.value[1]).to.equal(3)
			expect(args.value[2]).to.equal(4)
			expect(args.value["foo"]).to.equal(3)

			update3(8)
			expect(spy.callCount).to.equal(3)

			args = spy:captureValues("value")
			expect(args.value).to.be.a("table")
			expect(args.value[1]).to.equal(3)
			expect(args.value[2]).to.equal(4)
			expect(args.value["foo"]).to.equal(8)
		end)

		it("should disconnect from all upstream bindings", function()
			local binding1, update1 = Binding.create(1)
			local binding2, update2 = Binding.create(2)

			local joined = Binding.join({binding1, binding2})

			local spy = createSpy()
			local disconnect = Binding.subscribe(joined, spy.value)

			expect(spy.callCount).to.equal(0)

			update1(3)
			expect(spy.callCount).to.equal(1)

			update2(3)
			expect(spy.callCount).to.equal(2)

			disconnect()
			update1(4)
			expect(spy.callCount).to.equal(2)

			update2(2)
			expect(spy.callCount).to.equal(2)

			local value = joined:getValue()
			expect(value[1]).to.equal(4)
			expect(value[2]).to.equal(2)
		end)

		it("should be okay with calling disconnect multiple times", function()
			local joined = Binding.join({})

			local disconnect = Binding.subscribe(joined, function() end)

			disconnect()
			disconnect()
		end)

		it("should throw if updated directly", function()
			local joined = Binding.join({})

			expect(function()
				Binding.update(joined, 0)
			end)
		end)

		it("should throw when a non-table value is passed", function()
			GlobalConfig.scoped({
				typeChecks = true,
			}, function()
				expect(function()
					Binding.join("hi")
				end).to.throw()
			end)
		end)

		it("should throw when a non-binding value is passed via table", function()
			GlobalConfig.scoped({
				typeChecks = true,
			}, function()
				expect(function()
					local binding = Binding.create(123)

					Binding.join({
						binding,
						"abcde",
					})
				end).to.throw()
			end)
		end)
	end)
end