QA + polish on the unmerged SP3 constraint-reach work, plus the node-id
redesign and bundle-aware bulk toolbar.
- reach badge reactivity: pairingBadgeEqual compares reach + pairingMapEqual
compares contributors (context.ts) so a target_select edit re-renders.
- TargetReachSection: neutral section highlight + custom up/down count stepper.
- nested carrier renders ONE badge (constraint-pairs.ts: nested coverage goes
to viaInbound only, not also contributors).
- instance target_select override reaches the badge: cross-node-pairings.ts
folds the instance value over payload; the modal collapses to null only when
it equals the library default (preserves an explicit "all"). Engine
precedence (instance over payload) locked by tests.
- graph.ts writeBindings honors instance.variable_binding, fixing the duplicate
shadow ("override") badge + missing-var scan + preview for renamed bindings.
- node identity: fixed, unique, POV-independent adjective-noun codename from the
litegraph node id (node-codename.ts); read-only neutral chip; dropped the
editable node_label + A/B/C letters.
- nested-pick label reads "@<target> in <carrier>" (target by display name).
- bulk toolbar: toggle-all flips bundle masters + top-level modules (not bundle
children); collapse/expand-all flips bundle frames; buttons resized to the
row-icon size.
70 lines
2.7 KiB
TypeScript
70 lines
2.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { baseCodename, assignCodenames } from "./node-codename";
|
|
|
|
describe("baseCodename", () => {
|
|
it("is a lowercase adjective-noun pair", () => {
|
|
expect(baseCodename(7)).toMatch(/^[a-z]+-[a-z]+$/);
|
|
});
|
|
|
|
it("is deterministic — same id always yields the same name", () => {
|
|
expect(baseCodename(42)).toBe(baseCodename(42));
|
|
expect(baseCodename("node-99")).toBe(baseCodename("node-99"));
|
|
});
|
|
|
|
it("treats numeric and string forms of an id identically", () => {
|
|
// Litegraph ids are numbers; some call sites stringify. Both must agree.
|
|
expect(baseCodename(13)).toBe(baseCodename("13"));
|
|
});
|
|
|
|
it("spreads distinct ids across distinct names (mostly)", () => {
|
|
// Not a uniqueness guarantee (that's assignCodenames' job) — just a
|
|
// sanity check that the hash actually varies the output.
|
|
const names = new Set(Array.from({ length: 50 }, (_, i) => baseCodename(i)));
|
|
expect(names.size).toBeGreaterThan(40);
|
|
});
|
|
});
|
|
|
|
describe("assignCodenames", () => {
|
|
it("maps each id to its base codename when there are no collisions", () => {
|
|
const ids = [1, 2, 3];
|
|
const map = assignCodenames(ids);
|
|
for (const id of ids) {
|
|
// No collision among these → pure base name, no suffix.
|
|
expect(map.get(String(id))).toBe(baseCodename(id));
|
|
}
|
|
});
|
|
|
|
it("GUARANTEES uniqueness across a large id set (the core contract)", () => {
|
|
// Even if base codenames collide within this set, every assigned name
|
|
// must be distinct — that's the "does not duplicate on the canvas"
|
|
// guarantee. 300 ids comfortably forces some base collisions at 4096
|
|
// combos, exercising the dedup path.
|
|
const ids = Array.from({ length: 300 }, (_, i) => i + 1);
|
|
const map = assignCodenames(ids);
|
|
const values = [...map.values()];
|
|
expect(values.length).toBe(ids.length);
|
|
expect(new Set(values).size).toBe(ids.length); // all unique
|
|
});
|
|
|
|
it("deduped names keep the adjective-noun base + a numeric suffix", () => {
|
|
const ids = Array.from({ length: 300 }, (_, i) => i + 1);
|
|
for (const name of assignCodenames(ids).values()) {
|
|
expect(name).toMatch(/^[a-z]+-[a-z]+(-\d+)?$/);
|
|
}
|
|
});
|
|
|
|
it("is order-independent: the assigned name for an id doesn't depend on input order", () => {
|
|
// Stable identity — feeding the same id set in any order yields the same
|
|
// per-id name (dedup resolves deterministically by id, not arrival order).
|
|
const a = assignCodenames([10, 20, 30, 40]);
|
|
const b = assignCodenames([40, 10, 30, 20]);
|
|
for (const id of [10, 20, 30, 40]) {
|
|
expect(b.get(String(id))).toBe(a.get(String(id)));
|
|
}
|
|
});
|
|
|
|
it("handles an empty input", () => {
|
|
expect(assignCodenames([]).size).toBe(0);
|
|
});
|
|
});
|