add gs
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
local Roact = require(script.Parent.Parent.Roact)
|
||||
|
||||
local storeKey = require(script.Parent.storeKey)
|
||||
|
||||
local StoreProvider = Roact.Component:extend("StoreProvider")
|
||||
|
||||
function StoreProvider:init(props)
|
||||
local store = props.store
|
||||
|
||||
if store == nil then
|
||||
error("Error initializing StoreProvider. Expected a `store` prop to be a Rodux store.")
|
||||
end
|
||||
|
||||
self._context[storeKey] = store
|
||||
end
|
||||
|
||||
function StoreProvider:render()
|
||||
return Roact.oneChild(self.props[Roact.Children])
|
||||
end
|
||||
|
||||
return StoreProvider
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
return function()
|
||||
local StoreProvider = require(script.Parent.StoreProvider)
|
||||
|
||||
local Roact = require(script.Parent.Parent.Roact)
|
||||
local Rodux = require(script.Parent.Parent.Rodux)
|
||||
|
||||
it("should be instantiable as a component", function()
|
||||
local store = Rodux.Store.new(function()
|
||||
return 0
|
||||
end)
|
||||
local element = Roact.createElement(StoreProvider, {
|
||||
store = store
|
||||
})
|
||||
|
||||
expect(element).to.be.ok()
|
||||
|
||||
local handle = Roact.mount(element, nil, "StoreProvider-test")
|
||||
|
||||
Roact.unmount(handle)
|
||||
store:destruct()
|
||||
end)
|
||||
|
||||
it("should expect a 'store' prop", function()
|
||||
local element = Roact.createElement(StoreProvider)
|
||||
|
||||
expect(function()
|
||||
Roact.mount(element)
|
||||
end).to.throw()
|
||||
end)
|
||||
end
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
--[[
|
||||
A 'Symbol' is an opaque marker type that can be used to signify unique
|
||||
statuses. Symbols have the type 'userdata', but when printed to the console,
|
||||
the name of the symbol is shown.
|
||||
]]
|
||||
|
||||
local Symbol = {}
|
||||
|
||||
--[[
|
||||
Creates a Symbol with the given name.
|
||||
|
||||
When printed or coerced to a string, the symbol will turn into the string
|
||||
given as its name.
|
||||
]]
|
||||
function Symbol.named(name)
|
||||
assert(type(name) == "string", "Symbols must be created using a string name!")
|
||||
|
||||
local self = newproxy(true)
|
||||
|
||||
local wrappedName = ("Symbol(%s)"):format(name)
|
||||
|
||||
getmetatable(self).__tostring = function()
|
||||
return wrappedName
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[
|
||||
Create an unnamed Symbol. Usually, you should create a named Symbol using
|
||||
Symbol.named(name)
|
||||
]]
|
||||
function Symbol.unnamed()
|
||||
local self = newproxy(true)
|
||||
|
||||
getmetatable(self).__tostring = function()
|
||||
return "Unnamed Symbol"
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return Symbol
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
return function()
|
||||
local Symbol = require(script.Parent.Symbol)
|
||||
|
||||
describe("named", function()
|
||||
it("should give an opaque object", function()
|
||||
local symbol = Symbol.named("foo")
|
||||
|
||||
expect(symbol).to.be.a("userdata")
|
||||
end)
|
||||
|
||||
it("should coerce to the given name", function()
|
||||
local symbol = Symbol.named("foo")
|
||||
|
||||
expect(tostring(symbol):find("foo")).to.be.ok()
|
||||
end)
|
||||
|
||||
it("should be unique when constructed", function()
|
||||
local symbolA = Symbol.named("abc")
|
||||
local symbolB = Symbol.named("abc")
|
||||
|
||||
expect(symbolA).never.to.equal(symbolB)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("unnamed", function()
|
||||
it("should give an opaque object", function()
|
||||
local symbol = Symbol.unnamed()
|
||||
|
||||
expect(symbol).to.be.a("userdata")
|
||||
end)
|
||||
|
||||
it("should coerce to some string", function()
|
||||
local symbol = Symbol.unnamed()
|
||||
|
||||
expect(tostring(symbol)).to.be.a("string")
|
||||
end)
|
||||
|
||||
it("should be unique when constructed", function()
|
||||
local symbolA = Symbol.unnamed()
|
||||
local symbolB = Symbol.unnamed()
|
||||
|
||||
expect(symbolA).never.to.equal(symbolB)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
return {
|
||||
newConnectionOrder = true,
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
local Roact = require(script.Parent.Parent.Roact)
|
||||
local getStore = require(script.Parent.getStore)
|
||||
local shallowEqual = require(script.Parent.shallowEqual)
|
||||
local join = require(script.Parent.join)
|
||||
|
||||
local TempConfig = require(script.Parent.TempConfig)
|
||||
|
||||
--[[
|
||||
Formats a multi-line message with printf-style placeholders.
|
||||
]]
|
||||
local function formatMessage(lines, parameters)
|
||||
return table.concat(lines, "\n"):format(unpack(parameters or {}))
|
||||
end
|
||||
|
||||
local function noop()
|
||||
return nil
|
||||
end
|
||||
|
||||
--[[
|
||||
The stateUpdater accepts props when they update and computes the
|
||||
complete set of props that should be passed to the wrapped component.
|
||||
|
||||
Each connected component will have a stateUpdater created for it.
|
||||
|
||||
stateUpdater is put into the component's state in order for
|
||||
getDerivedStateFromProps to be able to access it. It is not mutated.
|
||||
]]
|
||||
local function makeStateUpdater(store)
|
||||
return function(nextProps, prevState, mappedStoreState)
|
||||
-- The caller can optionally provide mappedStoreState if it needed that
|
||||
-- value beforehand. Doing so is purely an optimization.
|
||||
if mappedStoreState == nil then
|
||||
mappedStoreState = prevState.mapStateToProps(store:getState(), nextProps)
|
||||
end
|
||||
|
||||
local propsForChild = join(nextProps, mappedStoreState, prevState.mappedStoreDispatch)
|
||||
|
||||
return {
|
||||
mappedStoreState = mappedStoreState,
|
||||
propsForChild = propsForChild,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
mapStateToProps:
|
||||
(storeState, props) -> partialProps
|
||||
OR
|
||||
() -> (storeState, props) -> partialProps
|
||||
mapDispatchToProps: (dispatch) -> partialProps
|
||||
]]
|
||||
local function connect(mapStateToPropsOrThunk, mapDispatchToProps)
|
||||
local connectTrace = debug.traceback()
|
||||
|
||||
if mapStateToPropsOrThunk ~= nil then
|
||||
assert(typeof(mapStateToPropsOrThunk) == "function", "mapStateToProps must be a function or nil!")
|
||||
else
|
||||
mapStateToPropsOrThunk = noop
|
||||
end
|
||||
|
||||
if mapDispatchToProps ~= nil then
|
||||
assert(typeof(mapDispatchToProps) == "function", "mapDispatchToProps must be a function or nil!")
|
||||
else
|
||||
mapDispatchToProps = noop
|
||||
end
|
||||
|
||||
return function(innerComponent)
|
||||
if innerComponent == nil then
|
||||
local message = formatMessage({
|
||||
"connect returns a function that must be passed a component.",
|
||||
"Check the connection at:",
|
||||
"%s",
|
||||
}, {
|
||||
connectTrace,
|
||||
})
|
||||
|
||||
error(message, 2)
|
||||
end
|
||||
|
||||
local componentName = ("RoduxConnection(%s)"):format(tostring(innerComponent))
|
||||
|
||||
local Connection = Roact.Component:extend(componentName)
|
||||
|
||||
function Connection.getDerivedStateFromProps(nextProps, prevState)
|
||||
if prevState.stateUpdater ~= nil then
|
||||
return prevState.stateUpdater(nextProps, prevState)
|
||||
end
|
||||
end
|
||||
|
||||
function Connection:createStoreConnection()
|
||||
self.storeChangedConnection = self.store.changed:connect(function(storeState)
|
||||
self:setState(function(prevState, props)
|
||||
local mappedStoreState = prevState.mapStateToProps(storeState, props)
|
||||
|
||||
-- We run this check here so that we only check shallow
|
||||
-- equality with the result of mapStateToProps, and not the
|
||||
-- other props that could be passed through the connector.
|
||||
if shallowEqual(mappedStoreState, prevState.mappedStoreState) then
|
||||
return nil
|
||||
end
|
||||
|
||||
return prevState.stateUpdater(props, prevState, mappedStoreState)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function Connection:init()
|
||||
self.store = getStore(self)
|
||||
|
||||
if self.store == nil then
|
||||
local message = formatMessage({
|
||||
"Cannot initialize Roact-Rodux connection without being a descendent of StoreProvider!",
|
||||
"Tried to wrap component %q",
|
||||
"Make sure there is a StoreProvider above this component in the tree.",
|
||||
}, {
|
||||
tostring(innerComponent),
|
||||
})
|
||||
|
||||
error(message)
|
||||
end
|
||||
|
||||
local storeState = self.store:getState()
|
||||
|
||||
local mapStateToProps = mapStateToPropsOrThunk
|
||||
local mappedStoreState = mapStateToProps(storeState, self.props)
|
||||
|
||||
-- mapStateToPropsOrThunk can return a function instead of a state
|
||||
-- value. In this variant, we keep that value as mapStateToProps
|
||||
-- instead of the original mapStateToProps. This matches react-redux
|
||||
-- and enables connectors to keep instance-level state.
|
||||
if typeof(mappedStoreState) == "function" then
|
||||
mapStateToProps = mappedStoreState
|
||||
mappedStoreState = mapStateToProps(storeState, self.props)
|
||||
end
|
||||
|
||||
if mappedStoreState ~= nil and typeof(mappedStoreState) ~= "table" then
|
||||
local message = formatMessage({
|
||||
"mapStateToProps must either return a table, or return another function that returns a table.",
|
||||
"Instead, it returned %q, which is of type %s.",
|
||||
}, {
|
||||
tostring(mappedStoreState),
|
||||
typeof(mappedStoreState),
|
||||
})
|
||||
|
||||
error(message)
|
||||
end
|
||||
|
||||
local mappedStoreDispatch = mapDispatchToProps(function(...)
|
||||
return self.store:dispatch(...)
|
||||
end)
|
||||
|
||||
local stateUpdater = makeStateUpdater(self.store)
|
||||
|
||||
self.state = {
|
||||
-- Combines props, mappedStoreDispatch, and the result of
|
||||
-- mapStateToProps into propsForChild. Stored in state so that
|
||||
-- getDerivedStateFromProps can access it.
|
||||
stateUpdater = stateUpdater,
|
||||
|
||||
-- Used by the store changed connection and stateUpdater to
|
||||
-- construct propsForChild.
|
||||
mapStateToProps = mapStateToProps,
|
||||
|
||||
-- Used by stateUpdater to construct propsForChild.
|
||||
mappedStoreDispatch = mappedStoreDispatch,
|
||||
|
||||
-- Passed directly into the component that Connection is
|
||||
-- wrapping.
|
||||
propsForChild = nil,
|
||||
}
|
||||
|
||||
local extraState = stateUpdater(self.props, self.state, mappedStoreState)
|
||||
|
||||
for key, value in pairs(extraState) do
|
||||
self.state[key] = value
|
||||
end
|
||||
|
||||
if TempConfig.newConnectionOrder then
|
||||
self:createStoreConnection()
|
||||
end
|
||||
end
|
||||
|
||||
function Connection:didMount()
|
||||
if not TempConfig.newConnectionOrder then
|
||||
self:createStoreConnection()
|
||||
end
|
||||
end
|
||||
|
||||
function Connection:willUnmount()
|
||||
self.storeChangedConnection:disconnect()
|
||||
end
|
||||
|
||||
function Connection:render()
|
||||
return Roact.createElement(innerComponent, self.state.propsForChild)
|
||||
end
|
||||
|
||||
return Connection
|
||||
end
|
||||
end
|
||||
|
||||
return connect
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
return function()
|
||||
local connect = require(script.Parent.connect)
|
||||
|
||||
local StoreProvider = require(script.Parent.StoreProvider)
|
||||
|
||||
local Roact = require(script.Parent.Parent.Roact)
|
||||
local Rodux = require(script.Parent.Parent.Rodux)
|
||||
|
||||
local TempConfig = require(script.Parent.TempConfig)
|
||||
|
||||
local function noop()
|
||||
return nil
|
||||
end
|
||||
|
||||
local function NoopComponent()
|
||||
return nil
|
||||
end
|
||||
|
||||
local function countReducer(state, action)
|
||||
state = state or 0
|
||||
|
||||
if action.type == "increment" then
|
||||
return state + 1
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
|
||||
local reducer = Rodux.combineReducers({
|
||||
count = countReducer,
|
||||
})
|
||||
|
||||
describe("Argument validation", function()
|
||||
it("should accept no arguments", function()
|
||||
connect()
|
||||
end)
|
||||
|
||||
it("should accept one function", function()
|
||||
connect(noop)
|
||||
end)
|
||||
|
||||
it("should accept two functions", function()
|
||||
connect(noop, noop)
|
||||
end)
|
||||
|
||||
it("should accept only the second function", function()
|
||||
connect(nil, function() end)
|
||||
end)
|
||||
|
||||
it("should throw if not passed a component", function()
|
||||
local selector = function(store)
|
||||
return {}
|
||||
end
|
||||
|
||||
expect(function()
|
||||
connect(selector)(nil)
|
||||
end).to.throw()
|
||||
end)
|
||||
end)
|
||||
|
||||
it("should throw if not mounted under a StoreProvider", function()
|
||||
local ConnectedSomeComponent = connect()(NoopComponent)
|
||||
|
||||
expect(function()
|
||||
Roact.mount(Roact.createElement(ConnectedSomeComponent))
|
||||
end).to.throw()
|
||||
end)
|
||||
|
||||
it("should accept a higher-order function mapStateToProps", function()
|
||||
local function mapStateToProps()
|
||||
return function(state)
|
||||
return {
|
||||
count = state.count,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local ConnectedSomeComponent = connect(mapStateToProps)(NoopComponent)
|
||||
|
||||
local store = Rodux.Store.new(reducer)
|
||||
local tree = Roact.createElement(StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
someComponent = Roact.createElement(ConnectedSomeComponent),
|
||||
})
|
||||
|
||||
local handle = Roact.mount(tree)
|
||||
|
||||
Roact.unmount(handle)
|
||||
end)
|
||||
|
||||
it("should not accept a higher-order mapStateToProps that returns a non-table value", function()
|
||||
local function mapStateToProps()
|
||||
return function(state)
|
||||
return "nope"
|
||||
end
|
||||
end
|
||||
|
||||
local ConnectedSomeComponent = connect(mapStateToProps)(NoopComponent)
|
||||
|
||||
local store = Rodux.Store.new(reducer)
|
||||
local tree = Roact.createElement(StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
someComponent = Roact.createElement(ConnectedSomeComponent),
|
||||
})
|
||||
|
||||
expect(function()
|
||||
Roact.mount(tree)
|
||||
end).to.throw()
|
||||
end)
|
||||
|
||||
it("should not accept a mapStateToProps that returns a non-table value", function()
|
||||
local function mapStateToProps()
|
||||
return "nah"
|
||||
end
|
||||
|
||||
local ConnectedSomeComponent = connect(mapStateToProps)(NoopComponent)
|
||||
|
||||
local store = Rodux.Store.new(reducer)
|
||||
local tree = Roact.createElement(StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
someComponent = Roact.createElement(ConnectedSomeComponent),
|
||||
})
|
||||
|
||||
expect(function()
|
||||
Roact.mount(tree)
|
||||
end).to.throw()
|
||||
end)
|
||||
|
||||
it("should abort renders when mapStateToProps returns the same data", function()
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
count = state.count,
|
||||
}
|
||||
end
|
||||
|
||||
local renderCount = 0
|
||||
local function SomeComponent(props)
|
||||
renderCount = renderCount + 1
|
||||
end
|
||||
|
||||
local ConnectedSomeComponent = connect(mapStateToProps)(SomeComponent)
|
||||
|
||||
local store = Rodux.Store.new(reducer)
|
||||
local tree = Roact.createElement(StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
someComponent = Roact.createElement(ConnectedSomeComponent),
|
||||
})
|
||||
|
||||
local handle = Roact.mount(tree)
|
||||
|
||||
expect(renderCount).to.equal(1)
|
||||
|
||||
store:dispatch({ type = "an unknown action" })
|
||||
store:flush()
|
||||
|
||||
expect(renderCount).to.equal(1)
|
||||
|
||||
store:dispatch({ type = "increment" })
|
||||
store:flush()
|
||||
|
||||
expect(renderCount).to.equal(2)
|
||||
|
||||
Roact.unmount(handle)
|
||||
end)
|
||||
|
||||
it("should only call mapDispatchToProps once and never re-render if no mapStateToProps was passed", function()
|
||||
local dispatchCount = 0
|
||||
local mapDispatchToProps = function(dispatch)
|
||||
dispatchCount = dispatchCount + 1
|
||||
|
||||
return {
|
||||
increment = function()
|
||||
return dispatch({ type = "increment" })
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local renderCount = 0
|
||||
local function SomeComponent(props)
|
||||
renderCount = renderCount + 1
|
||||
end
|
||||
|
||||
local ConnectedSomeComponent = connect(nil, mapDispatchToProps)(SomeComponent)
|
||||
|
||||
local store = Rodux.Store.new(reducer)
|
||||
local tree = Roact.createElement(StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
someComponent = Roact.createElement(ConnectedSomeComponent),
|
||||
})
|
||||
|
||||
local handle = Roact.mount(tree)
|
||||
|
||||
expect(dispatchCount).to.equal(1)
|
||||
expect(renderCount).to.equal(1)
|
||||
|
||||
store:dispatch({ type = "an unknown action" })
|
||||
store:flush()
|
||||
|
||||
expect(dispatchCount).to.equal(1)
|
||||
expect(renderCount).to.equal(1)
|
||||
|
||||
store:dispatch({ type = "increment" })
|
||||
store:flush()
|
||||
|
||||
expect(dispatchCount).to.equal(1)
|
||||
expect(renderCount).to.equal(1)
|
||||
|
||||
Roact.unmount(handle)
|
||||
end)
|
||||
|
||||
it("should return result values from the dispatch passed to mapDispatchToProps", function()
|
||||
local function reducer()
|
||||
return 0
|
||||
end
|
||||
|
||||
local function fiveThunk()
|
||||
return 5
|
||||
end
|
||||
|
||||
local dispatch
|
||||
local function SomeComponent(props)
|
||||
dispatch = props.dispatch
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatch = dispatch
|
||||
}
|
||||
end
|
||||
|
||||
local ConnectedSomeComponent = connect(nil, mapDispatchToProps)(SomeComponent)
|
||||
|
||||
-- We'll use the thunk middleware, as it should always return its result
|
||||
local store = Rodux.Store.new(reducer, nil, { Rodux.thunkMiddleware })
|
||||
local tree = Roact.createElement(StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
someComponent = Roact.createElement(ConnectedSomeComponent)
|
||||
})
|
||||
|
||||
local handle = Roact.mount(tree)
|
||||
|
||||
expect(dispatch).to.be.a("function")
|
||||
expect(dispatch(fiveThunk)).to.equal(5)
|
||||
|
||||
Roact.unmount(handle)
|
||||
end)
|
||||
|
||||
it("should render parent elements before children", function()
|
||||
local oldNewConnectionOrder = TempConfig.newConnectionOrder
|
||||
TempConfig.newConnectionOrder = true
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
count = state.count,
|
||||
}
|
||||
end
|
||||
|
||||
local childWasRenderedFirst = false
|
||||
|
||||
local function ChildComponent(props)
|
||||
if props.count > props.parentCount then
|
||||
childWasRenderedFirst = true
|
||||
end
|
||||
end
|
||||
|
||||
local ConnectedChildComponent = connect(mapStateToProps)(ChildComponent)
|
||||
|
||||
local function ParentComponent(props)
|
||||
return Roact.createElement(ConnectedChildComponent, {
|
||||
parentCount = props.count,
|
||||
})
|
||||
end
|
||||
|
||||
local ConnectedParentComponent = connect(mapStateToProps)(ParentComponent)
|
||||
|
||||
local store = Rodux.Store.new(reducer)
|
||||
local tree = Roact.createElement(StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
parent = Roact.createElement(ConnectedParentComponent),
|
||||
})
|
||||
|
||||
local handle = Roact.mount(tree)
|
||||
|
||||
store:dispatch({ type = "increment" })
|
||||
store:flush()
|
||||
|
||||
store:dispatch({ type = "increment" })
|
||||
store:flush()
|
||||
|
||||
Roact.unmount(handle)
|
||||
|
||||
expect(childWasRenderedFirst).to.equal(false)
|
||||
|
||||
TempConfig.newConnectionOrder = oldNewConnectionOrder
|
||||
end)
|
||||
|
||||
it("should render child elements before children when TempConfig.newConnectionOrder is false", function()
|
||||
local oldNewConnectionOrder = TempConfig.newConnectionOrder
|
||||
TempConfig.newConnectionOrder = false
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
count = state.count,
|
||||
}
|
||||
end
|
||||
|
||||
local childWasRenderedFirst = false
|
||||
|
||||
local function ChildComponent(props)
|
||||
if props.count > props.parentCount then
|
||||
childWasRenderedFirst = true
|
||||
end
|
||||
end
|
||||
|
||||
local ConnectedChildComponent = connect(mapStateToProps)(ChildComponent)
|
||||
|
||||
local function ParentComponent(props)
|
||||
return Roact.createElement(ConnectedChildComponent, {
|
||||
parentCount = props.count,
|
||||
})
|
||||
end
|
||||
|
||||
local ConnectedParentComponent = connect(mapStateToProps)(ParentComponent)
|
||||
|
||||
local store = Rodux.Store.new(reducer)
|
||||
local tree = Roact.createElement(StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
parent = Roact.createElement(ConnectedParentComponent),
|
||||
})
|
||||
|
||||
local handle = Roact.mount(tree)
|
||||
|
||||
store:dispatch({ type = "increment" })
|
||||
store:flush()
|
||||
|
||||
store:dispatch({ type = "increment" })
|
||||
store:flush()
|
||||
|
||||
Roact.unmount(handle)
|
||||
|
||||
expect(childWasRenderedFirst).to.equal(true)
|
||||
|
||||
TempConfig.newConnectionOrder = oldNewConnectionOrder
|
||||
end)
|
||||
end
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local storeKey = require(script.Parent.storeKey)
|
||||
|
||||
local function getStore(componentInstance)
|
||||
return componentInstance._context[storeKey]
|
||||
end
|
||||
|
||||
return getStore
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
return function()
|
||||
local Roact = require(script.Parent.Parent.Roact)
|
||||
local Rodux = require(script.Parent.Parent.Rodux)
|
||||
|
||||
local StoreProvider = require(script.Parent.StoreProvider)
|
||||
|
||||
local getStore = require(script.Parent.getStore)
|
||||
|
||||
it("should return the store when present", function()
|
||||
local function reducer()
|
||||
return 0
|
||||
end
|
||||
|
||||
local store = Rodux.Store.new(reducer)
|
||||
local consumedStore = nil
|
||||
|
||||
local StoreConsumer = Roact.Component:extend("StoreConsumer")
|
||||
|
||||
function StoreConsumer:init()
|
||||
consumedStore = getStore(self)
|
||||
end
|
||||
|
||||
function StoreConsumer:render()
|
||||
return nil
|
||||
end
|
||||
|
||||
local tree = Roact.createElement(StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
Consumer = Roact.createElement(StoreConsumer),
|
||||
})
|
||||
|
||||
local handle = Roact.mount(tree)
|
||||
|
||||
expect(consumedStore).to.equal(store)
|
||||
|
||||
Roact.unmount(handle)
|
||||
store:destruct()
|
||||
end)
|
||||
|
||||
it("should return nil when the store is not present", function()
|
||||
-- Use a non-nil value to know for sure if StoreConsumer:init was called
|
||||
local consumedStore = 6
|
||||
|
||||
local StoreConsumer = Roact.Component:extend("StoreConsumer")
|
||||
|
||||
function StoreConsumer:init()
|
||||
consumedStore = getStore(self)
|
||||
end
|
||||
|
||||
function StoreConsumer:render()
|
||||
return nil
|
||||
end
|
||||
|
||||
local tree = Roact.createElement(StoreConsumer)
|
||||
local handle = Roact.mount(tree)
|
||||
|
||||
expect(consumedStore).to.equal(nil)
|
||||
|
||||
Roact.unmount(handle)
|
||||
end)
|
||||
end
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
local StoreProvider = require(script.StoreProvider)
|
||||
local connect = require(script.connect)
|
||||
local getStore = require(script.getStore)
|
||||
local TempConfig = require(script.TempConfig)
|
||||
|
||||
return {
|
||||
StoreProvider = StoreProvider,
|
||||
connect = connect,
|
||||
UNSTABLE_getStore = getStore,
|
||||
UNSTABLE_connect2 = connect,
|
||||
|
||||
TEMP_CONFIG = TempConfig,
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
local function join(...)
|
||||
local result = {}
|
||||
|
||||
for i = 1, select("#", ...) do
|
||||
local source = select(i, ...)
|
||||
|
||||
if source ~= nil then
|
||||
for key, value in pairs(source) do
|
||||
result[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return join
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
local function shallowEqual(a, b)
|
||||
if a == nil then
|
||||
return b == nil
|
||||
elseif b == nil then
|
||||
return a == nil
|
||||
end
|
||||
|
||||
for key, value in pairs(a) do
|
||||
if value ~= b[key] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for key, value in pairs(b) do
|
||||
if value ~= a[key] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return shallowEqual
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
return function()
|
||||
local shallowEqual = require(script.Parent.shallowEqual)
|
||||
|
||||
it("should compare dictionaries", function()
|
||||
local a = {
|
||||
a = "a",
|
||||
b = {},
|
||||
c = 6,
|
||||
}
|
||||
|
||||
local b = {
|
||||
b = a.b,
|
||||
c = a.c,
|
||||
a = a.a,
|
||||
}
|
||||
|
||||
local c = {
|
||||
b = {},
|
||||
a = a.a,
|
||||
c = a.c,
|
||||
}
|
||||
|
||||
local d = {
|
||||
a = a.a,
|
||||
b = a.b,
|
||||
c = a.c,
|
||||
d = "hello",
|
||||
}
|
||||
|
||||
expect(shallowEqual(a, a)).to.equal(true)
|
||||
expect(shallowEqual(a, b)).to.equal(true)
|
||||
expect(shallowEqual(a, c)).to.equal(false)
|
||||
expect(shallowEqual(b, c)).to.equal(false)
|
||||
expect(shallowEqual(a, d)).to.equal(false)
|
||||
expect(shallowEqual(b, d)).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should handle nil for either argument", function()
|
||||
local a = {}
|
||||
|
||||
expect(shallowEqual(nil, nil)).to.equal(true)
|
||||
expect(shallowEqual(a, nil)).to.equal(false)
|
||||
expect(shallowEqual(nil, a)).to.equal(false)
|
||||
end)
|
||||
end
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local Symbol = require(script.Parent.Symbol)
|
||||
|
||||
return Symbol.named("RoduxStore")
|
||||
Reference in New Issue
Block a user