495 lines
12 KiB
Lua
495 lines
12 KiB
Lua
return function()
|
|
local Roact = require(script.Parent)
|
|
|
|
it("should load with all public APIs", function()
|
|
local publicApi = {
|
|
createElement = "function",
|
|
createRef = "function",
|
|
mount = "function",
|
|
unmount = "function",
|
|
reconcile = "function",
|
|
oneChild = "function",
|
|
setGlobalConfig = "function",
|
|
getGlobalConfigValue = "function",
|
|
|
|
-- These functions are deprecated and will throw warnings soon!
|
|
reify = "function",
|
|
teardown = "function",
|
|
|
|
Component = true,
|
|
PureComponent = true,
|
|
Portal = true,
|
|
Children = true,
|
|
Event = true,
|
|
Change = true,
|
|
Ref = true,
|
|
None = true,
|
|
Element = true,
|
|
UNSTABLE = true,
|
|
}
|
|
|
|
expect(Roact).to.be.ok()
|
|
|
|
for key, valueType in pairs(publicApi) do
|
|
local success
|
|
if typeof(valueType) == "string" then
|
|
success = typeof(Roact[key]) == valueType
|
|
else
|
|
success = Roact[key] ~= nil
|
|
end
|
|
|
|
if not success then
|
|
local existence = typeof(valueType) == "boolean" and "present" or "of type " .. valueType
|
|
local message = (
|
|
"Expected public API member %q to be %s, but instead it was of type %s"
|
|
):format(tostring(key), existence, typeof(Roact[key]))
|
|
|
|
error(message)
|
|
end
|
|
end
|
|
|
|
for key in pairs(Roact) do
|
|
if publicApi[key] == nil then
|
|
local message = (
|
|
"Found unknown public API key %q!"
|
|
):format(tostring(key))
|
|
|
|
error(message)
|
|
end
|
|
end
|
|
end)
|
|
|
|
describe("Props", function()
|
|
it("should be passed to primitive components", function()
|
|
local container = Instance.new("IntValue")
|
|
|
|
local element = Roact.createElement("StringValue", {
|
|
Value = "foo",
|
|
})
|
|
|
|
Roact.mount(element, container, "TestStringValue")
|
|
|
|
local rbx = container:FindFirstChild("TestStringValue")
|
|
|
|
expect(rbx).to.be.ok()
|
|
expect(rbx.Value).to.equal("foo")
|
|
end)
|
|
|
|
it("should be passed to functional components", function()
|
|
local testProp = {}
|
|
|
|
local callCount = 0
|
|
|
|
local function TestComponent(props)
|
|
expect(props.testProp).to.equal(testProp)
|
|
callCount = callCount + 1
|
|
end
|
|
|
|
local element = Roact.createElement(TestComponent, {
|
|
testProp = testProp,
|
|
})
|
|
|
|
Roact.mount(element)
|
|
|
|
-- The only guarantee is that the function will be invoked at least once
|
|
expect(callCount > 0).to.equal(true)
|
|
end)
|
|
|
|
it("should be passed to stateful components", function()
|
|
local testProp = {}
|
|
|
|
local callCount = 0
|
|
|
|
local TestComponent = Roact.Component:extend("TestComponent")
|
|
|
|
function TestComponent:init(props)
|
|
expect(props.testProp).to.equal(testProp)
|
|
callCount = callCount + 1
|
|
end
|
|
|
|
function TestComponent:render()
|
|
end
|
|
|
|
local element = Roact.createElement(TestComponent, {
|
|
testProp = testProp,
|
|
})
|
|
|
|
Roact.mount(element)
|
|
|
|
expect(callCount).to.equal(1)
|
|
end)
|
|
end)
|
|
|
|
describe("State", function()
|
|
it("should trigger a re-render of child components", function()
|
|
local renderCount = 0
|
|
local listener = nil
|
|
|
|
local TestChild = Roact.Component:extend("TestChild")
|
|
|
|
function TestChild:render()
|
|
renderCount = renderCount + 1
|
|
return nil
|
|
end
|
|
|
|
local TestParent = Roact.Component:extend("TestParent")
|
|
|
|
function TestParent:init(props)
|
|
self.state = {
|
|
value = 0,
|
|
}
|
|
end
|
|
|
|
function TestParent:didMount()
|
|
listener = function()
|
|
self:setState({
|
|
value = self.state.value + 1,
|
|
})
|
|
end
|
|
end
|
|
|
|
function TestParent:render()
|
|
return Roact.createElement(TestChild, {
|
|
value = self.state.value,
|
|
})
|
|
end
|
|
|
|
local element = Roact.createElement(TestParent)
|
|
Roact.mount(element)
|
|
|
|
expect(renderCount >= 1).to.equal(true)
|
|
expect(listener).to.be.a("function")
|
|
|
|
listener()
|
|
|
|
expect(renderCount >= 2).to.equal(true)
|
|
end)
|
|
end)
|
|
|
|
describe("Context", function()
|
|
it("should be passed to children through primitive and functional components", function()
|
|
local testValue = {}
|
|
|
|
local callCount = 0
|
|
|
|
local ContextConsumer = Roact.Component:extend("ContextConsumer")
|
|
|
|
function ContextConsumer:init(props)
|
|
expect(self._context.testValue).to.equal(testValue)
|
|
|
|
callCount = callCount + 1
|
|
end
|
|
|
|
function ContextConsumer:render()
|
|
return
|
|
end
|
|
|
|
local function ContextBarrier(props)
|
|
return Roact.createElement(ContextConsumer)
|
|
end
|
|
|
|
local ContextProvider = Roact.Component:extend("ContextProvider")
|
|
|
|
function ContextProvider:init(props)
|
|
self._context.testValue = props.testValue
|
|
end
|
|
|
|
function ContextProvider:render()
|
|
return Roact.createElement("Frame", {}, {
|
|
Child = Roact.createElement(ContextBarrier),
|
|
})
|
|
end
|
|
|
|
local element = Roact.createElement(ContextProvider, {
|
|
testValue = testValue,
|
|
})
|
|
|
|
Roact.mount(element)
|
|
|
|
expect(callCount).to.equal(1)
|
|
end)
|
|
end)
|
|
|
|
describe("Ref", function()
|
|
it("should call back with a Roblox object after properties and children", function()
|
|
local callCount = 0
|
|
|
|
local function ref(rbx)
|
|
expect(rbx).to.be.ok()
|
|
expect(rbx.ClassName).to.equal("StringValue")
|
|
expect(rbx.Value).to.equal("Hey!")
|
|
expect(rbx.Name).to.equal("RefTest")
|
|
expect(#rbx:GetChildren()).to.equal(1)
|
|
|
|
callCount = callCount + 1
|
|
end
|
|
|
|
local element = Roact.createElement("StringValue", {
|
|
Value = "Hey!",
|
|
[Roact.Ref] = ref,
|
|
}, {
|
|
TestChild = Roact.createElement("StringValue"),
|
|
})
|
|
|
|
Roact.mount(element, nil, "RefTest")
|
|
|
|
expect(callCount).to.equal(1)
|
|
end)
|
|
|
|
it("should pass nil to refs for tearing down", function()
|
|
local callCount = 0
|
|
local currentRef
|
|
|
|
local function ref(rbx)
|
|
currentRef = rbx
|
|
callCount = callCount + 1
|
|
end
|
|
|
|
local element = Roact.createElement("StringValue", {
|
|
[Roact.Ref] = ref,
|
|
})
|
|
|
|
local instance = Roact.mount(element, nil, "RefTest")
|
|
|
|
expect(callCount).to.equal(1)
|
|
expect(currentRef).to.be.ok()
|
|
expect(currentRef.Name).to.equal("RefTest")
|
|
|
|
Roact.unmount(instance)
|
|
|
|
expect(callCount).to.equal(2)
|
|
expect(currentRef).to.equal(nil)
|
|
end)
|
|
|
|
it("should tear down refs when switched out of the tree", function()
|
|
local updateMethod
|
|
local refCount = 0
|
|
local currentRef
|
|
|
|
local function ref(rbx)
|
|
currentRef = rbx
|
|
refCount = refCount + 1
|
|
end
|
|
|
|
local function RefWrapper()
|
|
return Roact.createElement("StringValue", {
|
|
Value = "ooba ooba",
|
|
[Roact.Ref] = ref,
|
|
})
|
|
end
|
|
|
|
local Root = Roact.Component:extend("RefTestRoot")
|
|
|
|
function Root:init()
|
|
updateMethod = function(show)
|
|
self:setState({
|
|
show = show,
|
|
})
|
|
end
|
|
end
|
|
|
|
function Root:render()
|
|
if self.state.show then
|
|
return Roact.createElement(RefWrapper)
|
|
end
|
|
end
|
|
|
|
local element = Roact.createElement(Root)
|
|
Roact.mount(element)
|
|
|
|
expect(refCount).to.equal(0)
|
|
expect(currentRef).to.equal(nil)
|
|
|
|
updateMethod(true)
|
|
|
|
expect(refCount).to.equal(1)
|
|
expect(currentRef.Value).to.equal("ooba ooba")
|
|
|
|
updateMethod(false)
|
|
|
|
expect(refCount).to.equal(2)
|
|
expect(currentRef).to.equal(nil)
|
|
end)
|
|
end)
|
|
|
|
describe("Portal", function()
|
|
it("should place all children as children of the target Roblox instance", function()
|
|
local target = Instance.new("Folder")
|
|
|
|
local function FunctionalComponent(props)
|
|
local intValue = props.value
|
|
|
|
return Roact.createElement("IntValue", {
|
|
Value = intValue,
|
|
})
|
|
end
|
|
|
|
local portal = Roact.createElement(Roact.Portal, {
|
|
target = target
|
|
}, {
|
|
folderOne = Roact.createElement("Folder"),
|
|
folderTwo = Roact.createElement("Folder"),
|
|
intValueOne = Roact.createElement(FunctionalComponent, {
|
|
value = 42,
|
|
}),
|
|
})
|
|
Roact.mount(portal)
|
|
|
|
expect(target:FindFirstChild("folderOne")).to.be.ok()
|
|
expect(target:FindFirstChild("folderTwo")).to.be.ok()
|
|
expect(target:FindFirstChild("intValueOne")).to.be.ok()
|
|
expect(target:FindFirstChild("intValueOne").Value).to.equal(42)
|
|
end)
|
|
|
|
it("should error if the target is nil", function()
|
|
local portal = Roact.createElement(Roact.Portal, {}, {
|
|
folderOne = Roact.createElement("Folder"),
|
|
folderTwo = Roact.createElement("Folder"),
|
|
})
|
|
|
|
expect(function()
|
|
Roact.mount(portal)
|
|
end).to.throw()
|
|
end)
|
|
|
|
it("should error if the target is not a Roblox instance", function()
|
|
local portal = Roact.createElement(Roact.Portal, {
|
|
target = "NotARobloxInstance",
|
|
}, {
|
|
folderOne = Roact.createElement("Folder"),
|
|
folderTwo = Roact.createElement("Folder"),
|
|
})
|
|
|
|
expect(function()
|
|
Roact.mount(portal)
|
|
end).to.throw()
|
|
end)
|
|
|
|
it("should update if parent changes the target", function()
|
|
local targetOne = Instance.new("Folder")
|
|
local targetTwo = Instance.new("Folder")
|
|
local countWillUnmount = 0
|
|
local changeState
|
|
|
|
local TestUnmountComponent = Roact.Component:extend("TestUnmountComponent")
|
|
|
|
function TestUnmountComponent:render()
|
|
return nil
|
|
end
|
|
|
|
function TestUnmountComponent:willUnmount()
|
|
countWillUnmount = countWillUnmount + 1
|
|
end
|
|
|
|
local PortalContainer = Roact.Component:extend("PortalContainer")
|
|
|
|
function PortalContainer:init()
|
|
self.state = {
|
|
target = targetOne,
|
|
}
|
|
end
|
|
|
|
function PortalContainer:render()
|
|
return Roact.createElement(Roact.Portal, {
|
|
target = self.state.target,
|
|
}, {
|
|
folderOne = Roact.createElement("Folder"),
|
|
folderTwo = Roact.createElement("Folder"),
|
|
testUnmount = Roact.createElement(TestUnmountComponent),
|
|
})
|
|
end
|
|
|
|
function PortalContainer:didMount()
|
|
expect(self.state.target:FindFirstChild("folderOne")).to.be.ok()
|
|
expect(self.state.target:FindFirstChild("folderTwo")).to.be.ok()
|
|
|
|
changeState = function(newState)
|
|
self:setState(newState)
|
|
end
|
|
end
|
|
|
|
Roact.mount(Roact.createElement(PortalContainer))
|
|
|
|
expect(targetOne:FindFirstChild("folderOne")).to.be.ok()
|
|
expect(targetOne:FindFirstChild("folderTwo")).to.be.ok()
|
|
|
|
changeState({
|
|
target = targetTwo,
|
|
})
|
|
|
|
expect(countWillUnmount).to.equal(1)
|
|
|
|
expect(targetOne:FindFirstChild("folderOne")).never.to.be.ok()
|
|
expect(targetOne:FindFirstChild("folderTwo")).never.to.be.ok()
|
|
expect(targetTwo:FindFirstChild("folderOne")).to.be.ok()
|
|
expect(targetTwo:FindFirstChild("folderTwo")).to.be.ok()
|
|
end)
|
|
|
|
it("should update Roblox instance properties when relevant parent props are changed", function()
|
|
local target = Instance.new("Folder")
|
|
local changeState
|
|
|
|
local PortalContainer = Roact.Component:extend("PortalContainer")
|
|
|
|
function PortalContainer:init()
|
|
self.state = {
|
|
value = "initialStringValue",
|
|
}
|
|
end
|
|
|
|
function PortalContainer:render()
|
|
return Roact.createElement(Roact.Portal, {
|
|
target = target,
|
|
}, {
|
|
TestStringValue = Roact.createElement("StringValue", {
|
|
Value = self.state.value,
|
|
})
|
|
})
|
|
end
|
|
|
|
function PortalContainer:didMount()
|
|
changeState = function(newState)
|
|
self:setState(newState)
|
|
end
|
|
end
|
|
|
|
Roact.mount(Roact.createElement(PortalContainer))
|
|
|
|
expect(target:FindFirstChild("TestStringValue")).to.be.ok()
|
|
expect(target:FindFirstChild("TestStringValue").Value).to.equal("initialStringValue")
|
|
|
|
changeState({
|
|
value = "newStringValue",
|
|
})
|
|
|
|
expect(target:FindFirstChild("TestStringValue")).to.be.ok()
|
|
expect(target:FindFirstChild("TestStringValue").Value).to.equal("newStringValue")
|
|
end)
|
|
|
|
it("should properly teardown the Portal", function()
|
|
local target = Instance.new("Folder")
|
|
|
|
local portal = Roact.createElement(Roact.Portal, {
|
|
target = target
|
|
}, {
|
|
folderOne = Roact.createElement("Folder"),
|
|
folderTwo = Roact.createElement("Folder"),
|
|
})
|
|
local instance = Roact.mount(portal)
|
|
|
|
local folderThree = Instance.new("Folder")
|
|
folderThree.Name = "folderThree"
|
|
folderThree.Parent = target
|
|
|
|
expect(target:FindFirstChild("folderOne")).to.be.ok()
|
|
expect(target:FindFirstChild("folderTwo")).to.be.ok()
|
|
expect(target:FindFirstChild("folderThree")).to.be.ok()
|
|
|
|
Roact.unmount(instance)
|
|
|
|
expect(target:FindFirstChild("folderOne")).never.to.be.ok()
|
|
expect(target:FindFirstChild("folderTwo")).never.to.be.ok()
|
|
expect(target:FindFirstChild("folderThree")).to.be.ok()
|
|
end)
|
|
end)
|
|
end |