This repository has been archived by the owner on Dec 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added Context api Added createContext, provide, and consume APIs to Roact. * Remove provide and consume Removes provide and consume from the new Roact context API, so that createContext returns a Provider and Consumer. * Update from Code Review comments -Use fragment instead of oneChild -Use spies for testing -Use Component over PureComponent -Check for duplicates in didUpdate -nil check disconnect * Update to use new Internal Context API Updates the createContext API to internally use the new internal Context API.
- Loading branch information
1 parent
68e0932
commit d9b7f96
Showing
5 changed files
with
226 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
local Symbol = require(script.Parent.Symbol) | ||
local Binding = require(script.Parent.Binding) | ||
local createFragment = require(script.Parent.createFragment) | ||
local Children = require(script.Parent.PropMarkers.Children) | ||
local Component = require(script.Parent.Component) | ||
|
||
local function createProvider(context) | ||
local Provider = Component:extend("Provider") | ||
|
||
function Provider:init(props) | ||
self.binding, self.updateValue = Binding.create(props.value) | ||
|
||
local key = context.key | ||
self:__addContext(key, self.binding) | ||
end | ||
|
||
function Provider:didUpdate(prevProps) | ||
if prevProps.value ~= self.props.value then | ||
self.updateValue(self.props.value) | ||
end | ||
end | ||
|
||
function Provider:render() | ||
return createFragment(self.props[Children]) | ||
end | ||
|
||
return Provider | ||
end | ||
|
||
local function createConsumer(context) | ||
local Consumer = Component:extend("Consumer") | ||
|
||
function Consumer:init(props) | ||
local key = context.key | ||
local binding = self:__getContext(key) | ||
|
||
if binding ~= nil then | ||
self.state = { | ||
value = binding:getValue(), | ||
} | ||
|
||
-- Update if the Context updated | ||
self.disconnect = Binding.subscribe(binding, function() | ||
self:setState({ | ||
value = binding:getValue(), | ||
}) | ||
end) | ||
else | ||
-- Fall back to the default value if no Provider exists | ||
self.state = { | ||
value = context.defaultValue, | ||
} | ||
end | ||
end | ||
|
||
function Consumer.validateProps(props) | ||
if type(props.render) ~= "function" then | ||
return false, "Consumer expects a `render` function" | ||
else | ||
return true | ||
end | ||
end | ||
|
||
function Consumer:render() | ||
return self.props.render(self.state.value) | ||
end | ||
|
||
function Consumer:willUnmount() | ||
if self.disconnect ~= nil then | ||
self.disconnect() | ||
end | ||
end | ||
|
||
return Consumer | ||
end | ||
|
||
local Context = {} | ||
Context.__index = Context | ||
|
||
function Context.new(defaultValue) | ||
local self = { | ||
defaultValue = defaultValue, | ||
key = Symbol.named("ContextKey"), | ||
} | ||
setmetatable(self, Context) | ||
return self | ||
end | ||
|
||
function Context:__tostring() | ||
return "RoactContext" | ||
end | ||
|
||
local function createContext(defaultValue) | ||
local context = Context.new(defaultValue) | ||
return { | ||
Provider = createProvider(context), | ||
Consumer = createConsumer(context), | ||
} | ||
end | ||
|
||
return createContext |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
return function() | ||
local createContext = require(script.Parent.createContext) | ||
local createElement = require(script.Parent.createElement) | ||
local NoopRenderer = require(script.Parent.NoopRenderer) | ||
local createReconciler = require(script.Parent.createReconciler) | ||
local createSpy = require(script.Parent.createSpy) | ||
|
||
local noopReconciler = createReconciler(NoopRenderer) | ||
|
||
it("should return a table", function() | ||
local context = createContext("Test") | ||
expect(context).to.be.ok() | ||
expect(type(context)).to.equal("table") | ||
end) | ||
|
||
it("should contain a Provider and a Consumer", function() | ||
local context = createContext("Test") | ||
expect(context.Provider).to.be.ok() | ||
expect(context.Consumer).to.be.ok() | ||
end) | ||
|
||
describe("Provider", function() | ||
it("should render its children", function() | ||
local context = createContext("Test") | ||
|
||
local Listener = createSpy(function() | ||
return nil | ||
end) | ||
|
||
local element = createElement(context.Provider, { | ||
value = "Test", | ||
}, { | ||
Listener = createElement(Listener.value), | ||
}) | ||
|
||
local tree = noopReconciler.mountVirtualTree(element, nil, "Provide Tree") | ||
noopReconciler.unmountVirtualTree(tree) | ||
|
||
expect(Listener.callCount).to.equal(1) | ||
end) | ||
end) | ||
|
||
describe("Consumer", function() | ||
it("should expect a render function", function() | ||
local context = createContext("Test") | ||
local element = createElement(context.Consumer) | ||
|
||
expect(function() | ||
noopReconciler.mountVirtualTree(element, nil, "Provide Tree") | ||
end).to.throw() | ||
end) | ||
|
||
it("should return the default value if there is no Provider", function() | ||
local valueSpy = createSpy() | ||
local context = createContext("Test") | ||
|
||
local element = createElement(context.Consumer, { | ||
render = valueSpy.value, | ||
}) | ||
|
||
local tree = noopReconciler.mountVirtualTree(element, nil, "Provide Tree") | ||
noopReconciler.unmountVirtualTree(tree) | ||
|
||
valueSpy:assertCalledWith("Test") | ||
end) | ||
|
||
it("should pass the value to the render function", function() | ||
local valueSpy = createSpy() | ||
local context = createContext("Test") | ||
|
||
local function Listener() | ||
return createElement(context.Consumer, { | ||
render = valueSpy.value, | ||
}) | ||
end | ||
|
||
local element = createElement(context.Provider, { | ||
value = "NewTest", | ||
}, { | ||
Listener = createElement(Listener), | ||
}) | ||
|
||
local tree = noopReconciler.mountVirtualTree(element, nil, "Provide Tree") | ||
noopReconciler.unmountVirtualTree(tree) | ||
|
||
valueSpy:assertCalledWith("NewTest") | ||
end) | ||
|
||
it("should update when the value updates", function() | ||
local valueSpy = createSpy() | ||
local context = createContext("Test") | ||
|
||
local function Listener() | ||
return createElement(context.Consumer, { | ||
render = valueSpy.value, | ||
}) | ||
end | ||
|
||
local element = createElement(context.Provider, { | ||
value = "NewTest", | ||
}, { | ||
Listener = createElement(Listener), | ||
}) | ||
|
||
local tree = noopReconciler.mountVirtualTree(element, nil, "Provide Tree") | ||
|
||
expect(valueSpy.callCount).to.equal(1) | ||
valueSpy:assertCalledWith("NewTest") | ||
|
||
noopReconciler.updateVirtualTree(tree, createElement(context.Provider, { | ||
value = "ThirdTest", | ||
}, { | ||
Listener = createElement(Listener), | ||
})) | ||
|
||
expect(valueSpy.callCount).to.equal(3) | ||
valueSpy:assertCalledWith("ThirdTest") | ||
|
||
noopReconciler.unmountVirtualTree(tree) | ||
end) | ||
end) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters