expand linearMode output history and preview test coverage

- extract shared result item factory
- more tests!
This commit is contained in:
pythongosssss
2026-04-10 04:41:29 -07:00
parent be3154abcd
commit dc3bdb9dc0
8 changed files with 490 additions and 119 deletions

View File

@@ -1,32 +1,26 @@
import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import { describe, expect, it, onTestFinished } from 'vitest'
import ImageComparePreview from '@/renderer/extensions/linearMode/ImageComparePreview.vue'
import { makeResultItem } from '@/renderer/extensions/linearMode/__fixtures__/testResultItemFactory'
import type { CompareImages } from '@/stores/queueStore'
import { ResultItemImpl } from '@/stores/queueStore'
function makeResultItem(filename: string): ResultItemImpl {
return new ResultItemImpl({
filename,
subfolder: '',
type: 'output',
mediaType: 'images',
nodeId: '1'
})
}
function makeCompareImages(
beforeFiles: string[],
afterFiles: string[]
): CompareImages {
return {
before: beforeFiles.map(makeResultItem),
after: afterFiles.map(makeResultItem)
before: beforeFiles.map((f) => makeResultItem({ filename: f })),
after: afterFiles.map((f) => makeResultItem({ filename: f }))
}
}
function mountComponent(compareImages: CompareImages) {
function mountComponent(
compareImages: CompareImages,
{ attachTo }: { attachTo?: HTMLElement } = {}
) {
return mount(ImageComparePreview, {
attachTo,
global: {
mocks: {
$t: (key: string, params?: Record<string, unknown>) => {
@@ -41,73 +35,62 @@ function mountComponent(compareImages: CompareImages) {
})
}
function mountAttached(compareImages: CompareImages) {
const host = document.createElement('div')
document.body.appendChild(host)
const wrapper = mountComponent(compareImages, { attachTo: host })
onTestFinished(() => {
wrapper.unmount()
host.remove()
})
return wrapper
}
describe('ImageComparePreview', () => {
it('renders both before and after images', () => {
const compareImages = makeCompareImages(['before.png'], ['after.png'])
const wrapper = mountComponent(compareImages)
it('renders split view with slider when both sides have images', () => {
const wrapper = mountComponent(
makeCompareImages(['before.png'], ['after.png'])
)
const images = wrapper.findAll('img')
expect(images).toHaveLength(2)
expect(images[0].attributes('alt')).toBe('imageCompare.altAfter')
expect(images[1].attributes('alt')).toBe('imageCompare.altBefore')
expect(wrapper.find('[data-testid="image-compare-slider"]').exists()).toBe(
true
)
expect(wrapper.find('[data-testid="batch-nav"]').exists()).toBe(false)
})
it('renders slider handle when both images present', () => {
const compareImages = makeCompareImages(['before.png'], ['after.png'])
const wrapper = mountComponent(compareImages)
it('renders single image without slider when only one side has images', () => {
const before = mountComponent(makeCompareImages(['before.png'], []))
expect(before.findAll('img')).toHaveLength(1)
expect(before.find('img').attributes('alt')).toBe('imageCompare.altBefore')
expect(before.find('[data-testid="image-compare-slider"]').exists()).toBe(
false
)
const handles = wrapper.findAll('[role="presentation"]')
expect(handles.length).toBeGreaterThanOrEqual(1)
})
it('renders only before image when no after images', () => {
const compareImages = makeCompareImages(['before.png'], [])
const wrapper = mountComponent(compareImages)
const images = wrapper.findAll('img')
expect(images).toHaveLength(1)
expect(images[0].attributes('alt')).toBe('imageCompare.altBefore')
})
it('renders only after image when no before images', () => {
const compareImages = makeCompareImages([], ['after.png'])
const wrapper = mountComponent(compareImages)
const images = wrapper.findAll('img')
expect(images).toHaveLength(1)
expect(images[0].attributes('alt')).toBe('imageCompare.altAfter')
const after = mountComponent(makeCompareImages([], ['after.png']))
expect(after.findAll('img')).toHaveLength(1)
expect(after.find('img').attributes('alt')).toBe('imageCompare.altAfter')
})
it('shows no-images message when both arrays are empty', () => {
const compareImages = makeCompareImages([], [])
const wrapper = mountComponent(compareImages)
const wrapper = mountComponent(makeCompareImages([], []))
expect(wrapper.findAll('img')).toHaveLength(0)
expect(wrapper.text()).toContain('imageCompare.noImages')
})
it('hides batch nav for single images', () => {
const compareImages = makeCompareImages(['before.png'], ['after.png'])
const wrapper = mountComponent(compareImages)
expect(wrapper.find('[data-testid="batch-nav"]').exists()).toBe(false)
})
it('shows batch nav when multiple images on either side', () => {
const compareImages = makeCompareImages(['a1.png', 'a2.png'], ['b1.png'])
const wrapper = mountComponent(compareImages)
it('shows batch nav and navigates when multiple images on a side', async () => {
const wrapper = mountComponent(
makeCompareImages(['a1.png', 'a2.png', 'a3.png'], ['b1.png'])
)
expect(wrapper.find('[data-testid="batch-nav"]').exists()).toBe(true)
})
it('navigates before images with batch controls', async () => {
const compareImages = makeCompareImages(
['a1.png', 'a2.png', 'a3.png'],
['b1.png']
)
const wrapper = mountComponent(compareImages)
const beforeBatch = wrapper.find('[data-testid="before-batch"]')
await beforeBatch.find('[data-testid="batch-next"]').trigger('click')
expect(beforeBatch.find('[data-testid="batch-counter"]').text()).toBe(
@@ -115,10 +98,115 @@ describe('ImageComparePreview', () => {
)
})
it('does not render slider handle when only one side has images', () => {
const compareImages = makeCompareImages(['before.png'], [])
const wrapper = mountComponent(compareImages)
it('resets slider and aspect ratio when compareImages changes', async () => {
const wrapper = mountAttached(makeCompareImages(['a.png'], ['b.png']))
expect(wrapper.find('[role="presentation"]').exists()).toBe(false)
const container = wrapper.find('[data-testid="image-compare-preview"]')
const el = container.element as HTMLElement
el.getBoundingClientRect = () =>
DOMRect.fromRect({ x: 0, y: 0, width: 200, height: 100 })
// Set aspect ratio via load event
const img = wrapper.find('img')
Object.defineProperty(img.element, 'naturalWidth', { value: 800 })
Object.defineProperty(img.element, 'naturalHeight', { value: 600 })
await img.trigger('load')
expect(container.attributes('style')).toContain('800 / 600')
el.dispatchEvent(new PointerEvent('pointermove', { clientX: 150 }))
await wrapper.vm.$nextTick()
expect(
wrapper.find('[data-testid="image-compare-slider"]').attributes('style')
).toContain('left: 75%')
// Change props — both slider and aspect should reset
await wrapper.setProps({
compareImages: makeCompareImages(['c.png'], ['d.png'])
})
expect(
wrapper.find('[data-testid="image-compare-slider"]').attributes('style')
).toContain('50%')
expect(
wrapper.find('[data-testid="image-compare-preview"]').attributes('style')
).toBeUndefined()
})
it('moves slider on pointermove and clamps to 0-100 range', async () => {
const wrapper = mountAttached(
makeCompareImages(['before.png'], ['after.png'])
)
const container = wrapper.find('[data-testid="image-compare-preview"]')
const el = container.element as HTMLElement
el.getBoundingClientRect = () =>
DOMRect.fromRect({ x: 100, y: 0, width: 200, height: 100 })
// Wait for component's pointermove listener to be registered
await wrapper.vm.$nextTick()
// Pointer at 150 → (150-100)/200 = 25%
el.dispatchEvent(new PointerEvent('pointermove', { clientX: 150 }))
await wrapper.vm.$nextTick()
const slider = wrapper.find('[data-testid="image-compare-slider"]')
expect(slider.attributes('style')).toContain('left: 25%')
// Pointer before left edge → clamps to 0%
el.dispatchEvent(new PointerEvent('pointermove', { clientX: 50 }))
await wrapper.vm.$nextTick()
expect(slider.attributes('style')).toContain('left: 0%')
})
it('sets aspect ratio from image natural dimensions on load', async () => {
const wrapper = mountComponent(
makeCompareImages(['before.png'], ['after.png'])
)
const img = wrapper.find('img')
Object.defineProperty(img.element, 'naturalWidth', { value: 800 })
Object.defineProperty(img.element, 'naturalHeight', { value: 600 })
await img.trigger('load')
expect(
wrapper.find('[data-testid="image-compare-preview"]').attributes('style')
).toContain('800 / 600')
})
it('does not set aspect ratio when natural dimensions are zero', async () => {
const wrapper = mountComponent(
makeCompareImages(['before.png'], ['after.png'])
)
const img = wrapper.find('img')
Object.defineProperty(img.element, 'naturalWidth', { value: 0 })
Object.defineProperty(img.element, 'naturalHeight', { value: 0 })
await img.trigger('load')
expect(
wrapper.find('[data-testid="image-compare-preview"]').attributes('style')
).toBeUndefined()
})
it('clamps beforeIndex when compareImages shrinks', async () => {
const wrapper = mountComponent(
makeCompareImages(['a1.png', 'a2.png', 'a3.png'], ['b1.png'])
)
const beforeBatch = wrapper.find('[data-testid="before-batch"]')
await beforeBatch.find('[data-testid="batch-next"]').trigger('click')
await beforeBatch.find('[data-testid="batch-next"]').trigger('click')
expect(beforeBatch.find('[data-testid="batch-counter"]').text()).toBe(
'3 / 3'
)
await wrapper.setProps({
compareImages: makeCompareImages(['x.png'], ['b1.png'])
})
const beforeImg = wrapper
.findAll('img')
.find((img) => img.attributes('alt') === 'imageCompare.altBefore')
expect(beforeImg?.attributes('src')).toContain('x.png')
})
})

View File

@@ -9,12 +9,17 @@ const hasNodes = ref(false)
const hasOutputs = ref(false)
const enterBuilder = vi.fn()
const { setModeFn, showFn } = vi.hoisted(() => ({
setModeFn: vi.fn(),
showFn: vi.fn()
}))
vi.mock('@/composables/useAppMode', () => ({
useAppMode: () => ({ setMode: vi.fn() })
useAppMode: () => ({ setMode: setModeFn })
}))
vi.mock('@/composables/useWorkflowTemplateSelectorDialog', () => ({
useWorkflowTemplateSelectorDialog: () => ({ show: vi.fn() })
useWorkflowTemplateSelectorDialog: () => ({ show: showFn })
}))
vi.mock('@/stores/appModeStore', () => ({
@@ -47,7 +52,7 @@ describe('LinearWelcome', () => {
beforeEach(() => {
hasNodes.value = false
hasOutputs.value = false
vi.clearAllMocks()
vi.resetAllMocks()
})
it('shows empty workflow text when there are no nodes', () => {
@@ -77,4 +82,29 @@ describe('LinearWelcome', () => {
.trigger('click')
expect(enterBuilder).toHaveBeenCalled()
})
it('shows getStarted content when hasOutputs is true', () => {
const wrapper = mountComponent({ hasOutputs: true })
expect(wrapper.text()).toContain('linearMode.welcome.getStarted')
expect(
wrapper.find('[data-testid="linear-welcome-back-to-workflow"]').exists()
).toBe(false)
})
it('shows load template button when hasNodes is false and clicking calls show', async () => {
const wrapper = mountComponent({ hasNodes: false })
const loadBtn = wrapper.find('[data-testid="linear-welcome-load-template"]')
expect(loadBtn.exists()).toBe(true)
await loadBtn.trigger('click')
expect(showFn).toHaveBeenCalledWith('appbuilder')
})
it('back-to-workflow button calls setMode with graph', async () => {
const wrapper = mountComponent()
await wrapper
.find('[data-testid="linear-welcome-back-to-workflow"]')
.trigger('click')
expect(setModeFn).toHaveBeenCalledWith('graph')
})
})

View File

@@ -1,44 +1,122 @@
import { createTestingPinia } from '@pinia/testing'
import { shallowMount } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import { setActivePinia } from 'pinia'
import { describe, expect, it, vi } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'
import Loader from '@/components/loader/Loader.vue'
import OutputHistoryActiveQueueItem from './OutputHistoryActiveQueueItem.vue'
const i18n = createI18n({ legacy: false, locale: 'en' })
setActivePinia(createTestingPinia({ stubActions: false }))
const i18n = createI18n({ legacy: false, locale: 'en', missingWarn: false })
const { executeFn, runningTasksRef } = vi.hoisted(() => ({
executeFn: vi.fn(),
runningTasksRef: { value: [] as Array<{ jobId: string }> }
}))
vi.mock('@/stores/commandStore', () => ({
useCommandStore: () => ({
execute: vi.fn()
execute: executeFn
})
}))
vi.mock('@/stores/queueStore', () => ({
useQueueStore: () => ({
get runningTasks() {
return runningTasksRef.value
}
})
}))
const closeFn = vi.fn()
// Stub Popover to render both slots inline (no portal) so we can test content
const PopoverStub = {
setup() {
return { closeFn }
},
template: `<div>
<slot name="button" />
<slot :close="closeFn" />
</div>`
}
function mountComponent(queueCount: number) {
return shallowMount(OutputHistoryActiveQueueItem, {
return mount(OutputHistoryActiveQueueItem, {
props: { queueCount },
global: { plugins: [i18n] }
global: {
plugins: [i18n],
directives: { tooltip: {} },
stubs: { Popover: PopoverStub }
}
})
}
describe('OutputHistoryActiveQueueItem', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))
runningTasksRef.value = []
vi.resetAllMocks()
})
it('hides badge when queueCount is 1', () => {
const wrapper = mountComponent(1)
const badge = wrapper.find('[aria-hidden="true"]')
const badge = wrapper.find('[data-testid="linear-job-badge"]')
expect(badge.exists()).toBe(false)
})
it('shows badge with correct count when queueCount is 3', () => {
const wrapper = mountComponent(3)
const badge = wrapper.find('[aria-hidden="true"]')
const badge = wrapper.find('[data-testid="linear-job-badge"]')
expect(badge.exists()).toBe(true)
expect(badge.text()).toBe('3')
})
it('renders Loader with loader-circle variant when running tasks exist', () => {
runningTasksRef.value = [{ jobId: 'job-1' }]
const wrapper = mountComponent(1)
const loader = wrapper.findComponent(Loader)
expect(loader.exists()).toBe(true)
expect(loader.props('variant')).toBe('loader-circle')
})
it('renders Loader with loader variant when no running tasks', () => {
runningTasksRef.value = []
const wrapper = mountComponent(1)
const loader = wrapper.findComponent(Loader)
expect(loader.exists()).toBe(true)
expect(loader.props('variant')).toBe('loader')
})
it('hides badge when queueCount is 0', () => {
const wrapper = mountComponent(0)
const badge = wrapper.find('[aria-hidden="true"]')
const badge = wrapper.find('[data-testid="linear-job-badge"]')
expect(badge.exists()).toBe(false)
})
it('clicking clear button calls ClearPendingTasks command', async () => {
const wrapper = mountComponent(3)
const clearButton = wrapper.find(
'[data-testid="linear-queue-clear-button"]'
)
expect(clearButton.exists()).toBe(true)
await clearButton.trigger('click')
expect(executeFn).toHaveBeenCalledWith('Comfy.ClearPendingTasks')
expect(closeFn).toHaveBeenCalled()
})
it('clear button is disabled when queueCount is 0', () => {
const wrapper = mountComponent(0)
const clearButton = wrapper.find(
'[data-testid="linear-queue-clear-button"]'
)
expect(clearButton.exists()).toBe(true)
expect(clearButton.attributes('disabled')).toBeDefined()
})
})

View File

@@ -48,6 +48,7 @@ function clearQueue(close: () => void) {
:disabled="queueCount === 0"
variant="textonly"
class="px-4 text-sm text-destructive-background"
data-testid="linear-queue-clear-button"
@click="clearQueue(close)"
>
<i class="icon-[lucide--list-x]" />

View File

@@ -2,23 +2,8 @@ import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import OutputHistoryItem from '@/renderer/extensions/linearMode/OutputHistoryItem.vue'
import type { CompareImages } from '@/stores/queueStore'
import { ResultItemImpl } from '@/stores/queueStore'
function makeResultItem(
filename: string,
mediaType: string,
compareImages?: CompareImages
): ResultItemImpl {
return new ResultItemImpl({
filename,
subfolder: '',
type: 'output',
mediaType,
nodeId: '1',
compareImages
})
}
import { makeResultItem } from '@/renderer/extensions/linearMode/__fixtures__/testResultItemFactory'
import type { ResultItemImpl } from '@/stores/queueStore'
function mountComponent(output: ResultItemImpl) {
return mount(OutputHistoryItem, {
@@ -28,11 +13,11 @@ function mountComponent(output: ResultItemImpl) {
describe('OutputHistoryItem', () => {
it('renders split 50/50 thumbnail for image_compare items', () => {
const before = [makeResultItem('before.png', 'images')]
const after = [makeResultItem('after.png', 'images')]
const output = makeResultItem('', 'image_compare', {
before,
after
const before = [makeResultItem({ filename: 'before.png' })]
const after = [makeResultItem({ filename: 'after.png' })]
const output = makeResultItem({
mediaType: 'image_compare',
compareImages: { before, after }
})
const wrapper = mountComponent(output)
@@ -44,7 +29,7 @@ describe('OutputHistoryItem', () => {
})
it('renders image thumbnail for regular image items', () => {
const output = makeResultItem('photo.png', 'images')
const output = makeResultItem({ filename: 'photo.png' })
const wrapper = mountComponent(output)
@@ -52,4 +37,68 @@ describe('OutputHistoryItem', () => {
expect(img.exists()).toBe(true)
expect(img.attributes('src')).toContain('photo.png')
})
it('renders video element for video output', () => {
const output = makeResultItem({ filename: 'clip.mp4', mediaType: 'video' })
const wrapper = mountComponent(output)
const video = wrapper.find('[data-testid="linear-video-output"]')
expect(video.exists()).toBe(true)
expect(video.element.tagName).toBe('VIDEO')
expect(video.attributes('src')).toContain('clip.mp4')
})
it('renders fallback icon when image_compare has no compareImages', () => {
const output = makeResultItem({ mediaType: 'image_compare' })
const wrapper = mountComponent(output)
expect(wrapper.find('[data-testid="linear-compare-output"]').exists()).toBe(
false
)
const icon = wrapper.find('i')
expect(icon.exists()).toBe(true)
})
it('renders single image when only before side of compare has data', () => {
const wrapper = mountComponent(
makeResultItem({
mediaType: 'image_compare',
compareImages: {
before: [makeResultItem({ filename: 'before.png' })],
after: []
}
})
)
expect(wrapper.findAll('img')).toHaveLength(1)
expect(wrapper.find('img').attributes('src')).toContain('before.png')
})
it('renders single image when only after side of compare has data', () => {
const wrapper = mountComponent(
makeResultItem({
mediaType: 'image_compare',
compareImages: {
before: [],
after: [makeResultItem({ filename: 'after.png' })]
}
})
)
expect(wrapper.findAll('img')).toHaveLength(1)
expect(wrapper.find('img').attributes('src')).toContain('after.png')
})
it.for([
{ mediaType: 'audio', filename: 'sound.mp3' },
{ mediaType: 'text', filename: 'notes.txt' },
{ mediaType: '3d', filename: 'model.glb' }
])(
'renders fallback icon for $mediaType media type',
({ mediaType, filename }) => {
const wrapper = mountComponent(makeResultItem({ filename, mediaType }))
const icon = wrapper.find('i')
expect(icon.exists()).toBe(true)
}
)
})

View File

@@ -0,0 +1,21 @@
import type { ResultItemType } from '@/schemas/apiSchema'
import type { CompareImages } from '@/stores/queueStore'
import { ResultItemImpl } from '@/stores/queueStore'
export function makeResultItem(opts: {
filename?: string
mediaType?: string
nodeId?: string
subfolder?: string
type?: ResultItemType
compareImages?: CompareImages
}): ResultItemImpl {
return new ResultItemImpl({
filename: opts.filename ?? '',
subfolder: opts.subfolder ?? '',
type: opts.type ?? 'output',
mediaType: opts.mediaType ?? 'images',
nodeId: opts.nodeId ?? '1',
compareImages: opts.compareImages
})
}

View File

@@ -0,0 +1,37 @@
import { describe, expect, it } from 'vitest'
import { getMediaType } from '@/renderer/extensions/linearMode/mediaTypes'
import { makeResultItem } from '@/renderer/extensions/linearMode/__fixtures__/testResultItemFactory'
describe('getMediaType', () => {
it('returns empty string for undefined output', () => {
expect(getMediaType()).toBe('')
})
it('prioritises video suffix over mediaType', () => {
expect(
getMediaType(
makeResultItem({ filename: 'clip.mp4', mediaType: 'images' })
)
).toBe('video')
})
it('prioritises image suffix over mediaType', () => {
expect(
getMediaType(
makeResultItem({ filename: 'photo.png', mediaType: 'video' })
)
).toBe('images')
})
it.for([
{ mediaType: 'image_compare', expected: 'image_compare' },
{ mediaType: 'audio', expected: 'audio' },
{ mediaType: '3d', expected: '3d' }
])(
'falls back to raw mediaType for $mediaType',
({ mediaType, expected }) => {
expect(getMediaType(makeResultItem({ mediaType }))).toBe(expected)
}
)
})

View File

@@ -4,6 +4,7 @@ import { nextTick, ref } from 'vue'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import type { InProgressItem } from '@/renderer/extensions/linearMode/linearModeTypes'
import { makeResultItem } from '@/renderer/extensions/linearMode/__fixtures__/testResultItemFactory'
import {
buildTimeline,
useOutputHistory
@@ -98,8 +99,27 @@ vi.mock('@/stores/queueStore', async (importOriginal) => {
}
})
const { jobDetailResults } = vi.hoisted(() => ({
jobDetailResults: new Map<string, unknown>()
const { jobDetailResults, commandExecuteFn, apiDeleteItemFn } = vi.hoisted(
() => ({
jobDetailResults: new Map<string, unknown>(),
commandExecuteFn: vi.fn(),
apiDeleteItemFn: vi.fn().mockResolvedValue(undefined)
})
)
vi.mock('@/stores/commandStore', () => ({
useCommandStore: () => ({
execute: commandExecuteFn
})
}))
vi.mock('@/scripts/api', () => ({
api: {
apiURL: (path: string) => path,
deleteItem: apiDeleteItemFn,
addEventListener: vi.fn(),
removeEventListener: vi.fn()
}
}))
vi.mock('@/services/jobOutputCache', () => ({
@@ -147,13 +167,7 @@ function makeAsset(
}
function makeResult(filename: string, nodeId: string = '1'): ResultItemImpl {
return new ResultItemImpl({
filename,
subfolder: '',
type: 'output',
nodeId,
mediaType: 'images'
})
return makeResultItem({ filename, nodeId })
}
describe(useOutputHistory, () => {
@@ -172,8 +186,8 @@ describe(useOutputHistory, () => {
pendingTasksRef.value = []
resolvedOutputsCacheRef.clear()
jobDetailResults.clear()
selectAsLatestFn.mockReset()
resolveIfReadyFn.mockReset()
vi.resetAllMocks()
apiDeleteItemFn.mockResolvedValue(undefined)
})
describe('sessionMedia filtering', () => {
@@ -501,6 +515,65 @@ describe(useOutputHistory, () => {
expect(mayBeActiveWorkflowPending.value).toBe(false)
})
})
describe('cancelActiveWorkflowJobs', () => {
it('interrupts running job when it matches active workflow', async () => {
activeWorkflowPathRef.value = 'workflows/test.json'
runningTasksRef.value = [{ jobId: 'job-1' }]
jobIdToPathRef.value = new Map([['job-1', 'workflows/test.json']])
const { cancelActiveWorkflowJobs } = useOutputHistory()
await cancelActiveWorkflowJobs()
expect(commandExecuteFn).toHaveBeenCalledWith('Comfy.Interrupt')
expect(apiDeleteItemFn).not.toHaveBeenCalled()
})
it('deletes only the first matching pending job when no running job matches', async () => {
activeWorkflowPathRef.value = 'workflows/test.json'
runningTasksRef.value = []
pendingTasksRef.value = [{ jobId: 'job-2' }, { jobId: 'job-3' }]
jobIdToPathRef.value = new Map([
['job-2', 'workflows/test.json'],
['job-3', 'workflows/test.json']
])
const { cancelActiveWorkflowJobs } = useOutputHistory()
await cancelActiveWorkflowJobs()
expect(commandExecuteFn).not.toHaveBeenCalled()
expect(apiDeleteItemFn).toHaveBeenCalledOnce()
expect(apiDeleteItemFn).toHaveBeenCalledWith('queue', 'job-2')
})
it('falls through to pending when running job belongs to a different workflow', async () => {
activeWorkflowPathRef.value = 'workflows/test.json'
runningTasksRef.value = [{ jobId: 'job-1' }]
pendingTasksRef.value = [{ jobId: 'job-2' }]
jobIdToPathRef.value = new Map([
['job-1', 'workflows/other.json'],
['job-2', 'workflows/test.json']
])
const { cancelActiveWorkflowJobs } = useOutputHistory()
await cancelActiveWorkflowJobs()
expect(commandExecuteFn).not.toHaveBeenCalled()
expect(apiDeleteItemFn).toHaveBeenCalledOnce()
expect(apiDeleteItemFn).toHaveBeenCalledWith('queue', 'job-2')
})
it('does nothing when no workflow path is set', async () => {
activeWorkflowPathRef.value = ''
runningTasksRef.value = [{ jobId: 'job-1' }]
const { cancelActiveWorkflowJobs } = useOutputHistory()
await cancelActiveWorkflowJobs()
expect(commandExecuteFn).not.toHaveBeenCalled()
expect(apiDeleteItemFn).not.toHaveBeenCalled()
})
})
})
describe(buildTimeline, () => {
@@ -515,13 +588,7 @@ describe(buildTimeline, () => {
}
function makeOutput(filename: string): ResultItemImpl {
return new ResultItemImpl({
filename,
subfolder: '',
type: 'output',
nodeId: '1',
mediaType: 'images'
})
return makeResultItem({ filename })
}
it('returns empty for no history and no non-asset outputs', () => {