Files
xmarre-ComfyUI-Image-Conveyor/tests/test_reference_shelf.mjs

236 lines
9.0 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import {
OUTPUT_MODE_PERSISTENT,
OUTPUT_MODE_QUEUE_GROUP,
applyReferenceAssignments,
calculateReferenceShelfLayout,
classifyReferenceDrag,
connectedReferenceOutputSlots,
effectiveQueueGroupSize,
loadPresetSnapshot,
moveReferenceSlot,
normalizeOutputMode,
normalizeReferenceSlots,
referencePresetDisplay,
referenceShelfHit,
referenceSlotsEqual,
relinkReferenceSlots,
snapshotReferenceOutputConnections
} from '../web/image_conveyor_math.mjs'
const reference = (name) => ({
annotated: `refs/${name}.png [input]`,
filename: `${name}.png`,
subfolder: 'refs',
type: 'input'
})
test('legacy output-mode migration preserves grouped workflows deterministically', () => {
assert.equal(normalizeOutputMode(undefined, 1, false), OUTPUT_MODE_PERSISTENT)
assert.equal(normalizeOutputMode(undefined, 2, false), OUTPUT_MODE_QUEUE_GROUP)
assert.equal(normalizeOutputMode('bad', 9, true), OUTPUT_MODE_PERSISTENT)
assert.equal(normalizeOutputMode(OUTPUT_MODE_QUEUE_GROUP, 1, true), OUTPUT_MODE_QUEUE_GROUP)
})
test('persistent mode always has an effective queue group size of one', () => {
assert.equal(effectiveQueueGroupSize(OUTPUT_MODE_PERSISTENT, 9), 1)
assert.equal(effectiveQueueGroupSize(OUTPUT_MODE_QUEUE_GROUP, 9), 9)
})
test('connected reference outputs snapshot exact sparse socket topology', () => {
const outputs = Array.from({ length: 14 }, () => ({ links: null }))
outputs[5].links = [100]
outputs[6].links = [101, 102]
outputs[8].links = []
outputs[10].links = [103]
outputs[13].links = [104]
assert.deepEqual(connectedReferenceOutputSlots(outputs), [1, 5, 8])
assert.deepEqual(connectedReferenceOutputSlots(null), [])
})
test('persistent reservations carry an explicit connection snapshot including none', () => {
const outputs = Array.from({ length: 14 }, () => ({ links: null }))
const reservation = { id: 'A', annotated: 'A.png [input]' }
assert.deepEqual(
snapshotReferenceOutputConnections(reservation, OUTPUT_MODE_PERSISTENT, outputs),
{ ...reservation, reference_output_slots: [] }
)
outputs[6].links = [1]
outputs[13].links = [2]
assert.deepEqual(
snapshotReferenceOutputConnections(reservation, OUTPUT_MODE_PERSISTENT, outputs),
{ ...reservation, reference_output_slots: [1, 8] }
)
assert.equal(snapshotReferenceOutputConnections(null, OUTPUT_MODE_PERSISTENT, outputs), null)
assert.equal(
snapshotReferenceOutputConnections(reservation, OUTPUT_MODE_QUEUE_GROUP, outputs),
reservation
)
})
test('reference slots normalize to exactly eight sparse input records', () => {
const slots = normalizeReferenceSlots([reference('a'), 'bad', null, { annotated: 'x.png [output]' }])
assert.equal(slots.length, 8)
assert.deepEqual(slots[0], reference('a'))
assert.deepEqual(slots.slice(1), Array(7).fill(null))
})
test('reference slot normalization rejects path escapes and unsupported storage', () => {
const slots = normalizeReferenceSlots([
{ annotated: '/tmp/a.png [input]' },
{ annotated: '../a.png [input]' },
{ annotated: 'C:\\tmp\\a.png [input]' },
{ annotated: 'a.svg [input]' },
{ annotated: 'a.png [output]' }
])
assert.deepEqual(slots, Array(8).fill(null))
})
test('reference slot normalization rejects dotless extension lookalikes', () => {
assert.deepEqual(
normalizeReferenceSlots([{ annotated: 'refs/png [input]' }]),
Array(8).fill(null)
)
})
test('reference-only assignment does not append, remove, or reorder Conveyor items', () => {
const items = [{ id: 'A' }, { id: 'B' }]
const state = { items, reference_slots: Array(8).fill(null) }
const next = applyReferenceAssignments(state, 2, [reference('a'), reference('b')])
assert.equal(next.items, items)
assert.deepEqual(next.items.map((item) => item.id), ['A', 'B'])
assert.deepEqual(next.reference_slots[2], reference('a'))
assert.deepEqual(next.reference_slots[3], reference('b'))
})
test('sparse assignment clamps to the fixed slot range', () => {
const next = applyReferenceAssignments(
{ items: [], reference_slots: [reference('existing')] },
7,
[reference('last'), reference('overflow')]
)
assert.equal(next.reference_slots.length, 8)
assert.deepEqual(next.reference_slots[0], reference('existing'))
assert.deepEqual(next.reference_slots[7], reference('last'))
})
test('reference shelf drag-sort moves one populated slot and preserves sparse order', () => {
const slots = [reference('a'), reference('b'), null, reference('d')]
assert.deepEqual(
moveReferenceSlot(slots, 0, 2).slice(0, 4),
[reference('b'), null, reference('a'), reference('d')]
)
assert.deepEqual(
moveReferenceSlot(slots, 3, 0).slice(0, 4),
[reference('d'), reference('a'), reference('b'), null]
)
assert.deepEqual(moveReferenceSlot(slots, 2, 0), normalizeReferenceSlots(slots))
})
test('preset snapshots are detached normalized copies and dirty comparison is exact', () => {
const preset = { id: 'preset-id', slots: [reference('a')] }
const loaded = loadPresetSnapshot(preset)
assert.equal(loaded.activePresetId, 'preset-id')
assert.equal(referenceSlotsEqual(loaded.slots, preset.slots), true)
loaded.slots[0].annotated = 'refs/changed.png [input]'
assert.equal(referenceSlotsEqual(loaded.slots, preset.slots), false)
assert.equal(preset.slots[0].annotated, 'refs/a.png [input]')
})
test('saved preset labels distinguish startup hydration from unsaved references', () => {
const slots = normalizeReferenceSlots([reference('a')])
assert.deepEqual(referencePresetDisplay([], false, 'preset-id', slots), {
active: null,
dirty: false,
label: 'Loading character…'
})
assert.equal(referencePresetDisplay([], false, '', slots).label, 'Unsaved references')
const preset = { id: 'preset-id', name: 'Mara', slots }
assert.deepEqual(referencePresetDisplay([preset], true, 'preset-id', slots), {
active: preset,
dirty: false,
label: 'Mara'
})
assert.equal(
referencePresetDisplay([preset], true, 'preset-id', [reference('changed')]).label,
'Mara *'
)
})
test('duplicate cleanup relinks sparse live reference slots without touching others', () => {
const result = relinkReferenceSlots(
[reference('legacy'), null, reference('keep')],
[{ relative_path: 'refs/legacy.png', keep_path: 'canonical/legacy.png' }]
)
assert.equal(result.changed, 1)
assert.deepEqual(result.slots[0], {
annotated: 'canonical/legacy.png [input]',
filename: 'legacy.png',
subfolder: 'canonical',
type: 'input'
})
assert.equal(result.slots[1], null)
assert.deepEqual(result.slots[2], reference('keep'))
})
test('drag classification separates reorder-capable Conveyor images from imports', () => {
assert.deepEqual(
classifyReferenceDrag({ id: 'A' }, 'conveyor', true),
{ kind: 'conveyor', requiresImport: false, canReorder: true }
)
assert.deepEqual(
classifyReferenceDrag({ id: 'A' }, 'conveyor', false),
{ kind: 'conveyor', requiresImport: false, canReorder: false }
)
assert.deepEqual(
classifyReferenceDrag({ relative_path: 'a.png' }, 'input', true),
{ kind: 'input', requiresImport: false, canReorder: false }
)
assert.deepEqual(
classifyReferenceDrag({ localFile: {} }, 'folder:id:', true),
{ kind: 'local', requiresImport: true, canReorder: false }
)
assert.equal(classifyReferenceDrag({ kind: 'folder' }, 'input'), null)
})
test('4x2 shelf layout and hit testing stay within measured pre-widget geometry', () => {
const layout = calculateReferenceShelfLayout(520, 290, 112, 30)
assert.equal(layout.usable, true)
assert.equal(layout.slots.length, 8)
assert.equal(layout.slots[0].y, layout.slots[3].y)
assert.ok(layout.slots[4].y > layout.slots[0].y)
assert.ok(layout.slots.every((slot) => slot.x >= layout.left && slot.x + slot.width <= layout.right))
const first = layout.slots[0]
assert.deepEqual(referenceShelfHit(layout, first.x + 4, first.y + 4), { type: 'slot', index: 0 })
assert.deepEqual(
referenceShelfHit(layout, first.x + first.width - 2, first.y + 2),
{ type: 'clear', index: 0 }
)
assert.equal(referenceShelfHit(layout, layout.right + 1, layout.bottom + 1), null)
})
test('shelf header hit regions map to save, menu, and preset actions', () => {
const layout = calculateReferenceShelfLayout(520, 290, 112, 30)
const menuWidth = Math.min(54, layout.width * 0.18)
const saveWidth = Math.min(52, layout.width * 0.18)
const headerY = layout.top + layout.headerHeight / 2
assert.deepEqual(referenceShelfHit(layout, layout.right - 1, headerY), { type: 'menu' })
assert.deepEqual(
referenceShelfHit(layout, layout.right - menuWidth - 1, headerY),
{ type: 'save' }
)
assert.deepEqual(
referenceShelfHit(layout, layout.right - menuWidth - saveWidth - 1, headerY),
{ type: 'preset' }
)
})
test('a narrow node produces an unusable shelf layout with no hits', () => {
const layout = calculateReferenceShelfLayout(200, 120, 112, 30)
assert.equal(layout.usable, false)
assert.equal(referenceShelfHit(layout, layout.left + 1, layout.top + 1), null)
})