return function()local q=game:GetService("\067\111\108\108\101\099\116\105\111\110\083\101\114\118\105\099\101")local O=require(script.Parent.Parent.t)local e=require(script.Parent)describe("\110\101\119\040\041",function()it("\115\104\111\117\108\100 \099\114\101\097\116\101 \097 \110\101\119 \084\097\103 \105\110\115\116\097\110\099\101",function()local q=e.new("\070\111\111");(expect(q)).to.be.a("\116\097\098\108\101");(expect(q.name)).to.equal("\070\111\111")end)it("\115\104\111\117\108\100 \114\101\116\117\114\110 \116\104\101 \110\097\109\101 \119\104\101\110 \099\111\101\114\099\105\110\103 \105\110\116\111 \097 \115\116\114\105\110\103",function()local q=e.new("\070\111\111");(expect(tostring(q))).to.equal("\070\111\111")end)end)describe("\097\100\100\040\041",function()it("\115\104\111\117\108\100 \097\100\100 \116\104\101 \116\097\103 \116\111 \097\110 \105\110\115\116\097\110\099\101",function()local O=Instance.new("\077\111\100\101\108")local N=e.new("\070\111\111")N:add(O);(expect(q:HasTag(O,"\070\111\111"))).to.equal(true)end)it("\115\104\111\117\108\100 \101\114\114\111\114 \119\104\101\110 \103\105\118\101\110 \097\110 \105\110\099\111\114\114\101\099\116 \105\110\115\116\097\110\099\101",function()local q=e.new("\070\111\111",O.instanceOf("\077\111\100\101\108"))local N=Instance.new("\080\097\114\116");(expect(function()q:add(N)end)).to.throw()end)end)describe("\114\101\109\111\118\101\040\041",function()it("\115\104\111\117\108\100 \114\101\109\111\118\101 \116\104\101 \116\097\103 \102\114\111\109 \097\110 \105\110\115\116\097\110\099\101",function()local O=Instance.new("\077\111\100\101\108")local N=e.new("\070\111\111")N:add(O);(expect(q:HasTag(O,"\070\111\111"))).to.equal(true)N:remove(O);(expect(q:HasTag(O,"\070\111\111"))).to.equal(false)end)end)describe("\116\111\103\103\108\101\040\041",function()it("\115\104\111\117\108\100 \097\100\100 \116\104\101 \116\097\103 \105\102 \105\116\'\115 \110\111\116 \097\108\114\101\097\100\121 \097\112\112\108\105\101\100",function()local q=Instance.new("\077\111\100\101\108")local O=e.new("\070\111\111");(expect(O:has(q))).to.equal(false)O:toggle(q);(expect(O:has(q))).to.equal(true)end)it("\115\104\111\117\108\100 \114\101\109\111\118\101 \116\104\101 \116\097\103 \105\102 \105\116\'\115 \097\108\114\101\097\100\121 \097\112\112\108\105\101\100",function()local q=Instance.new("\077\111\100\101\108")local O=e.new("\070\111\111")O:add(q);(expect(O:has(q))).to.equal(true)O:toggle(q);(expect(O:has(q))).to.equal(false)end)it("\115\104\111\117\108\100 \101\114\114\111\114 \119\104\101\110 \103\105\118\101\110 \097\110 \105\110\099\111\114\114\101\099\116 \105\110\115\116\097\110\099\101",function()local q=e.new("\070\111\111",O.instanceOf("\077\111\100\101\108"))local N=Instance.new("\080\097\114\116");(expect(function()q:toggle(N)end)).to.throw()end)end)describe("\104\097\115\040\041",function()it("\115\104\111\117\108\100 \114\101\116\117\114\110 \116\114\117\101 \105\102 \116\104\101 \116\097\103 \105\115 \097\112\112\108\105\101\100",function()local q=Instance.new("\077\111\100\101\108")local O=e.new("\070\111\111")O:add(q);(expect(O:has(q))).to.equal(true)end)it("\115\104\111\117\108\100 \114\101\116\117\114\110 \102\097\108\115\101 \105\102 \116\104\101 \116\097\103 \105\115 \110\111\116 \097\112\112\108\105\101\100",function()local q=Instance.new("\077\111\100\101\108")local O=e.new("\070\111\111");(expect(O:has(q))).to.equal(false)end)end)describe("\103\101\116\084\097\103\103\101\100\040\041",function()it("\115\104\111\117\108\100 \103\101\116 \097\108\108 \116\104\101 \105\110\115\116\097\110\099\101\115 \116\104\101 \116\097\103 \105\115 \097\112\112\108\105\101\100 \116\111",function()local q=e.new("\070\111\111")local O={}for e=1,3,1 do local N=Instance.new("\077\111\100\101\108")q:add(N)N.Parent=game table.insert(O,N)end;(expect(#q:getTagged())).to.equal(3)for q,O in ipairs(O)do O:Destroy()end end)end)describe("\111\110\065\100\100\101\100\040\041",function()it("\115\104\111\117\108\100 \114\117\110 \116\104\101 \099\097\108\108\098\097\099\107 \111\110 \097\108\108 \101\120\105\115\116\105\110\103 \116\097\103\103\101\100 \105\110\115\116\097\110\099\101\115",function()local q=e.new("\070\111\111")local O=Instance.new("\077\111\100\101\108")local N=false O.Parent=game q:add(O)local g=q:onAdded(function(q)N=O==q end);(expect(N)).to.equal(true)g:Disconnect()O:Destroy()end)it("\115\104\111\117\108\100 \114\117\110 \119\104\101\110 \097\110 \105\110\115\116\097\110\099\101 \105\115 \116\097\103\103\101\100",function()local q=Instance.new("\077\111\100\101\108")local O=e.new("\070\111\111")local N=false q.Parent=game local g=O:onAdded(function(O)N=q==O end)O:add(q);(expect(N)).to.equal(true)g:Disconnect()q:Destroy()end)it("\115\104\111\117\108\100 \101\114\114\111\114 \119\104\101\110 \097\110 \101\120\105\115\116\105\110\103 \105\110\115\116\097\110\099\101 \119\105\116\104 \116\104\101 \116\097\103 \100\111\101\115\110\'\116 \099\104\101\099\107",function()local N=e.new("\070\111\111",O.instanceOf("\077\111\100\101\108"))local g=Instance.new("\080\097\114\116")q:AddTag(g,"\070\111\111")g.Parent=game;(expect(function()N:onAdded(function() end)end)).to.throw()g:Destroy()end)itSKIP("\115\104\111\117\108\100 \101\114\114\111\114 \119\104\101\110 \097\110 \105\110\115\116\097\110\099\101 \097\100\100\101\100 \119\105\116\104 \116\104\101 \116\097\103 \100\111\101\115\110\'\116 \099\104\101\099\107",function() end)end)describe("\111\110\082\101\109\111\118\101\100\040\041",function()it("\115\104\111\117\108\100 \114\117\110 \119\104\101\110 \116\104\101 \116\097\103 \105\115 \114\101\109\111\118\101\100 \102\114\111\109 \097\110 \105\110\115\116\097\110\099\101",function()local q=Instance.new("\077\111\100\101\108")local O=e.new("\070\111\111")local N=false q.Parent=game O:add(q)local g=O:onRemoved(function(O)N=q==O end)O:remove(q);(expect(N)).to.equal(true)g:Disconnect()q:Destroy()end)it("\115\104\111\117\108\100 \114\117\110 \119\104\101\110 \097\110 \105\110\115\116\097\110\099\101 \105\115 \114\101\109\111\118\101\100",function()local q=Instance.new("\077\111\100\101\108")local O=e.new("\070\111\111")local N=false O:add(q)q.Parent=game local g=O:onRemoved(function(O)N=q==O end)q:Destroy();(expect(N)).to.equal(true)g:Disconnect()end)end)end
return function()
	local CollectionService = game:GetService("CollectionService")

	local t = require(script.Parent.Parent.t)
	local Tag = require(script.Parent)

	describe("new()", function()
		it("should create a new Tag instance", function()
			local tag = Tag.new("Foo")

			expect(tag).to.be.a("table")
			expect(tag.name).to.equal("Foo")
		end)

		it("should return the name when coercing into a string", function()
			local tag = Tag.new("Foo")

			expect(tostring(tag)).to.equal("Foo")
		end)

		-- Skipping this idea for the time being. Nesting tags in this way seems
		-- like an anti-pattern at first glance,

		-- it("should allow nesting tags inside of it", function()
		-- 	local tag = Tag.new("Foo", {
		-- 		Bar = Tag.new("Foo:Bar"),
		-- 		Baz = Tag.new("Foo:Baz")
		-- 	})
		--
		-- 	expect(tag.Bar).to.be.ok()
		-- 	expect(tag.Baz).to.be.ok()
		-- end)
	end)

	describe("add()", function()
		it("should add the tag to an instance", function()
			local instance = Instance.new("Model")

			local tag = Tag.new("Foo")
			tag:add(instance)

			expect(CollectionService:HasTag(instance, "Foo")).to.equal(true)
		end)

		it("should error when given an incorrect instance", function()
			local tag = Tag.new("Foo", t.instanceOf("Model"))
			local part = Instance.new("Part")

			expect(function()
				tag:add(part)
			end).to.throw()
		end)
	end)

	describe("remove()", function()
		it("should remove the tag from an instance", function()
			local instance = Instance.new("Model")

			local tag = Tag.new("Foo")
			tag:add(instance)

			expect(CollectionService:HasTag(instance, "Foo")).to.equal(true)

			tag:remove(instance)

			expect(CollectionService:HasTag(instance, "Foo")).to.equal(false)
		end)
	end)

	describe("toggle()", function()
		it("should add the tag if it's not already applied", function()
			local instance = Instance.new("Model")
			local tag = Tag.new("Foo")

			expect(tag:has(instance)).to.equal(false)

			tag:toggle(instance)

			expect(tag:has(instance)).to.equal(true)
		end)

		it("should remove the tag if it's already applied", function()
			local instance = Instance.new("Model")
			local tag = Tag.new("Foo")

			tag:add(instance)

			expect(tag:has(instance)).to.equal(true)

			tag:toggle(instance)

			expect(tag:has(instance)).to.equal(false)
		end)

		it("should error when given an incorrect instance", function()
			local tag = Tag.new("Foo", t.instanceOf("Model"))
			local part = Instance.new("Part")

			expect(function()
				tag:toggle(part)
			end).to.throw()
		end)
	end)

	describe("has()", function()
		it("should return true if the tag is applied", function()
			local instance = Instance.new("Model")

			local tag = Tag.new("Foo")
			tag:add(instance)

			expect(tag:has(instance)).to.equal(true)
		end)

		it("should return false if the tag is not applied", function()
			local instance = Instance.new("Model")

			local tag = Tag.new("Foo")

			expect(tag:has(instance)).to.equal(false)
		end)
	end)

	describe("getTagged()", function()
		it("should get all the instances the tag is applied to", function()
			local tag = Tag.new("Foo")
			local instances = {}

			for _ = 1, 3 do
				local instance = Instance.new("Model")
				tag:add(instance)

				-- Need to parent to the DataModel for GetTagged to pick them up.
				instance.Parent = game

				table.insert(instances, instance)
			end

			expect(#tag:getTagged()).to.equal(3)

			for _, instance in ipairs(instances) do
				instance:Destroy()
			end
		end)
	end)

	describe("onAdded()", function()
		it("should run the callback on all existing tagged instances", function()
			local tag = Tag.new("Foo")
			local instance = Instance.new("Model")
			local wasAdded = false

			instance.Parent = game

			-- Add tag before listening
			tag:add(instance)

			local connection = tag:onAdded(function(other)
				wasAdded = instance == other
			end)

			expect(wasAdded).to.equal(true)

			connection:Disconnect()
			instance:Destroy()
		end)

		it("should run when an instance is tagged", function()
			local instance = Instance.new("Model")
			local tag = Tag.new("Foo")
			local wasAdded = false

			instance.Parent = game

			local connection = tag:onAdded(function(other)
				wasAdded = instance == other
			end)

			-- Add tag after listening
			tag:add(instance)

			expect(wasAdded).to.equal(true)

			connection:Disconnect()
			instance:Destroy()
		end)

		it("should error when an existing instance with the tag doesn't check", function()
			local tag = Tag.new("Foo", t.instanceOf("Model"))

			local part = Instance.new("Part")
			CollectionService:AddTag(part, "Foo")

			part.Parent = game

			expect(function()
				tag:onAdded(function() end)
			end).to.throw()

			part:Destroy()
		end)

		-- This is hard to test because we're dealing with an event. It would
		-- require mocking out _onAddedSignal so we could examine it, which is
		-- quite a bit of effort. For now we're relying on manual testing to
		-- make sure it works.
		itSKIP("should error when an instance added with the tag doesn't check", function() end)
	end)

	describe("onRemoved()", function()
		it("should run when the tag is removed from an instance", function()
			local instance = Instance.new("Model")
			local tag = Tag.new("Foo")
			local wasRemoved = false

			instance.Parent = game
			tag:add(instance)

			local connection = tag:onRemoved(function(other)
				wasRemoved = instance == other
			end)

			tag:remove(instance)

			expect(wasRemoved).to.equal(true)

			connection:Disconnect()
			instance:Destroy()
		end)

		it("should run when an instance is removed", function()
			local instance = Instance.new("Model")
			local tag = Tag.new("Foo")
			local wasRemoved = false

			tag:add(instance)

			instance.Parent = game

			local connection = tag:onRemoved(function(other)
				wasRemoved = instance == other
			end)

			instance:Destroy()

			expect(wasRemoved).to.equal(true)

			connection:Disconnect()
		end)
	end)
end
