return function()local m=require(script.Parent.MockRemoteFunction)describe("\077\111\099\107\082\101\109\111\116\101\070\117\110\099\116\105\111\110\046\110\101\119",function()it("\115\104\111\117\108\100 \099\114\101\097\116\101 \097 \110\101\119 \077\111\099\107\082\101\109\111\116\101\070\117\110\099\116\105\111\110",function()local q=m.new();(expect(q)).to.be.a("\116\097\098\108\101");(expect(getmetatable(q))).to.equal(m)q:destroy()end)it("\115\104\111\117\108\100 \099\114\101\097\116\101 \097 \110\101\119 \077\111\099\107\082\101\109\111\116\101\070\117\110\099\116\105\111\110 \119\105\116\104 \097 \099\108\105\101\110\116 \105\100 \105\102 \112\097\115\115\101\100",function()local q=m.new("\117\115\101\114");(expect(q:getClient())).to.equal("\117\115\101\114")q:destroy()end)it("\115\104\111\117\108\100 \104\097\118\101 \097 \114\101\097\100\097\098\108\101 \079\110\067\108\105\101\110\116\073\110\118\111\107\101 \112\114\111\112\101\114\116\121",function()local q=m.new("\117\115\101\114");(expect(q.OnClientInvoke)).to.equal(nil)local function x() end q.OnClientInvoke=x;(expect(q.OnClientInvoke)).to.equal(x)q:destroy()end)it("\115\104\111\117\108\100 \104\097\118\101 \097 \114\101\097\100\097\098\108\101 \079\110\083\101\114\118\101\114\073\110\118\111\107\101 \112\114\111\112\101\114\116\121",function()local q=m.new("\117\115\101\114");(expect(q.OnServerInvoke)).to.equal(nil)local function x() end q.OnServerInvoke=x;(expect(q.OnServerInvoke)).to.equal(x)q:destroy()end)end)describe("\077\111\099\107\082\101\109\111\116\101\070\117\110\099\116\105\111\110\058\105\110\118\111\107\101\083\101\114\118\101\114",function()it("\115\104\111\117\108\100 \114\101\116\117\114\110 \097 \114\101\115\112\111\110\115\101 \102\114\111\109 \116\104\101 \114\101\113\117\101\115\116 \105\102 \097 \104\097\110\100\108\101\114 \101\120\105\115\116\115",function()local q=m.new("\117\115\101\114")q.OnServerInvoke=function(m,q)return q*2 end;(expect(q:invokeServer(1))).to.equal(2)q:destroy()end)it("\115\104\111\117\108\100 \113\117\101\117\101 \116\104\101 \114\101\113\117\101\115\116 \105\102 \097 \104\097\110\100\108\101\114 \100\111\101\115\110\'\116 \101\120\105\115\116 \097\110\100 \114\101\115\112\111\110\100 \111\110\099\101 \105\116 \100\111\101\115",function()local q=m.new("\117\115\101\114")local x=0 task.spawn(function()local m=q:invokeServer(1)x+=m end)task.spawn(function()local m=q:invokeServer(2)x+=m end)q.OnServerInvoke=function(m,q)return q*2 end;(expect(x)).to.equal(6)q:destroy()end)it("\115\104\111\117\108\100 \116\104\114\111\119 \105\102 \116\097\098\108\101\115 \119\105\116\104 \099\121\099\108\105\099 \118\097\108\117\101\115 \097\114\101 \112\097\115\115\101\100",function()local q=m.new("\117\115\101\114")local x={a=1}x.b=x;(expect(function()q:invokeServer(x)end)).to.throw()q:destroy()end)it("\115\104\111\117\108\100 \112\097\115\115 \100\101\101\112 \099\111\112\105\101\115 \111\102 \100\097\116\097 \097\110\100 \099\111\110\118\101\114\116 \105\110\115\116\097\110\099\101 \097\110\100 \116\097\098\108\101 \107\101\121\115 \116\111 \115\116\114\105\110\103\115",function()local q=m.new("\117\115\101\114")local x=Instance.new("\080\097\114\116")local L={ok=1}local E={a={[x]=1},b={key=2};[L]=3}task.spawn(function()q:invokeServer(E,E,x)end)local A,b,i q.OnServerInvoke=function(m,...)A,b,i=...end;(expect(A)).to.never.equal(E);(expect(b)).to.never.equal(E);(expect(A)).to.never.equal(b);(expect(A.a[x])).to.never.be.ok();(expect(A.a["\060\073\110\115\116\097\110\099\101\062 \040\080\097\114\116\041"])).to.equal(1);(expect(b.a[x])).to.never.be.ok();(expect(b.a["\060\073\110\115\116\097\110\099\101\062 \040\080\097\114\116\041"])).to.equal(1);(expect(A[L])).to.never.be.ok();(expect(A["\060\084\097\098\108\101\062 \040"..(tostring(L).."\041")])).to.equal(3);(expect(b[L])).to.never.be.ok();(expect(b["\060\084\097\098\108\101\062 \040"..(tostring(L).."\041")])).to.equal(3);(expect(i)).to.equal(x)x:Destroy()q:destroy()end)end)describe("\077\111\099\107\082\101\109\111\116\101\070\117\110\099\116\105\111\110\058\105\110\118\111\107\101\067\108\105\101\110\116",function()it("\115\104\111\117\108\100 \114\101\116\117\114\110 \097 \114\101\115\112\111\110\115\101 \102\114\111\109 \116\104\101 \114\101\113\117\101\115\116 \105\102 \097 \104\097\110\100\108\101\114 \101\120\105\115\116\115",function()local q=m.new("\117\115\101\114")q.OnClientInvoke=function(m)return m*2 end;(expect(q:invokeClient("\117\115\101\114",1))).to.equal(2)q:destroy()end)it("\115\104\111\117\108\100 \113\117\101\117\101 \116\104\101 \114\101\113\117\101\115\116 \105\102 \097 \104\097\110\100\108\101\114 \100\111\101\115\110\'\116 \101\120\105\115\116 \097\110\100 \114\101\115\112\111\110\100 \111\110\099\101 \105\116 \100\111\101\115",function()local q=m.new("\117\115\101\114")local x=0 task.spawn(function()local m=q:invokeClient("\117\115\101\114",1)x+=m end)task.spawn(function()local m=q:invokeClient("\117\115\101\114",2)x+=m end)q.OnClientInvoke=function(m)return m*2 end;(expect(x)).to.equal(6)q:destroy()end)it("\115\104\111\117\108\100 \116\104\114\111\119 \105\102 \116\104\101 \105\110\105\116\105\097\108 \099\108\105\101\110\116 \097\114\103 \105\115 \105\110\099\111\114\114\101\099\116",function()local q=m.new("\117\115\101\114");(expect(function()q:invokeClient("\112\108\097\121\101\114",1)end)).to.throw()q:destroy()end)it("\115\104\111\117\108\100 \116\104\114\111\119 \105\102 \116\097\098\108\101\115 \119\105\116\104 \099\121\099\108\105\099 \118\097\108\117\101\115 \097\114\101 \112\097\115\115\101\100",function()local q=m.new("\117\115\101\114")local x={a=1}x.b=x;(expect(function()q:invokeClient("\117\115\101\114",x)end)).to.throw()q:destroy()end)it("\115\104\111\117\108\100 \112\097\115\115 \100\101\101\112 \099\111\112\105\101\115 \111\102 \100\097\116\097 \097\110\100 \099\111\110\118\101\114\116 \105\110\115\116\097\110\099\101 \097\110\100 \116\097\098\108\101 \107\101\121\115 \116\111 \115\116\114\105\110\103\115",function()local q=m.new("\117\115\101\114")local x=Instance.new("\080\097\114\116")local L={ok=1}local E={a={[x]=1},b={key=2};[L]=3}task.spawn(function()q:invokeClient("\117\115\101\114",E,E,x)end)local A,b,i q.OnClientInvoke=function(...)A,b,i=...end;(expect(A)).to.never.equal(E);(expect(b)).to.never.equal(E);(expect(A)).to.never.equal(b);(expect(A.a[x])).to.never.be.ok();(expect(A.a["\060\073\110\115\116\097\110\099\101\062 \040\080\097\114\116\041"])).to.equal(1);(expect(b.a[x])).to.never.be.ok();(expect(b.a["\060\073\110\115\116\097\110\099\101\062 \040\080\097\114\116\041"])).to.equal(1);(expect(A[L])).to.never.be.ok();(expect(A["\060\084\097\098\108\101\062 \040"..(tostring(L).."\041")])).to.equal(3);(expect(b[L])).to.never.be.ok();(expect(b["\060\084\097\098\108\101\062 \040"..(tostring(L).."\041")])).to.equal(3);(expect(i)).to.equal(x)x:Destroy()q:destroy()end)end)describe("\077\111\099\107\082\101\109\111\116\101\070\117\110\099\116\105\111\110\058\103\101\116\067\108\105\101\110\116",function()it("\115\104\111\117\108\100 \114\101\116\117\114\110 \099\108\105\101\110\116 \112\097\115\115\101\100 \105\110 \099\111\110\115\116\114\117\099\116\111\114",function()local q=m.new("\117\115\101\114");(expect(q:getClient())).to.equal("\117\115\101\114")q:destroy()end)end)describe("\077\111\099\107\082\101\109\111\116\101\070\117\110\099\116\105\111\110\058\100\101\115\116\114\111\121",function()it("\115\104\111\117\108\100 \115\101\116 \100\101\115\116\114\111\121\101\100 \102\105\101\108\100 \116\111 \116\114\117\101",function()local q=m.new("\117\115\101\114")q:destroy();(expect(q.destroyed)).to.equal(true)end)end)end
return function()
    local MockRemoteFunction = require(script.Parent.MockRemoteFunction)

    describe("MockRemoteFunction.new", function()
        it("should create a new MockRemoteFunction", function()
            local mockRemoteFunction = MockRemoteFunction.new()

            expect(mockRemoteFunction).to.be.a("table")
            expect(getmetatable(mockRemoteFunction)).to.equal(MockRemoteFunction)

            mockRemoteFunction:destroy()
        end)

        it("should create a new MockRemoteFunction with a client id if passed", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")

            expect(mockRemoteFunction:getClient()).to.equal("user")

            mockRemoteFunction:destroy()

        end)

        it("should have a readable OnClientInvoke property", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")

            expect(mockRemoteFunction.OnClientInvoke).to.equal(nil)

            local function onClientInvoke()
                
            end

            mockRemoteFunction.OnClientInvoke = onClientInvoke
            expect(mockRemoteFunction.OnClientInvoke).to.equal(onClientInvoke)

            mockRemoteFunction:destroy()
        end)

        it("should have a readable OnServerInvoke property", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")

            expect(mockRemoteFunction.OnServerInvoke).to.equal(nil)

            local function onServerInvoke()
                
            end

            mockRemoteFunction.OnServerInvoke = onServerInvoke
            expect(mockRemoteFunction.OnServerInvoke).to.equal(onServerInvoke)

            mockRemoteFunction:destroy()
        end)
    end)

    describe("MockRemoteFunction:invokeServer", function()
        it("should return a response from the request if a handler exists", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")

            mockRemoteFunction.OnServerInvoke = function(_, value)
                return value * 2
            end

            expect(mockRemoteFunction:invokeServer(1)).to.equal(2)

            mockRemoteFunction:destroy()
        end)

        it("should queue the request if a handler doesn't exist and respond once it does", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")
            local count = 0

            --[[
                ran into a bug where
                doing count += mockRemoteFunction:invokeServer(...) would make tests fail,
                this is because it's adding the result of the invoke to the value of count before it finishes
                (fixed now)
            ]]

            task.spawn(function()
                local new = mockRemoteFunction:invokeServer(1)
                count += new
            end)

            task.spawn(function()
                local new = mockRemoteFunction:invokeServer(2)
                count += new
            end)

            mockRemoteFunction.OnServerInvoke = function(_, value)
                return value * 2
            end

            expect(count).to.equal(6)

            mockRemoteFunction:destroy()
        end)

        it("should throw if tables with cyclic values are passed", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")

            local data = {
                a = 1,
            }

            data.b = data

            expect(function()
                mockRemoteFunction:invokeServer(data)
            end).to.throw()

            mockRemoteFunction:destroy()
        end)

        it("should pass deep copies of data and convert instance and table keys to strings", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")
            local part = Instance.new("Part")

            local tab = {ok = 1}

            local data = {
                a = {[part] = 1},
                b = {key = 2},
                [tab] = 3,
            }

            task.spawn(function()
                mockRemoteFunction:invokeServer(data, data, part)
            end)

            local data1, data2, passedPart

            mockRemoteFunction.OnServerInvoke = function(_, ...)
                data1, data2, passedPart = ...
            end

            expect(data1).to.never.equal(data)
            expect(data2).to.never.equal(data)

            expect(data1).to.never.equal(data2)

            expect(data1.a[part]).to.never.be.ok()
            expect(data1.a["<Instance> (Part)"]).to.equal(1)

            expect(data2.a[part]).to.never.be.ok()
            expect(data2.a["<Instance> (Part)"]).to.equal(1)

            expect(data1[tab]).to.never.be.ok()
            expect(data1["<Table> ("..tostring(tab)..")"]).to.equal(3)

            expect(data2[tab]).to.never.be.ok()
            expect(data2["<Table> ("..tostring(tab)..")"]).to.equal(3)

            expect(passedPart).to.equal(part)

            part:Destroy()
            mockRemoteFunction:destroy()
        end)

    end)

    describe("MockRemoteFunction:invokeClient", function()
        it("should return a response from the request if a handler exists", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")
            
            mockRemoteFunction.OnClientInvoke = function(value)
                return value * 2
            end

            expect(mockRemoteFunction:invokeClient("user", 1)).to.equal(2)

            mockRemoteFunction:destroy()
        end)

        it("should queue the request if a handler doesn't exist and respond once it does", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")
            local count = 0

            task.spawn(function()
                local new = mockRemoteFunction:invokeClient("user", 1)
                count += new
            end)

            task.spawn(function()
                local new = mockRemoteFunction:invokeClient("user", 2)
                count += new
            end)

            mockRemoteFunction.OnClientInvoke = function(value)
                return value * 2
            end

            expect(count).to.equal(6)

            mockRemoteFunction:destroy()
        end)

        it("should throw if the initial client arg is incorrect", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")

            expect(function()
                mockRemoteFunction:invokeClient("player", 1)
            end).to.throw()

            mockRemoteFunction:destroy()
        end)

        it("should throw if tables with cyclic values are passed", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")

            local data = {
                a = 1,
            }

            data.b = data

            expect(function()
                mockRemoteFunction:invokeClient("user", data)
            end).to.throw()

            mockRemoteFunction:destroy()
        end)

        it("should pass deep copies of data and convert instance and table keys to strings", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")
            local part = Instance.new("Part")

            local tab = {ok = 1}

            local data = {
                a = {[part] = 1},
                b = {key = 2},
                [tab] = 3,
            }

            task.spawn(function()
                mockRemoteFunction:invokeClient("user", data, data, part)
            end)

            local data1, data2, passedPart

            mockRemoteFunction.OnClientInvoke = function(...)
                data1, data2, passedPart = ...
            end

            expect(data1).to.never.equal(data)
            expect(data2).to.never.equal(data)

            expect(data1).to.never.equal(data2)

            expect(data1.a[part]).to.never.be.ok()
            expect(data1.a["<Instance> (Part)"]).to.equal(1)

            expect(data2.a[part]).to.never.be.ok()
            expect(data2.a["<Instance> (Part)"]).to.equal(1)

            expect(data1[tab]).to.never.be.ok()
            expect(data1["<Table> ("..tostring(tab)..")"]).to.equal(3)

            expect(data2[tab]).to.never.be.ok()
            expect(data2["<Table> ("..tostring(tab)..")"]).to.equal(3)

            expect(passedPart).to.equal(part)

            part:Destroy()
            mockRemoteFunction:destroy()
        end)
    end)

    describe("MockRemoteFunction:getClient", function()
        it("should return client passed in constructor", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")

            expect(mockRemoteFunction:getClient()).to.equal("user")

            mockRemoteFunction:destroy()
        end)
    end)

    describe("MockRemoteFunction:destroy", function()
        it("should set destroyed field to true", function()
            local mockRemoteFunction = MockRemoteFunction.new("user")

            mockRemoteFunction:destroy()

            expect(mockRemoteFunction.destroyed).to.equal(true)
        end)
    end)
end