return function()
local assign = require(script.Parent.assign)
local createElement = require(script.Parent.createElement)
local createFragment = require(script.Parent.createFragment)
local createSpy = require(script.Parent.createSpy)
local NoopRenderer = require(script.Parent.NoopRenderer)
local Type = require(script.Parent.Type)
local ElementKind = require(script.Parent.ElementKind)
local createReconciler = require(script.Parent.createReconciler)
local noopReconciler = createReconciler(NoopRenderer)
describe("tree operations", function()
it("should mount and unmount", function()
local tree = noopReconciler.mountVirtualTree(createElement("StringValue"))
expect(tree).to.be.ok()
noopReconciler.unmountVirtualTree(tree)
end)
it("should mount, update, and unmount", function()
local tree = noopReconciler.mountVirtualTree(createElement("StringValue"))
expect(tree).to.be.ok()
noopReconciler.updateVirtualTree(tree, createElement("StringValue"))
noopReconciler.unmountVirtualTree(tree)
end)
end)
describe("booleans", function()
it("should mount booleans as nil", function()
local node = noopReconciler.mountVirtualNode(false, nil, "test")
expect(node).to.equal(nil)
end)
it("should unmount nodes if they are updated to a boolean value", function()
local node = noopReconciler.mountVirtualNode(createElement("StringValue"), nil, "test")
expect(node).to.be.ok()
node = noopReconciler.updateVirtualNode(node, true)
expect(node).to.equal(nil)
end)
end)
describe("invalid elements", function()
it("should throw errors when attempting to mount invalid elements", function()
local returnsString = function()
return "Hello"
end
local returnsNumber = function()
return 1
end
local returnsFunction = function()
return function() end
end
local returnsTable = function()
return {}
end
local hostParent = nil
local key = "Some Key"
expect(function()
noopReconciler.mountVirtualNode(createElement(returnsString), hostParent, key)
end).to.throw()
expect(function()
noopReconciler.mountVirtualNode(createElement(returnsNumber), hostParent, key)
end).to.throw()
expect(function()
noopReconciler.mountVirtualNode(createElement(returnsFunction), hostParent, key)
end).to.throw()
expect(function()
noopReconciler.mountVirtualNode(createElement(returnsTable), hostParent, key)
end).to.throw()
end)
end)
describe("Host components", function()
it("should invoke the renderer to mount host nodes", function()
local mountHostNode = createSpy(NoopRenderer.mountHostNode)
local renderer = assign({}, NoopRenderer, {
mountHostNode = mountHostNode.value,
})
local reconciler = createReconciler(renderer)
local element = createElement("StringValue")
local hostParent = nil
local key = "Some Key"
local node = reconciler.mountVirtualNode(element, hostParent, key)
expect(Type.of(node)).to.equal(Type.VirtualNode)
expect(mountHostNode.callCount).to.equal(1)
local values = mountHostNode:captureValues("reconciler", "node")
expect(values.reconciler).to.equal(reconciler)
expect(values.node).to.equal(node)
end)
it("should invoke the renderer to update host nodes", function()
local updateHostNode = createSpy(NoopRenderer.updateHostNode)
local renderer = assign({}, NoopRenderer, {
mountHostNode = NoopRenderer.mountHostNode,
updateHostNode = updateHostNode.value,
})
local reconciler = createReconciler(renderer)
local element = createElement("StringValue")
local hostParent = nil
local key = "Key"
local node = reconciler.mountVirtualNode(element, hostParent, key)
expect(Type.of(node)).to.equal(Type.VirtualNode)
local newElement = createElement("StringValue")
local newNode = reconciler.updateVirtualNode(node, newElement)
expect(newNode).to.equal(node)
expect(updateHostNode.callCount).to.equal(1)
local values = updateHostNode:captureValues("reconciler", "node", "newElement")
expect(values.reconciler).to.equal(reconciler)
expect(values.node).to.equal(node)
expect(values.newElement).to.equal(newElement)
end)
it("should invoke the renderer to unmount host nodes", function()
local unmountHostNode = createSpy(NoopRenderer.unmountHostNode)
local renderer = assign({}, NoopRenderer, {
mountHostNode = NoopRenderer.mountHostNode,
unmountHostNode = unmountHostNode.value,
})
local reconciler = createReconciler(renderer)
local element = createElement("StringValue")
local hostParent = nil
local key = "Key"
local node = reconciler.mountVirtualNode(element, hostParent, key)
expect(Type.of(node)).to.equal(Type.VirtualNode)
reconciler.unmountVirtualNode(node)
expect(unmountHostNode.callCount).to.equal(1)
local values = unmountHostNode:captureValues("reconciler", "node")
expect(values.reconciler).to.equal(reconciler)
expect(values.node).to.equal(node)
end)
end)
describe("Function components", function()
it("should mount and unmount function components", function()
local componentSpy = createSpy(function(_props)
return nil
end)
local element = createElement(componentSpy.value, {
someValue = 5,
})
local hostParent = nil
local key = "A Key"
local node = noopReconciler.mountVirtualNode(element, hostParent, key)
expect(Type.of(node)).to.equal(Type.VirtualNode)
expect(componentSpy.callCount).to.equal(1)
local calledWith = componentSpy:captureValues("props")
expect(calledWith.props).to.be.a("table")
expect(calledWith.props.someValue).to.equal(5)
noopReconciler.unmountVirtualNode(node)
expect(componentSpy.callCount).to.equal(1)
end)
it("should mount single children of function components", function()
local childComponentSpy = createSpy(function(_props)
return nil
end)
local parentComponentSpy = createSpy(function(props)
return createElement(childComponentSpy.value, {
value = props.value + 1,
})
end)
local element = createElement(parentComponentSpy.value, {
value = 13,
})
local hostParent = nil
local key = "A Key"
local node = noopReconciler.mountVirtualNode(element, hostParent, key)
expect(Type.of(node)).to.equal(Type.VirtualNode)
expect(parentComponentSpy.callCount).to.equal(1)
expect(childComponentSpy.callCount).to.equal(1)
local parentCalledWith = parentComponentSpy:captureValues("props")
local childCalledWith = childComponentSpy:captureValues("props")
expect(parentCalledWith.props).to.be.a("table")
expect(parentCalledWith.props.value).to.equal(13)
expect(childCalledWith.props).to.be.a("table")
expect(childCalledWith.props.value).to.equal(14)
noopReconciler.unmountVirtualNode(node)
expect(parentComponentSpy.callCount).to.equal(1)
expect(childComponentSpy.callCount).to.equal(1)
end)
it("should mount fragments returned by function components", function()
local childAComponentSpy = createSpy(function(_props)
return nil
end)
local childBComponentSpy = createSpy(function(_props)
return nil
end)
local parentComponentSpy = createSpy(function(props)
return createFragment({
A = createElement(childAComponentSpy.value, {
value = props.value + 1,
}),
B = createElement(childBComponentSpy.value, {
value = props.value + 5,
}),
})
end)
local element = createElement(parentComponentSpy.value, {
value = 17,
})
local hostParent = nil
local key = "A Key"
local node = noopReconciler.mountVirtualNode(element, hostParent, key)
expect(Type.of(node)).to.equal(Type.VirtualNode)
expect(parentComponentSpy.callCount).to.equal(1)
expect(childAComponentSpy.callCount).to.equal(1)
expect(childBComponentSpy.callCount).to.equal(1)
local parentCalledWith = parentComponentSpy:captureValues("props")
local childACalledWith = childAComponentSpy:captureValues("props")
local childBCalledWith = childBComponentSpy:captureValues("props")
expect(parentCalledWith.props).to.be.a("table")
expect(parentCalledWith.props.value).to.equal(17)
expect(childACalledWith.props).to.be.a("table")
expect(childACalledWith.props.value).to.equal(18)
expect(childBCalledWith.props).to.be.a("table")
expect(childBCalledWith.props.value).to.equal(22)
noopReconciler.unmountVirtualNode(node)
expect(parentComponentSpy.callCount).to.equal(1)
expect(childAComponentSpy.callCount).to.equal(1)
expect(childBComponentSpy.callCount).to.equal(1)
end)
end)
describe("Fragments", function()
it("should mount fragments", function()
local fragment = createFragment({})
local node = noopReconciler.mountVirtualNode(fragment, nil, "test")
expect(node).to.be.ok()
expect(ElementKind.of(node.currentElement)).to.equal(ElementKind.Fragment)
end)
it("should mount an empty fragment", function()
local emptyFragment = createFragment({})
local node = noopReconciler.mountVirtualNode(emptyFragment, nil, "test")
expect(node).to.be.ok()
local nextNode = next(node.children)
expect(nextNode).to.never.be.ok()
end)
it("should mount all fragment's children", function()
local childComponentSpy = createSpy(function(_props)
return nil
end)
local elements = {}
local totalElements = 5
for i = 1, totalElements do
elements["key" .. tostring(i)] = createElement(childComponentSpy.value, {})
end
local fragments = createFragment(elements)
local node = noopReconciler.mountVirtualNode(fragments, nil, "test")
expect(node).to.be.ok()
expect(childComponentSpy.callCount).to.equal(totalElements)
end)
end)
end