Files
xmarre-ComfyUI-Image-Conveyor/tests/test_reference_toggles.mjs
T
xmarre 6cfefb4d60 Add main, last-frame, and reference output controls (#37)
Release vehicle for Image Conveyor v1.6.0.

Adds backend-authoritative main/reference output toggles, a dedicated queue-driven last_frame output, deterministic queue-role snapshots, disabled-branch prompt pruning, append-only saved-node migration, and v1.6.0 release metadata.

Validated in live ComfyUI and by the complete GitHub Actions suite.
2026-08-17 21:31:30 +02:00

192 lines
6.7 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import {
applyMainOutputToggleToReservation,
applyReferenceToggleMaskToReservation,
calculateReferenceToggleRect,
filterReferenceOutputSlotsByToggleMask,
inputRequiredFromNodeDef,
normalizeMainOutputEnabled,
normalizeReferenceToggleMask,
pruneDisabledOutputBranches,
referenceToggleHit,
toggleMainOutputEnabled,
toggleReferenceToggleMask
} from '../web/image_conveyor_reference_toggles_math.mjs'
test('reference toggles default enabled and only explicit false disables a slot', () => {
assert.deepEqual(normalizeReferenceToggleMask(undefined), Array(8).fill(true))
assert.deepEqual(
normalizeReferenceToggleMask([false, true, null, 0, '', false]),
[false, true, true, true, true, false, true, true]
)
})
test('main output toggle defaults enabled and round-trips explicit disable', () => {
assert.equal(normalizeMainOutputEnabled(undefined), true)
assert.equal(normalizeMainOutputEnabled(null), true)
assert.equal(normalizeMainOutputEnabled(false), false)
assert.equal(toggleMainOutputEnabled(undefined), false)
assert.equal(toggleMainOutputEnabled(false), true)
})
test('toggling preserves the fixed eight-slot shape', () => {
assert.deepEqual(
toggleReferenceToggleMask(undefined, 2),
[true, true, false, true, true, true, true, true]
)
assert.deepEqual(toggleReferenceToggleMask([false], -1), [false, true, true, true, true, true, true, true])
})
test('queue snapshot filtering removes only valid disabled reference slots', () => {
const mask = [true, false, true, true, false, true, true, false]
assert.deepEqual(filterReferenceOutputSlotsByToggleMask([1, 2, 5, 8], mask), [1])
// Preserve malformed entries so backend validation remains authoritative instead of hiding corruption.
assert.deepEqual(
filterReferenceOutputSlotsByToggleMask([1, '2', 9, 2], mask),
[1, '2', 9]
)
assert.equal(filterReferenceOutputSlotsByToggleMask(null, mask), null)
})
test('reservation masking preserves unrelated payload fields', () => {
const payload = { id: 'A', annotated: 'A.png [input]', reference_output_slots: [1, 2, 8] }
assert.deepEqual(applyReferenceToggleMaskToReservation(payload, [true, false, true, true, true, true, true, false]), {
id: 'A', annotated: 'A.png [input]', reference_output_slots: [1]
})
assert.equal(applyReferenceToggleMaskToReservation(null, [false]), null)
assert.deepEqual(applyReferenceToggleMaskToReservation({ id: 'A' }, [false]), { id: 'A' })
})
test('disabling main output strips queue reservation members but preserves reference snapshot', () => {
const payload = {
id: 'A',
annotated: 'A.png [input]',
items: [{ id: 'A', annotated: 'A.png [input]' }],
reference_output_slots: [1, 4, 8]
}
assert.deepEqual(applyMainOutputToggleToReservation(payload, false), {
main_output_enabled: false,
reference_output_slots: [1, 4, 8]
})
assert.deepEqual(applyMainOutputToggleToReservation(payload, true), {
...payload,
main_output_enabled: true
})
})
test('object-info input contracts distinguish required and optional sockets', () => {
const h3Definition = {
input: {
required: {
model: ['MODEL', {}],
positive: ['CONDITIONING', {}]
},
optional: {
first_frame: ['IMAGE', {}],
reference_image_1: ['IMAGE', {}]
}
}
}
assert.equal(inputRequiredFromNodeDef(h3Definition, 'model'), true)
assert.equal(inputRequiredFromNodeDef(h3Definition, 'first_frame'), false)
assert.equal(inputRequiredFromNodeDef(h3Definition, 'reference_image_1'), false)
assert.equal(inputRequiredFromNodeDef(h3Definition, 'missing'), null)
assert.equal(inputRequiredFromNodeDef(null, 'first_frame'), null)
})
test('disabled main prompt pruning uses object-info contracts at the scaler-to-Continuum boundary', () => {
const prompt = {
'164': { inputs: {}, class_type: 'ImageConveyor' },
'123': {
inputs: { image: ['164', 0], megapixels: 0.7 },
class_type: 'ImageScaleToTotalPixelsX'
},
'200': {
inputs: {
first_frame: ['123', 0],
reference_image_1: ['164', 6],
model: ['300', 0]
},
class_type: 'H3ContinuumSamplerProduction'
},
'300': { inputs: {}, class_type: 'ModelLoader' }
}
const nodeDefs = {
ImageScaleToTotalPixelsX: {
input: {
required: { image: ['IMAGE', {}], megapixels: ['FLOAT', {}] },
optional: {}
}
},
H3ContinuumSamplerProduction: {
input: {
required: { model: ['MODEL', {}] },
optional: {
first_frame: ['IMAGE', {}],
reference_image_1: ['IMAGE', {}]
}
}
}
}
const resolve = (nodeId, inputName) => {
const classType = prompt[String(nodeId)]?.class_type
return inputRequiredFromNodeDef(nodeDefs[classType], inputName) ?? true
}
pruneDisabledOutputBranches(
prompt,
[{ nodeId: '164', outputIndexes: [0, 1] }],
resolve
)
assert.equal(prompt['123'], undefined)
assert.equal(Object.hasOwn(prompt['200'].inputs, 'first_frame'), false)
assert.deepEqual(prompt['200'].inputs.reference_image_1, ['164', 6])
assert.deepEqual(prompt['200'].inputs.model, ['300', 0])
assert.ok(prompt['164'])
assert.ok(prompt['200'])
})
test('disabled main prompt pruning removes image and mask consumers independently', () => {
const prompt = {
'1': { inputs: {}, class_type: 'ImageConveyor' },
'2': { inputs: { image: ['1', 0] }, class_type: 'RequiredImageNode' },
'3': { inputs: { mask: ['1', 1] }, class_type: 'RequiredMaskNode' },
'4': {
inputs: { optional_image: ['2', 0], optional_mask: ['3', 0], keep: 1 },
class_type: 'OptionalConsumer'
}
}
pruneDisabledOutputBranches(
prompt,
[{ nodeId: 1, outputIndexes: [0, 1] }],
(nodeId, inputName) => nodeId === '2' || nodeId === '3'
)
assert.equal(prompt['2'], undefined)
assert.equal(prompt['3'], undefined)
assert.deepEqual(prompt['4'].inputs, { keep: 1 })
})
test('unknown prompt input contracts fail closed as required', () => {
const prompt = {
'1': { inputs: {}, class_type: 'ImageConveyor' },
'2': { inputs: { mystery: ['1', 0] }, class_type: 'UnknownNode' }
}
pruneDisabledOutputBranches(prompt, [{ nodeId: '1', outputIndexes: [0] }])
assert.equal(prompt['2'], undefined)
})
test('toggle geometry stays between the shelf and output label and hit testing is exact', () => {
const rect = calculateReferenceToggleRect(390, 455, 210)
assert.deepEqual(rect, { x: 422, y: 203, width: 26, height: 14 })
assert.equal(referenceToggleHit([{ index: 3, ...rect }], 435, 210), 3)
assert.equal(referenceToggleHit([{ index: 3, ...rect }], 455, 210), null)
assert.equal(calculateReferenceToggleRect(430, 450, 210), null)
})