Files
ComfyAssets-ComfyUI_PromptM…/tests/js/inspection-semantics-sync.test.js
Vito Sansevero 815ece045b feat(inspect): port legacy parser rules the new resolver missed (upstream nodes)
R6 and R9 for nodes not installed beside ComfyUI here. Their input names
and output slots come from each pack's source at the commit the saved
workflows record in the node's cnr_id and ver properties (the task report
lists repository, commit, file and line):

- samplers ClownsharKSampler_Beta (RES4LYF), KSampler (Efficient) and
  ttN KSampler_v2 (latent input "latent");
- pass-throughs SeedVarianceEnhancer and ApplyInstantIDAdvanced (positive
  1, negative 2; output 0 is the model);
- selector Switch conditioning [Crystools];
- encoder smZ CLIPTextEncode: its text when with_SDXL is false; when true
  the connected CLIP decides at run time which inputs are read, so the
  prompt is known only when text, text_g and text_l agree;
- strings ttN text, CR Text, CR Prompt Text and TextBoxMira;
- facts model pass-through CR Apply LoRA Stack.

Inputs that replace what a sampler reads are not followed: RES4LYF options
(may carry a guider, and with sigmas replace the settings), an Efficiency
script and a ttN XY plot leave the prompt unsupported and the facts
unresolved. The overwritten PromptManager refusal (R1) still holds behind
SeedVarianceEnhancer. The default profile does not read the table.
2026-10-19 15:00:33 -07:00

80 lines
4.4 KiB
JavaScript

// Run with: node --test tests/js/
// comfy-metadata.js stays a classic script for the legacy pages, so its facts
// tables are inline literals. This test keeps them equal to the one semantics
// table (web/js/inspection-semantics.json) that Python also loads.
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const metadata = require("../../web/js/comfy-metadata.js");
const tablePath = path.join(__dirname, "..", "..", "web", "js", "inspection-semantics.json");
const semantics = JSON.parse(fs.readFileSync(tablePath, "utf8"));
function plain(value) {
if (value instanceof Set) return [...value];
if (Array.isArray(value)) return value.map(plain);
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, plain(item)]));
return value;
}
test("facts tables equal semantics.facts", () => {
assert.equal(typeof metadata._factsTables, "object", "comfy-metadata exposes its facts tables for this test");
assert.deepStrictEqual(plain(metadata._factsTables), semantics.facts);
});
test("facts guider providers are guiders in the semantics table", () => {
const providers = Object.entries(semantics.facts.configuration_outputs).filter(([, adapter]) => adapter.kind === "guider");
assert.ok(providers.length > 0);
for (const [name] of providers) assert.ok(Object.hasOwn(semantics.guiders, name), name);
});
test("the facts tables are not part of the legacy page contract", () => {
assert.deepStrictEqual(Object.keys(metadata).filter(key => !key.startsWith("_")).sort(), ["PLACEHOLDERS", "extractGenerationFacts", "extractGenerationParams"]);
assert.ok(Object.isFrozen(metadata._factsTables));
});
// Task 2.1 checked only `facts`; the facts code also reads the samplers table
// (which classes are samplers, their facts shape and a detailer's image input).
test("sampler facts table equals semantics.samplers", () => {
assert.equal(typeof metadata._samplerTable, "object", "comfy-metadata exposes its sampler table for this test");
// Task 2.7b: plus `fact_overrides`; `prompt_overrides` is read only by the resolver.
const expected = Object.fromEntries(Object.entries(semantics.samplers).map(([name, entry]) => [name, {
facts: entry.facts,
...(entry.image_stage === undefined ? {} : { image_stage: entry.image_stage }),
...(entry.fact_overrides === undefined ? {} : { fact_overrides: entry.fact_overrides }),
}]));
assert.deepStrictEqual(plain(metadata._samplerTable), expected);
assert.ok(Object.isFrozen(metadata._samplerTable));
});
test("detailer-stage samplers, and only they, name an image stage", () => {
for (const [name, entry] of Object.entries(semantics.samplers)) {
assert.equal(Object.hasOwn(metadata._samplerTable[name], "image_stage"), entry.strategy === "detailer-stage", name);
}
});
// The stage walk treats every table class as known (the resolver's `all_classes`).
test("stage walk table classes equal every class in the semantics table", () => {
const classes = new Set();
for (const category of ["samplers", "guiders", "selectors", "composites", "savers"]) Object.keys(semantics[category]).forEach(name => classes.add(name));
for (const category of ["conditioning", "encoders", "strings"]) semantics[category].forEach(entry => classes.add(entry.class));
for (const category of ["runtime", "non_file_outputs"]) semantics[category].forEach(name => classes.add(name));
assert.deepStrictEqual([...metadata._tableClasses].sort(), [...classes].sort());
});
// Review I-2: one source for the stage-walk input lists (Python reads them there too).
test("stage walk inputs equal semantics.stage_inputs", () => {
assert.equal(typeof semantics.stage_inputs, "object");
assert.equal(typeof metadata._stageInputs, "object", "comfy-metadata exposes its stage inputs for this test");
assert.deepStrictEqual(plain(metadata._stageInputs), semantics.stage_inputs);
assert.ok(Object.isFrozen(metadata._stageInputs));
});
// Review M-3: JS treats every samplers entry as a candidate; the server only these strategies.
test("every samplers entry is a candidate strategy", () => {
for (const [name, entry] of Object.entries(semantics.samplers)) {
assert.ok(["ksampler", "custom-advanced", "detailer-stage"].includes(entry.strategy), name);
}
});