return function()local o=require(script.Parent.assertDeepEqual)local h=require(script.Parent.createSpy)local c=require(script.Parent.Logging)local C=require(script.Parent.SingleEventManager)describe("\110\101\119",function()it("\115\104\111\117\108\100 \099\114\101\097\116\101 \097 \083\105\110\103\108\101\069\118\101\110\116\077\097\110\097\103\101\114",function()local o=C.new();(expect(o)).to.be.ok()end)end)describe("\099\111\110\110\101\099\116\069\118\101\110\116",function()it("\115\104\111\117\108\100 \099\111\110\110\101\099\116 \116\111 \101\118\101\110\116\115",function()local o=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local c=C.new(o)local W=h()c:connectEvent("\069\118\101\110\116",W.value)c:resume()o:Fire("\102\111\111");(expect(W.callCount)).to.equal(1)W:assertCalledWith(o,"\102\111\111")o:Fire("\098\097\114");(expect(W.callCount)).to.equal(2)W:assertCalledWith(o,"\098\097\114")c:connectEvent("\069\118\101\110\116",nil)o:Fire("\098\097\122");(expect(W.callCount)).to.equal(2)end)it("\115\104\111\117\108\100 \100\114\111\112 \101\118\101\110\116\115 \117\110\116\105\108 \114\101\115\117\109\101\100 \105\110\105\116\105\097\108\108\121",function()local o=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local c=C.new(o)local W=h()c:connectEvent("\069\118\101\110\116",W.value)o:Fire("\102\111\111");(expect(W.callCount)).to.equal(0)c:resume()o:Fire("\098\097\114");(expect(W.callCount)).to.equal(1)W:assertCalledWith(o,"\098\097\114")end)it("\115\104\111\117\108\100 \105\110\118\111\107\101 \115\117\115\112\101\110\100\101\100 \101\118\101\110\116\115 \119\104\101\110 \114\101\115\117\109\101\100",function()local o=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local c=C.new(o)local W=h()c:connectEvent("\069\118\101\110\116",W.value)c:resume()o:Fire("\102\111\111");(expect(W.callCount)).to.equal(1)W:assertCalledWith(o,"\102\111\111")c:suspend()o:Fire("\098\097\114");(expect(W.callCount)).to.equal(1)c:resume();(expect(W.callCount)).to.equal(2)W:assertCalledWith(o,"\098\097\114")end)it("\115\104\111\117\108\100 \105\110\118\111\107\101 \101\118\101\110\116\115 \116\114\105\103\103\101\114\101\100 \100\117\114\105\110\103 \114\101\115\117\109\112\116\105\111\110 \105\110 \116\104\101 \099\111\114\114\101\099\116 \111\114\100\101\114",function()local c=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local W=C.new(c)local Z={}local v=h(function(o,h)table.insert(Z,h)if h==2 then c:Fire(3)elseif h==3 then c:Fire(4)end end)W:connectEvent("\069\118\101\110\116",v.value)W:suspend()c:Fire(1)c:Fire(2)W:resume();(expect(v.callCount)).to.equal(4)o(Z,{1;2;3;4})end)it("\115\104\111\117\108\100 \110\111\116 \105\110\118\111\107\101 \101\118\101\110\116\115 \102\105\114\101\100 \100\117\114\105\110\103 \115\117\115\112\101\110\115\105\111\110 \098\117\116 \100\105\115\099\111\110\110\101\099\116\101\100 \098\101\102\111\114\101 \114\101\115\117\109\112\116\105\111\110",function()local o=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local c=C.new(o)local W=h()c:connectEvent("\069\118\101\110\116",W.value)c:suspend()o:Fire(1)c:connectEvent("\069\118\101\110\116",nil)c:resume();(expect(W.callCount)).to.equal(0)end)it("\115\104\111\117\108\100 \110\111\116 \121\105\101\108\100 \101\118\101\110\116\115 \116\104\114\111\117\103\104 \116\104\101 \083\105\110\103\108\101\069\118\101\110\116\077\097\110\097\103\101\114 \119\104\101\110 \114\101\115\117\109\105\110\103",function()local o=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local h=C.new(o)h:connectEvent("\069\118\101\110\116",function()coroutine.yield()end)h:resume()local c=coroutine.create(function()o:Fire(5)end)assert(coroutine.resume(c));(expect(coroutine.status(c))).to.equal("\100\101\097\100")h:suspend()o:Fire(5)c=coroutine.create(function()h:resume()end)assert(coroutine.resume(c));(expect(coroutine.status(c))).to.equal("\100\101\097\100")end)it("\115\104\111\117\108\100 \110\111\116 \116\104\114\111\119 \101\114\114\111\114\115 \116\104\114\111\117\103\104 \083\105\110\103\108\101\069\118\101\110\116\077\097\110\097\103\101\114 \119\104\101\110 \114\101\115\117\109\105\110\103",function()local o="\069\114\114\111\114 \102\114\111\109 \083\105\110\103\108\101\069\118\101\110\116\077\097\110\097\103\101\114 \116\101\115\116"local h=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local W=C.new(h)W:connectEvent("\069\118\101\110\116",function()error(o)end)W:resume()W:suspend()h:Fire(5)local Z=c.capture(function()W:resume()end);(expect(#Z.errors)).to.equal(0);(expect(#Z.warnings)).to.equal(1);(expect(#Z.infos)).to.equal(0);(expect(Z.warnings[1]:find(o))).to.be.ok()end)it("\115\104\111\117\108\100 \110\111\116 \111\118\101\114\102\108\111\119 \119\105\116\104 \101\118\101\110\116\115 \105\102 \109\097\110\097\103\101\114\058\114\101\115\117\109\101\040\041 \105\115 \105\110\118\111\107\101\100 \119\104\101\110 \114\101\115\117\109\105\110\103 \097 \115\117\115\112\101\110\100\101\100 \101\118\101\110\116",function()local o=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local c=C.new(o)local W=h(function(h,C)if C==1 then c:suspend()o:Fire(2)c:resume()end end)c:connectEvent("\069\118\101\110\116",W.value)c:suspend()o:Fire(1)c:resume();(expect(W.callCount)).to.equal(2)end)end)describe("\099\111\110\110\101\099\116\080\114\111\112\101\114\116\121\067\104\097\110\103\101",function()it("\115\104\111\117\108\100 \099\111\110\110\101\099\116 \116\111 \112\114\111\112\101\114\116\121 \099\104\097\110\103\101\115",function()local o=Instance.new("\070\111\108\100\101\114")local c=C.new(o)local W=h()c:connectPropertyChange("\078\097\109\101",W.value)c:resume()o.Name="\102\111\111";(expect(W.callCount)).to.equal(1)W:assertCalledWith(o)o.Name="\098\097\114";(expect(W.callCount)).to.equal(2)W:assertCalledWith(o)c:connectPropertyChange("\078\097\109\101")o.Name="\098\097\122";(expect(W.callCount)).to.equal(2)end)it("\115\104\111\117\108\100 \116\104\114\111\119 \097\110 \101\114\114\111\114 \105\102 \116\104\101 \112\114\111\112\101\114\116\121 \105\115 \105\110\118\097\108\105\100",function()local o=Instance.new("\070\111\108\100\101\114")local h=C.new(o);(expect(function()h:connectPropertyChange("\102\111\111",function() end)end)).to.throw()end)end)end
return function()
	local assertDeepEqual = require(script.Parent.assertDeepEqual)
	local createSpy = require(script.Parent.createSpy)
	local Logging = require(script.Parent.Logging)

	local SingleEventManager = require(script.Parent.SingleEventManager)

	describe("new", function()
		it("should create a SingleEventManager", function()
			local manager = SingleEventManager.new()

			expect(manager).to.be.ok()
		end)
	end)

	describe("connectEvent", function()
		it("should connect to events", function()
			local instance = Instance.new("BindableEvent")
			local manager = SingleEventManager.new(instance)
			local eventSpy = createSpy()

			manager:connectEvent("Event", eventSpy.value)
			manager:resume()

			instance:Fire("foo")
			expect(eventSpy.callCount).to.equal(1)
			eventSpy:assertCalledWith(instance, "foo")

			instance:Fire("bar")
			expect(eventSpy.callCount).to.equal(2)
			eventSpy:assertCalledWith(instance, "bar")

			manager:connectEvent("Event", nil)

			instance:Fire("baz")
			expect(eventSpy.callCount).to.equal(2)
		end)

		it("should drop events until resumed initially", function()
			local instance = Instance.new("BindableEvent")
			local manager = SingleEventManager.new(instance)
			local eventSpy = createSpy()

			manager:connectEvent("Event", eventSpy.value)

			instance:Fire("foo")
			expect(eventSpy.callCount).to.equal(0)

			manager:resume()

			instance:Fire("bar")
			expect(eventSpy.callCount).to.equal(1)
			eventSpy:assertCalledWith(instance, "bar")
		end)

		it("should invoke suspended events when resumed", function()
			local instance = Instance.new("BindableEvent")
			local manager = SingleEventManager.new(instance)
			local eventSpy = createSpy()

			manager:connectEvent("Event", eventSpy.value)
			manager:resume()

			instance:Fire("foo")
			expect(eventSpy.callCount).to.equal(1)
			eventSpy:assertCalledWith(instance, "foo")

			manager:suspend()

			instance:Fire("bar")
			expect(eventSpy.callCount).to.equal(1)

			manager:resume()
			expect(eventSpy.callCount).to.equal(2)
			eventSpy:assertCalledWith(instance, "bar")
		end)

		it("should invoke events triggered during resumption in the correct order", function()
			local instance = Instance.new("BindableEvent")
			local manager = SingleEventManager.new(instance)

			local recordedValues = {}
			local eventSpy = createSpy(function(_, value)
				table.insert(recordedValues, value)

				if value == 2 then
					instance:Fire(3)
				elseif value == 3 then
					instance:Fire(4)
				end
			end)

			manager:connectEvent("Event", eventSpy.value)
			manager:suspend()

			instance:Fire(1)
			instance:Fire(2)

			manager:resume()
			expect(eventSpy.callCount).to.equal(4)
			assertDeepEqual(recordedValues, { 1, 2, 3, 4 })
		end)

		it("should not invoke events fired during suspension but disconnected before resumption", function()
			local instance = Instance.new("BindableEvent")
			local manager = SingleEventManager.new(instance)
			local eventSpy = createSpy()

			manager:connectEvent("Event", eventSpy.value)
			manager:suspend()

			instance:Fire(1)

			manager:connectEvent("Event", nil)

			manager:resume()
			expect(eventSpy.callCount).to.equal(0)
		end)

		it("should not yield events through the SingleEventManager when resuming", function()
			local instance = Instance.new("BindableEvent")
			local manager = SingleEventManager.new(instance)

			manager:connectEvent("Event", function()
				coroutine.yield()
			end)

			manager:resume()

			local co = coroutine.create(function()
				instance:Fire(5)
			end)

			assert(coroutine.resume(co))
			expect(coroutine.status(co)).to.equal("dead")

			manager:suspend()
			instance:Fire(5)

			co = coroutine.create(function()
				manager:resume()
			end)

			assert(coroutine.resume(co))
			expect(coroutine.status(co)).to.equal("dead")
		end)

		it("should not throw errors through SingleEventManager when resuming", function()
			local errorText = "Error from SingleEventManager test"

			local instance = Instance.new("BindableEvent")
			local manager = SingleEventManager.new(instance)

			manager:connectEvent("Event", function()
				error(errorText)
			end)

			manager:resume()

			-- If we call instance:Fire() here, the error message will leak to
			-- the console since the thread's resumption will be handled by
			-- Roblox's scheduler.

			manager:suspend()
			instance:Fire(5)

			local logInfo = Logging.capture(function()
				manager:resume()
			end)

			expect(#logInfo.errors).to.equal(0)
			expect(#logInfo.warnings).to.equal(1)
			expect(#logInfo.infos).to.equal(0)

			expect(logInfo.warnings[1]:find(errorText)).to.be.ok()
		end)

		it("should not overflow with events if manager:resume() is invoked when resuming a suspended event", function()
			local instance = Instance.new("BindableEvent")
			local manager = SingleEventManager.new(instance)

			-- This connection emulates what happens if reconciliation is
			-- triggered again in response to reconciliation. Without
			-- appropriate guards, the inner resume() call will process the
			-- Fire(1) event again, causing a nasty stack overflow.
			local eventSpy = createSpy(function(_, value)
				if value == 1 then
					manager:suspend()
					instance:Fire(2)
					manager:resume()
				end
			end)

			manager:connectEvent("Event", eventSpy.value)

			manager:suspend()
			instance:Fire(1)
			manager:resume()

			expect(eventSpy.callCount).to.equal(2)
		end)
	end)

	describe("connectPropertyChange", function()
		-- Since property changes utilize the same mechanisms as other events,
		-- the tests here are slimmed down to reduce redundancy.

		it("should connect to property changes", function()
			local instance = Instance.new("Folder")
			local manager = SingleEventManager.new(instance)
			local eventSpy = createSpy()

			manager:connectPropertyChange("Name", eventSpy.value)
			manager:resume()

			instance.Name = "foo"
			expect(eventSpy.callCount).to.equal(1)
			eventSpy:assertCalledWith(instance)

			instance.Name = "bar"
			expect(eventSpy.callCount).to.equal(2)
			eventSpy:assertCalledWith(instance)

			manager:connectPropertyChange("Name")

			instance.Name = "baz"
			expect(eventSpy.callCount).to.equal(2)
		end)

		it("should throw an error if the property is invalid", function()
			local instance = Instance.new("Folder")
			local manager = SingleEventManager.new(instance)

			expect(function()
				manager:connectPropertyChange("foo", function() end)
			end).to.throw()
		end)
	end)
end
