add gs
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["roblox_cryo"]["cryo"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["freeze"]["freeze"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["lua-promise"]["lua-promise"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["roblox_rodux"]["rodux"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
@@ -0,0 +1,12 @@
|
||||
# Generated by Rotriever. Format subject to change in future releases.
|
||||
name = "rodux-networking"
|
||||
version = "1.0.0"
|
||||
commit = "ea19cfe3c71e5747fa3ab4235beed4fc21d63e4d"
|
||||
source = "git+https://github.rbx.com/roblox/rodux-networking#v1.0.1"
|
||||
dependencies = [
|
||||
"Cryo roblox/cryo 1.0.0 url+https://github.com/roblox/cryo",
|
||||
"Freeze freeze ef89f9d0 git+https://github.rbx.com/roblox/freeze#master",
|
||||
"Promise lua-promise bbb9e162 git+https://github.rbx.com/roblox/lua-promise#master",
|
||||
"Rodux roblox/rodux 1.0.0 url+https://github.com/roblox/rodux",
|
||||
"tutils tutils 0be577db git+https://github.rbx.com/roblox/tutils#master",
|
||||
]
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
--[[
|
||||
A helper function to define a Rodux action creator with an associated name.
|
||||
|
||||
Normally when creating a Rodux action, you can just create a function:
|
||||
|
||||
return function(value)
|
||||
return {
|
||||
type = "MyAction",
|
||||
value = value,
|
||||
}
|
||||
end
|
||||
|
||||
And then when you check for it in your reducer, you either use a constant,
|
||||
or type out the string name:
|
||||
|
||||
if action.type == "MyAction" then
|
||||
-- change some state
|
||||
end
|
||||
|
||||
Typos here are a remarkably common bug. We also have the issue that there's
|
||||
no link between reducers and the actions that they respond to!
|
||||
|
||||
`Action` (this helper) provides a utility that makes this a bit cleaner.
|
||||
|
||||
Instead, define your Rodux action like this:
|
||||
|
||||
return Action("MyAction", function(value)
|
||||
return {
|
||||
value = value,
|
||||
}
|
||||
end)
|
||||
|
||||
We no longer need to add the `type` field manually.
|
||||
|
||||
Additionally, the returned action creator now has a 'name' property that can
|
||||
be checked by your reducer:
|
||||
|
||||
local MyAction = require(Reducers.MyAction)
|
||||
|
||||
...
|
||||
|
||||
if action.type == MyAction.name then
|
||||
-- change some state!
|
||||
end
|
||||
|
||||
Now we have a clear link between our reducers and the actions they use, and
|
||||
if we ever typo a name, we'll get a warning in LuaCheck as well as an error
|
||||
at runtime!
|
||||
]]
|
||||
|
||||
return function(name, fn)
|
||||
assert(type(name) == "string", "A name must be provided to create an Action")
|
||||
assert(type(fn) == "function", "A function must be provided to create an Action")
|
||||
|
||||
return setmetatable({
|
||||
name = name,
|
||||
}, {
|
||||
__call = function(self, ...)
|
||||
local result = fn(...)
|
||||
|
||||
assert(type(result) == "table", "An action must return a table")
|
||||
|
||||
result.type = name
|
||||
|
||||
return result
|
||||
end
|
||||
})
|
||||
end
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local root = script.Parent
|
||||
local Packages = root.Parent
|
||||
|
||||
return require(Packages.Cryo)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local root = script.Parent
|
||||
local Packages = root.Parent
|
||||
|
||||
return require(Packages.Freeze)
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
local root = script.Parent
|
||||
local makeRequestApi = require(root.makeRequestApi)
|
||||
|
||||
return function(options)
|
||||
return makeRequestApi(options, "GET")
|
||||
end
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
return function()
|
||||
local root = script.Parent
|
||||
local mockNetworkImpl = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
local GET = require(root.GET)({
|
||||
keyPath = "hello.world",
|
||||
networkImpl = mockNetworkImpl,
|
||||
})
|
||||
|
||||
describe("WHEN invoked", function()
|
||||
local endpoint = GET(script, function(requestBuilder)
|
||||
return requestBuilder("example.com")
|
||||
end)
|
||||
|
||||
it("SHOULD return an object have all expected fields", function()
|
||||
expect(endpoint.API).to.be.ok()
|
||||
expect(endpoint.getStatus).to.be.ok()
|
||||
expect(endpoint.Succeeded).to.be.ok()
|
||||
expect(endpoint.Failed).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
local EnumNetworkStatus = {}
|
||||
|
||||
local EnumValues = {
|
||||
NotStarted = "NotStarted",
|
||||
Fetching = "Fetching",
|
||||
Done = "Done",
|
||||
Failed = "Failed",
|
||||
}
|
||||
|
||||
setmetatable(EnumNetworkStatus,
|
||||
{
|
||||
__newindex = function(t, key, index)
|
||||
end,
|
||||
__index = function(t, index)
|
||||
assert(EnumValues[index] ~= nil, ("EnumNetworkStatus has no value: " .. tostring(index)))
|
||||
return EnumValues[index]
|
||||
end
|
||||
})
|
||||
|
||||
return EnumNetworkStatus
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local root = script.Parent.Parent
|
||||
local Packages = root.Parent
|
||||
|
||||
return require(Packages.Freeze)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local root = script.Parent.Parent
|
||||
local Packages = root.Parent
|
||||
|
||||
return require(Packages.Promise)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local root = script.Parent.Parent
|
||||
local Packages = root.Parent
|
||||
|
||||
return require(Packages.Rodux)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
return function(options)
|
||||
return "networkStatus:" .. tostring(options.keyPath)
|
||||
end
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
return function()
|
||||
local buildActionName = require(script.Parent.buildActionName)
|
||||
|
||||
it("SHOULD concat networkStatus: to keyPath", function()
|
||||
local result = buildActionName({
|
||||
keyPath = "Hello",
|
||||
})
|
||||
expect(result).to.equal("networkStatus:Hello")
|
||||
end)
|
||||
|
||||
it("SHOULD concat networkStatus: to keyPath with depth", function()
|
||||
local result = buildActionName({
|
||||
keyPath = "Hello.World",
|
||||
})
|
||||
expect(result).to.equal("networkStatus:Hello.World")
|
||||
end)
|
||||
end
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
return function(tab, keyPath)
|
||||
local currentNode = tab
|
||||
for _, key in ipairs(keyPath:split(".")) do
|
||||
if not currentNode[key] then
|
||||
return nil
|
||||
end
|
||||
|
||||
currentNode = currentNode[key]
|
||||
end
|
||||
|
||||
return currentNode
|
||||
end
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
return function()
|
||||
local getDeepValue = require(script.Parent.getDeepValue)
|
||||
|
||||
describe("GIVEN an empty array", function()
|
||||
local tab = {}
|
||||
it("SHOULD return nil", function()
|
||||
expect(getDeepValue(tab, "")).to.equal(nil)
|
||||
expect(getDeepValue(tab, "hello.world")).to.equal(nil)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a dictionary with hello.world", function()
|
||||
local tab = {
|
||||
hello = {
|
||||
world = 100,
|
||||
},
|
||||
}
|
||||
describe("GIVEN an empty string as the second argument", function()
|
||||
it("SHOULD return nil", function()
|
||||
expect(getDeepValue(tab, "")).to.equal(nil)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN `goodbye.world` as the second argument", function()
|
||||
it("SHOULD return nil", function()
|
||||
expect(getDeepValue(tab, "goodbye.world")).to.equal(nil)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN `hello.there` as the second argument", function()
|
||||
it("SHOULD return nil", function()
|
||||
expect(getDeepValue(tab, "hello.there")).to.equal(nil)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN `hello.there` as the second argument", function()
|
||||
it("SHOULD return nil", function()
|
||||
expect(getDeepValue(tab, "hello.there")).to.equal(nil)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN `hello` as the second argument", function()
|
||||
it("SHOULD return the hello table", function()
|
||||
expect(getDeepValue(tab, "hello")).to.equal(tab.hello)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN `hello.world` as the second argument", function()
|
||||
it("SHOULD return the 100 (the value mapped to hello.world)", function()
|
||||
expect(getDeepValue(tab, "hello.world")).to.equal(100)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
local root = script.Parent
|
||||
local getDeepValue = require(root.getDeepValue)
|
||||
local EnumNetworkStatus = require(root.EnumNetworkStatus)
|
||||
|
||||
return function(options)
|
||||
local keyPath = options.keyPath
|
||||
|
||||
return function(state, fetchStatusKey)
|
||||
assert(typeof(state) == "table")
|
||||
assert(typeof(fetchStatusKey) == "string")
|
||||
assert(#fetchStatusKey > 0)
|
||||
local reducerValue = getDeepValue(state, keyPath)
|
||||
assert(reducerValue, string.format(
|
||||
"Reducer not found for keyPath: %s. Did you forget to call `installReducer`?",
|
||||
keyPath
|
||||
))
|
||||
|
||||
return reducerValue:get(fetchStatusKey) or EnumNetworkStatus.NotStarted
|
||||
end
|
||||
end
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
local Freeze = require(script.Parent.Freeze)
|
||||
|
||||
return function()
|
||||
local getStatus = require(script.Parent.getStatus)({
|
||||
keyPath = "testingKeyPathStatus",
|
||||
})
|
||||
local EnumNetworkStatus = require(script.Parent.EnumNetworkStatus)
|
||||
local TEST_KEY_1 = "item_key"
|
||||
|
||||
it("should return NotStarted for missing key", function()
|
||||
local state = { testingKeyPathStatus = Freeze.UnorderedMap.new({}) }
|
||||
local status = getStatus(state, TEST_KEY_1)
|
||||
|
||||
expect(status).to.equal(EnumNetworkStatus.NotStarted)
|
||||
end)
|
||||
|
||||
it("should return matching status for state in store", function()
|
||||
local statusesToTest = {
|
||||
EnumNetworkStatus.NotStarted,
|
||||
EnumNetworkStatus.Fetching,
|
||||
EnumNetworkStatus.Done,
|
||||
EnumNetworkStatus.Failed
|
||||
}
|
||||
|
||||
for _, testStatus in ipairs(statusesToTest) do
|
||||
local state = {
|
||||
testingKeyPathStatus = Freeze.UnorderedMap.new({
|
||||
[TEST_KEY_1] = testStatus
|
||||
})
|
||||
}
|
||||
|
||||
expect(getStatus(state, TEST_KEY_1)).to.equal(testStatus)
|
||||
end
|
||||
end)
|
||||
end
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
return function(options)
|
||||
return {
|
||||
getStatus = require(script.getStatus)(options),
|
||||
setStatus = require(script.setStatus)(options),
|
||||
installReducer = require(script.installReducer)(options),
|
||||
|
||||
Enum = {
|
||||
Status = require(script.EnumNetworkStatus),
|
||||
},
|
||||
}
|
||||
end
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
local Rodux = require(script.Parent.Rodux)
|
||||
local Freeze = require(script.Parent.Freeze)
|
||||
local buildActionName = require(script.Parent.buildActionName)
|
||||
|
||||
return function(options)
|
||||
return function()
|
||||
return Rodux.createReducer(Freeze.UnorderedMap.new({}), {
|
||||
[buildActionName(options)] = function(state, action)
|
||||
local updatedStatus = {}
|
||||
if #action.ids == 0 then
|
||||
updatedStatus[action.keymapper()] = action.status
|
||||
else
|
||||
for _, id in ipairs(action.ids) do
|
||||
local mappedId = action.keymapper(id)
|
||||
updatedStatus[mappedId] = action.status
|
||||
end
|
||||
end
|
||||
|
||||
return state:batchSet(updatedStatus)
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
return function()
|
||||
local options = {
|
||||
keyPath = "networkStatus"
|
||||
}
|
||||
local installReducer = require(script.Parent.installReducer)(options)
|
||||
local buildActionName = require(script.Parent.buildActionName)
|
||||
local getStatus = require(script.Parent.getStatus)(options)
|
||||
local reducer = installReducer()
|
||||
|
||||
describe("GIVEN an action with no id", function()
|
||||
local initialAction = {
|
||||
type = buildActionName(options),
|
||||
ids = {},
|
||||
keymapper = function() return "key" end,
|
||||
status = "test",
|
||||
}
|
||||
|
||||
it("SHOULD return a new UnorderedMap with the key mapped properly", function()
|
||||
local result = reducer(nil, initialAction)
|
||||
|
||||
expect(result).to.be.ok()
|
||||
expect(result:get("key")).to.equal("test")
|
||||
end)
|
||||
|
||||
describe("GIVEN another action that rewrites the previous key", function()
|
||||
local overwriteAction = {
|
||||
type = buildActionName(options),
|
||||
ids = {},
|
||||
keymapper = function() return "key" end,
|
||||
status = "next-best-thing",
|
||||
}
|
||||
|
||||
it("SHOULD update the key accordingly", function()
|
||||
local result = reducer(reducer(nil, initialAction), overwriteAction)
|
||||
|
||||
expect(result).to.be.ok()
|
||||
expect(result:get("key")).to.equal("next-best-thing")
|
||||
end)
|
||||
end)
|
||||
|
||||
it("SHOULD be retrievable with getStatus", function()
|
||||
local state = {
|
||||
networkStatus = reducer(nil, initialAction),
|
||||
}
|
||||
local result = getStatus(state, "key")
|
||||
expect(result).to.equal("test")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN an action with one id", function()
|
||||
local initialAction = {
|
||||
type = buildActionName(options),
|
||||
ids = { "123" },
|
||||
keymapper = function(id) return "key:" .. id end,
|
||||
status = "test",
|
||||
}
|
||||
|
||||
it("SHOULD return a new UnorderedMap with the key mapped properly", function()
|
||||
local result = reducer(nil, initialAction)
|
||||
|
||||
expect(result).to.be.ok()
|
||||
expect(result:get("key:123")).to.equal("test")
|
||||
end)
|
||||
|
||||
describe("GIVEN another action that rewrites the previous key", function()
|
||||
local overwriteAction = {
|
||||
type = buildActionName(options),
|
||||
ids = { "123" },
|
||||
keymapper = function(id) return "key:" .. id end,
|
||||
status = "next-best-thing",
|
||||
}
|
||||
|
||||
it("SHOULD update the key accordingly", function()
|
||||
local result = reducer(reducer(nil, initialAction), overwriteAction)
|
||||
|
||||
expect(result).to.be.ok()
|
||||
expect(result:get("key:123")).to.equal("next-best-thing")
|
||||
end)
|
||||
end)
|
||||
|
||||
it("SHOULD be retrievable with getStatus", function()
|
||||
local state = {
|
||||
networkStatus = reducer(nil, initialAction),
|
||||
}
|
||||
local result = getStatus(state, "key:123")
|
||||
expect(result).to.equal("test")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN an action with multiple ids", function()
|
||||
local batchAction = {
|
||||
type = buildActionName(options),
|
||||
ids = { "123", "456", "789" },
|
||||
keymapper = function(id) return "key:" .. id end,
|
||||
status = "the-same-status",
|
||||
}
|
||||
|
||||
it("SHOULD update all keys to the same status", function()
|
||||
local result = reducer(nil, batchAction)
|
||||
|
||||
expect(result).to.be.ok()
|
||||
expect(result:get("key:123")).to.equal("the-same-status")
|
||||
expect(result:get("key:456")).to.equal("the-same-status")
|
||||
expect(result:get("key:789")).to.equal("the-same-status")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
local EnumNetworkStatus = require(script.Parent.EnumNetworkStatus)
|
||||
local buildActionName = require(script.Parent.buildActionName)
|
||||
|
||||
return function(options)
|
||||
local getStatus = require(script.Parent.getStatus)(options)
|
||||
|
||||
local function actionCreator(ids, keymapper, status)
|
||||
return {
|
||||
ids = ids,
|
||||
keymapper = keymapper,
|
||||
status = status,
|
||||
type = buildActionName(options),
|
||||
}
|
||||
end
|
||||
|
||||
local function filter(state, ids, keymapper)
|
||||
local filteredIds = {}
|
||||
for _, id in ipairs(ids) do
|
||||
local status = getStatus(state, keymapper(id))
|
||||
if status ~= EnumNetworkStatus.Fetching then
|
||||
table.insert(filteredIds, id)
|
||||
end
|
||||
end
|
||||
return filteredIds
|
||||
end
|
||||
|
||||
return function(store, ids, keymapper, promiseFunction)
|
||||
local filteredIds = filter(store:getState(), ids, keymapper)
|
||||
|
||||
store:dispatch(actionCreator(filteredIds, keymapper, EnumNetworkStatus.Fetching))
|
||||
|
||||
return promiseFunction(store, filteredIds):andThen(function(result)
|
||||
store:dispatch(actionCreator(filteredIds, keymapper, EnumNetworkStatus.Done))
|
||||
return result
|
||||
end,
|
||||
function(errorString)
|
||||
store:dispatch(actionCreator(filteredIds, keymapper, EnumNetworkStatus.Failed))
|
||||
error(errorString)
|
||||
end)
|
||||
end
|
||||
end
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
return function()
|
||||
local Freeze = require(script.Parent.Freeze)
|
||||
local EnumNetworkStatus = require(script.Parent.EnumNetworkStatus)
|
||||
local Promise = require(script.Parent.Promise)
|
||||
local setStatus = require(script.Parent.setStatus)({
|
||||
keyPath = "testingKeyPath",
|
||||
})
|
||||
local mockStore = require(script.Parent.Parent.mockStore)
|
||||
|
||||
describe("GIVEN a store", function()
|
||||
local actionHistory = {}
|
||||
local roduxStore = mockStore.config({
|
||||
dispatch = function(self, action)
|
||||
if type(action) == "function" then
|
||||
action(self)
|
||||
else
|
||||
table.insert(actionHistory, action)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
describe("GIVEN multiple ids with a valid keymapper and promise function", function()
|
||||
local ids = { "123", "456", "789" }
|
||||
local keymapper = function(id)
|
||||
return "key" .. tostring(id)
|
||||
end
|
||||
|
||||
describe("WHEN there are no ongoing requests", function()
|
||||
it("SHOULD not filter any ids", function()
|
||||
roduxStore:setState({
|
||||
testingKeyPath = Freeze.UnorderedMap.new({}),
|
||||
})
|
||||
|
||||
local promiseFunction = function(store, filteredIds)
|
||||
expect(store).to.be.ok()
|
||||
expect(#filteredIds).to.equal(#ids)
|
||||
return Promise.resolve(filteredIds)
|
||||
end
|
||||
|
||||
setStatus(roduxStore, ids, keymapper, promiseFunction)
|
||||
end)
|
||||
|
||||
it("SHOULD only dispatch relevant actions", function()
|
||||
expect(#actionHistory).to.equal(2)
|
||||
|
||||
local secondToLastAction = actionHistory[#actionHistory - 1]
|
||||
expect(secondToLastAction).to.be.ok()
|
||||
expect(type(secondToLastAction)).to.equal("table")
|
||||
expect(secondToLastAction.type).to.equal("networkStatus:testingKeyPath")
|
||||
expect(secondToLastAction.status).to.equal(EnumNetworkStatus.Fetching)
|
||||
|
||||
local lastAction = actionHistory[#actionHistory]
|
||||
expect(lastAction).to.be.ok()
|
||||
expect(type(lastAction)).to.equal("table")
|
||||
expect(lastAction.type).to.equal("networkStatus:testingKeyPath")
|
||||
expect(lastAction.status).to.equal(EnumNetworkStatus.Done)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("WHEN there is already an ongoing request", function()
|
||||
it("SHOULD filter the ids that are currently ongoing", function()
|
||||
actionHistory = {}
|
||||
roduxStore:setState({
|
||||
testingKeyPath = Freeze.UnorderedMap.new({
|
||||
[keymapper("123")] = EnumNetworkStatus.Fetching,
|
||||
[keymapper("456")] = EnumNetworkStatus.Fetching,
|
||||
[keymapper("789")] = EnumNetworkStatus.Fetching,
|
||||
}),
|
||||
})
|
||||
local promiseFunction = function(store, filteredIds)
|
||||
expect(store).to.be.ok()
|
||||
expect(#filteredIds).to.equal(0)
|
||||
return Promise.resolve(filteredIds)
|
||||
end
|
||||
|
||||
setStatus(roduxStore, ids, keymapper, promiseFunction)
|
||||
end)
|
||||
|
||||
it("SHOULD still fire any actions if there are no ids", function()
|
||||
expect(#actionHistory).to.equal(2)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
local root = script.Parent
|
||||
local makeRequestApi = require(root.makeRequestApi)
|
||||
|
||||
return function(options)
|
||||
return makeRequestApi(options, "POST")
|
||||
end
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
return function()
|
||||
local root = script.Parent
|
||||
local mockNetworkImpl = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
local POST = require(root.POST)({
|
||||
keyPath = "hello.world",
|
||||
networkImpl = mockNetworkImpl,
|
||||
})
|
||||
|
||||
describe("WHEN invoked", function()
|
||||
local endpoint = POST(script, function(requestBuilder)
|
||||
return requestBuilder("example.com")
|
||||
end)
|
||||
|
||||
it("SHOULD return an object have all expected fields", function()
|
||||
expect(endpoint.API).to.be.ok()
|
||||
expect(endpoint.getStatus).to.be.ok()
|
||||
expect(endpoint.Succeeded).to.be.ok()
|
||||
expect(endpoint.Failed).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local root = script.Parent
|
||||
local Packages = root.Parent
|
||||
|
||||
return require(Packages.Promise)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local root = script.Parent.Parent
|
||||
local Packages = root.Parent
|
||||
|
||||
return require(Packages.Cryo)
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
local root = script.Parent
|
||||
local Cryo = require(root.Cryo)
|
||||
|
||||
local UriIds = {}
|
||||
UriIds.__index = UriIds
|
||||
|
||||
function UriIds:new(ids, delimiter)
|
||||
return setmetatable({
|
||||
ids = UriIds.makeArray(ids),
|
||||
delimiter = delimiter
|
||||
}, self)
|
||||
end
|
||||
|
||||
function UriIds:setIds(ids)
|
||||
self.ids = UriIds.makeArray(ids)
|
||||
end
|
||||
|
||||
function UriIds.makeArray(ids)
|
||||
if type(ids) == "table" then
|
||||
return ids
|
||||
end
|
||||
return {ids}
|
||||
end
|
||||
|
||||
function UriIds:__tostring()
|
||||
return table.concat(self.ids, self.delimiter)
|
||||
end
|
||||
|
||||
local RequestBuilder = {}
|
||||
RequestBuilder.__index = RequestBuilder
|
||||
|
||||
function RequestBuilder:new(baseUrl)
|
||||
return setmetatable({
|
||||
baseUrl = baseUrl,
|
||||
keyMapper = nil,
|
||||
args = {},
|
||||
pathElements = {},
|
||||
configurableIds = nil,
|
||||
namedIds = {},
|
||||
idsDelimiter = ";",
|
||||
options = {},
|
||||
}, self)
|
||||
end
|
||||
|
||||
function RequestBuilder:path(path)
|
||||
table.insert(self.pathElements, path)
|
||||
return self
|
||||
end
|
||||
|
||||
function RequestBuilder:id(ids, key)
|
||||
if not key and #self.pathElements < 1 then
|
||||
warn("Cannot name id or ids because there is no leading path segment and no name is provided")
|
||||
end
|
||||
local name = key or self.pathElements[#self.pathElements]
|
||||
self.namedIds[name] = ids
|
||||
|
||||
self.configurableIds = UriIds:new(ids, self.idsDelimiter)
|
||||
table.insert(self.pathElements, self.configurableIds)
|
||||
return self
|
||||
end
|
||||
|
||||
function RequestBuilder:queryArgWithIds(argName, ids)
|
||||
self.namedIds[argName] = ids
|
||||
self.configurableIds = UriIds:new(ids, self.idsDelimiter)
|
||||
self:queryArgs({
|
||||
[argName] = self.configurableIds
|
||||
})
|
||||
return self
|
||||
end
|
||||
|
||||
function RequestBuilder:queryArgs(args)
|
||||
self.args = Cryo.Dictionary.join(self.args, args)
|
||||
return self
|
||||
end
|
||||
|
||||
function RequestBuilder:body(dictionary)
|
||||
self.options.postBody = dictionary
|
||||
return self
|
||||
end
|
||||
|
||||
function RequestBuilder:makeKeyMapper()
|
||||
return function(someId)
|
||||
return self:makeUrl(someId)
|
||||
end
|
||||
end
|
||||
|
||||
function RequestBuilder:makeUri(ids)
|
||||
local fullPath = ""
|
||||
for _, element in ipairs(self.pathElements) do
|
||||
fullPath = fullPath .. "/" .. tostring(element)
|
||||
end
|
||||
return fullPath
|
||||
end
|
||||
|
||||
function RequestBuilder:makeQueryArgs(ids)
|
||||
self:_plugInConfigurableIds(ids)
|
||||
local argsString = ""
|
||||
for k,v in pairs(self.args) do
|
||||
local arg = tostring(k) .. "=" .. tostring(v)
|
||||
if argsString:len() > 1 then
|
||||
argsString = argsString .. "&" .. arg
|
||||
else
|
||||
argsString = arg
|
||||
end
|
||||
end
|
||||
if argsString:len() > 1 then
|
||||
return "?" .. argsString
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
function RequestBuilder:makeUrl(ids)
|
||||
self:_plugInConfigurableIds(ids)
|
||||
local fullUrl = self.baseUrl .. self:makeUri(ids) .. self:makeQueryArgs(ids)
|
||||
return fullUrl
|
||||
end
|
||||
|
||||
function RequestBuilder:makeOptions()
|
||||
return self.options
|
||||
end
|
||||
|
||||
function RequestBuilder:_plugInConfigurableIds(ids)
|
||||
if ids ~= nil and self.configurableIds then
|
||||
self.configurableIds:setIds(ids)
|
||||
end
|
||||
end
|
||||
|
||||
function RequestBuilder:getIds()
|
||||
if self.configurableIds and self.configurableIds.ids then
|
||||
return self.configurableIds.ids
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
function RequestBuilder:getNamedIds()
|
||||
return self.namedIds
|
||||
end
|
||||
|
||||
return RequestBuilder
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
return function()
|
||||
local root = script.Parent
|
||||
local RequestBuilder = require(root.RequestBuilder)
|
||||
local tutils = require(root.tutils)
|
||||
|
||||
local baseUrl = "https://example.com"
|
||||
describe("RequestBuilder basics", function()
|
||||
it("builder functions should return self", function()
|
||||
local builder = RequestBuilder:new()
|
||||
|
||||
expect(builder).to.equal(builder:path("test"))
|
||||
expect(builder).to.equal(builder:id("test"))
|
||||
expect(builder).to.equal(builder:queryArgs({ "test" }))
|
||||
expect(builder).to.equal(builder:body({ "test" }))
|
||||
end)
|
||||
|
||||
it("should be constructible from base URL", function()
|
||||
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
expect(builder).to.be.ok()
|
||||
expect(builder:makeUrl()).to.equal(baseUrl)
|
||||
end)
|
||||
|
||||
it("should be constructible from successive path calls", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:path("some"):path("element")
|
||||
expect(builder:makeUrl()).to.equal(baseUrl .. "/some/element")
|
||||
expect(tutils.shallowEqual(builder:getIds(), {})).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should allow building query args", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:queryArgs({
|
||||
arg = "value"
|
||||
})
|
||||
expect(builder:makeUrl()).to.equal(baseUrl .. "?arg=value")
|
||||
expect(tutils.shallowEqual(builder:getIds(), {})).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should allow building multiple query args", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:queryArgs({
|
||||
arg = "value"
|
||||
})
|
||||
builder:queryArgs({
|
||||
arg2 = "value2"
|
||||
})
|
||||
expect(builder:makeUrl()).to.equal(baseUrl .. "?arg2=value2&arg=value")
|
||||
expect(tutils.shallowEqual(builder:getIds(), {})).to.equal(true)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("RequestBuilder path ids", function()
|
||||
it("should be constructible from path and single id", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:path("some/element"):id(123)
|
||||
|
||||
local expectedUrl = baseUrl .. "/some/element/123"
|
||||
expect(builder:makeUrl()).to.equal(expectedUrl)
|
||||
|
||||
expect(builder:makeKeyMapper()).to.be.ok()
|
||||
expect(builder:makeKeyMapper()()).to.equal(expectedUrl)
|
||||
|
||||
expect(tutils.shallowEqual(builder:getIds(), {123})).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should be constructible from path and ids array", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:path("some/element"):id({123, 321})
|
||||
|
||||
local staticUrlPart = baseUrl .. "/some/element/"
|
||||
expect(builder:makeUrl()).to.equal(staticUrlPart .. "123;321")
|
||||
|
||||
expect(builder:makeKeyMapper()(123)).to.equal(staticUrlPart .. "123")
|
||||
expect(builder:makeKeyMapper()(321)).to.equal(staticUrlPart .. "321")
|
||||
end)
|
||||
|
||||
it("should allow swapping ids in makeUrl call", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:path("some/element"):id({})
|
||||
expect(builder:makeUrl({567, 789})).to.equal(baseUrl .. "/some/element/567;789")
|
||||
end)
|
||||
|
||||
it("should allow swapping ids in makeUrl call, but only for last ids group", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:path("some/element"):id(123):path("other"):id({})
|
||||
|
||||
local staticUrlPart = baseUrl .. "/some/element/123/other"
|
||||
expect(builder:makeUrl(567)).to.equal(staticUrlPart .. "/567")
|
||||
expect(builder:makeUrl({567})).to.equal(staticUrlPart .. "/567")
|
||||
expect(builder:makeUrl({567, 789})).to.equal(staticUrlPart .. "/567;789")
|
||||
|
||||
expect(builder:makeKeyMapper()(567)).to.equal(staticUrlPart .. "/567")
|
||||
expect(builder:makeKeyMapper()(789)).to.equal(staticUrlPart .. "/789")
|
||||
end)
|
||||
|
||||
it("should map previous path segment to id", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:path("pathexample"):id(444)
|
||||
local namedIds = builder:getNamedIds()
|
||||
expect(namedIds["pathexample"]).to.be.ok()
|
||||
expect(namedIds["pathexample"]).to.equal(444)
|
||||
end)
|
||||
|
||||
it("should map previous path segment to id with multiple path segments and ids", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:path("firstpathexample"):id(444):path("anotherpathexample"):path("yetanotherpathexample"):id(555)
|
||||
local namedIds = builder:getNamedIds()
|
||||
expect(namedIds["firstpathexample"]).to.be.ok()
|
||||
expect(namedIds["anotherpathexample"]).to.never.be.ok()
|
||||
expect(namedIds["yetanotherpathexample"]).to.be.ok()
|
||||
|
||||
expect(namedIds["firstpathexample"]).to.equal(444)
|
||||
expect(namedIds["yetanotherpathexample"]).to.equal(555)
|
||||
end)
|
||||
|
||||
it("should map previous path segment to id with multiple path segments and ids, with multiple ids given to function", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:path("firstpathexample"):id({ 333, 444 }):path("anotherpathexample"):path("yetanotherpathexample"):id(555)
|
||||
local namedIds = builder:getNamedIds()
|
||||
expect(namedIds["firstpathexample"]).to.be.ok()
|
||||
expect(namedIds["anotherpathexample"]).to.never.be.ok()
|
||||
expect(namedIds["yetanotherpathexample"]).to.be.ok()
|
||||
|
||||
expect(tutils.deepEqual(namedIds["firstpathexample"], { 333, 444 })).to.equal(true)
|
||||
expect(namedIds["yetanotherpathexample"]).to.equal(555)
|
||||
end)
|
||||
|
||||
it("should allow one to override the default key", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
local KEY1 = "KEY1"
|
||||
builder:path("firstpathexample"):id({ 333, 444 }, KEY1):path("anotherpathexample"):path("yetanotherpathexample"):id(555)
|
||||
local namedIds = builder:getNamedIds()
|
||||
expect(namedIds["firstpathexample"]).to.never.be.ok()
|
||||
expect(namedIds["anotherpathexample"]).to.never.be.ok()
|
||||
expect(namedIds["yetanotherpathexample"]).to.be.ok()
|
||||
|
||||
expect(tutils.deepEqual(namedIds[KEY1], { 333, 444 })).to.equal(true)
|
||||
expect(namedIds["yetanotherpathexample"]).to.equal(555)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("RequestBuilder query args and ids", function()
|
||||
it("should allow swapping query argument ids in makeUrl call", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:queryArgWithIds("arg", {123})
|
||||
expect(builder:makeUrl()).to.equal(baseUrl .. "?arg=123")
|
||||
end)
|
||||
|
||||
it("should allow swapping multiple query argument ids in makeUrl call", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:queryArgWithIds("arg", {})
|
||||
expect(builder:makeUrl(123)).to.equal(baseUrl .. "?arg=123")
|
||||
expect(builder:makeUrl({345, 456})).to.equal(baseUrl .. "?arg=345;456")
|
||||
|
||||
expect(builder:makeKeyMapper()(567)).to.equal(baseUrl .. "?arg=567")
|
||||
expect(builder:makeKeyMapper()({567, 321})).to.equal(baseUrl .. "?arg=567;321")
|
||||
end)
|
||||
|
||||
it("should allow swapping query argument ids in makeUrl call and not affect path ids", function()
|
||||
local builder = RequestBuilder:new(baseUrl)
|
||||
builder:path("some/element"):id(999):queryArgWithIds("arg", {})
|
||||
expect(builder:makeUrl(123)).to.equal(baseUrl .. "/some/element/999?arg=123")
|
||||
expect(builder:makeUrl({345, 456})).to.equal(baseUrl .. "/some/element/999?arg=345;456")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("RequestBuilder body", function()
|
||||
it("should replace postBody", function()
|
||||
local myBody = { hi = "there" }
|
||||
local builder = RequestBuilder:new(baseUrl):body(myBody)
|
||||
|
||||
expect(builder:makeOptions()).to.be.ok()
|
||||
expect(builder:makeOptions().postBody.hi).to.equal("there")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+1
@@ -0,0 +1 @@
|
||||
return require(script.RequestBuilder)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local root = script.Parent.Parent
|
||||
local Packages = root.Parent
|
||||
|
||||
return require(Packages.tutils)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
local GET = require(script.Parent.GET)
|
||||
local POST = require(script.Parent.POST)
|
||||
|
||||
local RoduxNetworking = {}
|
||||
RoduxNetworking.__index = RoduxNetworking
|
||||
|
||||
function RoduxNetworking.new(options)
|
||||
assert(options, "Expected options to be passed into RoduxNetworking")
|
||||
assert(options.keyPath, "Expected options.keyPath to be passed into RoduxNetworking")
|
||||
assert(options.networkImpl, "Expected options.networkImpl to be passed into RoduxNetworking")
|
||||
|
||||
local self = {
|
||||
options = options,
|
||||
}
|
||||
|
||||
return setmetatable(self, RoduxNetworking)
|
||||
end
|
||||
|
||||
function RoduxNetworking:GET(moduleScript, constructBuilderFunction)
|
||||
assert(moduleScript, "RoduxNetworking:GET expects moduleScript argument")
|
||||
assert(moduleScript, "RoduxNetworking:GET expects constructBuilderFunction argument")
|
||||
return GET(self.options)(moduleScript, constructBuilderFunction)
|
||||
end
|
||||
|
||||
function RoduxNetworking:POST(...)
|
||||
return POST(self.options)(...)
|
||||
end
|
||||
|
||||
function RoduxNetworking:getNetworkImpl()
|
||||
return self.options.networkImpl
|
||||
end
|
||||
|
||||
function RoduxNetworking:setNetworkImpl(networkImpl)
|
||||
self.options.networkImpl = networkImpl
|
||||
end
|
||||
|
||||
return RoduxNetworking
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
return function()
|
||||
local RoduxNetworking = require(script.Parent.RoduxNetworking)
|
||||
local Promise = require(script.Parent.Promise)
|
||||
local noOpt = function()
|
||||
return Promise.resolve()
|
||||
end
|
||||
local moduleScript = {
|
||||
Name = "moduleScript",
|
||||
}
|
||||
local builderConstructorFunction = function()
|
||||
end
|
||||
|
||||
describe("GIVEN an options configuration", function()
|
||||
local options = {
|
||||
keyPath = "",
|
||||
networkImpl = noOpt
|
||||
}
|
||||
it("SHOULD return a unique object when .new is called", function()
|
||||
local instance1 = RoduxNetworking.new(options)
|
||||
local instance2 = RoduxNetworking.new(options)
|
||||
|
||||
expect(instance1).to.be.ok()
|
||||
expect(instance2).to.be.ok()
|
||||
expect(instance1).to.never.equal(instance2)
|
||||
end)
|
||||
|
||||
describe("API", function()
|
||||
local instance = RoduxNetworking.new(options)
|
||||
it("SHOULD return an object when GET is called", function()
|
||||
local result = instance:GET(moduleScript, builderConstructorFunction)
|
||||
expect(result).to.be.ok()
|
||||
end)
|
||||
|
||||
it("SHOULD return an object when POST is called", function()
|
||||
local result = instance:POST(moduleScript, builderConstructorFunction)
|
||||
expect(result).to.be.ok()
|
||||
end)
|
||||
|
||||
describe("WHEN setNetworkImpl is called", function()
|
||||
local newNetworkImpl = function()
|
||||
return Promise.reject()
|
||||
end
|
||||
instance:setNetworkImpl(newNetworkImpl)
|
||||
it("SHOULD return the given value when getNetworkImpl is called", function()
|
||||
expect(instance:getNetworkImpl()).to.equal(newNetworkImpl)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local root = script.Parent
|
||||
local Packages = root.Parent
|
||||
|
||||
return require(Packages.UrlBuilder)
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
local RoduxNetworking = require(script.RoduxNetworking)
|
||||
local NetworkStatus = require(script.NetworkStatus)
|
||||
|
||||
return {
|
||||
config = function(options)
|
||||
local roduxNetworkingInstance = RoduxNetworking.new(options)
|
||||
local networkStatusInstance = NetworkStatus(options)
|
||||
|
||||
return {
|
||||
GET = function(...)
|
||||
return roduxNetworkingInstance:GET(...)
|
||||
end,
|
||||
POST = function(...)
|
||||
return roduxNetworkingInstance:POST(...)
|
||||
end,
|
||||
getNetworkImpl = function()
|
||||
return roduxNetworkingInstance:getNetworkImpl()
|
||||
end,
|
||||
setNetworkImpl = function(...)
|
||||
roduxNetworkingInstance:setNetworkImpl(...)
|
||||
end,
|
||||
|
||||
installReducer = networkStatusInstance.installReducer,
|
||||
Enum = {
|
||||
NetworkStatus = networkStatusInstance.Enum.Status,
|
||||
},
|
||||
}
|
||||
end,
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
return function()
|
||||
local init = require(script.Parent)
|
||||
local noOpt = function()
|
||||
end
|
||||
|
||||
describe("GIVEN an options configuration", function()
|
||||
local instance = init.config({
|
||||
keyPath = "hello.world",
|
||||
networkImpl = noOpt,
|
||||
})
|
||||
|
||||
it("SHOULD have all expected fields", function()
|
||||
expect(instance.GET).to.be.ok()
|
||||
expect(instance.POST).to.be.ok()
|
||||
expect(instance.Enum).to.be.ok()
|
||||
expect(instance.installReducer).to.be.ok()
|
||||
expect(instance.getNetworkImpl).to.be.ok()
|
||||
expect(instance.setNetworkImpl).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
local root = script.Parent
|
||||
local Action = require(root.Action)
|
||||
|
||||
return function(networkRequestScript)
|
||||
return {
|
||||
Succeeded = Action(networkRequestScript.Name .. "_Succeeded", function(ids, responseBody, namedIds)
|
||||
return {
|
||||
ids = ids,
|
||||
responseBody = responseBody,
|
||||
namedIds = namedIds,
|
||||
}
|
||||
end),
|
||||
Failed = Action(networkRequestScript.Name .. "_Failed", function(ids, error, namedIds)
|
||||
return {
|
||||
ids = ids,
|
||||
error = error,
|
||||
namedIds = namedIds,
|
||||
}
|
||||
end),
|
||||
}
|
||||
end
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
return function()
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
describe("GIVEN a script", function()
|
||||
local myScript = Instance.new("ModuleScript")
|
||||
local result = makeActionCreator(myScript)
|
||||
|
||||
it("SHOULD return an object with a success field and failed field", function()
|
||||
expect(result.Succeeded).to.be.ok()
|
||||
expect(result.Failed).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
local root = script.Parent
|
||||
local makeActionCreator = require(root.makeActionCreator)
|
||||
local RequestBuilder = require(root.RequestBuilder)
|
||||
local NetworkStatus = require(root.NetworkStatus)
|
||||
|
||||
return function(options, methodType)
|
||||
local keyPath = options.keyPath
|
||||
|
||||
local myNetworkStatus = NetworkStatus({
|
||||
keyPath = keyPath,
|
||||
})
|
||||
|
||||
return function(moduleScript, constructBuilderFunction)
|
||||
local self = makeActionCreator(moduleScript)
|
||||
self.API = function(...)
|
||||
local userRequestBuilder = constructBuilderFunction(function(...)
|
||||
return RequestBuilder:new(...)
|
||||
end, ...)
|
||||
|
||||
return function(store)
|
||||
return myNetworkStatus.setStatus(store, userRequestBuilder:getIds(), userRequestBuilder:makeKeyMapper(), function(store, filteredIds)
|
||||
local networkImpl = options.networkImpl
|
||||
return networkImpl(userRequestBuilder:makeUrl(filteredIds), methodType, userRequestBuilder:makeOptions()):andThen(
|
||||
function(payload)
|
||||
store:dispatch(self.Succeeded(filteredIds, payload.responseBody, userRequestBuilder:getNamedIds()))
|
||||
return payload
|
||||
end,
|
||||
function(errorString)
|
||||
store:dispatch(self.Failed(filteredIds, error, userRequestBuilder:getNamedIds()))
|
||||
-- Throw again so we can catch it outside of library
|
||||
error(errorString)
|
||||
end
|
||||
)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
self.getStatus = (methodType == "GET") and function(state, key)
|
||||
local userRequestBuilder = constructBuilderFunction(function(...)
|
||||
return RequestBuilder:new(...)
|
||||
end, key)
|
||||
|
||||
local keymapper = userRequestBuilder:makeKeyMapper()
|
||||
local mappedKey = keymapper(key)
|
||||
|
||||
return myNetworkStatus.getStatus(state, mappedKey)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
end
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
return function()
|
||||
local root = script.Parent
|
||||
local Freeze = require(root.Freeze)
|
||||
local EnumNetworkStatus = require(root.NetworkStatus.EnumNetworkStatus)
|
||||
local Promise = require(root.Promise)
|
||||
local mockNetworkImpl = function(url, method, options)
|
||||
return Promise.resolve({
|
||||
responseBody = "benj",
|
||||
})
|
||||
end
|
||||
local mockStore = require(root.mockStore)
|
||||
|
||||
local GET = require(root.makeRequestApi)({
|
||||
keyPath = "hello.world",
|
||||
networkImpl = mockNetworkImpl,
|
||||
}, "GET")
|
||||
|
||||
describe("GIVEN a store", function()
|
||||
local actionHistory = {}
|
||||
local roduxStore = mockStore.config({
|
||||
dispatch = function(self, action)
|
||||
if type(action) == "function" then
|
||||
action(self)
|
||||
else
|
||||
table.insert(actionHistory, action)
|
||||
end
|
||||
end,
|
||||
|
||||
state = {
|
||||
hello = {
|
||||
world = Freeze.UnorderedMap.new({}),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe("GIVEN a module script and builderFunction w/out parameters", function()
|
||||
local hasBuilderFunctionRun = false
|
||||
local receivedChannelArgument = nil
|
||||
local mockGETChannels = GET(script, function(requestBuilder, channel)
|
||||
hasBuilderFunctionRun = true
|
||||
receivedChannelArgument = channel
|
||||
|
||||
return requestBuilder("example.com"):path("v1"):path("channels"):id(channel):path("messages")
|
||||
end)
|
||||
|
||||
describe("GIVEN no parameters for a url", function()
|
||||
it("SHOULD return a valid thunk", function()
|
||||
local thunk = mockGETChannels.API("weather-channel")
|
||||
expect(type(thunk)).to.equal("function")
|
||||
end)
|
||||
|
||||
describe("WHEN thunk is dispatched", function()
|
||||
|
||||
it("SHOULD invoke builder function given to GET constructor", function()
|
||||
roduxStore:dispatch(mockGETChannels.API("announcements"))
|
||||
expect(hasBuilderFunctionRun).to.equal(true)
|
||||
expect(receivedChannelArgument).to.equal("announcements")
|
||||
end)
|
||||
|
||||
it("SHOULD dispatch network status actions and payload action", function()
|
||||
actionHistory = {}
|
||||
roduxStore:dispatch(mockGETChannels.API("announcements"))
|
||||
local action1 = actionHistory[1]
|
||||
expect(action1).to.be.ok()
|
||||
expect(action1.type).to.equal("networkStatus:hello.world")
|
||||
expect(action1.status).to.equal(EnumNetworkStatus.Fetching)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Action Creators", function()
|
||||
it("SHOULD have a Succeeded action creator", function()
|
||||
expect(mockGETChannels.Succeeded).to.be.ok()
|
||||
local action = mockGETChannels.Succeeded({}, {}, {})
|
||||
expect(action).to.be.ok()
|
||||
expect(action.ids).to.be.ok()
|
||||
expect(action.responseBody).to.be.ok()
|
||||
expect(action.namedIds).to.be.ok()
|
||||
end)
|
||||
|
||||
it("SHOULD have a Failed action creator", function()
|
||||
expect(mockGETChannels.Failed).to.be.ok()
|
||||
local action = mockGETChannels.Failed({}, {}, {})
|
||||
expect(action).to.be.ok()
|
||||
expect(action.ids).to.be.ok()
|
||||
expect(action.error).to.be.ok()
|
||||
expect(action.namedIds).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("getStatus", function()
|
||||
it("SHOULD return Done for successful network responses", function()
|
||||
local store = mockStore.config({
|
||||
state = {
|
||||
hello = {
|
||||
world = Freeze.UnorderedMap.new({
|
||||
-- key mapper would usually generate this
|
||||
["example.com/v1/channels/faq/messages"] = "testingStatus",
|
||||
}),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
local status = mockGETChannels.getStatus(store:getState(), "faq")
|
||||
expect(status).to.equal("testingStatus")
|
||||
end)
|
||||
|
||||
it("SHOULD throw for non-GET request types", function()
|
||||
local POST = require(root.makeRequestApi)({
|
||||
keyPath = "hello.world",
|
||||
networkImpl = mockNetworkImpl,
|
||||
}, "POST")
|
||||
|
||||
expect(function()
|
||||
POST.getStatus({}, "testing")
|
||||
end).to.throw()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
return function()
|
||||
local root = script.Parent
|
||||
local Freeze = require(root.Freeze)
|
||||
local EnumNetworkStatus = require(root.NetworkStatus.EnumNetworkStatus)
|
||||
local Promise = require(root.Promise)
|
||||
local mockNetworkImpl = function(url, method, options)
|
||||
return Promise.reject("networkError")
|
||||
end
|
||||
local mockStore = require(root.mockStore)
|
||||
|
||||
local GET = require(root.makeRequestApi)({
|
||||
methodType = "GET",
|
||||
keyPath = "hello.world",
|
||||
networkImpl = mockNetworkImpl,
|
||||
})
|
||||
|
||||
local function noOpt()
|
||||
end
|
||||
|
||||
describe("GIVEN a store", function()
|
||||
local actionHistory = {}
|
||||
local roduxStore = mockStore.config({
|
||||
dispatch = function(self, action)
|
||||
if type(action) == "function" then
|
||||
return action(self)
|
||||
else
|
||||
table.insert(actionHistory, action)
|
||||
end
|
||||
end,
|
||||
|
||||
state = {
|
||||
hello = {
|
||||
world = Freeze.UnorderedMap.new({}),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe("GIVEN a module script and builderFunction w/out parameters", function()
|
||||
local hasBuilderFunctionRun = false
|
||||
local receivedChannelArgument = nil
|
||||
local mockGETChannels = GET(script, function(requestBuilder, channel)
|
||||
hasBuilderFunctionRun = true
|
||||
receivedChannelArgument = channel
|
||||
|
||||
return requestBuilder("example.com"):path("v1"):path("channels"):id(channel):path("messages")
|
||||
end)
|
||||
|
||||
describe("WHEN thunk is dispatched", function()
|
||||
it("SHOULD invoke builder function given to GET constructor", function()
|
||||
roduxStore:dispatch(mockGETChannels.API("announcements")):catch(noOpt)
|
||||
expect(hasBuilderFunctionRun).to.equal(true)
|
||||
expect(receivedChannelArgument).to.equal("announcements")
|
||||
end)
|
||||
|
||||
it("SHOULD dispatch network status actions and payload action", function()
|
||||
actionHistory = {}
|
||||
roduxStore:dispatch(mockGETChannels.API("announcements")):catch(noOpt)
|
||||
local action1 = actionHistory[1]
|
||||
expect(action1).to.be.ok()
|
||||
expect(action1.type).to.equal("networkStatus:hello.world")
|
||||
expect(action1.status).to.equal(EnumNetworkStatus.Fetching)
|
||||
end)
|
||||
|
||||
it("SHOULD dispatch a failed network status action", function()
|
||||
actionHistory = {}
|
||||
roduxStore:dispatch(mockGETChannels.API("announcements")):catch(noOpt)
|
||||
local action2 = actionHistory[2]
|
||||
expect(action2).to.be.ok()
|
||||
local isFound = string.find(action2.type, "Failed")
|
||||
expect(isFound).to.be.ok()
|
||||
end)
|
||||
|
||||
it("SHOULD allow the promise to be caught", function()
|
||||
local itWasCaught = false
|
||||
roduxStore:dispatch(mockGETChannels.API("announcements")):catch(function()
|
||||
itWasCaught = true
|
||||
end)
|
||||
|
||||
assert(itWasCaught, "Promise was not able to be caught when network fails")
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
return {
|
||||
config = function(options)
|
||||
options = options or {}
|
||||
|
||||
local state = options.state or {}
|
||||
return {
|
||||
dispatch = options.dispatch or function(self, thunk)
|
||||
if type(thunk) == "function" then
|
||||
thunk(self)
|
||||
end
|
||||
end,
|
||||
|
||||
getState = function()
|
||||
return state
|
||||
end,
|
||||
|
||||
setState = function(_, newState)
|
||||
state = newState
|
||||
end,
|
||||
}
|
||||
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["tutils"]["tutils"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
Reference in New Issue
Block a user