Files
ComfyUI_frontend/tests-ui/tests/stores/queueStore.loadWorkflow.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

139 lines
3.6 KiB
TypeScript

import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { JobListItem } from '@/platform/remote/comfyui/jobs/types/jobTypes'
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
import type { ComfyApp } from '@/scripts/app'
import { TaskItemImpl } from '@/stores/queueStore'
import * as jobsModule from '@/platform/remote/comfyui/jobs'
vi.mock('@/services/extensionService', () => ({
useExtensionService: vi.fn(() => ({
invokeExtensions: vi.fn()
}))
}))
const mockWorkflow: ComfyWorkflowJSON = {
id: 'test-workflow-id',
revision: 0,
last_node_id: 5,
last_link_id: 3,
nodes: [],
links: [],
groups: [],
config: {},
extra: {},
version: 0.4
}
// Mock job detail response (matches actual /jobs/{id} API response structure)
const mockJobDetail = {
id: 'test-prompt-id',
status: 'completed' as const,
create_time: Date.now(),
execution_time: 10.5,
extra_data: {
extra_pnginfo: {
workflow: mockWorkflow
}
},
prompt: {},
outputs: {
'1': { images: [{ filename: 'test.png', subfolder: '', type: 'output' }] }
}
}
function createHistoryJob(id: string): JobListItem {
const now = Date.now()
return {
id,
status: 'completed',
create_time: now,
priority: now
}
}
function createRunningJob(id: string): JobListItem {
const now = Date.now()
return {
id,
status: 'in_progress',
create_time: now,
priority: now
}
}
describe('TaskItemImpl.loadWorkflow - workflow fetching', () => {
let mockApp: ComfyApp
let mockFetchApi: ReturnType<typeof vi.fn>
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
mockFetchApi = vi.fn()
mockApp = {
loadGraphData: vi.fn(),
nodeOutputs: {},
api: {
fetchApi: mockFetchApi
}
} as unknown as ComfyApp
})
it('should fetch workflow from API for history tasks', async () => {
const job = createHistoryJob('test-prompt-id')
const task = new TaskItemImpl(job)
vi.spyOn(jobsModule, 'fetchJobDetail').mockResolvedValue(
mockJobDetail as jobsModule.JobDetail
)
await task.loadWorkflow(mockApp)
expect(jobsModule.fetchJobDetail).toHaveBeenCalledWith(
expect.any(Function),
'test-prompt-id'
)
expect(mockApp.loadGraphData).toHaveBeenCalledWith(mockWorkflow)
})
it('should not load workflow when fetch returns undefined', async () => {
const job = createHistoryJob('test-prompt-id')
const task = new TaskItemImpl(job)
vi.spyOn(jobsModule, 'fetchJobDetail').mockResolvedValue(undefined)
await task.loadWorkflow(mockApp)
expect(jobsModule.fetchJobDetail).toHaveBeenCalled()
expect(mockApp.loadGraphData).not.toHaveBeenCalled()
})
it('should only fetch for history tasks, not running tasks', async () => {
const job = createRunningJob('test-prompt-id')
const runningTask = new TaskItemImpl(job)
vi.spyOn(jobsModule, 'fetchJobDetail').mockResolvedValue(
mockJobDetail as jobsModule.JobDetail
)
await runningTask.loadWorkflow(mockApp)
expect(jobsModule.fetchJobDetail).not.toHaveBeenCalled()
expect(mockApp.loadGraphData).not.toHaveBeenCalled()
})
it('should handle fetch errors gracefully by returning undefined', async () => {
const job = createHistoryJob('test-prompt-id')
const task = new TaskItemImpl(job)
vi.spyOn(jobsModule, 'fetchJobDetail').mockResolvedValue(undefined)
await task.loadWorkflow(mockApp)
expect(jobsModule.fetchJobDetail).toHaveBeenCalled()
expect(mockApp.loadGraphData).not.toHaveBeenCalled()
})
})