mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-27 01:39:47 +00:00
refactor: encapsulate error extraction in TaskItemImpl getters (#7650)
## Summary - Add `errorMessage` and `executionError` getters to `TaskItemImpl` that extract error info from status messages - Update `useJobErrorReporting` composable to use these getters instead of standalone function - Remove the standalone `extractExecutionError` function This encapsulates error extraction within `TaskItemImpl`, preparing for the Jobs API migration where the underlying data format will change but the getter interface will remain stable. ## Test plan - [x] All existing tests pass - [x] New tests added for `TaskItemImpl.errorMessage` and `TaskItemImpl.executionError` getters - [x] TypeScript, lint, and knip checks pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7650-refactor-encapsulate-error-extraction-in-TaskItemImpl-getters-2ce6d73d365081caae33dcc7e1e07720) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org>
This commit is contained in:
@@ -1,35 +1,74 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { useResultGallery } from '@/composables/queue/useResultGallery'
|
||||
import type { JobListItem } from '@/composables/queue/useJobList'
|
||||
import type { JobListItem as JobListViewItem } from '@/composables/queue/useJobList'
|
||||
import type { JobListItem } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import { ResultItemImpl, TaskItemImpl } from '@/stores/queueStore'
|
||||
|
||||
type PreviewLike = { url: string; supportsPreview: boolean }
|
||||
const createResultItem = (
|
||||
url: string,
|
||||
supportsPreview = true
|
||||
): ResultItemImpl => {
|
||||
const item = new ResultItemImpl({
|
||||
filename: url,
|
||||
subfolder: '',
|
||||
type: 'output',
|
||||
nodeId: 'node-1',
|
||||
mediaType: supportsPreview ? 'images' : 'unknown'
|
||||
})
|
||||
// Override url getter for test matching
|
||||
Object.defineProperty(item, 'url', { get: () => url })
|
||||
Object.defineProperty(item, 'supportsPreview', { get: () => supportsPreview })
|
||||
return item
|
||||
}
|
||||
|
||||
const createPreview = (url: string, supportsPreview = true): PreviewLike => ({
|
||||
url,
|
||||
supportsPreview
|
||||
const createMockJob = (id: string, outputsCount = 1): JobListItem => ({
|
||||
id,
|
||||
status: 'completed',
|
||||
create_time: Date.now(),
|
||||
preview_output: null,
|
||||
outputs_count: outputsCount,
|
||||
priority: 0
|
||||
})
|
||||
|
||||
const createTask = (preview?: PreviewLike) => ({
|
||||
previewOutput: preview
|
||||
})
|
||||
const createTask = (
|
||||
preview?: ResultItemImpl,
|
||||
allOutputs?: ResultItemImpl[],
|
||||
outputsCount = 1
|
||||
): TaskItemImpl => {
|
||||
const job = createMockJob(
|
||||
`task-${Math.random().toString(36).slice(2)}`,
|
||||
outputsCount
|
||||
)
|
||||
const flatOutputs = allOutputs ?? (preview ? [preview] : [])
|
||||
return new TaskItemImpl(job, {}, flatOutputs)
|
||||
}
|
||||
|
||||
const createJobItem = (id: string, preview?: PreviewLike): JobListItem =>
|
||||
const createJobViewItem = (
|
||||
id: string,
|
||||
taskRef?: TaskItemImpl
|
||||
): JobListViewItem =>
|
||||
({
|
||||
id,
|
||||
title: `Job ${id}`,
|
||||
meta: '',
|
||||
state: 'completed',
|
||||
showClear: false,
|
||||
taskRef: preview ? { previewOutput: preview } : undefined
|
||||
}) as JobListItem
|
||||
taskRef
|
||||
}) as JobListViewItem
|
||||
|
||||
describe('useResultGallery', () => {
|
||||
it('collects only previewable outputs and preserves their order', () => {
|
||||
const previewable = [createPreview('p-1'), createPreview('p-2')]
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
it('collects only previewable outputs and preserves their order', async () => {
|
||||
const previewable = [createResultItem('p-1'), createResultItem('p-2')]
|
||||
const nonPreviewable = createResultItem('skip-me', false)
|
||||
const tasks = [
|
||||
createTask(previewable[0]),
|
||||
createTask({ url: 'skip-me', supportsPreview: false }),
|
||||
createTask(nonPreviewable),
|
||||
createTask(previewable[1]),
|
||||
createTask()
|
||||
]
|
||||
@@ -38,28 +77,28 @@ describe('useResultGallery', () => {
|
||||
() => tasks
|
||||
)
|
||||
|
||||
onViewItem(createJobItem('job-1', previewable[0]))
|
||||
await onViewItem(createJobViewItem('job-1', tasks[0]))
|
||||
|
||||
expect(galleryItems.value).toEqual(previewable)
|
||||
expect(galleryItems.value).toEqual([previewable[0]])
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
})
|
||||
|
||||
it('does not change state when there are no previewable tasks', () => {
|
||||
it('does not change state when there are no previewable tasks', async () => {
|
||||
const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery(
|
||||
() => []
|
||||
)
|
||||
|
||||
onViewItem(createJobItem('job-missing'))
|
||||
await onViewItem(createJobViewItem('job-missing'))
|
||||
|
||||
expect(galleryItems.value).toEqual([])
|
||||
expect(galleryActiveIndex.value).toBe(-1)
|
||||
})
|
||||
|
||||
it('activates the index that matches the viewed preview URL', () => {
|
||||
it('activates the index that matches the viewed preview URL', async () => {
|
||||
const previewable = [
|
||||
createPreview('p-1'),
|
||||
createPreview('p-2'),
|
||||
createPreview('p-3')
|
||||
createResultItem('p-1'),
|
||||
createResultItem('p-2'),
|
||||
createResultItem('p-3')
|
||||
]
|
||||
const tasks = previewable.map((preview) => createTask(preview))
|
||||
|
||||
@@ -67,37 +106,66 @@ describe('useResultGallery', () => {
|
||||
() => tasks
|
||||
)
|
||||
|
||||
onViewItem(createJobItem('job-2', createPreview('p-2')))
|
||||
await onViewItem(createJobViewItem('job-2', tasks[1]))
|
||||
|
||||
expect(galleryItems.value).toEqual(previewable)
|
||||
expect(galleryActiveIndex.value).toBe(1)
|
||||
expect(galleryItems.value).toEqual([previewable[1]])
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
})
|
||||
|
||||
it('defaults to the first entry when the clicked job lacks a preview', () => {
|
||||
const previewable = [createPreview('p-1'), createPreview('p-2')]
|
||||
it('defaults to the first entry when the clicked job lacks a preview', async () => {
|
||||
const previewable = [createResultItem('p-1'), createResultItem('p-2')]
|
||||
const tasks = previewable.map((preview) => createTask(preview))
|
||||
|
||||
const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery(
|
||||
() => tasks
|
||||
)
|
||||
|
||||
onViewItem(createJobItem('job-no-preview'))
|
||||
await onViewItem(createJobViewItem('job-no-preview'))
|
||||
|
||||
expect(galleryItems.value).toEqual(previewable)
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
})
|
||||
|
||||
it('defaults to the first entry when no gallery item matches the preview URL', () => {
|
||||
const previewable = [createPreview('p-1'), createPreview('p-2')]
|
||||
it('defaults to the first entry when no gallery item matches the preview URL', async () => {
|
||||
const previewable = [createResultItem('p-1'), createResultItem('p-2')]
|
||||
const tasks = previewable.map((preview) => createTask(preview))
|
||||
|
||||
const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery(
|
||||
() => tasks
|
||||
)
|
||||
|
||||
onViewItem(createJobItem('job-mismatch', createPreview('missing')))
|
||||
const taskWithMismatchedPreview = createTask(createResultItem('missing'))
|
||||
await onViewItem(
|
||||
createJobViewItem('job-mismatch', taskWithMismatchedPreview)
|
||||
)
|
||||
|
||||
expect(galleryItems.value).toEqual(previewable)
|
||||
expect(galleryItems.value).toEqual([createResultItem('missing')])
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
})
|
||||
|
||||
it('loads full outputs when task has only preview outputs', async () => {
|
||||
const previewOutput = createResultItem('preview-1')
|
||||
const fullOutputs = [
|
||||
createResultItem('full-1'),
|
||||
createResultItem('full-2'),
|
||||
createResultItem('full-3')
|
||||
]
|
||||
|
||||
// Create a task with outputsCount > 1 to trigger lazy loading
|
||||
const job = createMockJob('task-1', 3)
|
||||
const task = new TaskItemImpl(job, {}, [previewOutput])
|
||||
|
||||
// Mock loadFullOutputs to return full outputs
|
||||
const loadedTask = new TaskItemImpl(job, {}, fullOutputs)
|
||||
task.loadFullOutputs = async () => loadedTask
|
||||
|
||||
const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery(
|
||||
() => [task]
|
||||
)
|
||||
|
||||
await onViewItem(createJobViewItem('job-1', task))
|
||||
|
||||
expect(galleryItems.value).toEqual(fullOutputs)
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user