test: add all sync tests

This commit is contained in:
littensy 2025-10-01 01:30:31 -07:00
parent 0dbc0587a0
commit 5a05f01223
5 changed files with 492 additions and 53 deletions

View file

@ -0,0 +1,96 @@
local Charm = require("../../Charm")
local client = require("../client")
local config = require("../config")
local server = require("../server")
local suite = require("../../../../test/suite")
local afterEach = suite.afterEach
local test = suite.test
afterEach(function()
server.reset()
client.clearAtoms()
config.preserveHistory = false
end)
test("should replicate server state to client", function()
local target = {}
local serverA = Charm.atom({ count = 0 })
local serverB = Charm.atom({ count = 0 })
local clientA = Charm.atom({ count = -1 })
local clientB = Charm.atom({ count = -1 })
client.registerAtoms({ a = clientA, b = clientB })
server.syncAtomsToClient(target, { a = serverA, b = serverB })
server.startSync(function(syncTarget, payloads)
if syncTarget == target then
client.applyPayloads(payloads)
end
end)
server.flush()
assert(clientA().count == 0, "Client atom A should be initialized to 0")
assert(clientB().count == 0, "Client atom B should be initialized to 0")
serverA({ count = 1 })
serverB({ count = 1 })
server.flush()
assert(clientA().count == 1, "Client atom A should be updated to 1")
assert(clientB().count == 1, "Client atom B should be updated to 1")
local prevClientB = clientB()
serverA({ count = 2 })
server.flush()
assert(clientA().count == 2, "Client atom A should be updated to 2")
assert(clientB() == prevClientB, "Client atom B should not be updated")
end)
test("should replicate historical server state to client", function()
local target = {}
local serverA = Charm.atom({ count = 0 })
local serverB = Charm.atom({ count = 0 })
local clientA = Charm.atom({ count = -1 })
local clientB = Charm.atom({ count = -1 })
config.preserveHistory = true
client.registerAtoms({ a = clientA, b = clientB })
server.syncAtomsToClient(target, { a = serverA, b = serverB })
server.startSync(function(syncTarget, payloads)
if syncTarget == target then
client.applyPayloads(payloads)
end
end)
local expectedA = { -1, 0, 1, 2, 3, 4 }
local expectedB = { -1, 0, 1, 2 }
Charm.effect(function()
local expected = table.remove(expectedA, 1)
assert(expected == clientA().count, `Client atom A should be {expected}`)
end)
Charm.effect(function()
local expected = table.remove(expectedB, 1)
assert(expected == clientB().count, `Client atom B should be {expected}`)
end)
server.flush()
serverA({ count = 1 })
serverA({ count = 2 })
serverB({ count = 1 })
serverB({ count = 2 })
server.flush()
assert(clientA().count == 2, "Client atom A should be updated to 2")
assert(clientB().count == 2, "Client atom B should be updated to 2")
local prevClientB = clientB()
serverA({ count = 3 })
serverA({ count = 4 })
server.flush()
assert(clientA().count == 4, "Client atom A should be updated to 4")
assert(clientB() == prevClientB, "Client atom B should not be updated")
end)
return {}

View file

@ -0,0 +1,306 @@
local Charm = require("../../Charm")
local config = require("../config")
local patch = require("../patch")
local server = require("../server")
local suite = require("../../../../test/suite")
local afterEach = suite.afterEach
local beforeEach = suite.beforeEach
local describe = suite.describe
local test = suite.test
afterEach(function()
server.reset()
end)
test("should replicate state changes", function()
local client = {}
local a = Charm.atom(0)
local b = Charm.atom(0)
local c = Charm.atom(0)
local target, payloads
server.syncAtomsToClient(client, { a = a, b = b, c = c })
server.startSync(function(...)
target, payloads = ...
end)
server.flush()
assert(target == client, "Expected target to be the client")
assert(payloads[1].type == "init", "Expected first payload to be init")
assert(payloads[1].data.a == a(), "Expected init payload to contain atom 'a'")
assert(payloads[1].data.b == b(), "Expected init payload to contain atom 'b'")
assert(payloads[1].data.c == c(), "Expected init payload to contain atom 'c'")
a(1)
b(2)
server.flush()
assert(target == client, "Expected target to be the client")
assert(payloads[1].type == "patch", "Expected first payload to be patch")
assert(payloads[1].data.a == a(), "Expected patch payload to contain atom 'a'")
assert(payloads[1].data.b == b(), "Expected patch payload to contain atom 'b'")
end)
test("should replicate partial state patches", function()
local client = {}
local a = Charm.atom({ x = 0, y = 0 })
local b = Charm.atom(0 :: number?)
local target, payloads
server.syncAtomsToClient(client, { a = a, b = b })
server.startSync(function(...)
target, payloads = ...
end)
server.flush()
assert(target == client, "Expected target to be the client")
assert(payloads[1].type == "init", "Expected first payload to be init")
assert(payloads[1].data.a.x == a().x, "Expected init payload to contain atom 'a.x'")
assert(payloads[1].data.a.y == a().y, "Expected init payload to contain atom 'a.y'")
assert(payloads[1].data.b == b(), "Expected init payload to contain atom 'b'")
a({ x = 1, y = 0 })
b(nil)
server.flush()
assert(target == client, "Expected target to be the client")
assert(payloads[1].type == "patch", "Expected first payload to be patch")
assert(payloads[1].data.a.x == 1, "Expected patch payload to contain atom 'a.x'")
assert(payloads[1].data.a.y == nil, "Expected patch payload to not contain atom 'a.y'")
assert(patch.isNone(payloads[1].data.b), "Expected patch payload to contain atom 'b' as None")
end)
test("should init new clients", function()
local clientA = {}
local clientB = {}
local a = Charm.atom(0)
local payloadsA, payloadsB
server.syncAtomsToClient(clientA, { a = a })
server.startSync(function(target, payloads)
if target == clientA then
payloadsA = payloads
elseif target == clientB then
payloadsB = payloads
end
end)
a(1)
server.flush()
assert(payloadsA[1].type == "init", "Expected first payload to be init for client A")
assert(payloadsA[1].data.a == a(), "Expected init payload to contain atom 'a' for client A")
server.syncAtomsToClient(clientB, { a = a })
a(2)
server.flush()
assert(payloadsA[1].type == "patch", "Expected first payload to be patch for client A")
assert(payloadsA[1].data.a == a(), "Expected patch payload to contain atom 'a' for client A")
assert(payloadsB[1].type == "init", "Expected first payload to be init for client B")
assert(payloadsB[1].data.a == a(), "Expected init payload to contain atom 'a' for client B")
end)
test("should init new atoms", function()
local client = {}
local a = Charm.atom(0)
local b = Charm.atom(0)
local target, payloads
server.syncAtomsToClient(client, { a = a })
server.startSync(function(...)
target, payloads = ...
end)
server.flush()
server.syncAtomsToClient(client, { b = b })
a(1)
b(1)
server.flush()
assert(target == client, "Expected target to be the client")
assert(payloads[1].type == "init", "Expected first payload to be init")
assert(payloads[1].data.b == b(), "Expected init payload to contain atom 'b'")
assert(payloads[2].type == "patch", "Expected second payload to be patch")
assert(payloads[2].data.a == a(), "Expected patch payload to contain atom 'a'")
assert(payloads[2].data.b == nil, "Expected patch payload to not contain atom 'b'")
end)
test("should only replicate to synced clients", function()
local clientA = {}
local clientB = {}
local a = Charm.atom(0)
local b = Charm.atom(0)
local payloadsA, payloadsB
server.syncAtomsToClient(clientA, { a = a })
server.syncAtomsToClient(clientB, { b = b })
server.startSync(function(target, payloads)
if target == clientA then
payloadsA = payloads
elseif target == clientB then
payloadsB = payloads
end
end)
server.flush()
assert(payloadsA[1].type == "init", "Expected first payload to be init for client A")
assert(payloadsA[1].data.a == a(), "Expected init payload to contain atom 'a' for client A")
assert(payloadsA[1].data.b == nil, "Expected init payload to not contain atom 'b' for client A")
assert(payloadsB[1].type == "init", "Expected first payload to be init for client B")
assert(payloadsB[1].data.b == b(), "Expected init payload to contain atom 'b' for client B")
assert(payloadsB[1].data.a == nil, "Expected init payload to not contain atom 'a' for client B")
payloadsA = {}
payloadsB = {}
a(1)
server.flush()
assert(payloadsA[1].type == "patch", "Expected first payload to be patch for client A")
assert(payloadsA[1].data.a == a(), "Expected patch payload to contain atom 'a' for client A")
assert(payloadsA[1].data.b == nil, "Expected patch payload to not contain atom 'b' for client A")
assert(#payloadsB == 0, "Expected no payloads for client B")
payloadsA = {}
payloadsB = {}
b(1)
server.flush()
assert(#payloadsA == 0, "Expected no payloads for client A")
assert(payloadsB[1].type == "patch", "Expected first payload to be patch for client B")
assert(payloadsB[1].data.b == b(), "Expected patch payload to contain atom 'b' for client B")
assert(payloadsB[1].data.a == nil, "Expected patch payload to not contain atom 'a' for client B")
end)
test("should validate state", function()
local client = {}
local a = Charm.atom({})
server.syncAtomsToClient(client, { a = a })
server.startSync(function() end)
a({ [0.5] = true })
assert(not pcall(server.flush), "Expected server.flush to fail")
a({ [0.5] = true, valid = true })
server.flush()
end)
test("should serialize state", function()
local client = {}
local a = Charm.atom({ 1, 2, 3, 4, 5 })
local payloads
server.syncAtomsToClient(client, { a = a })
server.startSync(function(_, p)
payloads = p
end)
server.flush()
a({ 1, 2, 0, 4 })
server.flush()
assert(payloads[1].data.a["3"] == 0, "Expected index 3 to be serialized as string key")
assert(patch.isNone(payloads[1].data.a["5"]), "Expected index 5 to be serialized as string key")
end)
describe("historical", function()
beforeEach(function()
config.preserveHistory = true
end)
afterEach(function()
config.preserveHistory = false
end)
test("should replicate granular state changes", function()
local client = {}
local a = Charm.atom(0)
local b = Charm.atom(0)
local c = Charm.atom(0)
local target, payloads
server.syncAtomsToClient(client, { a = a, b = b, c = c })
server.startSync(function(...)
target, payloads = ...
end)
server.flush()
assert(target == client, "Expected target to be the client")
assert(payloads[1].type == "init", "Expected first payload to be init")
assert(payloads[1].data.a == 0, "Expected init payload to contain atom 'a'")
assert(payloads[1].data.b == 0, "Expected init payload to contain atom 'b'")
assert(payloads[1].data.c == 0, "Expected init payload to contain atom 'c'")
a(1)
a(2)
b(2)
server.flush()
assert(target == client, "Expected target to be the client")
assert(payloads[1].type == "patch", "Expected first payload to be patch")
assert(payloads[1].data.a == 1, "Expected patch payload to contain atom 'a'")
assert(payloads[2].data.a == 2, "Expected patch payload to contain atom 'a'")
assert(payloads[3].data.b == 2, "Expected patch payload to contain atom 'b'")
end)
test("should init new clients", function()
local clientA = {}
local clientB = {}
local a = Charm.atom(0)
local payloadsA, payloadsB
server.syncAtomsToClient(clientA, { a = a })
server.startSync(function(target, payloads)
if target == clientA then
payloadsA = payloads
elseif target == clientB then
payloadsB = payloads
end
end)
a(1)
a(2)
server.flush()
assert(payloadsA[1].type == "init", "Expected first payload to be init for client A")
assert(payloadsA[1].data.a == 1, "Expected init payload to contain atom 'a' for client A")
assert(payloadsA[2].type == "patch", "Expected second payload to be patch for client A")
assert(payloadsA[2].data.a == 2, "Expected second patch payload to contain atom 'a' with value 1 for client A")
server.syncAtomsToClient(clientB, { a = a })
a(3)
a(4)
server.flush()
assert(payloadsA[1].type == "patch", "Expected first payload to be patch for client A")
assert(payloadsA[1].data.a == 3, "Expected patch payload to contain atom 'a' for client A")
assert(payloadsA[2].data.a == 4, "Expected patch payload to contain atom 'a' for client A")
assert(payloadsB[1].type == "init", "Expected first payload to be init for client B")
assert(payloadsB[1].data.a == 3, "Expected init payload to contain atom 'a' for client B")
assert(payloadsB[2].type == "patch", "Expected second payload to be patch for client B")
assert(payloadsB[2].data.a == 4, "Expected second patch payload to contain atom 'a' with value 3 for client B")
end)
test("should init new atoms", function()
local client = {}
local a = Charm.atom(0)
local b = Charm.atom(0)
local payloads
server.syncAtomsToClient(client, { a = a })
server.startSync(function(_, ...)
payloads = ...
end)
server.flush()
assert(payloads[1].type == "init", "Expected first payload to be init")
assert(payloads[1].data.a == 0, "Expected init payload to contain atom 'a'")
server.syncAtomsToClient(client, { b = b })
a(1)
a(2)
b(1)
b(2)
server.flush()
assert(payloads[1].type == "patch", "Expected first payload to be patch")
assert(payloads[1].data.a == 1, "Expected patch payload to contain atom 'a'")
assert(payloads[2].data.a == 2, "Expected patch payload to contain atom 'a'")
assert(payloads[3].type == "init", "Expected third payload to be init")
assert(payloads[3].data.b == 1, "Expected init payload to contain atom 'b'")
assert(payloads[4].type == "patch", "Expected fourth payload to be patch")
assert(payloads[4].data.b == 2, "Expected patch payload to contain atom 'b'")
end)
end)
return {}

View file

@ -4,6 +4,10 @@ suite.describe("client", function()
require("./client.spec")
end)
suite.describe("e2e", function()
require("./e2e.spec")
end)
suite.describe("flatten", function()
require("./flatten.spec")
end)
@ -12,6 +16,10 @@ suite.describe("patch", function()
require("./patch.spec")
end)
suite.describe("server", function()
require("./server.spec")
end)
suite.describe("validate", function()
require("./validate.spec")
end)