82 lines
4.7 KiB
JavaScript
82 lines
4.7 KiB
JavaScript
const { test } = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const vm = require("node:vm");
|
|
const { loadBackground } = require("./support/characterization.cjs");
|
|
const factory = (file, name) => new vm.Script(fs.readFileSync(file, "utf8") + "\n" + name).runInNewContext();
|
|
const model = factory("src/features/org-chart/model.js", "createOrgChartModel")();
|
|
const person = (id, parentId = null) => ({ id, parentId, name: id, role: "Director", notes: "", collapsed: false });
|
|
const plain = value => JSON.parse(JSON.stringify(value));
|
|
|
|
test("contact PartyNumber survives normalization and edits while legacy nodes remain supported", () => {
|
|
const nodes = model.normalize([{...person("a"), contactPartyId:"123", PartyNumber:18725565}]);
|
|
assert.equal(nodes[0].PartyNumber,"18725565");
|
|
assert.equal(model.normalize([{...person("alpha"), PartyNumber:"A4KCS2P"}])[0].PartyNumber,"A4KCS2P");
|
|
assert.equal(model.upsert(nodes,{...nodes[0],name:"Updated"})[0].PartyNumber,"18725565");
|
|
assert.equal(model.normalize([person("legacy")])[0].PartyNumber,undefined);
|
|
assert.throws(()=>model.normalize([{...person("a"),PartyNumber:"bad'query"}]),/PartyNumber/);
|
|
});
|
|
|
|
test("organization charts validate IDs, parents, cycles, required fields and limits", () => {
|
|
const nodes = [person("a"), person("b", "a"), person("c", "b")];
|
|
assert.equal(model.normalize(nodes).length, 3);
|
|
for (const invalid of [
|
|
[person("a"), person("a")], [person("a", "missing")], [person("a", "a")],
|
|
[person("a", "b"), person("b", "a")], [{ ...person("a"), name: " " }],
|
|
[{ ...person("a"), notes: "x".repeat(4001) }],
|
|
Array.from({ length: 501 }, (_, i) => person("p" + i))
|
|
]) assert.throws(() => model.normalize(invalid));
|
|
assert.throws(() => model.upsert(nodes, { ...nodes[0], parentId: "c" }), /cycle/);
|
|
assert.deepEqual([...model.descendants(nodes, "b")], ["b", "c"]);
|
|
});
|
|
|
|
test("editing and deleting preserve other people and promote direct reports without losing notes", () => {
|
|
const nodes = [person("a"), { ...person("b", "a"), notes: "<script>literal text</script>" }, person("c", "b")];
|
|
const before = JSON.stringify(nodes);
|
|
const updated = model.upsert(nodes, { ...nodes[1], name: "Changed" });
|
|
assert.equal(updated[1].name, "Changed");
|
|
const removed = model.remove(updated, "a");
|
|
assert.equal(removed[0].parentId, null);
|
|
assert.equal(removed[1].parentId, "b");
|
|
assert.equal(removed[0].notes, nodes[1].notes);
|
|
assert.equal(JSON.stringify(nodes), before);
|
|
});
|
|
|
|
test("collapsed branches omit descendants from layout, not persisted data", () => {
|
|
const nodes = [person("a"), { ...person("b", "a"), collapsed: true }, person("c", "b"), person("d")];
|
|
const root = model.hierarchy(nodes);
|
|
assert.deepEqual(plain(root.children.map(node => node.id)), ["a", "d"]);
|
|
assert.equal(root.children[0].children[0].children.length, 0);
|
|
assert.equal(nodes.length, 4);
|
|
});
|
|
|
|
test("contact identity survives persistence while legacy people remain valid", () => {
|
|
const nodes = model.normalize([person("legacy"), { ...person("contact"), contactPartyId: "300008443263169" }]);
|
|
assert.equal(nodes[0].contactPartyId, undefined);
|
|
assert.equal(nodes[1].contactPartyId, "300008443263169");
|
|
assert.throws(() => model.normalize([{ ...person("a"), contactPartyId: "" }]));
|
|
});
|
|
|
|
test("normalization persists Job Title but never email address", () => {
|
|
const [node] = model.normalize([{ ...person("a"), emailAddress: "test@example.invalid", jobTitle: "Director" }]);
|
|
assert.equal(Object.hasOwn(node, "role"), false);
|
|
assert.equal(Object.hasOwn(node, "emailAddress"), false);
|
|
assert.equal(node.jobTitle, "Director");
|
|
});
|
|
|
|
test("organization storage rejects invalid input and unavailable IndexedDB without claiming a save", async () => {
|
|
const store = factory("src/features/org-chart/storage.js", "createOrgChartStorage")({ environment: {}, model });
|
|
await assert.rejects(store.load({ principal: "", customerId: "1" }), /current user/);
|
|
await assert.rejects(store.load({ principal: "test@example.invalid", customerId: "1" }), /IndexedDB/);
|
|
await assert.rejects(store.save({ principal: "test@example.invalid", customerId: "1", revision: 0, nodes: [person("x", "missing")] }), /missing manager/);
|
|
});
|
|
|
|
for (const family of ["chrome", "firefox"]) test(family + ": organization messages return storage errors through the existing reply contract", async () => {
|
|
const app = loadBackground({ family });
|
|
for (const operation of ["loadOrganizationChart", "saveOrganizationChart"]) {
|
|
const result = await app.dispatch({ type: "opportunitiesExtension." + operation, principal: "test@example.invalid", customerId: "1", revision: 0, nodes: [] });
|
|
assert.equal(result.ok, false);
|
|
assert.match(result.error, /IndexedDB/);
|
|
}
|
|
});
|