Implemented the Organization Chart feature to create customer organizational
This commit is contained in:
81
tests/orgChart.test.cjs
Normal file
81
tests/orgChart.test.cjs
Normal file
@@ -0,0 +1,81 @@
|
||||
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/);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user