add gs
This commit is contained in:
+12
@@ -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
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["roblox_lumberyak-c9fb3068-76fee23f"]["lumberyak"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["jtaylor_mock"]["mock"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
+12
@@ -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
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["roblox_roact"]["roact"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["roblox_lua-symbol"]["lua-symbol"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Generated by Rotriever. Format subject to change in future releases.
|
||||
name = "roblox/lua-roact-policy-provider"
|
||||
version = "0.1.0"
|
||||
commit = "5f782af8bb981d6e9d2b3fa92f69b02bd4f4cf37"
|
||||
source = "git+https://github.com/roblox/lua-roact-policy-provider#master"
|
||||
dependencies = [
|
||||
"Cryo roblox/cryo 1.0.0 url+https://github.com/roblox/cryo",
|
||||
"Lumberyak roblox/lumberyak 76fee23f git+https://github.com/roblox/lumberyak#master",
|
||||
"Mock jtaylor/mock d2c4005c git+https://github.com/roblox/mock#master",
|
||||
"Promise lua-promise 1bb842c3 git+https://github.com/roblox/lua-promise#master",
|
||||
"Roact roblox/roact 1.3.1 url+https://github.com/roblox/roact",
|
||||
"Symbol roblox/lua-symbol 139fdfe6 git+https://github.com/roblox/lua-symbol#master",
|
||||
"tutils tutils 937da4f7 git+https://github.com/roblox/tutils#master",
|
||||
]
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
local Packages = script.Parent.Parent
|
||||
local Lumberyak = require(Packages.Lumberyak)
|
||||
|
||||
local logger = Lumberyak.Logger.new()
|
||||
|
||||
return logger
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
local Packages = script.Parent.Parent
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local appPolicyKey = require(script.Parent.appPolicyKey)
|
||||
|
||||
return function()
|
||||
local PolicyProvider = Roact.Component:extend("PolicyProvider")
|
||||
|
||||
function PolicyProvider:init(props)
|
||||
assert(type(props.policy) == "table", "Provider expects props.policy to be a table")
|
||||
|
||||
self._context[appPolicyKey] = {
|
||||
presentationPolicy = props.policy,
|
||||
staticExternalPolicy = props.policyData,
|
||||
}
|
||||
end
|
||||
|
||||
function PolicyProvider:render()
|
||||
return Roact.oneChild(self.props[Roact.Children])
|
||||
end
|
||||
|
||||
return PolicyProvider
|
||||
end
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local Packages = script.Parent.Parent
|
||||
local Symbol = require(Packages.Symbol)
|
||||
|
||||
return Symbol.named("AppPolicy")
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
return function()
|
||||
local appPolicyKey = require(script.Parent.appPolicyKey)
|
||||
|
||||
describe("require return value", function()
|
||||
it("SHOULD return a valid Symbol", function()
|
||||
expect(appPolicyKey).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
local Packages = script.Parent.Parent
|
||||
local Roact = require(Packages.Roact)
|
||||
local Cryo = require(Packages.Cryo)
|
||||
local Promise = require(Packages.Promise)
|
||||
|
||||
local Logger = require(script.Parent.Logger)
|
||||
local appPolicyKey = require(script.Parent.appPolicyKey)
|
||||
|
||||
local function mergePolicies(base, params)
|
||||
local policyWrapper = {}
|
||||
|
||||
for _, wrapper in ipairs(params) do
|
||||
policyWrapper = Cryo.Dictionary.join(policyWrapper, wrapper(base))
|
||||
end
|
||||
|
||||
return policyWrapper
|
||||
end
|
||||
|
||||
return function(getPolicyImpl)
|
||||
assert(getPolicyImpl, "expected getPolicyImpl")
|
||||
|
||||
return function(mapper)
|
||||
assert(type(mapper) == "function", "connect expects mapper to be a function")
|
||||
|
||||
return function(component)
|
||||
local name = ("AppPolicy(%s)"):format(tostring(component))
|
||||
local componentLogger = Logger:new(name)
|
||||
componentLogger:setContext({
|
||||
prefix = string.format("%s: ", name),
|
||||
})
|
||||
|
||||
local providerNotFound = string.format("%s: Not a descendent of PolicyProvider", name)
|
||||
local Connection = Roact.PureComponent:extend(name)
|
||||
|
||||
componentLogger:trace("Connected to component: {}", tostring(component))
|
||||
|
||||
function Connection:init(props)
|
||||
self.policyContext = self._context[appPolicyKey]
|
||||
assert(self.policyContext, providerNotFound)
|
||||
|
||||
self.setWithEmptyPolicy = function()
|
||||
self.state = {
|
||||
policy = mergePolicies({}, self.policyContext.presentationPolicy),
|
||||
}
|
||||
end
|
||||
|
||||
if self.policyContext.staticExternalPolicy then
|
||||
-- if we already have a staticExternalPolicy, there is
|
||||
-- no need to read it from our implementation
|
||||
self.state = {
|
||||
policy = mergePolicies(self.policyContext.staticExternalPolicy, self.policyContext.presentationPolicy),
|
||||
}
|
||||
else
|
||||
local retrievedExternalPolicy = getPolicyImpl.read()
|
||||
if retrievedExternalPolicy then
|
||||
self.state = {
|
||||
policy = mergePolicies(retrievedExternalPolicy, self.policyContext.presentationPolicy),
|
||||
}
|
||||
else
|
||||
self.setWithEmptyPolicy()
|
||||
componentLogger:trace("No app policy data available")
|
||||
end
|
||||
|
||||
self.onPolicyChanged = function(newExternalPolicy)
|
||||
self:setState({
|
||||
policy = mergePolicies(newExternalPolicy, self.policyContext.presentationPolicy),
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Connection:didMount()
|
||||
if self._context[appPolicyKey].staticExternalPolicy then
|
||||
return
|
||||
end
|
||||
self.connection = getPolicyImpl.onPolicyChanged(function(incomingExternalPolicy)
|
||||
componentLogger:trace("Received policy update from MemStorageService")
|
||||
self.onPolicyChanged(incomingExternalPolicy)
|
||||
end)
|
||||
end
|
||||
|
||||
function Connection:render()
|
||||
local policyProps = mapper(self.state.policy, self.props)
|
||||
local newProps = Cryo.Dictionary.join(self.props, policyProps)
|
||||
return Roact.createElement(component, newProps)
|
||||
end
|
||||
|
||||
function Connection:willUnmount()
|
||||
if self.connection then
|
||||
self.connection:Disconnect()
|
||||
end
|
||||
-- sometimes the callback will fire even after :Disconnect was called
|
||||
self.onPolicyChange = nil
|
||||
end
|
||||
|
||||
return Connection
|
||||
end
|
||||
end
|
||||
end
|
||||
+542
@@ -0,0 +1,542 @@
|
||||
return function()
|
||||
local Packages = script.Parent.Parent
|
||||
local Roact = require(Packages.Roact)
|
||||
local tutils = require(Packages.tutils)
|
||||
local Mock = require(Packages.Mock)
|
||||
local MagicMock = Mock.MagicMock
|
||||
|
||||
local fromMemStorageService = require(script.Parent.getPolicyImplementations.fromMemStorageService)
|
||||
|
||||
local Provider = require(script.Parent.Provider)
|
||||
local providerInstance = Provider()
|
||||
|
||||
local function checkForPropsAfterMounting(params)
|
||||
assert(params.expectedProps)
|
||||
assert(params.mapper)
|
||||
assert(params.policyProp)
|
||||
assert(params.connect)
|
||||
assert(params.provider)
|
||||
|
||||
params.shouldCheckPropsIf = params.shouldCheckPropsIf or function()
|
||||
return true
|
||||
end
|
||||
|
||||
local hasBaseComponentEverRendered = false
|
||||
local hasPropsEverBeenChecked = false
|
||||
|
||||
local baseComponent = function(props)
|
||||
hasBaseComponentEverRendered = true
|
||||
|
||||
if params.shouldCheckPropsIf() then
|
||||
hasPropsEverBeenChecked = true
|
||||
for propName, propValue in pairs(params.expectedProps) do
|
||||
if props[propName] ~= propValue then
|
||||
fail(string.format(
|
||||
"Expected baseComponent to have prop `%s` = `%s`." .. " " ..
|
||||
"Got: `%s` instead." .. " " ..
|
||||
"(Check the `mapper` function is correctly formatted)",
|
||||
propName,
|
||||
tostring(propValue),
|
||||
tostring(props[propName])
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Folder")
|
||||
end
|
||||
|
||||
local wrappedComponent = params.connect(params.mapper)(baseComponent)
|
||||
|
||||
local tree = Roact.createElement(params.provider, {
|
||||
policy = params.policyProp,
|
||||
policyData = params.policyDataProp,
|
||||
}, {
|
||||
wrappedComponent = Roact.createElement(wrappedComponent)
|
||||
})
|
||||
|
||||
local instance = Roact.mount(tree)
|
||||
if params.funcAfterMounting then
|
||||
params.funcAfterMounting()
|
||||
end
|
||||
Roact.unmount(instance)
|
||||
|
||||
expect(hasBaseComponentEverRendered).to.equal(true)
|
||||
expect(hasPropsEverBeenChecked).to.equal(true)
|
||||
end
|
||||
|
||||
describe("WHEN required", function()
|
||||
local connect = require(script.Parent.connect)
|
||||
it("SHOULD return a function", function()
|
||||
expect(connect).to.be.a("function")
|
||||
end)
|
||||
|
||||
describe("GIVEN a fromMemStorageServiceWithBehavior", function()
|
||||
local behavior = "mockBehavior"
|
||||
|
||||
describe("GIVEN empty dependencies", function()
|
||||
local getPolicyImpl = fromMemStorageService({
|
||||
HttpService = MagicMock.new(),
|
||||
MemStorageService = MagicMock.new(),
|
||||
})(behavior)
|
||||
local connectInstance = connect(getPolicyImpl)
|
||||
|
||||
it("SHOULD return a function", function()
|
||||
expect(connectInstance).to.be.a("function")
|
||||
end)
|
||||
|
||||
describe("GIVEN a static mapper function", function()
|
||||
local mapper = function()
|
||||
return {
|
||||
foo = "bar",
|
||||
}
|
||||
end
|
||||
local mappedConnection = connectInstance(mapper)
|
||||
|
||||
it("SHOULD return a function", function()
|
||||
expect(mappedConnection).to.be.a("function")
|
||||
end)
|
||||
|
||||
describe("GIVEN a component", function()
|
||||
local baseComponent = function()
|
||||
return Roact.createElement("Folder")
|
||||
end
|
||||
local wrappedComponent = mappedConnection(baseComponent)
|
||||
|
||||
it("SHOULD return a new component", function()
|
||||
expect(baseComponent).to.never.equal(wrappedComponent)
|
||||
end)
|
||||
|
||||
describe("GIVEN a Roact tree without a Provider", function()
|
||||
local tree = Roact.createElement(wrappedComponent)
|
||||
|
||||
it("SHOULD throw", function()
|
||||
expect(function()
|
||||
Roact.mount(tree)
|
||||
end).to.throw()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a Roact tree with a Provider", function()
|
||||
describe("GIVEN a nil policy prop", function()
|
||||
local tree = Roact.createElement(providerInstance, {
|
||||
policy = nil,
|
||||
}, {
|
||||
wrappedComponent = Roact.createElement(wrappedComponent)
|
||||
})
|
||||
|
||||
it("SHOULD throw", function()
|
||||
expect(function()
|
||||
Roact.mount(tree)
|
||||
end).to.throw()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN an empty policy prop", function()
|
||||
local tree = Roact.createElement(providerInstance, {
|
||||
policy = {},
|
||||
}, {
|
||||
wrappedComponent = Roact.createElement(wrappedComponent)
|
||||
})
|
||||
|
||||
it("SHOULD mount and unmount successfully", function()
|
||||
local instance = Roact.mount(tree)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN policy prop with a single (static) definition", function()
|
||||
local mockPolicy1 = function(_)
|
||||
return {
|
||||
isFeatureEnabled = "mockPolicy1Enabled",
|
||||
}
|
||||
end
|
||||
|
||||
it("SHOULD allow mapper to create props for base component", function()
|
||||
checkForPropsAfterMounting({
|
||||
connect = connectInstance,
|
||||
provider = providerInstance,
|
||||
policyProp = { mockPolicy1 },
|
||||
mapper = function(policy)
|
||||
return {
|
||||
isPolicy1FeatureEnabled = policy.isFeatureEnabled,
|
||||
}
|
||||
end,
|
||||
expectedProps = {
|
||||
isPolicy1FeatureEnabled = "mockPolicy1Enabled"
|
||||
},
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN policy prop with a multiple (static) definitions", function()
|
||||
local mockPolicy1 = function(_)
|
||||
return {
|
||||
isFeature1Enabled = 100,
|
||||
}
|
||||
end
|
||||
|
||||
local mockPolicy2 = function(_)
|
||||
return {
|
||||
isFeature2Enabled = 200,
|
||||
}
|
||||
end
|
||||
|
||||
it("SHOULD allow mapper to create props for base component", function()
|
||||
checkForPropsAfterMounting({
|
||||
connect = connectInstance,
|
||||
provider = providerInstance,
|
||||
policyProp = { mockPolicy1, mockPolicy2 },
|
||||
mapper = function(policy)
|
||||
return {
|
||||
isPolicy1FeatureEnabled = policy.isFeature1Enabled,
|
||||
isPolicy2FeatureEnabled = policy.isFeature2Enabled,
|
||||
}
|
||||
end,
|
||||
expectedProps = {
|
||||
isPolicy1FeatureEnabled = 100,
|
||||
isPolicy2FeatureEnabled = 200,
|
||||
},
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN MemStorageService can retrieve a string and HttpService can decode it", function()
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function(str)
|
||||
return {
|
||||
foo = "bar",
|
||||
}
|
||||
end
|
||||
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return "mockStorageData"
|
||||
end
|
||||
|
||||
local getPolicyImpl = fromMemStorageService({
|
||||
HttpService = mockHttpService,
|
||||
MemStorageService = mockMemStorageService,
|
||||
})(behavior)
|
||||
|
||||
local connectInstance = connect(getPolicyImpl)
|
||||
|
||||
describe("GIVEN policy prop with a single dynamic definition (read from MemStorageService)", function()
|
||||
local mockPolicy1 = function(policy)
|
||||
return {
|
||||
isFeatureEnabled = policy.foo,
|
||||
}
|
||||
end
|
||||
|
||||
it("SHOULD allow mapper to create props for base component", function()
|
||||
checkForPropsAfterMounting({
|
||||
connect = connectInstance,
|
||||
provider = providerInstance,
|
||||
policyProp = { mockPolicy1 },
|
||||
mapper = function(policy)
|
||||
return {
|
||||
isPolicy1FeatureEnabled = policy.isFeatureEnabled,
|
||||
}
|
||||
end,
|
||||
expectedProps = {
|
||||
-- bar is the result from HttpService's JSONDecode
|
||||
-- (mocking the pull from MemStorageService)
|
||||
isPolicy1FeatureEnabled = "bar"
|
||||
},
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN MemStorageService cannot retrieve a string", function()
|
||||
local mockHttpService = MagicMock.new()
|
||||
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
local getPolicyImpl = fromMemStorageService({
|
||||
HttpService = mockHttpService,
|
||||
MemStorageService = mockMemStorageService,
|
||||
})(behavior)
|
||||
|
||||
local connectInstance = connect(getPolicyImpl)
|
||||
|
||||
describe("GIVEN policy prop with a single dynamic definition (read from MemStorageService)", function()
|
||||
it("SHOULD allow mapper to create props for base component", function()
|
||||
local wasMockPolicy1EverCalled = false
|
||||
local mockPolicy1 = function(policy)
|
||||
wasMockPolicy1EverCalled = true
|
||||
|
||||
-- policy should be an empty table because
|
||||
-- MemStorageService is providing invalid values
|
||||
expect(tutils.shallowEqual(policy, {})).to.equal(true)
|
||||
return {
|
||||
isFeatureEnabled = policy and policy.foo,
|
||||
}
|
||||
end
|
||||
|
||||
checkForPropsAfterMounting({
|
||||
connect = connectInstance,
|
||||
provider = providerInstance,
|
||||
policyProp = { mockPolicy1 },
|
||||
mapper = function(policy)
|
||||
return {
|
||||
isPolicy1FeatureEnabled = policy.isFeatureEnabled,
|
||||
}
|
||||
end,
|
||||
expectedProps = {},
|
||||
})
|
||||
|
||||
expect(wasMockPolicy1EverCalled).to.equal(true)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN MemStorageService updates with a new string", function()
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function(_, jsonString)
|
||||
if jsonString == "mockJsonString" then
|
||||
return {
|
||||
isFeatureEnabled = "hello.world",
|
||||
}
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
|
||||
local getPolicyImpl = fromMemStorageService({
|
||||
HttpService = mockHttpService,
|
||||
MemStorageService = mockMemStorageService,
|
||||
})(behavior)
|
||||
|
||||
local connectInstance = connect(getPolicyImpl)
|
||||
|
||||
describe("GIVEN policy prop with a single dynamic definition (read from MemStorageService)", function()
|
||||
it("SHOULD allow mapper to create props for base component", function()
|
||||
local numberOfTimesMockPolicy1EverCalled = 0
|
||||
local mockPolicy1 = function(policy)
|
||||
numberOfTimesMockPolicy1EverCalled = numberOfTimesMockPolicy1EverCalled + 1
|
||||
|
||||
return {
|
||||
isFeatureEnabled = policy and policy.isFeatureEnabled,
|
||||
}
|
||||
end
|
||||
|
||||
checkForPropsAfterMounting({
|
||||
funcAfterMounting = function()
|
||||
updateMemStorageService:Fire("mockJsonString")
|
||||
end,
|
||||
connect = connectInstance,
|
||||
provider = providerInstance,
|
||||
policyProp = { mockPolicy1 },
|
||||
mapper = function(policy)
|
||||
return {
|
||||
isPolicy1FeatureEnabled = policy.isFeatureEnabled,
|
||||
}
|
||||
end,
|
||||
shouldCheckPropsIf = function()
|
||||
return numberOfTimesMockPolicy1EverCalled > 1
|
||||
end,
|
||||
expectedProps = {
|
||||
isPolicy1FeatureEnabled = "hello.world",
|
||||
},
|
||||
})
|
||||
|
||||
expect(numberOfTimesMockPolicy1EverCalled).to.equal(2)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN MemStorageService updates with a nil value", function()
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function(_, jsonString)
|
||||
if jsonString == "mockJsonString" then
|
||||
return {
|
||||
isFeatureEnabled = "hello.world",
|
||||
}
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
|
||||
local getPolicyImpl = fromMemStorageService({
|
||||
HttpService = mockHttpService,
|
||||
MemStorageService = mockMemStorageService,
|
||||
})(behavior)
|
||||
|
||||
local connectInstance = connect(getPolicyImpl)
|
||||
|
||||
describe("GIVEN policy prop with a single dynamic definition (read from MemStorageService)", function()
|
||||
it("SHOULD allow mapper to create props for base component", function()
|
||||
local numberOfTimesMockPolicy1EverCalled = 0
|
||||
local mockPolicy1 = function(policy)
|
||||
numberOfTimesMockPolicy1EverCalled = numberOfTimesMockPolicy1EverCalled + 1
|
||||
|
||||
return {
|
||||
isFeatureEnabled = policy and policy.isFeatureEnabled,
|
||||
}
|
||||
end
|
||||
|
||||
checkForPropsAfterMounting({
|
||||
funcAfterMounting = function()
|
||||
updateMemStorageService:Fire("mockJsonString")
|
||||
end,
|
||||
connect = connectInstance,
|
||||
provider = providerInstance,
|
||||
policyProp = { mockPolicy1 },
|
||||
mapper = function(policy)
|
||||
return {
|
||||
isPolicy1FeatureEnabled = policy.isFeatureEnabled,
|
||||
}
|
||||
end,
|
||||
shouldCheckPropsIf = function()
|
||||
return numberOfTimesMockPolicy1EverCalled > 1
|
||||
end,
|
||||
expectedProps = {
|
||||
isPolicy1FeatureEnabled = "hello.world",
|
||||
},
|
||||
})
|
||||
|
||||
expect(numberOfTimesMockPolicy1EverCalled).to.equal(2)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a policyData prop", function()
|
||||
local mockHttpService = MagicMock.new()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
|
||||
local getPolicyImpl = fromMemStorageService({
|
||||
HttpService = mockHttpService,
|
||||
MemStorageService = mockMemStorageService,
|
||||
})(behavior)
|
||||
|
||||
local connectInstance = connect(getPolicyImpl)
|
||||
local mockPolicy1 = function(policy)
|
||||
return {
|
||||
isFeatureEnabled = policy.mockIsFeatureEnabled,
|
||||
}
|
||||
end
|
||||
|
||||
it("SHOULD return policyData and not call MemStorageService", function()
|
||||
checkForPropsAfterMounting({
|
||||
connect = connectInstance,
|
||||
provider = providerInstance,
|
||||
policyProp = { mockPolicy1 },
|
||||
policyDataProp = {
|
||||
mockIsFeatureEnabled = "hello.world",
|
||||
},
|
||||
mapper = function(policy)
|
||||
return {
|
||||
isPolicy1FeatureEnabled = policy.isFeatureEnabled,
|
||||
}
|
||||
end,
|
||||
expectedProps = {
|
||||
isPolicy1FeatureEnabled = "hello.world",
|
||||
},
|
||||
})
|
||||
|
||||
local callsGetItem = Mock.getCalls(mockMemStorageService.GetItem)
|
||||
expect(#callsGetItem).to.equal(0)
|
||||
|
||||
local callsBindAndFire = Mock.getCalls(mockMemStorageService.BindAndFire)
|
||||
expect(#callsBindAndFire).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a policy prop that reads `foo`", function()
|
||||
local mockPolicy1 = function(policy)
|
||||
return {
|
||||
isFeatureEnabled = policy.foo,
|
||||
}
|
||||
end
|
||||
|
||||
describe("GIVEN getPolicyImpl with a static response", function()
|
||||
local getPolicyImpl = MagicMock.new()
|
||||
getPolicyImpl.read = function()
|
||||
return {
|
||||
foo = "bar",
|
||||
}
|
||||
end
|
||||
|
||||
local connectInstance = connect(getPolicyImpl)
|
||||
|
||||
it("SHOULD pass props to the lower component from the Promise resolution", function()
|
||||
checkForPropsAfterMounting({
|
||||
connect = connectInstance,
|
||||
provider = providerInstance,
|
||||
policyProp = { mockPolicy1 },
|
||||
mapper = function(policy)
|
||||
return {
|
||||
isPolicy1FeatureEnabled = policy.isFeatureEnabled,
|
||||
hasProblemsFetching = (policy.isFeatureEnabled == nil),
|
||||
}
|
||||
end,
|
||||
expectedProps = {
|
||||
isPolicy1FeatureEnabled = "bar",
|
||||
hasProblemsFetching = false,
|
||||
},
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN getPolicyImpl with a nil response", function()
|
||||
local getPolicyImpl = MagicMock.new()
|
||||
getPolicyImpl.read = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
local connectInstance = connect(getPolicyImpl)
|
||||
|
||||
it("SHOULD not be able to define `isPolicy1FeatureEnabled`", function()
|
||||
checkForPropsAfterMounting({
|
||||
connect = connectInstance,
|
||||
provider = providerInstance,
|
||||
policyProp = { mockPolicy1 },
|
||||
mapper = function(policy)
|
||||
return {
|
||||
isPolicy1FeatureEnabled = policy.isFeatureEnabled,
|
||||
hasProblemsFetching = (policy.isFeatureEnabled == nil)
|
||||
}
|
||||
end,
|
||||
expectedProps = {
|
||||
isPolicy1FeatureEnabled = nil,
|
||||
hasProblemsFetching = true,
|
||||
},
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
return function()
|
||||
local Packages = script.Parent.Parent
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local PolicyProvider = require(script.Parent)
|
||||
|
||||
it("SHOULD work", function()
|
||||
local presentationPolicy = function(externalPolicy)
|
||||
return {
|
||||
isFeatureEnabled = externalPolicy.isFeatureEnabled or false,
|
||||
}
|
||||
end
|
||||
|
||||
local externalPolicy = {
|
||||
isFeatureEnabled = true,
|
||||
}
|
||||
local getPolicyImpl = PolicyProvider.GetPolicyImplementations.Static(externalPolicy)
|
||||
local UniversalAppPolicyProvider = PolicyProvider.withGetPolicyImplementation(getPolicyImpl)
|
||||
|
||||
local function MyComponent(props)
|
||||
-- Values from PolicyProvider can be accessed just like regular props
|
||||
local isFeatureEnabled = props.isFeatureEnabled
|
||||
|
||||
return Roact.createElement("ScreenGui", nil, {
|
||||
Label = Roact.createElement("TextLabel", {
|
||||
-- ...and used in your components!
|
||||
Text = "isFeatureEnabled: " .. tostring(isFeatureEnabled),
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
-- `mapPolicyToProps` should return a table containing props that will be passed to
|
||||
-- your component!
|
||||
|
||||
-- `connect` returns a function, so we call that function, passing in our
|
||||
-- component, getting back a new component!
|
||||
MyComponent = UniversalAppPolicyProvider.connect(
|
||||
function(incomingPresentationPolicy, props)
|
||||
-- mapPolicyToProps is run every time the policy's state updates.
|
||||
-- It's also run whenever the component receives new props.
|
||||
return {
|
||||
isFeatureEnabled = incomingPresentationPolicy.isFeatureEnabled,
|
||||
}
|
||||
end
|
||||
)(MyComponent)
|
||||
|
||||
local app = Roact.createElement(UniversalAppPolicyProvider.Provider, {
|
||||
-- policy can be a list of PresentationalPolicies
|
||||
policy = { presentationPolicy },
|
||||
}, {
|
||||
Main = Roact.createElement(MyComponent),
|
||||
})
|
||||
|
||||
local folder = Instance.new("Folder")
|
||||
Roact.mount(app, folder)
|
||||
|
||||
-- if the label has the following string, we know
|
||||
-- everything is working!
|
||||
local Label = folder:FindFirstChild("Label", true)
|
||||
expect(Label).to.be.ok()
|
||||
expect(Label.Text).to.equal("isFeatureEnabled: true")
|
||||
end)
|
||||
end
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
local DefaultHttpService = game:GetService("HttpService")
|
||||
local DefaultMemStorageService = game:GetService("MemStorageService")
|
||||
local DefaultPlayersService = game:GetService("Players")
|
||||
|
||||
return function(dependencies)
|
||||
dependencies = dependencies or {}
|
||||
dependencies.HttpService = dependencies.HttpService or DefaultHttpService
|
||||
dependencies.MemStorageService = dependencies.MemStorageService or DefaultMemStorageService
|
||||
dependencies.PlayersService = dependencies.PlayersService or DefaultPlayersService
|
||||
|
||||
assert(dependencies.HttpService, "expected dependencies.HttpService")
|
||||
assert(dependencies.MemStorageService, "expected dependencies.MemStorageService")
|
||||
assert(dependencies.PlayersService, "expected dependencies.PlayersService")
|
||||
|
||||
local HttpService = dependencies.HttpService
|
||||
local MemStorageService = dependencies.MemStorageService
|
||||
local PlayersService = dependencies.PlayersService
|
||||
|
||||
return function(behavior)
|
||||
assert(behavior, "expected behavior")
|
||||
|
||||
local function getStoreKey()
|
||||
local userId = -1
|
||||
if PlayersService.LocalPlayer then
|
||||
userId = PlayersService.LocalPlayer.UserId
|
||||
end
|
||||
return "GUAC:" .. userId .. ":" .. behavior
|
||||
end
|
||||
|
||||
local connectionStoreKey
|
||||
local memStorageConnection
|
||||
local previouslyReadValue
|
||||
|
||||
local onPolicyChangedEvent = Instance.new("BindableEvent")
|
||||
|
||||
local function onPolicyUpdated(newPolicyData)
|
||||
-- MemStorageService will not de-duplicate the same item from storage
|
||||
if newPolicyData ~= previouslyReadValue then
|
||||
if newPolicyData and #newPolicyData > 0 then
|
||||
local success, decodedExternalPolicy = pcall(function()
|
||||
return HttpService:JSONDecode(newPolicyData)
|
||||
end)
|
||||
if success then
|
||||
-- never store garbage
|
||||
previouslyReadValue = newPolicyData
|
||||
onPolicyChangedEvent:Fire(decodedExternalPolicy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
read = function()
|
||||
local storeKey = getStoreKey()
|
||||
local policyData = MemStorageService:GetItem(storeKey)
|
||||
if policyData and #policyData > 0 then
|
||||
local success, policy = pcall(function()
|
||||
return HttpService:JSONDecode(policyData)
|
||||
end)
|
||||
if success then
|
||||
-- Be sure to store the json string
|
||||
previouslyReadValue = policyData
|
||||
return policy
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end,
|
||||
|
||||
onPolicyChanged = function(func)
|
||||
local storeKey = getStoreKey()
|
||||
|
||||
local connection = onPolicyChangedEvent.Event:Connect(func)
|
||||
|
||||
if memStorageConnection and connectionStoreKey == storeKey then
|
||||
-- Fire listener with existing value
|
||||
if previouslyReadValue then
|
||||
local success, policy = pcall(function()
|
||||
return HttpService:JSONDecode(previouslyReadValue)
|
||||
end)
|
||||
|
||||
if success then
|
||||
func(policy)
|
||||
end
|
||||
end
|
||||
else
|
||||
if memStorageConnection then
|
||||
memStorageConnection:Disconnect()
|
||||
end
|
||||
connectionStoreKey = storeKey
|
||||
memStorageConnection = MemStorageService:BindAndFire(storeKey, onPolicyUpdated)
|
||||
end
|
||||
|
||||
return connection
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
return function()
|
||||
local Packages = script.Parent.Parent.Parent
|
||||
local Mock = require(Packages.Mock)
|
||||
local MagicMock = Mock.MagicMock
|
||||
|
||||
local fromMemStorageService = require(script.Parent.fromMemStorageService)
|
||||
|
||||
describe("GIVEN a behavior", function()
|
||||
local behavior = "mockBehavior"
|
||||
describe("GIVEN a MemStorageService and HttpService with functional GetItem, BindAndFire and JSONDecode", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return "jsonExternalPolicy"
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
local initialValue = mockMemStorageService.GetItem()
|
||||
local connection = updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
|
||||
func(initialValue)
|
||||
return connection
|
||||
end
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function()
|
||||
return "decodedExternalPolicy"
|
||||
end
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
it("SHOULD return the policy when `read` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
it("SHOULD return a Disconnect-able object when `onPolicyChanged` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function() end)
|
||||
expect(result).to.be.ok()
|
||||
expect(result.Disconnect).to.be.ok()
|
||||
result:Disconnect()
|
||||
end)
|
||||
|
||||
it("SHOULD invoke passed in function with JSONDecode results when updateMemStorageService is fired", function()
|
||||
local wasEverCalled = false
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
wasEverCalled = true
|
||||
expect(value).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("jsonExternalPolicyUpdated")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(wasEverCalled).to.equal(true)
|
||||
end)
|
||||
|
||||
it("SHOULD NOT invoke passed in function with JSONDecode results "..
|
||||
"when updateMemStorageService is fired with the same value", function()
|
||||
local timesEverCalled = 0
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
timesEverCalled = timesEverCalled + 1
|
||||
expect(value).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("foo")
|
||||
updateMemStorageService:Fire("foo")
|
||||
updateMemStorageService:Fire("foo")
|
||||
updateMemStorageService:Fire("foo")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
-- Once for initial value and then once for foo
|
||||
expect(timesEverCalled).to.equal(2)
|
||||
end)
|
||||
|
||||
it("SHOULD NOT invoke passed in function with JSONDecode results "..
|
||||
"when updateMemStorageService is fired with the same value, while ignore nils", function()
|
||||
local timesEverCalled = 0
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
timesEverCalled = timesEverCalled + 1
|
||||
expect(value).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("bar")
|
||||
updateMemStorageService:Fire(nil)
|
||||
updateMemStorageService:Fire("bar")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
-- Once for initial value and then once for bar
|
||||
expect(timesEverCalled).to.equal(2)
|
||||
end)
|
||||
|
||||
it("SHOULD invoke multiple listeners when updateMemStorageService is fired", function()
|
||||
local listenerACalled = 0
|
||||
local connA = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
listenerACalled = listenerACalled + 1
|
||||
expect(value).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
local listenerBCalled = 0
|
||||
local connB = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
listenerBCalled = listenerBCalled + 1
|
||||
expect(value).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("baz")
|
||||
|
||||
connA:Disconnect()
|
||||
connB:Disconnect()
|
||||
|
||||
-- Once for initial value and then once for baz
|
||||
expect(listenerACalled).to.equal(2)
|
||||
expect(listenerBCalled).to.equal(2)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a functional MemStorageService and broken HttpService JSONDecode", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return "jsonExternalPolicy"
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
it("SHOULD return nil when `read` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.equal(nil)
|
||||
end)
|
||||
|
||||
it("SHOULD invoke passed in function with JSONDecode results when updateMemStorageService is fired", function()
|
||||
local wasEverCalled = false
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
wasEverCalled = true
|
||||
expect(value).to.equal(nil)
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("jsonExternalPolicyUpdated")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(wasEverCalled).to.equal(true)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a MemStorageService that always returns invalid results", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return nil
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
local mockHttpService = MagicMock.new()
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
it("SHOULD return nil when `read` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.equal(nil)
|
||||
end)
|
||||
|
||||
it("SHOULD never invoke passed function with JSONDecode results when updateMemStorageService is fired", function()
|
||||
local wasEverCalled = false
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
wasEverCalled = true
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire(nil)
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(wasEverCalled).to.equal(false)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a PlayersService with a missing LocalPlayer", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return "mockStorageItemJSON"
|
||||
end
|
||||
|
||||
local mockHttpService = MagicMock.new()
|
||||
local mockPlayersService = MagicMock.new()
|
||||
mockPlayersService.LocalPlayer = nil
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
PlayersService = mockPlayersService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
|
||||
it("SHOULD still return a value when `read` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a HttpService that can throw based on MemStorageService's GetItem", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return "garbage"
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function(_, value)
|
||||
if value == "validJson" then
|
||||
return { foo = true }
|
||||
end
|
||||
|
||||
error("invalid json")
|
||||
end
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
|
||||
it("SHOULD return nil since GetItem returns garbage", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.never.be.ok()
|
||||
end)
|
||||
|
||||
it("SHOULD never invoke passed function with JSONDecode results when updateMemStorageService is fired", function()
|
||||
local numberOfTimesCalled = 0
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
numberOfTimesCalled = numberOfTimesCalled + 1
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("validJson")
|
||||
updateMemStorageService:Fire("garbage")
|
||||
updateMemStorageService:Fire("validJson")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(numberOfTimesCalled).to.equal(1)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a new instance of fromMemStorageService", function()
|
||||
local validJson = "validJson"
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return validJson
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function(_, value)
|
||||
return "tableFromJson"
|
||||
end
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
|
||||
describe("WHEN read returns a value and onPolicyChanged returns the same value", function()
|
||||
it("SHOULD not fire onPolicyChanged function", function()
|
||||
local numberOfTimesCalled = 0
|
||||
fromMemStorageServiceInstance.onPolicyChanged(function()
|
||||
numberOfTimesCalled = numberOfTimesCalled + 1
|
||||
end)
|
||||
local _ = fromMemStorageServiceInstance.read()
|
||||
-- intentionally fire the same value that we read
|
||||
updateMemStorageService:Fire(validJson)
|
||||
|
||||
expect(numberOfTimesCalled).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
local DefaultPolicyService = game:GetService("PolicyService")
|
||||
local DefaultPlayersService = game:GetService("Players")
|
||||
|
||||
local root = script.Parent.Parent
|
||||
local Packages = root.Parent
|
||||
local Promise = require(Packages.Promise)
|
||||
|
||||
local Logger = require(root.Logger)
|
||||
|
||||
return function(dependencies)
|
||||
dependencies = dependencies or {}
|
||||
dependencies.PolicyService = dependencies.PolicyService or DefaultPolicyService
|
||||
dependencies.PlayersService = dependencies.PlayersService or DefaultPlayersService
|
||||
|
||||
assert(dependencies.PolicyService, "expected dependencies.PolicyService")
|
||||
assert(dependencies.PlayersService, "expected dependencies.PlayersService")
|
||||
|
||||
local PolicyService = dependencies.PolicyService
|
||||
local PlayersService = dependencies.PlayersService
|
||||
|
||||
return function()
|
||||
return {
|
||||
read = function()
|
||||
return nil
|
||||
end,
|
||||
|
||||
onPolicyChanged = function(func)
|
||||
local onPolicyChangedEvent = Instance.new("BindableEvent")
|
||||
|
||||
-- be sure to connect before our Promise can resolve
|
||||
local connection = onPolicyChangedEvent.Event:Connect(func)
|
||||
|
||||
Promise.new(function(resolve, reject)
|
||||
local player = PlayersService.LocalPlayer
|
||||
if player then
|
||||
local success, result = pcall(function()
|
||||
return PolicyService:GetPolicyInfoForPlayerAsync(player)
|
||||
end)
|
||||
if success then
|
||||
if result then
|
||||
resolve(result)
|
||||
else
|
||||
reject("GetPolicyInfoForPlayerAsync return nil value")
|
||||
end
|
||||
else
|
||||
reject("GetPolicyInfoForPlayerAsync had an error when calling")
|
||||
end
|
||||
else
|
||||
reject("LocalPlayer not found")
|
||||
end
|
||||
end):andThen(function(newPolicy)
|
||||
onPolicyChangedEvent:Fire(newPolicy)
|
||||
end):catch(function(errorString)
|
||||
Logger:warning("Could not fetch from PolicyService due to error: {}", errorString)
|
||||
end)
|
||||
|
||||
return connection
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
return function()
|
||||
local Packages = script.Parent.Parent.Parent
|
||||
local Mock = require(Packages.Mock)
|
||||
local MagicMock = Mock.MagicMock
|
||||
|
||||
local fromPolicyService = require(script.Parent.fromPolicyService)
|
||||
|
||||
describe("GIVEN a fully mocked dependencies", function()
|
||||
local mockPolicyService = MagicMock.new()
|
||||
mockPolicyService.GetPolicyInfoForPlayerAsync = function()
|
||||
return "mockGetPolicyInfoForPlayerAsyncResponse"
|
||||
end
|
||||
|
||||
local dependencies = {
|
||||
PolicyService = mockPolicyService,
|
||||
PlayersService = MagicMock.new(),
|
||||
}
|
||||
|
||||
describe("WHEN invoked", function()
|
||||
local fromPolicyServiceInstance = fromPolicyService(dependencies)()
|
||||
|
||||
it("SHOULD return a table", function()
|
||||
expect(fromPolicyServiceInstance).to.be.a("table")
|
||||
end)
|
||||
|
||||
it("SHOULD return a table with read and `onPolicyChanged` fields", function()
|
||||
expect(fromPolicyServiceInstance.read).to.be.a("function")
|
||||
expect(fromPolicyServiceInstance.onPolicyChanged).to.be.a("function")
|
||||
end)
|
||||
|
||||
describe("WHEN `onPolicyChanged` is invoked", function()
|
||||
local timesEverCalled = 0
|
||||
local lastValue
|
||||
local connection = fromPolicyServiceInstance.onPolicyChanged(function(value)
|
||||
timesEverCalled = timesEverCalled + 1
|
||||
lastValue = value
|
||||
end)
|
||||
it("SHOULD return a Disconnect-able object when `onPolicyChanged` is invoked", function()
|
||||
expect(connection).to.be.ok()
|
||||
expect(connection.Disconnect).to.be.ok()
|
||||
end)
|
||||
|
||||
it("SHOULD fire when it resolves with data", function()
|
||||
expect(timesEverCalled).to.equal(1)
|
||||
expect(lastValue).to.equal("mockGetPolicyInfoForPlayerAsyncResponse")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("WHEN `read` is invoked", function()
|
||||
local result = fromPolicyServiceInstance.read()
|
||||
it("SHOULD return nil", function()
|
||||
expect(result).to.never.be.ok()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a PlayersService with no LocalPlayer", function()
|
||||
local mockPlayersService = MagicMock.new()
|
||||
mockPlayersService.LocalPlayer = nil
|
||||
|
||||
local dependencies = {
|
||||
PolicyService = MagicMock.new(),
|
||||
PlayersService = mockPlayersService,
|
||||
}
|
||||
|
||||
describe("WHEN invoked", function()
|
||||
local fromPolicyServiceInstance = fromPolicyService(dependencies)()
|
||||
|
||||
it("SHOULD return a table", function()
|
||||
expect(fromPolicyServiceInstance).to.be.a("table")
|
||||
end)
|
||||
|
||||
describe("WHEN `onPolicyChanged` is invoked", function()
|
||||
local timesEverCalled = 0
|
||||
local connection = fromPolicyServiceInstance.onPolicyChanged(function(value)
|
||||
timesEverCalled = timesEverCalled + 1
|
||||
end)
|
||||
it("SHOULD return a Disconnect-able object when `onPolicyChanged` is invoked", function()
|
||||
expect(connection).to.be.ok()
|
||||
expect(connection.Disconnect).to.be.ok()
|
||||
end)
|
||||
|
||||
it("SHOULD never fire when it rejects", function()
|
||||
expect(timesEverCalled).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a PolicyService that throws", function()
|
||||
local mockPolicyService = MagicMock.new()
|
||||
mockPolicyService.GetPolicyInfoForPlayerAsync = function()
|
||||
error("Throw")
|
||||
end
|
||||
|
||||
local dependencies = {
|
||||
PolicyService = mockPolicyService,
|
||||
PlayersService = MagicMock.new(),
|
||||
}
|
||||
|
||||
describe("WHEN invoked", function()
|
||||
local fromPolicyServiceInstance = fromPolicyService(dependencies)()
|
||||
|
||||
it("SHOULD return a table", function()
|
||||
expect(fromPolicyServiceInstance).to.be.a("table")
|
||||
end)
|
||||
|
||||
describe("WHEN `onPolicyChanged` is invoked", function()
|
||||
local timesEverCalled = 0
|
||||
local connection = fromPolicyServiceInstance.onPolicyChanged(function(value)
|
||||
timesEverCalled = timesEverCalled + 1
|
||||
end)
|
||||
it("SHOULD return a Disconnect-able object when `onPolicyChanged` is invoked", function()
|
||||
expect(connection).to.be.ok()
|
||||
expect(connection.Disconnect).to.be.ok()
|
||||
end)
|
||||
|
||||
it("SHOULD never fire when it rejects", function()
|
||||
expect(timesEverCalled).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a PolicyService that returns nil", function()
|
||||
local mockPolicyService = MagicMock.new()
|
||||
mockPolicyService.GetPolicyInfoForPlayerAsync = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
local dependencies = {
|
||||
PolicyService = mockPolicyService,
|
||||
PlayersService = MagicMock.new(),
|
||||
}
|
||||
|
||||
describe("WHEN invoked", function()
|
||||
local fromPolicyServiceInstance = fromPolicyService(dependencies)()
|
||||
|
||||
it("SHOULD return a table", function()
|
||||
expect(fromPolicyServiceInstance).to.be.a("table")
|
||||
end)
|
||||
|
||||
describe("WHEN `onPolicyChanged` is invoked", function()
|
||||
local timesEverCalled = 0
|
||||
local connection = fromPolicyServiceInstance.onPolicyChanged(function(value)
|
||||
timesEverCalled = timesEverCalled + 1
|
||||
end)
|
||||
it("SHOULD return a Disconnect-able object when `onPolicyChanged` is invoked", function()
|
||||
expect(connection).to.be.ok()
|
||||
expect(connection.Disconnect).to.be.ok()
|
||||
end)
|
||||
|
||||
it("SHOULD never fire when it rejects", function()
|
||||
expect(timesEverCalled).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
local function noOpt()
|
||||
end
|
||||
|
||||
return function()
|
||||
return function(externalPolicy)
|
||||
assert(externalPolicy, "expected externalPolicy")
|
||||
|
||||
return {
|
||||
read = function()
|
||||
return externalPolicy
|
||||
end,
|
||||
|
||||
onPolicyChanged = function(func)
|
||||
func = func or noOpt
|
||||
return Instance.new("BindableEvent").Event:Connect(func)
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
return function()
|
||||
describe("WHEN required", function()
|
||||
local fromStaticSource = require(script.Parent.fromStaticSource)
|
||||
|
||||
it("SHOULD have the following interface", function()
|
||||
expect(fromStaticSource).to.be.a("function")
|
||||
end)
|
||||
|
||||
describe("WHEN invoked", function()
|
||||
local fromStaticSourceInstance = fromStaticSource()
|
||||
it("SHOULD have the following interface", function()
|
||||
expect(fromStaticSourceInstance).to.be.a("function")
|
||||
end)
|
||||
|
||||
describe("GIVEN a static value", function()
|
||||
local mockExternalPolicy = "mockExternalPolicy"
|
||||
|
||||
describe("WHEN invoked", function()
|
||||
local fromStaticSourceInstanceWithExternalPolicy = fromStaticSourceInstance(mockExternalPolicy)
|
||||
|
||||
it("SHOULD have the following interface", function()
|
||||
expect(fromStaticSourceInstanceWithExternalPolicy).to.be.a("table")
|
||||
expect(fromStaticSourceInstanceWithExternalPolicy.read).to.be.a("function")
|
||||
expect(fromStaticSourceInstanceWithExternalPolicy.onPolicyChanged).to.be.a("function")
|
||||
end)
|
||||
|
||||
describe("WHEN read is invoked", function()
|
||||
local result = fromStaticSourceInstanceWithExternalPolicy.read()
|
||||
it("SHOULD return the static value", function()
|
||||
expect(result).to.equal(mockExternalPolicy)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("WHEN onPolicyChanged is invoked", function()
|
||||
local result = fromStaticSourceInstanceWithExternalPolicy.onPolicyChanged()
|
||||
it("SHOULD return a Disconnect-able object", function()
|
||||
expect(result).to.be.ok()
|
||||
expect(result.Disconnect).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
local DefaultHttpService = game:GetService("HttpService")
|
||||
local DefaultMemStorageService = game:GetService("MemStorageService")
|
||||
local DefaultPlayersService = game:GetService("Players")
|
||||
|
||||
return function(dependencies)
|
||||
dependencies = dependencies or {}
|
||||
dependencies.HttpService = dependencies.HttpService or DefaultHttpService
|
||||
dependencies.MemStorageService = dependencies.MemStorageService or DefaultMemStorageService
|
||||
dependencies.PlayersService = dependencies.PlayersService or DefaultPlayersService
|
||||
|
||||
assert(dependencies.HttpService, "expected dependencies.HttpService")
|
||||
assert(dependencies.MemStorageService, "expected dependencies.MemStorageService")
|
||||
assert(dependencies.PlayersService, "expected dependencies.PlayersService")
|
||||
|
||||
local HttpService = dependencies.HttpService
|
||||
local MemStorageService = dependencies.MemStorageService
|
||||
local PlayersService = dependencies.PlayersService
|
||||
|
||||
return function(behavior)
|
||||
assert(behavior, "expected behavior")
|
||||
|
||||
local function getStoreKey()
|
||||
local userId = -1
|
||||
if PlayersService.LocalPlayer then
|
||||
userId = PlayersService.LocalPlayer.UserId
|
||||
end
|
||||
return "GUAC:" .. userId .. ":" .. behavior
|
||||
end
|
||||
|
||||
local previouslyReadValue
|
||||
|
||||
return {
|
||||
read = function()
|
||||
local storeKey = getStoreKey()
|
||||
local policyData = MemStorageService:GetItem(storeKey)
|
||||
if policyData and #policyData > 0 then
|
||||
local success, policy = pcall(function()
|
||||
return HttpService:JSONDecode(policyData)
|
||||
end)
|
||||
if success then
|
||||
-- Be sure to store the json string
|
||||
previouslyReadValue = policyData
|
||||
return policy
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end,
|
||||
|
||||
onPolicyChanged = function(func)
|
||||
local storeKey = getStoreKey()
|
||||
local memStorageConnection = MemStorageService:BindAndFire(storeKey, function(newPolicyData)
|
||||
-- MemStorageService will not du-duplicate the same item from storage
|
||||
if newPolicyData ~= previouslyReadValue then
|
||||
if newPolicyData and #newPolicyData > 0 then
|
||||
local success, decodedExternalPolicy = pcall(function()
|
||||
return HttpService:JSONDecode(newPolicyData)
|
||||
end)
|
||||
if success then
|
||||
-- never store garbage
|
||||
previouslyReadValue = newPolicyData
|
||||
if func then
|
||||
func(decodedExternalPolicy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
return memStorageConnection
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
return function()
|
||||
local Packages = script.Parent.Parent.Parent
|
||||
local Mock = require(Packages.Mock)
|
||||
local MagicMock = Mock.MagicMock
|
||||
|
||||
local fromMemStorageService = require(script.Parent.oldFromMemStorageService)
|
||||
|
||||
describe("GIVEN a behavior", function()
|
||||
local behavior = "mockBehavior"
|
||||
describe("GIVEN a MemStorageService and HttpService with functional GetItem, BindAndFire and JSONDecode", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return "jsonExternalPolicy"
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function()
|
||||
return "decodedExternalPolicy"
|
||||
end
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
it("SHOULD return the policy when `read` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
it("SHOULD return a Disconnect-able object when `onPolicyChanged` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged()
|
||||
expect(result).to.be.ok()
|
||||
expect(result.Disconnect).to.be.ok()
|
||||
result:Disconnect()
|
||||
end)
|
||||
|
||||
it("SHOULD invoke passed in function with JSONDecode results when updateMemStorageService is fired", function()
|
||||
local wasEverCalled = false
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
wasEverCalled = true
|
||||
expect(value).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("jsonExternalPolicyUpdated")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(wasEverCalled).to.equal(true)
|
||||
end)
|
||||
|
||||
it("SHOULD NOT invoke passed in function with JSONDecode results "..
|
||||
"when updateMemStorageService is fired with the same value", function()
|
||||
local timesEverCalled = 0
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
timesEverCalled = timesEverCalled + 1
|
||||
expect(value).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("foo")
|
||||
updateMemStorageService:Fire("foo")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(timesEverCalled).to.equal(1)
|
||||
end)
|
||||
|
||||
it("SHOULD NOT invoke passed in function with JSONDecode results "..
|
||||
"when updateMemStorageService is fired with the same value, while ignore nils", function()
|
||||
local timesEverCalled = 0
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
timesEverCalled = timesEverCalled + 1
|
||||
expect(value).to.equal("decodedExternalPolicy")
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("bar")
|
||||
updateMemStorageService:Fire(nil)
|
||||
updateMemStorageService:Fire("bar")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(timesEverCalled).to.equal(1)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a functional MemStorageService and broken HttpService JSONDecode", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return "jsonExternalPolicy"
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
it("SHOULD return nil when `read` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.equal(nil)
|
||||
end)
|
||||
|
||||
it("SHOULD invoke passed in function with JSONDecode results when updateMemStorageService is fired", function()
|
||||
local wasEverCalled = false
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
wasEverCalled = true
|
||||
expect(value).to.equal(nil)
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("jsonExternalPolicyUpdated")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(wasEverCalled).to.equal(true)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a MemStorageService that always returns invalid results", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return nil
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
local mockHttpService = MagicMock.new()
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
it("SHOULD return nil when `read` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.equal(nil)
|
||||
end)
|
||||
|
||||
it("SHOULD never invoke passed function with JSONDecode results when updateMemStorageService is fired", function()
|
||||
local wasEverCalled = false
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
wasEverCalled = true
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire(nil)
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(wasEverCalled).to.equal(false)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a PlayersService with a missing LocalPlayer", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return "mockStorageItemJSON"
|
||||
end
|
||||
|
||||
local mockHttpService = MagicMock.new()
|
||||
local mockPlayersService = MagicMock.new()
|
||||
mockPlayersService.LocalPlayer = nil
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
PlayersService = mockPlayersService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
|
||||
it("SHOULD still return a value when `read` is invoked", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a HttpService that can throw based on MemStorageService's GetItem", function()
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return "garbage"
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function(_, value)
|
||||
if value == "validJson" then
|
||||
return { foo = true }
|
||||
end
|
||||
|
||||
error("invalid json")
|
||||
end
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
|
||||
it("SHOULD return nil since GetItem returns garbage", function()
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
expect(result).to.never.be.ok()
|
||||
end)
|
||||
|
||||
it("SHOULD never invoke passed function with JSONDecode results when updateMemStorageService is fired", function()
|
||||
local numberOfTimesCalled = 0
|
||||
local result = fromMemStorageServiceInstance.onPolicyChanged(function(value)
|
||||
numberOfTimesCalled = numberOfTimesCalled + 1
|
||||
end)
|
||||
|
||||
updateMemStorageService:Fire("validJson")
|
||||
updateMemStorageService:Fire("garbage")
|
||||
updateMemStorageService:Fire("validJson")
|
||||
|
||||
result:Disconnect()
|
||||
|
||||
expect(numberOfTimesCalled).to.equal(1)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a new instance of fromMemStorageService", function()
|
||||
local validJson = "validJson"
|
||||
local mockMemStorageService = MagicMock.new()
|
||||
mockMemStorageService.GetItem = function()
|
||||
return validJson
|
||||
end
|
||||
local updateMemStorageService = Instance.new("BindableEvent")
|
||||
mockMemStorageService.BindAndFire = function(_, _, func)
|
||||
return updateMemStorageService.Event:Connect(function(value, a)
|
||||
func(value)
|
||||
end)
|
||||
end
|
||||
|
||||
local mockHttpService = MagicMock.new()
|
||||
mockHttpService.JSONDecode = function(_, value)
|
||||
return "tableFromJson"
|
||||
end
|
||||
|
||||
local fromMemStorageServiceInstance = fromMemStorageService({
|
||||
MemStorageService = mockMemStorageService,
|
||||
HttpService = mockHttpService,
|
||||
})(behavior)
|
||||
|
||||
describe("WHEN read returns a value and onPolicyChanged returns the same value", function()
|
||||
it("SHOULD not fire onPolicyChanged function", function()
|
||||
local numberOfTimesCalled = 0
|
||||
fromMemStorageServiceInstance.onPolicyChanged(function()
|
||||
numberOfTimesCalled = numberOfTimesCalled + 1
|
||||
end)
|
||||
local result = fromMemStorageServiceInstance.read()
|
||||
-- intentionally fire the same value that we read
|
||||
updateMemStorageService:Fire(validJson)
|
||||
|
||||
expect(numberOfTimesCalled).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
local connect = require(script.connect)
|
||||
local Provider = require(script.Provider)
|
||||
local Logger = require(script.Logger)
|
||||
|
||||
local GetPolicyImplementations = script.getPolicyImplementations
|
||||
local fromMemStorageService
|
||||
local fromPolicyService = require(GetPolicyImplementations.fromPolicyService)
|
||||
local fromStaticSource = require(GetPolicyImplementations.fromStaticSource)
|
||||
|
||||
local FFlagPolicyProviderFromMemStorageServiceFix = game:DefineFastFlag("PolicyProviderFromMemStorageServiceFix", false)
|
||||
|
||||
if FFlagPolicyProviderFromMemStorageServiceFix then
|
||||
fromMemStorageService = require(GetPolicyImplementations.fromMemStorageService)
|
||||
else
|
||||
fromMemStorageService = require(GetPolicyImplementations.oldFromMemStorageService)
|
||||
end
|
||||
|
||||
return {
|
||||
withGetPolicyImplementation = function(getPolicyImpl)
|
||||
-- assign default
|
||||
assert(getPolicyImpl.read, "expected getPolicyImpl to have `read` function")
|
||||
assert(getPolicyImpl.onPolicyChanged, "expected getPolicyImpl to have `onPolicyChanged` function")
|
||||
return {
|
||||
connect = connect(getPolicyImpl),
|
||||
Provider = Provider(),
|
||||
}
|
||||
end,
|
||||
|
||||
GetPolicyImplementations = {
|
||||
MemStorageService = fromMemStorageService(),
|
||||
PolicyService = fromPolicyService(),
|
||||
Static = fromStaticSource(),
|
||||
},
|
||||
|
||||
Logger = Logger,
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
return function()
|
||||
local Packages = script.Parent.Parent
|
||||
local tutils = require(Packages.tutils)
|
||||
local Cryo = require(Packages.Cryo)
|
||||
|
||||
-- Given an interface and a list of expected interface members,
|
||||
-- this expectation will fail the test
|
||||
local function expectInterface(actualInterface, expectedInterface)
|
||||
-- build a list of keys that are in the actualInterface
|
||||
local foundKeys = Cryo.List.map(expectedInterface, function(value)
|
||||
if actualInterface[value] then
|
||||
return value
|
||||
end
|
||||
end)
|
||||
|
||||
-- compare the list of keys found in actualInterface and expectedInterface
|
||||
local result = tutils.shallowEqual(foundKeys, expectedInterface)
|
||||
if not result then
|
||||
local differences = tutils.listDifferences(expectedInterface, foundKeys)
|
||||
fail(string.format("Expected interface missing the following: %s", tutils.toString(differences)))
|
||||
end
|
||||
end
|
||||
|
||||
describe("WHEN require is invoked", function()
|
||||
local PolicyProvider = require(script.Parent)
|
||||
|
||||
it("SHOULD have the following public interface", function()
|
||||
expectInterface(PolicyProvider, { "withGetPolicyImplementation", "GetPolicyImplementations", "Logger" })
|
||||
end)
|
||||
|
||||
it("SHOULD have all GetPolicyImplementations", function()
|
||||
expectInterface(PolicyProvider.GetPolicyImplementations, { "MemStorageService", "PolicyService", "Static" })
|
||||
end)
|
||||
|
||||
describe("WHEN withGetPolicyImplementation is invoked", function()
|
||||
describe("GIVEN a stubbed GetPolicyImpl", function()
|
||||
local stubbedGetPolicyImpl = {
|
||||
read = function()
|
||||
end,
|
||||
onPolicyChanged = function()
|
||||
end,
|
||||
}
|
||||
it("SHOULD have the following public interface", function()
|
||||
local initializedProvider = PolicyProvider.withGetPolicyImplementation(stubbedGetPolicyImpl)
|
||||
expectInterface(initializedProvider, { "connect", "Provider" })
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a nothing", function()
|
||||
it("SHOULD throw", function()
|
||||
expect(function()
|
||||
PolicyProvider.withGetPolicyImplementation()
|
||||
end).to.throw()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a GetPolicyImpl without a read", function()
|
||||
local withoutRead = {
|
||||
onPolicyChanged = function()
|
||||
end
|
||||
}
|
||||
it("SHOULD throw", function()
|
||||
expect(function()
|
||||
PolicyProvider.withGetPolicyImplementation(withoutRead)
|
||||
end).to.throw()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("GIVEN a GetPolicyImpl without a onPolicyChanged", function()
|
||||
local withoutOnPolicyChanged = {
|
||||
read = function()
|
||||
end
|
||||
}
|
||||
it("SHOULD throw", function()
|
||||
expect(function()
|
||||
PolicyProvider.withGetPolicyImplementation(withoutOnPolicyChanged)
|
||||
end).to.throw()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+12
@@ -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