InGameAvatarEditor/src/ServerScriptService/AvatarEditorInGameSetup/AvatarEditorInGame/Modules/Packages/Rodux/Store.spec.lua
return function()
	local Store = require(script.Parent.Store)

	describe("new", function()
		it("should instantiate with a reducer", function()
			local store = Store.new(function(state, action)
				return "hello, world"
			end)

			expect(store).to.be.ok()
			expect(store:getState()).to.equal("hello, world")

			store:destruct()
		end)

		it("should instantiate with a reducer and an initial state", function()
			local store = Store.new(function(state, action)
				return state
			end, "initial state")

			expect(store).to.be.ok()
			expect(store:getState()).to.equal("initial state")

			store:destruct()
		end)

		it("should instantiate with a reducer, initial state, and middlewares", function()
			local store = Store.new(function(state, action)
				return state
			end, "initial state", {})

			expect(store).to.be.ok()
			expect(store:getState()).to.equal("initial state")

			store:destruct()
		end)

		it("should modify the dispatch method when middlewares are passed", function()
			local middlewareInstantiateCount = 0
			local middlewareInvokeCount = 0
			local passedDispatch
			local passedStore
			local passedAction

			local function reducer(state, action)
				if action.type == "test" then
					return "test state"
				end

				return state
			end

			local function testMiddleware(nextDispatch, store)
				middlewareInstantiateCount = middlewareInstantiateCount + 1
				passedDispatch = nextDispatch
				passedStore = store

				return function(action)
					middlewareInvokeCount = middlewareInvokeCount + 1
					passedAction = action

					nextDispatch(action)
				end
			end

			local store = Store.new(reducer, "initial state", { testMiddleware })

			expect(middlewareInstantiateCount).to.equal(1)
			expect(middlewareInvokeCount).to.equal(0)
			expect(passedDispatch).to.be.a("function")
			expect(passedStore).to.equal(store)

			store:dispatch({
				type = "test",
			})

			expect(middlewareInstantiateCount).to.equal(1)
			expect(middlewareInvokeCount).to.equal(1)
			expect(passedAction.type).to.equal("test")

			store:flush()

			expect(store:getState()).to.equal("test state")

			store:destruct()
		end)

		it("should execute middleware left-to-right", function()
			local events = {}

			local function reducer(state)
				return state
			end

			local function middlewareA(nextDispatch, store)
				table.insert(events, "instantiate a")
				return function(action)
					table.insert(events, "execute a")
					return nextDispatch(action)
				end
			end

			local function middlewareB(nextDispatch, store)
				table.insert(events, "instantiate b")
				return function(action)
					table.insert(events, "execute b")
					return nextDispatch(action)
				end
			end

			local store = Store.new(reducer, 5, { middlewareA, middlewareB })

			expect(#events).to.equal(2)
			expect(events[1]).to.equal("instantiate b")
			expect(events[2]).to.equal("instantiate a")

			store:dispatch({
				type = "test",
			})

			expect(#events).to.equal(4)
			expect(events[3]).to.equal("execute a")
			expect(events[4]).to.equal("execute b")
		end)

		it("should send an initial action with a 'type' field", function()
			local lastAction
			local callCount = 0

			local store = Store.new(function(state, action)
				lastAction = action
				callCount = callCount + 1

				return state
			end)

			expect(callCount).to.equal(1)
			expect(lastAction).to.be.a("table")
			expect(lastAction.type).to.be.ok()

			store:destruct()
		end)
	end)

	describe("getState", function()
		it("should get the current state", function()
			local store = Store.new(function(state, action)
				return "foo"
			end)

			local state = store:getState()

			expect(state).to.equal("foo")

			store:destruct()
		end)
	end)

	describe("dispatch", function()
		it("should be sent through the reducer", function()
			local store = Store.new(function(state, action)
				state = state or "foo"

				if action.type == "act" then
					return "bar"
				end

				return state
			end)

			expect(store).to.be.ok()
			expect(store:getState()).to.equal("foo")

			store:dispatch({
				type = "act",
			})

			store:flush()

			expect(store:getState()).to.equal("bar")

			store:destruct()
		end)

		it("should trigger the changed event after a flush", function()
			local store = Store.new(function(state, action)
				state = state or 0

				if action.type == "increment" then
					return state + 1
				end

				return state
			end)

			local callCount = 0

			store.changed:connect(function(state, oldState)
				expect(oldState).to.equal(0)
				expect(state).to.equal(1)

				callCount = callCount + 1
			end)

			store:dispatch({
				type = "increment",
			})

			store:flush()

			expect(callCount).to.equal(1)

			store:destruct()
		end)

		it("should handle actions dispatched within the changed event", function()
			local store = Store.new(function(state, action)
				state = state or {
					value = 0,
				}

				if action.type == "increment" then
					return {
						value = state.value + 1,
					}
				elseif action.type == "decrement" then
					return {
						value = state.value - 1,
					}
				end

				return state
			end)

			local changeCount = 0

			store.changed:connect(function(state, oldState)
				expect(state).never.to.equal(oldState)

				if state.value > 0 then
					store:dispatch({
						type = "decrement",
					})
				end

				changeCount = changeCount + 1
			end)

			store:dispatch({
				type = "increment",
			})
			store:flush()
			store:flush()

			expect(changeCount).to.equal(2)

			store:destruct()
		end)

		it("should prevent yielding from changed handler", function()
			local preCount = 0
			local postCount = 0

			local store = Store.new(function(state, action)
				state = state or 0
				return state + 1
			end)

			store.changed:connect(function(state, oldState)
				preCount = preCount + 1
				wait()
				postCount = postCount + 1
			end)

			store:dispatch({
				type = "increment",
			})

			expect(function()
				store:flush()
			end).to.throw()

			expect(preCount).to.equal(1)
			expect(postCount).to.equal(0)

			store:destruct()
		end)

		it("should throw if an action is dispatched without a type field", function()
			local store = Store.new(function(state, action)
				return state
			end)

			expect(function()
				store:dispatch({})
			end).to.throw()

			store:destruct()
		end)

		it("should throw if the action is not a function or table", function()
			local store = Store.new(function(state, action)
				return state
			end)

			expect(function()
				store:dispatch(1)
			end).to.throw()

			store:destruct()
		end)
	end)

	describe("flush", function()
		it("should not fire a changed event if there were no dispatches", function()
			local store = Store.new(function()
			end)

			local count = 0
			store.changed:connect(function()
				count = count + 1
			end)

			store:flush()

			expect(count).to.equal(0)

			store:dispatch({
				type = "increment",
			})
			store:flush()

			expect(count).to.equal(1)

			store:flush()

			expect(count).to.equal(1)

			store:destruct()
		end)
	end)
end
