test fixtures

This commit is contained in:
Joe 2026-02-14 11:04:28 -06:00
parent bf65d8bfa8
commit bbd9c0e0fa
22 changed files with 2107 additions and 0 deletions

View file

@ -71,4 +71,117 @@ return function()
expect(next(changes)).to.equal(nil)
end)
-- -------------------------------------------------------------------
-- Ref property in patch creation
-- -------------------------------------------------------------------
describe("Ref properties", function()
local container
beforeEach(function()
container = Instance.new("Folder")
container.Name = "RefPatchTestContainer"
container.Parent = game:GetService("Workspace")
end)
afterEach(function()
if container then
container:Destroy()
container = nil
end
end)
it("should include Ref property changes in patch", function()
local instanceMap = InstanceMap.new()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
instanceMap:insert("MODEL", model)
instanceMap:insert("PART", part)
local changes = {
[model] = { PrimaryPart = true },
}
local patch = createPatchSet(instanceMap, changes)
expect(#patch.updated).to.equal(1)
expect(patch.updated[1].changedProperties.PrimaryPart).to.be.ok()
expect(patch.updated[1].changedProperties.PrimaryPart.Ref).to.equal("PART")
end)
it("should skip Ref changes when target not tracked", function()
local instanceMap = InstanceMap.new()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
instanceMap:insert("MODEL", model)
-- part NOT in instanceMap
local changes = {
[model] = { PrimaryPart = true },
}
local patch = createPatchSet(instanceMap, changes)
-- No encodable changes → empty updated
expect(#patch.updated).to.equal(0)
end)
it("should handle removal alongside Ref change on different instance", function()
local instanceMap = InstanceMap.new()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
local toRemove = Instance.new("Part")
toRemove.Parent = container
toRemove.Parent = nil -- Simulate deletion
instanceMap:insert("MODEL", model)
instanceMap:insert("PART", part)
instanceMap:insert("REMOVED", toRemove)
local changes = {
[model] = { PrimaryPart = true },
[toRemove] = { Parent = true },
}
local patch = createPatchSet(instanceMap, changes)
expect(#patch.removed).to.equal(1)
expect(#patch.updated).to.equal(1)
end)
it("should pass instanceMap in syncSourceOnly mode", function()
local instanceMap = InstanceMap.new()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
instanceMap:insert("MODEL", model)
instanceMap:insert("PART", part)
local changes = {
[model] = { PrimaryPart = true },
}
-- In syncSourceOnly mode, only Source changes go through.
-- PrimaryPart is NOT Source, so it should be filtered out.
local patch = createPatchSet(instanceMap, changes, true)
expect(#patch.updated).to.equal(0)
end)
end)
end

View file

@ -59,4 +59,198 @@ return function()
expect(color.Color3[2]).to.be.near(0.2, 0.01)
expect(color.Color3[3]).to.be.near(0.9, 0.01)
end)
-- -------------------------------------------------------------------
-- Ref property encoding tests
-- -------------------------------------------------------------------
local InstanceMap = require(script.Parent.Parent.InstanceMap)
describe("Ref property encoding", function()
local container
local instanceMap
beforeEach(function()
container = Instance.new("Folder")
container.Name = "RefTestContainer"
container.Parent = game:GetService("Workspace")
instanceMap = InstanceMap.new()
end)
afterEach(function()
instanceMap:stop()
if container then
container:Destroy()
container = nil
end
end)
it("should encode Ref property to tracked instance", function()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
instanceMap:insert("MODEL_ID", model)
instanceMap:insert("PART_ID", part)
local update = encodePatchUpdate(model, "MODEL_ID", { PrimaryPart = true }, instanceMap)
expect(update).to.be.ok()
expect(update.changedProperties.PrimaryPart).to.be.ok()
expect(update.changedProperties.PrimaryPart.Ref).to.equal("PART_ID")
end)
it("should encode nil Ref as null ref", function()
local model = Instance.new("Model")
model.Parent = container
model.PrimaryPart = nil
instanceMap:insert("MODEL_ID", model)
local update = encodePatchUpdate(model, "MODEL_ID", { PrimaryPart = true }, instanceMap)
expect(update).to.be.ok()
expect(update.changedProperties.PrimaryPart).to.be.ok()
expect(update.changedProperties.PrimaryPart.Ref).to.equal("00000000000000000000000000000000")
end)
it("should skip Ref when target not in InstanceMap", function()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
instanceMap:insert("MODEL_ID", model)
-- part is NOT inserted into instanceMap
local update = encodePatchUpdate(model, "MODEL_ID", { PrimaryPart = true }, instanceMap)
-- PrimaryPart should be skipped (not encoded)
if update then
expect(update.changedProperties.PrimaryPart).to.equal(nil)
end
end)
it("should encode Ref alongside non-Ref properties", function()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
model.Name = "TestModel"
instanceMap:insert("MODEL_ID", model)
instanceMap:insert("PART_ID", part)
local update = encodePatchUpdate(
model,
"MODEL_ID",
{ PrimaryPart = true, Name = true },
instanceMap
)
expect(update).to.be.ok()
expect(update.changedProperties.PrimaryPart).to.be.ok()
expect(update.changedProperties.PrimaryPart.Ref).to.equal("PART_ID")
expect(update.changedName).to.equal("TestModel")
end)
it("should return nil when only unresolvable Ref changes", function()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
instanceMap:insert("MODEL_ID", model)
-- part not in map
local update = encodePatchUpdate(model, "MODEL_ID", { PrimaryPart = true }, instanceMap)
-- No encodable changes → nil
expect(update).to.equal(nil)
end)
it("should encode ObjectValue.Value Ref", function()
local objVal = Instance.new("ObjectValue")
objVal.Parent = container
local target = Instance.new("Part")
target.Parent = container
objVal.Value = target
instanceMap:insert("OBJVAL_ID", objVal)
instanceMap:insert("TARGET_ID", target)
local update = encodePatchUpdate(objVal, "OBJVAL_ID", { Value = true }, instanceMap)
expect(update).to.be.ok()
expect(update.changedProperties.Value).to.be.ok()
expect(update.changedProperties.Value.Ref).to.equal("TARGET_ID")
end)
it("should encode self-referencing Ref", function()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
instanceMap:insert("MODEL_ID", model)
-- PrimaryPart points to part, which IS in the map
instanceMap:insert("PART_ID", part)
local update = encodePatchUpdate(model, "MODEL_ID", { PrimaryPart = true }, instanceMap)
expect(update).to.be.ok()
expect(update.changedProperties.PrimaryPart.Ref).to.equal("PART_ID")
end)
it("should warn when no InstanceMap provided for Ref property", function()
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
-- Pass nil for instanceMap
local update = encodePatchUpdate(model, "MODEL_ID", { PrimaryPart = true }, nil)
-- Should not crash; PrimaryPart is skipped
if update then
expect(update.changedProperties.PrimaryPart).to.equal(nil)
end
end)
it("should encode multiple Ref properties on same instance", function()
-- Use a Weld which has Part0 and Part1 ref properties
local weld = Instance.new("WeldConstraint")
weld.Parent = container
local partA = Instance.new("Part")
partA.Parent = container
local partB = Instance.new("Part")
partB.Parent = container
weld.Part0 = partA
weld.Part1 = partB
instanceMap:insert("WELD_ID", weld)
instanceMap:insert("PART_A", partA)
instanceMap:insert("PART_B", partB)
local update = encodePatchUpdate(
weld,
"WELD_ID",
{ Part0 = true, Part1 = true },
instanceMap
)
expect(update).to.be.ok()
expect(update.changedProperties.Part0).to.be.ok()
expect(update.changedProperties.Part0.Ref).to.equal("PART_A")
expect(update.changedProperties.Part1).to.be.ok()
expect(update.changedProperties.Part1.Ref).to.equal("PART_B")
end)
end)
end

View file

@ -168,4 +168,125 @@ return function()
part:Destroy()
end)
end)
-- -------------------------------------------------------------------
-- Ref property batching tests
-- -------------------------------------------------------------------
describe("Ref property batching", function()
local container
beforeEach(function()
container = Instance.new("Folder")
container.Name = "RefBatchTestContainer"
container.Parent = game:GetService("Workspace")
end)
afterEach(function()
if container then
container:Destroy()
container = nil
end
end)
it("should accumulate Ref property changes", function()
local instanceMap = InstanceMap.new()
local changeBatcher = ChangeBatcher.new(instanceMap, noop)
local model = Instance.new("Model")
model.Parent = container
instanceMap:insert("MODEL", model)
changeBatcher:add(model, "PrimaryPart")
local properties = changeBatcher.__pendingPropertyChanges[model]
expect(properties).to.be.ok()
expect(properties.PrimaryPart).to.equal(true)
changeBatcher:stop()
end)
it("should produce patch with Ref when target is tracked", function()
local instanceMap = InstanceMap.new()
local lastPatch = nil
local changeBatcher = ChangeBatcher.new(instanceMap, function(patch)
lastPatch = patch
end)
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
instanceMap:insert("MODEL", model)
instanceMap:insert("PART", part)
changeBatcher.__pendingPropertyChanges[model] = { PrimaryPart = true }
changeBatcher:__cycle(1.0) -- Force flush
expect(lastPatch).to.be.ok()
expect(#lastPatch.updated).to.equal(1)
expect(lastPatch.updated[1].changedProperties.PrimaryPart).to.be.ok()
expect(lastPatch.updated[1].changedProperties.PrimaryPart.Ref).to.equal("PART")
changeBatcher:stop()
end)
it("should drop unresolvable Ref with warning", function()
local instanceMap = InstanceMap.new()
local lastPatch = nil
local changeBatcher = ChangeBatcher.new(instanceMap, function(patch)
lastPatch = patch
end)
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
instanceMap:insert("MODEL", model)
-- part NOT in instanceMap
changeBatcher.__pendingPropertyChanges[model] = { PrimaryPart = true }
changeBatcher:__cycle(1.0) -- Force flush
-- No encodable changes → no patch flushed
expect(lastPatch).to.equal(nil)
changeBatcher:stop()
end)
it("should batch Ref with other property changes", function()
local instanceMap = InstanceMap.new()
local lastPatch = nil
local changeBatcher = ChangeBatcher.new(instanceMap, function(patch)
lastPatch = patch
end)
local model = Instance.new("Model")
model.Parent = container
local part = Instance.new("Part")
part.Parent = model
model.PrimaryPart = part
model.Name = "TestModel"
instanceMap:insert("MODEL", model)
instanceMap:insert("PART", part)
changeBatcher.__pendingPropertyChanges[model] = {
PrimaryPart = true,
Name = true,
}
changeBatcher:__cycle(1.0) -- Force flush
expect(lastPatch).to.be.ok()
expect(#lastPatch.updated).to.equal(1)
expect(lastPatch.updated[1].changedProperties.PrimaryPart).to.be.ok()
expect(lastPatch.updated[1].changedName).to.equal("TestModel")
changeBatcher:stop()
end)
end)
end

View file

@ -201,6 +201,51 @@ return function()
instanceMap:stop()
end)
it("should decode ref after instance was inserted later", function()
local instanceMap = InstanceMap.new()
local part = Instance.new("Part")
part.Parent = container
local partId = generateId()
-- Try decoding BEFORE inserting → should fail
local success1, _ = decodeValue({ Ref = partId }, instanceMap)
expect(success1).to.equal(false)
-- Insert and try again → should succeed
instanceMap:insert(partId, part)
local success2, value2 = decodeValue({ Ref = partId }, instanceMap)
expect(success2).to.equal(true)
expect(value2).to.equal(part)
instanceMap:stop()
end)
it("should decode ref to different instance types", function()
local instanceMap = InstanceMap.new()
local folder = Instance.new("Folder")
folder.Parent = container
local folderId = generateId()
instanceMap:insert(folderId, folder)
local model = Instance.new("Model")
model.Parent = container
local modelId = generateId()
instanceMap:insert(modelId, model)
local s1, v1 = decodeValue({ Ref = folderId }, instanceMap)
expect(s1).to.equal(true)
expect(v1).to.equal(folder)
local s2, v2 = decodeValue({ Ref = modelId }, instanceMap)
expect(s2).to.equal(true)
expect(v2).to.equal(model)
instanceMap:stop()
end)
end)
describe("Vector3 decoding", function()