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

228 lines
8.5 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import {
calculateAutoQueueExtraExecutions,
completeExecutionGroupCount,
makeQueueReservationPayload,
markReservedGroupQueued,
normalizeImagesPerExecution,
processedItemIdsFromDelta,
queueReservationMembers,
selectExecutionGroup,
shouldReanchorGalleryResize
} from '../web/image_conveyor_math.mjs'
function queueItems(ids, status = 'pending') {
return ids.map((id) => ({
id,
annotated: `${id}.png [input]`,
status,
last_queued_at: 0,
last_processed_at: 0
}))
}
function reserveAndQueue(items, count, dontConsume = false, now = 100) {
const group = selectExecutionGroup(items, count, dontConsume)
const payload = group.length === normalizeImagesPerExecution(count)
? makeQueueReservationPayload(group)
: null
if (payload) markReservedGroupQueued(items, payload, dontConsume, now)
return { group, payload }
}
function applyProcessedIds(items, delta, now = 200) {
const ids = new Set(processedItemIdsFromDelta(delta))
let changed = 0
for (const item of items) {
if (!ids.has(item.id)) continue
if (delta.new_status === 'processed') item.status = 'processed'
item.last_processed_at = now
changed += 1
}
return changed
}
test('normalizes images-per-execution to the persisted 1-9 range', () => {
assert.equal(normalizeImagesPerExecution(0), 1)
assert.equal(normalizeImagesPerExecution(-4), 1)
assert.equal(normalizeImagesPerExecution('bad'), 1)
assert.equal(normalizeImagesPerExecution(10), 9)
for (let count = 1; count <= 9; count += 1) {
assert.equal(normalizeImagesPerExecution(count), count)
}
})
test('reserves the first complete pending group in Conveyor order', () => {
const items = queueItems(['A', 'B', 'C', 'D', 'E', 'F'])
assert.deepEqual(
selectExecutionGroup(items, 3).map((item) => item.id),
['A', 'B', 'C']
)
})
test('afterQueued marks the whole reserved group queued with one timestamp', () => {
const items = queueItems(['A', 'B', 'C', 'D'])
const { payload } = reserveAndQueue(items, 3, false, 12345)
assert.deepEqual(queueReservationMembers(payload).map((item) => item.id), ['A', 'B', 'C'])
assert.deepEqual(items.map((item) => item.status), ['queued', 'queued', 'queued', 'pending'])
assert.deepEqual(items.slice(0, 3).map((item) => item.last_queued_at), [12345, 12345, 12345])
})
test('next consuming reservation skips every already queued group member', () => {
const items = queueItems(['A', 'B', 'C', 'D', 'E', 'F'])
reserveAndQueue(items, 3, false, 1)
const second = selectExecutionGroup(items, 3, false)
assert.deepEqual(second.map((item) => item.id), ['D', 'E', 'F'])
})
test('three prompt reservations over nine items have zero overlap', () => {
const items = queueItems(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'])
const reservations = []
for (let prompt = 0; prompt < 3; prompt += 1) {
const { group } = reserveAndQueue(items, 3, false, prompt + 1)
reservations.push(group.map((item) => item.id))
}
assert.deepEqual(reservations, [
['A', 'B', 'C'],
['D', 'E', 'F'],
['G', 'H', 'I']
])
assert.equal(new Set(reservations.flat()).size, 9)
})
test("Don't consume leaves statuses unchanged and repeats the same group", () => {
const items = queueItems(['A', 'B', 'C', 'D'])
const first = reserveAndQueue(items, 3, true, 1)
const second = reserveAndQueue(items, 3, true, 2)
assert.deepEqual(first.group.map((item) => item.id), ['A', 'B', 'C'])
assert.deepEqual(second.group.map((item) => item.id), ['A', 'B', 'C'])
assert.deepEqual(items.map((item) => item.status), ['pending', 'pending', 'pending', 'pending'])
assert.deepEqual(items.map((item) => item.last_queued_at), [0, 0, 0, 0])
})
test("Don't consume uses processed fallback only when no pending or queued items exist", () => {
const items = queueItems(['A', 'B', 'C'], 'processed')
assert.deepEqual(
selectExecutionGroup(items, 2, true).map((item) => item.id),
['A', 'B']
)
items[0].status = 'pending'
assert.deepEqual(
selectExecutionGroup(items, 2, true).map((item) => item.id),
['A']
)
})
test('group payload preserves first-item compatibility fields and exact ordering', () => {
const items = queueItems(['A', 'B', 'C'])
const payload = makeQueueReservationPayload(items)
assert.equal(payload.id, 'A')
assert.equal(payload.annotated, 'A.png [input]')
assert.deepEqual(payload.items, [
{ id: 'A', annotated: 'A.png [input]' },
{ id: 'B', annotated: 'B.png [input]' },
{ id: 'C', annotated: 'C.png [input]' }
])
assert.deepEqual(queueReservationMembers(payload), payload.items)
})
test('single-item payload remains in legacy id/annotated shape', () => {
const [item] = queueItems(['A'])
const payload = makeQueueReservationPayload([item])
assert.deepEqual(payload, { id: 'A', annotated: 'A.png [input]' })
assert.deepEqual(queueReservationMembers(payload), [payload])
})
test('duplicate physical paths remain independent reservation members by queue id', () => {
const items = [
{ id: 'A1', annotated: 'same.png [input]', status: 'pending' },
{ id: 'A2', annotated: 'same.png [input]', status: 'pending' },
{ id: 'B', annotated: 'other.png [input]', status: 'pending' }
]
const payload = makeQueueReservationPayload(selectExecutionGroup(items, 3))
assert.deepEqual(queueReservationMembers(payload).map((item) => item.id), ['A1', 'A2', 'B'])
})
test('new grouped backend delta identifies every processed logical item', () => {
const delta = {
processed_item_id: 'A',
processed_items: [
{ id: 'A', annotated: 'A.png [input]' },
{ id: 'B', annotated: 'B.png [input]' },
{ id: 'C', annotated: 'C.png [input]' }
],
new_status: 'processed',
consumed: true
}
assert.deepEqual(processedItemIdsFromDelta(delta), ['A', 'B', 'C'])
const items = queueItems(['A', 'B', 'C', 'D'], 'queued')
assert.equal(applyProcessedIds(items, delta, 88), 3)
assert.deepEqual(items.map((item) => item.status), ['processed', 'processed', 'processed', 'queued'])
assert.deepEqual(items.map((item) => item.last_processed_at), [88, 88, 88, 0])
})
test('legacy singular backend delta remains supported', () => {
const delta = { processed_item_id: 'B', new_status: 'processed', consumed: true }
assert.deepEqual(processedItemIdsFromDelta(delta), ['B'])
const items = queueItems(['A', 'B'], 'queued')
applyProcessedIds(items, delta, 99)
assert.deepEqual(items.map((item) => item.status), ['queued', 'processed'])
})
test('consumed false backend delta produces zero frontend status mutations', () => {
const delta = {
processed_item_id: 'A',
processed_items: [{ id: 'A' }, { id: 'B' }],
new_status: 'processed',
consumed: false
}
const items = queueItems(['A', 'B'], 'pending')
assert.deepEqual(processedItemIdsFromDelta(delta), [])
assert.equal(applyProcessedIds(items, delta), 0)
assert.deepEqual(items.map((item) => item.status), ['pending', 'pending'])
})
test('auto queue counts only complete groups', () => {
assert.equal(completeExecutionGroupCount(7, 3), 2)
assert.equal(calculateAutoQueueExtraExecutions(7, 3, 1), 1)
})
test('auto queue count one preserves released arithmetic', () => {
assert.equal(completeExecutionGroupCount(7, 1), 7)
assert.equal(calculateAutoQueueExtraExecutions(7, 1, 1), 6)
})
test('auto queue respects an already requested batch count', () => {
assert.equal(calculateAutoQueueExtraExecutions(12, 3, 2), 2)
assert.equal(calculateAutoQueueExtraExecutions(6, 3, 2), 0)
assert.equal(calculateAutoQueueExtraExecutions(5, 3, 2), 0)
})
test('incomplete groups are never encoded as complete reservations', () => {
const items = queueItems(['A', 'B'])
const group = selectExecutionGroup(items, 3)
assert.deepEqual(group.map((item) => item.id), ['A', 'B'])
const payload = group.length === normalizeImagesPerExecution(3)
? makeQueueReservationPayload(group)
: null
assert.equal(payload, null)
})
test('clear queued semantics release every member because group state is per item', () => {
const items = queueItems(['A', 'B', 'C', 'D'])
reserveAndQueue(items, 3, false, 10)
for (const item of items) if (item.status === 'queued') item.status = 'pending'
assert.deepEqual(items.map((item) => item.status), ['pending', 'pending', 'pending', 'pending'])
})
test('scrollbar-only viewport width changes do not trigger resize reanchoring', () => {
assert.equal(shouldReanchorGalleryResize(620, 620, 603, 618), false)
})
test('real widget resize still triggers identity reanchoring', () => {
assert.equal(shouldReanchorGalleryResize(620, 760, 603, 743), true)
})