Clients/Client2018/content/LuaPackages/RoactImpl/init.spec.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