forked from rojo-rbx/rojo
Break apart plugin reconciler (#332)
* Start splitting apart reconciler, with tests * Reify children in reify * Baseline hydrate implementation * Remove debug print * Scaffold out diff implementation, just supporting name changes * invariant -> error in decodeValue * Flesh out diff and add getProperty * Clear out top-level reconciler interface, start updating code that touches it * Address review feedback * Add (experimental) Selene configuration * Add emptiness checks to PatchSet, remove unimplement invert method * Improve descendant destruction behavior in InstanceMap * Track instanceId on all reify errors * Base implementation of applyPatch, returning partial patches on failure * Change reify to accept InstanceMap and insert instances into it * Start testing applyPatch for removals * Add test for applyPatch adding instances successfully and not * Add , which is just error with formatting * Correctly use new diff and applyPatch APIs * Improve applyPatch logging and fix field name typo * Better debug output when reify fails * Print out unapplied patch in debug mode * Don't write properties if their values are not different. This was exposed trying to sync the Rojo plugin, which has a gigantic ModuleScript in it with the reflection database. This workaround was present in some form in many versions of Rojo, and I guess we still need it. This time, I actually documented why it's here so that I don't forget for the umpteenth time... * Add placeholder test that needs to happen still * Introduce easier plugin testing, write applyPatch properties test * Delete legacy get/setCanonicalProperty files * Fix trying to remove numbers instead of instances * Change applyPatch to return partial patches instead of binary success * Work towards being able to decode and apply refs * Add helpers for PatchSet assertions * Apply refs in reify, test all cases * Improve diagnostics when patches fail to apply * Stop logging when destroying untracked instances, it's ok * Remove read before setting property in applyPatch * Fix diff thinking all properties are changed
This commit is contained in:
committed by
GitHub
parent
50f0a2bd2e
commit
f66860bdfe
159
plugin/src/Reconciler/applyPatch.spec.lua
Normal file
159
plugin/src/Reconciler/applyPatch.spec.lua
Normal file
@@ -0,0 +1,159 @@
|
||||
return function()
|
||||
local applyPatch = require(script.Parent.applyPatch)
|
||||
|
||||
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||
|
||||
local dummy = Instance.new("Folder")
|
||||
local function wasDestroyed(instance)
|
||||
-- If an instance was destroyed, its parent property is locked.
|
||||
local ok = pcall(function()
|
||||
local oldParent = instance.Parent
|
||||
instance.Parent = dummy
|
||||
instance.Parent = oldParent
|
||||
end)
|
||||
|
||||
return not ok
|
||||
end
|
||||
|
||||
it("should return an empty patch if given an empty patch", function()
|
||||
local patch = applyPatch(InstanceMap.new(), PatchSet.newEmpty())
|
||||
assert(PatchSet.isEmpty(patch), "expected remaining patch to be empty")
|
||||
end)
|
||||
|
||||
it("should destroy instances listed for remove", function()
|
||||
local root = Instance.new("Folder")
|
||||
|
||||
local child = Instance.new("Folder")
|
||||
child.Name = "Child"
|
||||
child.Parent = root
|
||||
|
||||
local instanceMap = InstanceMap.new()
|
||||
instanceMap:insert("ROOT", root)
|
||||
instanceMap:insert("CHILD", child)
|
||||
|
||||
local patch = PatchSet.newEmpty()
|
||||
table.insert(patch.removed, child)
|
||||
|
||||
local unapplied = applyPatch(instanceMap, patch)
|
||||
assert(PatchSet.isEmpty(unapplied), "expected remaining patch to be empty")
|
||||
|
||||
assert(not wasDestroyed(root), "expected root to be left alone")
|
||||
assert(wasDestroyed(child), "expected child to be destroyed")
|
||||
|
||||
instanceMap:stop()
|
||||
end)
|
||||
|
||||
it("should destroy IDs listed for remove", function()
|
||||
local root = Instance.new("Folder")
|
||||
|
||||
local child = Instance.new("Folder")
|
||||
child.Name = "Child"
|
||||
child.Parent = root
|
||||
|
||||
local instanceMap = InstanceMap.new()
|
||||
instanceMap:insert("ROOT", root)
|
||||
instanceMap:insert("CHILD", child)
|
||||
|
||||
local patch = PatchSet.newEmpty()
|
||||
table.insert(patch.removed, "CHILD")
|
||||
|
||||
local unapplied = applyPatch(instanceMap, patch)
|
||||
assert(PatchSet.isEmpty(unapplied), "expected remaining patch to be empty")
|
||||
expect(instanceMap:size()).to.equal(1)
|
||||
|
||||
assert(not wasDestroyed(root), "expected root to be left alone")
|
||||
assert(wasDestroyed(child), "expected child to be destroyed")
|
||||
|
||||
instanceMap:stop()
|
||||
end)
|
||||
|
||||
it("should add instances to the DOM", function()
|
||||
-- Many of the details of this functionality are instead covered by
|
||||
-- tests on reify, not here.
|
||||
|
||||
local root = Instance.new("Folder")
|
||||
|
||||
local instanceMap = InstanceMap.new()
|
||||
instanceMap:insert("ROOT", root)
|
||||
|
||||
local patch = PatchSet.newEmpty()
|
||||
patch.added["CHILD"] = {
|
||||
Id = "CHILD",
|
||||
ClassName = "Model",
|
||||
Name = "Child",
|
||||
Parent = "ROOT",
|
||||
Children = {"GRANDCHILD"},
|
||||
Properties = {},
|
||||
}
|
||||
|
||||
patch.added["GRANDCHILD"] = {
|
||||
Id = "GRANDCHILD",
|
||||
ClassName = "Part",
|
||||
Name = "Grandchild",
|
||||
Parent = "CHILD",
|
||||
Children = {},
|
||||
Properties = {},
|
||||
}
|
||||
|
||||
local unapplied = applyPatch(instanceMap, patch)
|
||||
assert(PatchSet.isEmpty(unapplied), "expected remaining patch to be empty")
|
||||
expect(instanceMap:size()).to.equal(3)
|
||||
|
||||
local child = root:FindFirstChild("Child")
|
||||
expect(child).to.be.ok()
|
||||
expect(child.ClassName).to.equal("Model")
|
||||
expect(child).to.equal(instanceMap.fromIds["CHILD"])
|
||||
|
||||
local grandchild = child:FindFirstChild("Grandchild")
|
||||
expect(grandchild).to.be.ok()
|
||||
expect(grandchild.ClassName).to.equal("Part")
|
||||
expect(grandchild).to.equal(instanceMap.fromIds["GRANDCHILD"])
|
||||
end)
|
||||
|
||||
it("should return unapplied additions when instances cannot be created", function()
|
||||
local root = Instance.new("Folder")
|
||||
|
||||
local instanceMap = InstanceMap.new()
|
||||
instanceMap:insert("ROOT", root)
|
||||
|
||||
local patch = PatchSet.newEmpty()
|
||||
patch.added["OOPSIE"] = {
|
||||
Id = "OOPSIE",
|
||||
-- Hopefully Roblox never makes an instance with this ClassName.
|
||||
ClassName = "UH OH",
|
||||
Name = "FUBAR",
|
||||
Parent = "ROOT",
|
||||
Children = {},
|
||||
Properties = {},
|
||||
}
|
||||
|
||||
local unapplied = applyPatch(instanceMap, patch)
|
||||
expect(unapplied.added["OOPSIE"]).to.equal(patch.added["OOPSIE"])
|
||||
expect(instanceMap:size()).to.equal(1)
|
||||
expect(#root:GetChildren()).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should apply property changes to instances", function()
|
||||
local value = Instance.new("StringValue")
|
||||
value.Value = "HELLO"
|
||||
|
||||
local instanceMap = InstanceMap.new()
|
||||
instanceMap:insert("VALUE", value)
|
||||
|
||||
local patch = PatchSet.newEmpty()
|
||||
table.insert(patch.updated, {
|
||||
id = "VALUE",
|
||||
changedProperties = {
|
||||
Value = {
|
||||
Type = "String",
|
||||
Value = "WORLD",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
local unapplied = applyPatch(instanceMap, patch)
|
||||
assert(PatchSet.isEmpty(unapplied), "expected remaining patch to be empty")
|
||||
expect(value.Value).to.equal("WORLD")
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user