Files
ComfyUI_frontend/tests-ui/tests/components/queue/useJobErrorReporting.test.ts
Richard Yu b733b88628 [feat] Add Jobs API integration with memory optimization and lazy loading
Implements Jobs API endpoints (/jobs) for cloud distribution to replace
history_v2 API, providing 99.998% memory reduction per item.

Key changes:
- Jobs API types, schemas, and fetchers for list and detail endpoints
- Adapter to convert Jobs API format to TaskItem format
- Lazy loading for full outputs when loading workflows
- hasOnlyPreviewOutputs() detection for preview-only tasks
- Feature flag to toggle between Jobs API and history_v2

Implementation details:
- List endpoint: Returns preview_output only (100-200 bytes per job)
- Detail endpoint: Returns full workflow and outputs on demand
- Cloud builds use /jobs?status=completed for history view
- Desktop builds unchanged (still use history_v1)
- 21 unit and integration tests (all passing)

Memory optimization:
- Old: 300-600KB per history item (full outputs)
- New: 100-200 bytes per history item (preview only)
- Reduction: 99.998%

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 13:19:56 -08:00

102 lines
3.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { computed, ref } from 'vue'
import type { ComputedRef } from 'vue'
import type { TaskItemImpl } from '@/stores/queueStore'
import type { JobErrorDialogService } from '@/components/queue/job/useJobErrorReporting'
import * as jobErrorReporting from '@/components/queue/job/useJobErrorReporting'
/**
* Creates a mock TaskItemImpl with an error message.
*/
const createTaskWithError = (errorMessage?: string): TaskItemImpl =>
({ errorMessage }) as unknown as TaskItemImpl
describe('extractErrorMessage', () => {
it('returns null when task is null', () => {
expect(jobErrorReporting.extractErrorMessage(null)).toBeNull()
})
it('returns null when errorMessage is undefined', () => {
expect(
jobErrorReporting.extractErrorMessage(createTaskWithError())
).toBeNull()
})
it('returns the error message when present', () => {
expect(
jobErrorReporting.extractErrorMessage(
createTaskWithError('Something failed')
)
).toBe('Something failed')
})
})
describe('useJobErrorReporting', () => {
let taskState = ref<TaskItemImpl | null>(null)
let taskForJob: ComputedRef<TaskItemImpl | null>
let copyToClipboard: ReturnType<typeof vi.fn>
let showErrorDialog: ReturnType<typeof vi.fn>
let dialog: JobErrorDialogService
let composable: ReturnType<typeof jobErrorReporting.useJobErrorReporting>
beforeEach(() => {
taskState = ref<TaskItemImpl | null>(null)
taskForJob = computed(() => taskState.value)
copyToClipboard = vi.fn()
showErrorDialog = vi.fn()
dialog = { showErrorDialog }
composable = jobErrorReporting.useJobErrorReporting({
taskForJob,
copyToClipboard,
dialog
})
})
afterEach(() => {
vi.restoreAllMocks()
})
it('exposes a computed message that reflects the current task error', () => {
taskState.value = createTaskWithError('First failure')
expect(composable.errorMessageValue.value).toBe('First failure')
taskState.value = createTaskWithError('Second failure')
expect(composable.errorMessageValue.value).toBe('Second failure')
})
it('returns empty string when no error message', () => {
taskState.value = createTaskWithError()
expect(composable.errorMessageValue.value).toBe('')
})
it('only calls the copy handler when a message exists', () => {
taskState.value = createTaskWithError('Clipboard failure')
composable.copyErrorMessage()
expect(copyToClipboard).toHaveBeenCalledTimes(1)
expect(copyToClipboard).toHaveBeenCalledWith('Clipboard failure')
copyToClipboard.mockClear()
taskState.value = createTaskWithError()
composable.copyErrorMessage()
expect(copyToClipboard).not.toHaveBeenCalled()
})
it('shows error dialog with the error message', () => {
taskState.value = createTaskWithError('Queue job error')
composable.reportJobError()
expect(showErrorDialog).toHaveBeenCalledTimes(1)
const [errorArg, optionsArg] = showErrorDialog.mock.calls[0]
expect(errorArg).toBeInstanceOf(Error)
expect(errorArg.message).toBe('Queue job error')
expect(optionsArg).toEqual({ reportType: 'queueJobError' })
})
it('does nothing when no error message exists', () => {
taskState.value = createTaskWithError()
composable.reportJobError()
expect(showErrorDialog).not.toHaveBeenCalled()
})
})