Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { CRDT_CODES, CrdtError } from "../../errors/crdt-error.js";
import { PNCounter } from "./pn-counter.js";
describe("PNCounter", () => {
it("starts at 0", { timeout: 5000 }, () => {
expect(new PNCounter().total()).toBe(0);
});
it("increments", { timeout: 5000 }, () => {
const c = new PNCounter();
c.increment("n1", 10);
expect(c.total()).toBe(10);
});
it("decrements", { timeout: 5000 }, () => {
const c = new PNCounter();
c.increment("n1", 5);
c.decrement("n1", 3);
expect(c.total()).toBe(2);
});
it("can go negative", { timeout: 5000 }, () => {
const c = new PNCounter();
c.decrement("n1", 5);
expect(c.total()).toBe(-5);
});
it("throws for zero increment delta", { timeout: 5000 }, () => {
expect(() => new PNCounter().increment("n1", 0)).toThrow(CrdtError);
});
it("throws for negative decrement delta", { timeout: 5000 }, () => {
expect(() => new PNCounter().decrement("n1", -1)).toThrow(CrdtError);
});
it("decrement throws for zero delta", { timeout: 5000 }, () => {
const err = (() => {
try {
new PNCounter().decrement("n1", 0);
} catch (e) {
return e;
}
})();
expect((err as CrdtError).code).toBe(CRDT_CODES.OPERATION_INVALID);
});
it("merge", { timeout: 5000 }, () => {
const a = new PNCounter();
a.increment("n1", 5);
const b = new PNCounter();
b.increment("n2", 3);
b.decrement("n1", 2);
a.merge(b.state());
expect(a.total()).toBe(6); // 5+3-2
});
it("toJSON / fromJSON round-trip", { timeout: 5000 }, () => {
const a = new PNCounter();
a.increment("n1", 10);
a.decrement("n1", 4);
const b = new PNCounter();
b.fromJSON(a.toJSON());
expect(b.total()).toBe(6);
});
it("fromJSON throws for invalid type", { timeout: 5000 }, () => {
expect(() =>
new PNCounter().fromJSON(
JSON.stringify({
type: "wrong",
increments: {},
decrements: {},
})
)
).toThrow(CrdtError);
});
it("fromJSON throws for bad JSON", { timeout: 5000 }, () => {
expect(() => new PNCounter().fromJSON("bad")).toThrow(CrdtError);
});
it("clone is independent", { timeout: 5000 }, () => {
const a = new PNCounter();
a.increment("n1", 5);
const b = a.clone();
b.increment("n1", 10);
expect(a.total()).toBe(5);
});
it("value() equals total()", { timeout: 5000 }, () => {
const c = new PNCounter();
c.increment("n1", 7);
expect(c.value()).toBe(c.total());
});
it("default delta=1 for increment", { timeout: 5000 }, () => {
const c = new PNCounter();
c.increment("n1");
expect(c.total()).toBe(1);
});
it("default delta=1 for decrement", { timeout: 5000 }, () => {
const c = new PNCounter();
c.increment("n1", 5);
c.decrement("n1");
expect(c.total()).toBe(4);
});
it("merge does not regress lower remote increment", {
timeout: 5000,
}, () => {
const a = new PNCounter();
a.increment("n1", 10);
const b = new PNCounter();
b.increment("n1", 3); // lower than a
a.merge(b.state());
expect(a.total()).toBe(10); // should not decrease
});
it("merge does not regress lower remote decrement", {
timeout: 5000,
}, () => {
const a = new PNCounter();
a.decrement("n1", 10);
const b = new PNCounter();
b.decrement("n1", 3); // lower than a
a.merge(b.state());
expect(a.total()).toBe(-10); // should not change
});
});