mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-25 16:59:45 +00:00
[feat] Add Linear Mode infrastructure
Add core infrastructure for Linear Mode - a simplified form-based UI for ComfyUI workflows: - Add optional onQueued callback to app.queuePrompt() for deterministic prompt ID tracking - Create Linear Mode type definitions (widgets, templates, output images) - Add template configuration with 10 promoted widgets - Implement Pinia store with client-side history filtering via prompt ID tracking - Add service layer for template loading and widget value manipulation - Add composable for queue operations with automatic prompt tracking - Add i18n strings for Linear Mode UI - Add comprehensive unit tests (41 tests, 100% passing) All user-facing strings use i18n. Template uses placeholder IDs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,420 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
activateTemplate,
|
||||
getAllWidgetValues,
|
||||
getWidgetValue,
|
||||
initializeLinearMode,
|
||||
loadTemplate,
|
||||
setWidgetValue,
|
||||
updateWidgetValue
|
||||
} from '@/renderer/extensions/linearMode/linearModeService'
|
||||
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { PromotedWidget } from '@/renderer/extensions/linearMode/linearModeTypes'
|
||||
|
||||
const mockTemplate = {
|
||||
id: 'template-default-linear',
|
||||
name: 'Linear Mode Template',
|
||||
templatePath: '/templates/template-default-linear.json',
|
||||
promotedWidgets: [
|
||||
{
|
||||
nodeId: 6,
|
||||
widgetName: 'text',
|
||||
displayName: 'Prompt',
|
||||
type: 'text',
|
||||
config: { multiline: true },
|
||||
group: 'content'
|
||||
},
|
||||
{
|
||||
nodeId: 3,
|
||||
widgetName: 'seed',
|
||||
displayName: 'Seed',
|
||||
type: 'number',
|
||||
config: { min: 0 },
|
||||
group: 'generation'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
let mockWorkflow: Partial<ComfyWorkflowJSON> = {
|
||||
nodes: [
|
||||
{
|
||||
id: 6,
|
||||
widgets_values: { text: 'test prompt' }
|
||||
} as unknown as ComfyWorkflowJSON['nodes'][0],
|
||||
{
|
||||
id: 3,
|
||||
widgets_values: { seed: 12345, steps: 20 }
|
||||
} as unknown as ComfyWorkflowJSON['nodes'][0],
|
||||
{
|
||||
id: 5,
|
||||
widgets_values: { width: 1024 }
|
||||
} as unknown as ComfyWorkflowJSON['nodes'][0]
|
||||
]
|
||||
}
|
||||
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
fileURL: vi.fn((path: string) => `http://localhost:8188${path}`)
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/scripts/app', () => ({
|
||||
app: {
|
||||
loadGraphData: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: vi.fn(() => ({
|
||||
activeWorkflow: {
|
||||
get activeState() {
|
||||
return mockWorkflow
|
||||
}
|
||||
}
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/extensions/linearMode/stores/linearModeStore', () => ({
|
||||
useLinearModeStore: vi.fn(() => ({
|
||||
template: mockTemplate,
|
||||
promotedWidgets: mockTemplate.promotedWidgets,
|
||||
open: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('linearModeService', () => {
|
||||
beforeEach(async () => {
|
||||
setActivePinia(createPinia())
|
||||
vi.clearAllMocks()
|
||||
// Reset mockWorkflow for each test
|
||||
mockWorkflow = {
|
||||
nodes: [
|
||||
{
|
||||
id: 6,
|
||||
widgets_values: { text: 'test prompt' }
|
||||
} as unknown as ComfyWorkflowJSON['nodes'][0],
|
||||
{
|
||||
id: 3,
|
||||
widgets_values: { seed: 12345, steps: 20 }
|
||||
} as unknown as ComfyWorkflowJSON['nodes'][0],
|
||||
{
|
||||
id: 5,
|
||||
widgets_values: { width: 1024 }
|
||||
} as unknown as ComfyWorkflowJSON['nodes'][0]
|
||||
]
|
||||
}
|
||||
|
||||
// Reset the mock to use the fresh mockWorkflow
|
||||
const { useWorkflowStore } = await import(
|
||||
'@/platform/workflow/management/stores/workflowStore'
|
||||
)
|
||||
vi.mocked(useWorkflowStore).mockReturnValue({
|
||||
activeWorkflow: {
|
||||
get activeState() {
|
||||
return mockWorkflow
|
||||
}
|
||||
}
|
||||
} as unknown as ReturnType<typeof useWorkflowStore>)
|
||||
})
|
||||
|
||||
describe('loadTemplate()', () => {
|
||||
it('should load template from backend', async () => {
|
||||
const mockTemplateData = { nodes: [{ id: 1 }] }
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => mockTemplateData
|
||||
})
|
||||
|
||||
const result = await loadTemplate('/templates/test.json')
|
||||
|
||||
expect(result).toEqual(mockTemplateData)
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'http://localhost:8188/templates/test.json'
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw error when template load fails', async () => {
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
statusText: 'Not Found'
|
||||
})
|
||||
|
||||
await expect(loadTemplate('/templates/missing.json')).rejects.toThrow(
|
||||
'Failed to load template: Not Found'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getWidgetValue()', () => {
|
||||
it('should get widget value from workflow', () => {
|
||||
const value = getWidgetValue(
|
||||
mockWorkflow as unknown as ComfyWorkflowJSON,
|
||||
6,
|
||||
'text'
|
||||
)
|
||||
|
||||
expect(value).toBe('test prompt')
|
||||
})
|
||||
|
||||
it('should return undefined for non-existent node', () => {
|
||||
const value = getWidgetValue(
|
||||
mockWorkflow as unknown as ComfyWorkflowJSON,
|
||||
999,
|
||||
'text'
|
||||
)
|
||||
|
||||
expect(value).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for non-existent widget', () => {
|
||||
const value = getWidgetValue(
|
||||
mockWorkflow as unknown as ComfyWorkflowJSON,
|
||||
6,
|
||||
'nonexistent'
|
||||
)
|
||||
|
||||
expect(value).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle numeric node IDs', () => {
|
||||
const value = getWidgetValue(
|
||||
mockWorkflow as unknown as ComfyWorkflowJSON,
|
||||
3,
|
||||
'seed'
|
||||
)
|
||||
|
||||
expect(value).toBe(12345)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setWidgetValue()', () => {
|
||||
it('should set widget value in workflow', () => {
|
||||
const workflow = JSON.parse(
|
||||
JSON.stringify(mockWorkflow)
|
||||
) as typeof mockWorkflow
|
||||
|
||||
const result = setWidgetValue(
|
||||
workflow as unknown as ComfyWorkflowJSON,
|
||||
6,
|
||||
'text',
|
||||
'new prompt'
|
||||
)
|
||||
|
||||
expect(result).toBe(true)
|
||||
const node = workflow.nodes?.[0]
|
||||
if (node?.widgets_values && !Array.isArray(node.widgets_values)) {
|
||||
expect(node.widgets_values.text).toBe('new prompt')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return false for non-existent node', () => {
|
||||
const workflow = JSON.parse(
|
||||
JSON.stringify(mockWorkflow)
|
||||
) as typeof mockWorkflow
|
||||
|
||||
const result = setWidgetValue(
|
||||
workflow as unknown as ComfyWorkflowJSON,
|
||||
999,
|
||||
'text',
|
||||
'value'
|
||||
)
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('should create widgets_values object if missing', () => {
|
||||
const workflow: Partial<ComfyWorkflowJSON> = {
|
||||
nodes: [{ id: 10 } as unknown as ComfyWorkflowJSON['nodes'][0]]
|
||||
}
|
||||
|
||||
const result = setWidgetValue(
|
||||
workflow as unknown as ComfyWorkflowJSON,
|
||||
10,
|
||||
'newWidget',
|
||||
'value'
|
||||
)
|
||||
|
||||
expect(result).toBe(true)
|
||||
const node = workflow.nodes?.[0]
|
||||
if (node?.widgets_values && !Array.isArray(node.widgets_values)) {
|
||||
expect(node.widgets_values).toEqual({ newWidget: 'value' })
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle numeric values', () => {
|
||||
const workflow = JSON.parse(
|
||||
JSON.stringify(mockWorkflow)
|
||||
) as typeof mockWorkflow
|
||||
|
||||
const result = setWidgetValue(
|
||||
workflow as unknown as ComfyWorkflowJSON,
|
||||
3,
|
||||
'seed',
|
||||
99999
|
||||
)
|
||||
|
||||
expect(result).toBe(true)
|
||||
const node = workflow.nodes?.[1]
|
||||
if (node?.widgets_values && !Array.isArray(node.widgets_values)) {
|
||||
expect(node.widgets_values.seed).toBe(99999)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAllWidgetValues()', () => {
|
||||
it('should return all promoted widget values', () => {
|
||||
const values = getAllWidgetValues()
|
||||
|
||||
expect(values.size).toBe(2)
|
||||
expect(values.get('Prompt')).toBe('test prompt')
|
||||
expect(values.get('Seed')).toBe(12345)
|
||||
})
|
||||
|
||||
it('should return empty map when no workflow', async () => {
|
||||
const { useWorkflowStore } = await import(
|
||||
'@/platform/workflow/management/stores/workflowStore'
|
||||
)
|
||||
vi.mocked(useWorkflowStore).mockReturnValue({
|
||||
activeWorkflow: null
|
||||
} as unknown as ReturnType<typeof useWorkflowStore>)
|
||||
|
||||
const values = getAllWidgetValues()
|
||||
|
||||
expect(values.size).toBe(0)
|
||||
})
|
||||
|
||||
it('should handle missing widget values gracefully', async () => {
|
||||
const workflowMissingValues: Partial<ComfyWorkflowJSON> = {
|
||||
nodes: [{ id: 999 } as unknown as ComfyWorkflowJSON['nodes'][0]]
|
||||
}
|
||||
const { useWorkflowStore } = await import(
|
||||
'@/platform/workflow/management/stores/workflowStore'
|
||||
)
|
||||
vi.mocked(useWorkflowStore).mockReturnValue({
|
||||
activeWorkflow: { activeState: workflowMissingValues }
|
||||
} as unknown as ReturnType<typeof useWorkflowStore>)
|
||||
|
||||
const values = getAllWidgetValues()
|
||||
|
||||
expect(values.get('Prompt')).toBeUndefined()
|
||||
expect(values.get('Seed')).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateWidgetValue()', () => {
|
||||
it('should update widget value in current workflow', () => {
|
||||
const widget: PromotedWidget = {
|
||||
nodeId: 6,
|
||||
widgetName: 'text',
|
||||
displayName: 'Prompt',
|
||||
type: 'text',
|
||||
config: {}
|
||||
}
|
||||
|
||||
const result = updateWidgetValue(widget, 'updated prompt')
|
||||
|
||||
expect(result).toBe(true)
|
||||
const node = mockWorkflow.nodes?.[0]
|
||||
if (node?.widgets_values && !Array.isArray(node.widgets_values)) {
|
||||
expect(node.widgets_values.text).toBe('updated prompt')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return false when no workflow loaded', async () => {
|
||||
const { useWorkflowStore } = await import(
|
||||
'@/platform/workflow/management/stores/workflowStore'
|
||||
)
|
||||
vi.mocked(useWorkflowStore).mockReturnValue({
|
||||
activeWorkflow: null
|
||||
} as unknown as ReturnType<typeof useWorkflowStore>)
|
||||
|
||||
const widget: PromotedWidget = {
|
||||
nodeId: 6,
|
||||
widgetName: 'text',
|
||||
displayName: 'Prompt',
|
||||
type: 'text',
|
||||
config: {}
|
||||
}
|
||||
|
||||
const result = updateWidgetValue(widget, 'new value')
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('activateTemplate()', () => {
|
||||
it('should load and activate template', async () => {
|
||||
const mockTemplateData = { nodes: [{ id: 1 }] }
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => mockTemplateData
|
||||
})
|
||||
|
||||
const { app } = await import('@/scripts/app')
|
||||
|
||||
await activateTemplate('template-default-linear')
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'http://localhost:8188/templates/template-default-linear.json'
|
||||
)
|
||||
expect(app.loadGraphData).toHaveBeenCalledWith(mockTemplateData)
|
||||
})
|
||||
|
||||
it('should throw error when template not found in store', async () => {
|
||||
const { useLinearModeStore } = await import(
|
||||
'@/renderer/extensions/linearMode/stores/linearModeStore'
|
||||
)
|
||||
vi.mocked(useLinearModeStore).mockReturnValue({
|
||||
template: null,
|
||||
promotedWidgets: [],
|
||||
open: vi.fn()
|
||||
} as unknown as ReturnType<typeof useLinearModeStore>)
|
||||
|
||||
await expect(activateTemplate('template-default-linear')).rejects.toThrow(
|
||||
'Template not found: template-default-linear'
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw error when template ID mismatch', async () => {
|
||||
const { useLinearModeStore } = await import(
|
||||
'@/renderer/extensions/linearMode/stores/linearModeStore'
|
||||
)
|
||||
vi.mocked(useLinearModeStore).mockReturnValue({
|
||||
template: { ...mockTemplate, id: 'different-template' },
|
||||
promotedWidgets: [],
|
||||
open: vi.fn()
|
||||
} as unknown as ReturnType<typeof useLinearModeStore>)
|
||||
|
||||
await expect(activateTemplate('template-default-linear')).rejects.toThrow(
|
||||
'Template not found: template-default-linear'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('initializeLinearMode()', () => {
|
||||
it('should open Linear Mode and activate template', async () => {
|
||||
const mockTemplateData = { nodes: [{ id: 1 }] }
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => mockTemplateData
|
||||
})
|
||||
|
||||
const { useLinearModeStore } = await import(
|
||||
'@/renderer/extensions/linearMode/stores/linearModeStore'
|
||||
)
|
||||
const { app } = await import('@/scripts/app')
|
||||
const mockOpen = vi.fn()
|
||||
vi.mocked(useLinearModeStore).mockReturnValue({
|
||||
template: mockTemplate,
|
||||
promotedWidgets: mockTemplate.promotedWidgets,
|
||||
open: mockOpen
|
||||
} as unknown as ReturnType<typeof useLinearModeStore>)
|
||||
|
||||
await initializeLinearMode('template-default-linear')
|
||||
|
||||
expect(mockOpen).toHaveBeenCalledWith('template-default-linear')
|
||||
expect(app.loadGraphData).toHaveBeenCalledWith(mockTemplateData)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,311 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useLinearModeStore } from '@/renderer/extensions/linearMode/stores/linearModeStore'
|
||||
import type { OutputImage } from '@/renderer/extensions/linearMode/linearModeTypes'
|
||||
|
||||
vi.mock('@/renderer/extensions/linearMode/linearModeConfig', () => ({
|
||||
getTemplateConfig: vi.fn((id: string) => {
|
||||
if (id === 'template-default-linear') {
|
||||
return {
|
||||
id: 'template-default-linear',
|
||||
name: 'Linear Mode Template',
|
||||
templatePath: '/templates/template-default-linear.json',
|
||||
promotedWidgets: [
|
||||
{
|
||||
nodeId: 6,
|
||||
widgetName: 'text',
|
||||
displayName: 'Prompt',
|
||||
type: 'text',
|
||||
config: { multiline: true },
|
||||
group: 'content'
|
||||
},
|
||||
{
|
||||
nodeId: 3,
|
||||
widgetName: 'seed',
|
||||
displayName: 'Seed',
|
||||
type: 'number',
|
||||
config: { min: 0 },
|
||||
group: 'generation'
|
||||
}
|
||||
],
|
||||
description: 'Default template',
|
||||
tags: ['text-to-image']
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: () => ({
|
||||
activeWorkflow: {
|
||||
activeState: {
|
||||
nodes: [
|
||||
{ id: 6, widgets_values: { text: 'test prompt' } },
|
||||
{ id: 3, widgets_values: { seed: 12345 } }
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
interface MockTaskItem {
|
||||
promptId: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
vi.mock('@/stores/queueStore', () => {
|
||||
return {
|
||||
useQueueStore: () => ({
|
||||
pendingTasks: [
|
||||
{ promptId: 'prompt-1' },
|
||||
{ promptId: 'prompt-2' }
|
||||
] as MockTaskItem[],
|
||||
historyTasks: [
|
||||
{ promptId: 'prompt-1', status: 'completed' },
|
||||
{ promptId: 'prompt-2', status: 'completed' },
|
||||
{ promptId: 'prompt-3', status: 'completed' }
|
||||
] as MockTaskItem[]
|
||||
}),
|
||||
TaskItemImpl: class {}
|
||||
}
|
||||
})
|
||||
|
||||
describe('useLinearModeStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
describe('initial state', () => {
|
||||
it('should have correct default state', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
expect(store.templateId).toBe(null)
|
||||
expect(store.currentOutput).toBe(null)
|
||||
expect(store.generatedPromptIds).toBeInstanceOf(Set)
|
||||
expect(store.generatedPromptIds.size).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('open()', () => {
|
||||
it('should open Linear Mode with valid template', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
store.open('template-default-linear')
|
||||
|
||||
expect(store.isOpen).toBe(true)
|
||||
expect(store.templateId).toBe('template-default-linear')
|
||||
})
|
||||
|
||||
it('should throw error for invalid template ID', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
expect(() => store.open('invalid-template')).toThrow(
|
||||
'Invalid template ID: invalid-template'
|
||||
)
|
||||
})
|
||||
|
||||
it('should not affect other state when opening', () => {
|
||||
const store = useLinearModeStore()
|
||||
store.trackGeneratedPrompt('test-prompt-1')
|
||||
|
||||
store.open('template-default-linear')
|
||||
|
||||
expect(store.generatedPromptIds.has('test-prompt-1')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('close()', () => {
|
||||
it('should close Linear Mode', () => {
|
||||
const store = useLinearModeStore()
|
||||
store.open('template-default-linear')
|
||||
|
||||
store.close()
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
})
|
||||
|
||||
it('should preserve state when closing', () => {
|
||||
const store = useLinearModeStore()
|
||||
store.open('template-default-linear')
|
||||
store.trackGeneratedPrompt('test-prompt')
|
||||
|
||||
store.close()
|
||||
|
||||
expect(store.templateId).toBe('template-default-linear')
|
||||
expect(store.generatedPromptIds.has('test-prompt')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setOutput()', () => {
|
||||
it('should set current output', () => {
|
||||
const store = useLinearModeStore()
|
||||
const output: OutputImage = {
|
||||
filename: 'test.png',
|
||||
subfolder: 'output',
|
||||
type: 'output',
|
||||
prompt_id: 'prompt-123'
|
||||
}
|
||||
|
||||
store.setOutput(output)
|
||||
|
||||
expect(store.currentOutput).toEqual(output)
|
||||
})
|
||||
|
||||
it('should clear current output when null', () => {
|
||||
const store = useLinearModeStore()
|
||||
const output: OutputImage = {
|
||||
filename: 'test.png',
|
||||
subfolder: 'output',
|
||||
type: 'output',
|
||||
prompt_id: 'prompt-123'
|
||||
}
|
||||
store.setOutput(output)
|
||||
|
||||
store.setOutput(null)
|
||||
|
||||
expect(store.currentOutput).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('trackGeneratedPrompt()', () => {
|
||||
it('should add prompt ID to set', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
store.trackGeneratedPrompt('prompt-1')
|
||||
|
||||
expect(store.generatedPromptIds.has('prompt-1')).toBe(true)
|
||||
expect(store.generatedPromptIds.size).toBe(1)
|
||||
})
|
||||
|
||||
it('should handle multiple prompt IDs', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
store.trackGeneratedPrompt('prompt-1')
|
||||
store.trackGeneratedPrompt('prompt-2')
|
||||
store.trackGeneratedPrompt('prompt-3')
|
||||
|
||||
expect(store.generatedPromptIds.size).toBe(3)
|
||||
expect(store.generatedPromptIds.has('prompt-1')).toBe(true)
|
||||
expect(store.generatedPromptIds.has('prompt-2')).toBe(true)
|
||||
expect(store.generatedPromptIds.has('prompt-3')).toBe(true)
|
||||
})
|
||||
|
||||
it('should not duplicate prompt IDs', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
store.trackGeneratedPrompt('prompt-1')
|
||||
store.trackGeneratedPrompt('prompt-1')
|
||||
|
||||
expect(store.generatedPromptIds.size).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('reset()', () => {
|
||||
it('should reset all state', () => {
|
||||
const store = useLinearModeStore()
|
||||
store.open('template-default-linear')
|
||||
store.trackGeneratedPrompt('prompt-1')
|
||||
store.setOutput({
|
||||
filename: 'test.png',
|
||||
subfolder: 'output',
|
||||
type: 'output',
|
||||
prompt_id: 'prompt-1'
|
||||
})
|
||||
|
||||
store.reset()
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
expect(store.templateId).toBe(null)
|
||||
expect(store.currentOutput).toBe(null)
|
||||
expect(store.generatedPromptIds.size).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('template getter', () => {
|
||||
it('should return null when no template is selected', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
expect(store.template).toBe(null)
|
||||
})
|
||||
|
||||
it('should return template config when template is selected', () => {
|
||||
const store = useLinearModeStore()
|
||||
store.open('template-default-linear')
|
||||
|
||||
expect(store.template).not.toBe(null)
|
||||
expect(store.template?.id).toBe('template-default-linear')
|
||||
expect(store.template?.name).toBe('Linear Mode Template')
|
||||
})
|
||||
})
|
||||
|
||||
describe('promotedWidgets getter', () => {
|
||||
it('should return empty array when no template selected', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
expect(store.promotedWidgets).toEqual([])
|
||||
})
|
||||
|
||||
it('should return promoted widgets from template', () => {
|
||||
const store = useLinearModeStore()
|
||||
store.open('template-default-linear')
|
||||
|
||||
expect(store.promotedWidgets.length).toBe(2)
|
||||
expect(store.promotedWidgets[0].displayName).toBe('Prompt')
|
||||
expect(store.promotedWidgets[1].displayName).toBe('Seed')
|
||||
})
|
||||
})
|
||||
|
||||
describe('currentWorkflow getter', () => {
|
||||
it('should return workflow from workflowStore', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
expect(store.currentWorkflow).not.toBe(null)
|
||||
expect(store.currentWorkflow?.nodes).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('filteredHistory getter', () => {
|
||||
it('should return empty array when no prompts tracked', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
expect(store.filteredHistory).toEqual([])
|
||||
})
|
||||
|
||||
it('should filter history by tracked prompt IDs', () => {
|
||||
const store = useLinearModeStore()
|
||||
store.trackGeneratedPrompt('prompt-1')
|
||||
store.trackGeneratedPrompt('prompt-3')
|
||||
|
||||
const filtered = store.filteredHistory as unknown as MockTaskItem[]
|
||||
|
||||
expect(filtered.length).toBe(2)
|
||||
expect(filtered.some((item) => item.promptId === 'prompt-1')).toBe(true)
|
||||
expect(filtered.some((item) => item.promptId === 'prompt-3')).toBe(true)
|
||||
expect(filtered.some((item) => item.promptId === 'prompt-2')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isGenerating getter', () => {
|
||||
it('should return false when no prompts are tracked', () => {
|
||||
const store = useLinearModeStore()
|
||||
|
||||
expect(store.isGenerating).toBe(false)
|
||||
})
|
||||
|
||||
it('should return true when tracked prompt is in queue', () => {
|
||||
const store = useLinearModeStore()
|
||||
store.trackGeneratedPrompt('prompt-1')
|
||||
|
||||
expect(store.isGenerating).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false when tracked prompt is not in queue', () => {
|
||||
const store = useLinearModeStore()
|
||||
store.trackGeneratedPrompt('prompt-999')
|
||||
|
||||
expect(store.isGenerating).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user