feat: Complete manager migration with bug fixes and locale updates

- Restore proper task queue implementation with generated types
- Fix manager button visibility based on server feature flags
- Add task completion tracking with taskIdToPackId mapping
- Fix log separation with task-specific filtering
- Implement failed tab functionality with proper task partitioning
- Fix task progress status detection using actual queue state
- Add missing locale entries for all manager operations
- Remove legacy manager menu items, keep only 'Manage Extensions'
- Fix task panel expansion state and count display issues
- All TypeScript and ESLint checks pass

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
bymyself
2025-08-31 23:32:38 -07:00
parent e6aff692b4
commit 3853dcac90
43 changed files with 1982 additions and 1335 deletions

View File

@@ -183,9 +183,9 @@ describe('ManagerProgressFooter', () => {
// Setup queue running state
mockComfyManagerStore.uncompletedCount = 3
mockTaskLogs.push(
{ taskName: 'Installing pack1', logs: [] },
{ taskName: 'Installing pack2', logs: [] },
{ taskName: 'Installing pack3', logs: [] }
{ taskName: 'Installing pack1', taskId: 'task-1', logs: [] },
{ taskName: 'Installing pack2', taskId: 'task-2', logs: [] },
{ taskName: 'Installing pack3', taskId: 'task-3', logs: [] }
)
const wrapper = mountComponent()
@@ -209,7 +209,11 @@ describe('ManagerProgressFooter', () => {
it('should toggle expansion when expand button is clicked', async () => {
mockComfyManagerStore.uncompletedCount = 1
mockTaskLogs.push({ taskName: 'Installing', logs: [] })
mockTaskLogs.push({
taskName: 'Installing',
taskId: 'task-install',
logs: []
})
const wrapper = mountComponent()
@@ -225,8 +229,8 @@ describe('ManagerProgressFooter', () => {
// Setup tasks completed state
mockComfyManagerStore.uncompletedCount = 0
mockTaskLogs.push(
{ taskName: 'Installed pack1', logs: [] },
{ taskName: 'Installed pack2', logs: [] }
{ taskName: 'Installed pack1', taskId: 'task-done-1', logs: [] },
{ taskName: 'Installed pack2', taskId: 'task-done-2', logs: [] }
)
mockComfyManagerStore.allTasksDone = true

View File

@@ -1,329 +1,265 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { nextTick, ref } from 'vue'
import { useManagerQueue } from '@/composables/useManagerQueue'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
vi.mock('@/scripts/api', () => ({
api: {
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn()
// Mock VueUse's useEventListener
const mockEventListeners = new Map()
const mockWheneverCallback = vi.fn()
vi.mock('@vueuse/core', async () => {
const actual = await vi.importActual('@vueuse/core')
return {
...actual,
useEventListener: vi.fn((target, event, handler) => {
if (!mockEventListeners.has(event)) {
mockEventListeners.set(event, [])
}
mockEventListeners.get(event).push(handler)
// Mock the addEventListener behavior
if (target && target.addEventListener) {
target.addEventListener(event, handler)
}
// Return cleanup function
return () => {
if (target && target.removeEventListener) {
target.removeEventListener(event, handler)
}
}
}),
whenever: vi.fn((_source, cb) => {
mockWheneverCallback.mockImplementation(cb)
})
}
})
vi.mock('@/scripts/app', () => ({
app: {
api: {
clientId: 'test-client-id',
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn()
}
}
}))
vi.mock('@/services/comfyManagerService', () => ({
useComfyManagerService: vi.fn(() => ({
getTaskQueue: vi.fn().mockResolvedValue({
queue_running: [],
queue_pending: []
}),
getTaskHistory: vi.fn().mockResolvedValue({}),
clearTaskHistory: vi.fn().mockResolvedValue(null),
deleteTaskHistoryItems: vi.fn().mockResolvedValue(null)
}))
}))
const mockShowManagerProgressDialog = vi.fn()
vi.mock('@/services/dialogService', () => ({
useDialogService: vi.fn(() => ({
showManagerProgressDialog: mockShowManagerProgressDialog
}))
}))
describe('useManagerQueue', () => {
const createMockTask = (result: any = 'result') => ({
task: vi.fn().mockResolvedValue(result),
onComplete: vi.fn()
let taskHistory: any
let taskQueue: any
let installedPacks: any
// Helper functions
const createMockTask = (
id: string,
clientId = 'test-client-id',
additional = {}
) => ({
id,
client_id: clientId,
...additional
})
const createQueueWithMockTask = () => {
const queue = useManagerQueue()
const mockTask = createMockTask()
queue.enqueueTask(mockTask)
return { queue, mockTask }
}
const createMockHistoryItem = (
clientId = 'test-client-id',
result = 'success',
additional = {}
) => ({
client_id: clientId,
result,
...additional
})
const getEventListenerCallback = () =>
vi.mocked(api.addEventListener).mock.calls[0][1]
const createMockState = (overrides = {}) => ({
running_queue: [],
pending_queue: [],
history: {},
installed_packs: {},
...overrides
})
const simulateServerStatus = async (status: 'all-done' | 'in_progress') => {
const event = new CustomEvent('cm-queue-status', {
detail: { status }
})
getEventListenerCallback()!(event as any)
await nextTick()
const triggerWebSocketEvent = (eventType: string, state: any) => {
const mockEventListener = app.api.addEventListener as any
const eventCall = mockEventListener.mock.calls.find(
(call: any) => call[0] === eventType
)
if (eventCall) {
const handler = eventCall[1]
handler({
type: eventType,
detail: { state }
})
}
}
beforeEach(() => {
vi.clearAllMocks()
mockEventListeners.clear()
taskHistory = ref({})
taskQueue = ref({
history: {},
running_queue: [],
pending_queue: [],
installed_packs: {}
})
installedPacks = ref({})
})
afterEach(() => {
vi.clearAllMocks()
mockEventListeners.clear()
})
describe('initialization', () => {
it('should initialize with empty queue and DONE status', () => {
const queue = useManagerQueue()
const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
expect(queue.queueLength.value).toBe(0)
expect(queue.statusMessage.value).toBe('all-done')
expect(queue.allTasksDone.value).toBe(true)
})
it('should set up event listeners on creation', () => {
useManagerQueue(taskHistory, taskQueue, installedPacks)
expect(app.api.addEventListener).toHaveBeenCalled()
})
})
describe('queue management', () => {
it('should add tasks to the queue', () => {
const queue = useManagerQueue()
const mockTask = createMockTask()
describe('processing state handling', () => {
it('should update processing state based on queue length', async () => {
const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
queue.enqueueTask(mockTask)
expect(queue.queueLength.value).toBe(1)
expect(queue.allTasksDone.value).toBe(false)
})
it('should clear the queue when clearQueue is called', () => {
const queue = useManagerQueue()
// Add some tasks
queue.enqueueTask(createMockTask())
queue.enqueueTask(createMockTask())
expect(queue.queueLength.value).toBe(2)
// Clear the queue
queue.clearQueue()
expect(queue.queueLength.value).toBe(0)
// Initially empty queue
expect(queue.isProcessing.value).toBe(false)
expect(queue.allTasksDone.value).toBe(true)
})
})
describe('server status handling', () => {
it('should update server status when receiving websocket events', async () => {
const queue = useManagerQueue()
// Add tasks to queue
taskQueue.value.running_queue = [createMockTask('task1')]
taskQueue.value.pending_queue = [createMockTask('task2')]
await simulateServerStatus('in_progress')
expect(queue.statusMessage.value).toBe('in_progress')
expect(queue.allTasksDone.value).toBe(false)
})
it('should handle invalid status values gracefully', async () => {
const queue = useManagerQueue()
// Simulate an invalid status
const event = new CustomEvent('cm-queue-status', {
detail: null as any
})
getEventListenerCallback()!(event)
// Force reactivity update
await nextTick()
// Should maintain the default status
expect(queue.statusMessage.value).toBe('all-done')
expect(queue.allTasksDone.value).toBe(false)
})
it('should handle missing status property gracefully', async () => {
const queue = useManagerQueue()
it('should trigger progress dialog when queue length changes', async () => {
useManagerQueue(taskHistory, taskQueue, installedPacks)
// Simulate a detail object without status property
const event = new CustomEvent('cm-queue-status', {
detail: { someOtherProperty: 'value' } as any
})
// Trigger the whenever callback
mockWheneverCallback()
getEventListenerCallback()!(event)
await nextTick()
// Should maintain the default status
expect(queue.statusMessage.value).toBe('all-done')
expect(mockShowManagerProgressDialog).toHaveBeenCalled()
})
})
describe('task execution', () => {
it('should start the next task when server is idle and queue has items', async () => {
const { queue, mockTask } = createQueueWithMockTask()
describe('task state management', () => {
it('should reflect task queue state changes', async () => {
const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
await simulateServerStatus('all-done')
// Add running tasks
taskQueue.value.running_queue = [createMockTask('task1')]
taskQueue.value.pending_queue = [createMockTask('task2')]
// Task should have been started
expect(mockTask.task).toHaveBeenCalled()
expect(queue.queueLength.value).toBe(0)
})
await nextTick()
it('should execute onComplete callback when task completes and server becomes idle', async () => {
const { mockTask } = createQueueWithMockTask()
// Start the task
await simulateServerStatus('all-done')
expect(mockTask.task).toHaveBeenCalled()
// Simulate task completion
await mockTask.task.mock.results[0].value
// Simulate server cycle (in_progress -> done)
await simulateServerStatus('in_progress')
expect(mockTask.onComplete).not.toHaveBeenCalled()
await simulateServerStatus('all-done')
expect(mockTask.onComplete).toHaveBeenCalled()
})
it('should handle tasks without onComplete callback', async () => {
const queue = useManagerQueue()
const mockTask = { task: vi.fn().mockResolvedValue('result') }
queue.enqueueTask(mockTask)
// Start the task
await simulateServerStatus('all-done')
expect(mockTask.task).toHaveBeenCalled()
// Simulate task completion
await mockTask.task.mock.results[0].value
// Simulate server cycle
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
// Should not throw errors even without onComplete
expect(queue.allTasksDone.value).toBe(true)
})
it('should process multiple tasks in sequence', async () => {
const queue = useManagerQueue()
const mockTask1 = createMockTask('result1')
const mockTask2 = createMockTask('result2')
// Add tasks to the queue
queue.enqueueTask(mockTask1)
queue.enqueueTask(mockTask2)
expect(queue.queueLength.value).toBe(2)
// Process first task
await simulateServerStatus('all-done')
expect(mockTask1.task).toHaveBeenCalled()
expect(queue.queueLength.value).toBe(1)
// Complete first task
await mockTask1.task.mock.results[0].value
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
expect(mockTask1.onComplete).toHaveBeenCalled()
// Process second task
expect(mockTask2.task).toHaveBeenCalled()
expect(queue.queueLength.value).toBe(0)
// Complete second task
await mockTask2.task.mock.results[0].value
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
expect(mockTask2.onComplete).toHaveBeenCalled()
// Queue should be empty and all tasks done
expect(queue.queueLength.value).toBe(0)
expect(queue.allTasksDone.value).toBe(true)
})
it('should handle task that returns rejected promise', async () => {
const queue = useManagerQueue()
const mockTask = {
task: vi.fn().mockRejectedValue(new Error('Task failed')),
onComplete: vi.fn()
}
queue.enqueueTask(mockTask)
// Start the task
await simulateServerStatus('all-done')
expect(mockTask.task).toHaveBeenCalled()
// Let the promise rejection happen
try {
await mockTask.task()
} catch (e) {
// Ignore the error
}
// Simulate server cycle
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
// onComplete should still be called for failed tasks
expect(mockTask.onComplete).toHaveBeenCalled()
})
it('should handle multiple multiple tasks enqueued at once while server busy', async () => {
const queue = useManagerQueue()
const mockTask1 = createMockTask()
const mockTask2 = createMockTask()
const mockTask3 = createMockTask()
// Three tasks enqueued at once
await simulateServerStatus('in_progress')
await Promise.all([
queue.enqueueTask(mockTask1),
queue.enqueueTask(mockTask2),
queue.enqueueTask(mockTask3)
])
// Task 1
await simulateServerStatus('all-done')
expect(mockTask1.task).toHaveBeenCalled()
// Verify state of onComplete callbacks
expect(mockTask1.onComplete).toHaveBeenCalled()
expect(mockTask2.onComplete).not.toHaveBeenCalled()
expect(mockTask3.onComplete).not.toHaveBeenCalled()
// Verify state of queue
expect(queue.queueLength.value).toBe(2)
expect(queue.allTasksDone.value).toBe(false)
})
// Task 2
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
expect(mockTask2.task).toHaveBeenCalled()
it('should handle empty queue state', async () => {
const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
// Verify state of onComplete callbacks
expect(mockTask2.onComplete).toHaveBeenCalled()
expect(mockTask3.onComplete).not.toHaveBeenCalled()
taskQueue.value.running_queue = []
taskQueue.value.pending_queue = []
// Verify state of queue
expect(queue.queueLength.value).toBe(1)
await nextTick()
expect(queue.allTasksDone.value).toBe(true)
})
})
describe('WebSocket event handling', () => {
it('should handle task done events', async () => {
useManagerQueue(taskHistory, taskQueue, installedPacks)
const mockState = createMockState({
running_queue: [createMockTask('task1')],
history: {
task1: createMockHistoryItem()
},
installed_packs: { pack1: { version: '1.0' } }
})
triggerWebSocketEvent('cm-task-completed', mockState)
await nextTick()
expect(taskQueue.value.running_queue).toEqual([createMockTask('task1')])
expect(taskQueue.value.pending_queue).toEqual([])
expect(taskHistory.value).toEqual({
task1: createMockHistoryItem()
})
expect(installedPacks.value).toEqual({ pack1: { version: '1.0' } })
})
it('should filter tasks by client ID in WebSocket events', async () => {
const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
const mockState = createMockState({
running_queue: [
createMockTask('task1'),
createMockTask('task2', 'other-client-id')
],
pending_queue: [createMockTask('task3')],
history: {
task1: createMockHistoryItem(),
task2: createMockHistoryItem('other-client-id')
}
})
triggerWebSocketEvent('cm-task-completed', mockState)
await nextTick()
// Should only include tasks from this client
expect(taskQueue.value.running_queue).toEqual([createMockTask('task1')])
expect(taskQueue.value.pending_queue).toEqual([createMockTask('task3')])
expect(taskHistory.value).toEqual({
task1: createMockHistoryItem()
})
expect(queue.allTasksDone.value).toBe(false)
// Task 3
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
// Verify state of onComplete callbacks
expect(mockTask3.task).toHaveBeenCalled()
expect(mockTask3.onComplete).toHaveBeenCalled()
// Verify state of queue
expect(queue.queueLength.value).toBe(0)
expect(queue.allTasksDone.value).toBe(true)
})
})
it('should handle adding tasks while processing is in progress', async () => {
const queue = useManagerQueue()
const mockTask1 = createMockTask()
const mockTask2 = createMockTask()
describe('cleanup functionality', () => {
it('should clean up event listeners on cleanup', () => {
const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
// Add first task and start processing
queue.enqueueTask(mockTask1)
await simulateServerStatus('all-done')
expect(mockTask1.task).toHaveBeenCalled()
queue.cleanup()
// Add second task while first is processing
queue.enqueueTask(mockTask2)
expect(queue.queueLength.value).toBe(1)
// Complete first task
await mockTask1.task.mock.results[0].value
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
// Second task should now be processed
expect(mockTask2.task).toHaveBeenCalled()
})
it('should handle server status changes without tasks in queue', async () => {
const queue = useManagerQueue()
// Cycle server status without any tasks
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
// Should not cause any errors
expect(queue.allTasksDone.value).toBe(true)
// Verify cleanup was called
expect(queue.isProcessing.value).toBe(false)
expect(queue.isLoading.value).toBe(false)
})
})
})

View File

@@ -4,12 +4,10 @@ import { nextTick, ref } from 'vue'
import { useComfyManagerService } from '@/services/comfyManagerService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import {
InstalledPacksResponse,
ManagerChannel,
ManagerDatabaseSource,
ManagerPackInstalled
} from '@/types/comfyManagerTypes'
import { components } from '@/types/generatedManagerTypes'
type InstalledPacksResponse = components['schemas']['InstalledPacksResponse']
type ManagerPackInstalled = components['schemas']['ManagerPackInstalled']
vi.mock('@/services/comfyManagerService', () => ({
useComfyManagerService: vi.fn()
@@ -82,7 +80,7 @@ describe('useComfyManagerStore', () => {
isLoading: ref(false),
error: ref(null),
startQueue: vi.fn().mockResolvedValue(null),
resetQueue: vi.fn().mockResolvedValue(null),
getTaskHistory: vi.fn().mockResolvedValue({}),
getQueueStatus: vi.fn().mockResolvedValue(null),
listInstalledPacks: vi.fn().mockResolvedValue({}),
getImportFailInfo: vi.fn().mockResolvedValue(null),
@@ -366,8 +364,8 @@ describe('useComfyManagerStore', () => {
await store.installPack.call({
id: 'test-pack',
repository: 'https://github.com/test/test-pack',
channel: ManagerChannel.DEV,
mode: ManagerDatabaseSource.CACHE,
channel: 'dev' as const,
mode: 'cache' as const,
selected_version: 'latest',
version: 'latest'
})
@@ -383,8 +381,8 @@ describe('useComfyManagerStore', () => {
await store.installPack.call({
id: 'test-pack',
repository: 'https://github.com/test/test-pack',
channel: ManagerChannel.DEV,
mode: ManagerDatabaseSource.CACHE,
channel: 'dev' as const,
mode: 'cache' as const,
selected_version: 'latest',
version: 'latest'
})
@@ -396,8 +394,8 @@ describe('useComfyManagerStore', () => {
await store.installPack.call({
id: 'another-pack',
repository: 'https://github.com/test/another-pack',
channel: ManagerChannel.DEV,
mode: ManagerDatabaseSource.CACHE,
channel: 'dev' as const,
mode: 'cache' as const,
selected_version: 'latest',
version: 'latest'
})
@@ -414,8 +412,8 @@ describe('useComfyManagerStore', () => {
await store.installPack.call({
id: 'pack-1',
repository: 'https://github.com/test/pack-1',
channel: ManagerChannel.DEV,
mode: ManagerDatabaseSource.CACHE,
channel: 'dev' as const,
mode: 'cache' as const,
selected_version: 'latest',
version: 'latest'
})
@@ -424,8 +422,8 @@ describe('useComfyManagerStore', () => {
await store.installPack.call({
id: 'pack-2',
repository: 'https://github.com/test/pack-2',
channel: ManagerChannel.DEV,
mode: ManagerDatabaseSource.CACHE,
channel: 'dev' as const,
mode: 'cache' as const,
selected_version: 'latest',
version: 'latest'
})

View File

@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { api } from '@/scripts/api'
import { useComfyManagerService } from '@/services/comfyManagerService'
import { useExtensionStore } from '@/stores/extensionStore'
import {
ManagerUIState,
useManagerStateStore
@@ -24,8 +24,8 @@ vi.mock('@/composables/useFeatureFlags', () => ({
}))
}))
vi.mock('@/services/comfyManagerService', () => ({
useComfyManagerService: vi.fn()
vi.mock('@/stores/extensionStore', () => ({
useExtensionStore: vi.fn()
}))
vi.mock('@/stores/systemStatsStore', () => ({
@@ -38,36 +38,40 @@ describe('useManagerStateStore', () => {
vi.clearAllMocks()
})
describe('initializeManagerState', () => {
it('should set DISABLED state when --disable-manager is present', async () => {
describe('managerUIState computed', () => {
it('should return DISABLED state when --disable-manager is present', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: {
system: { argv: ['python', 'main.py', '--disable-manager'] }
}
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const store = useManagerStateStore()
await store.initializeManagerState()
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
})
it('should set LEGACY_UI state when --enable-manager-legacy-ui is present', async () => {
it('should return LEGACY_UI state when --enable-manager-legacy-ui is present', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: {
system: { argv: ['python', 'main.py', '--enable-manager-legacy-ui'] }
}
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const store = useManagerStateStore()
await store.initializeManagerState()
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
})
it('should set NEW_UI state when client and server both support v4', async () => {
it('should return NEW_UI state when client and server both support v4', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: { system: { argv: ['python', 'main.py'] } }
} as any)
@@ -78,17 +82,16 @@ describe('useManagerStateStore', () => {
flags: { supportsManagerV4: true },
featureFlag: vi.fn()
} as any)
vi.mocked(useComfyManagerService).mockReturnValue({
isLegacyManagerUI: vi.fn().mockResolvedValue({})
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const store = useManagerStateStore()
await store.initializeManagerState()
expect(store.managerUIState).toBe(ManagerUIState.NEW_UI)
})
it('should set LEGACY_UI state when client does not support v4', async () => {
it('should return LEGACY_UI state when server supports v4 but client does not', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: { system: { argv: ['python', 'main.py'] } }
} as any)
@@ -99,38 +102,16 @@ describe('useManagerStateStore', () => {
flags: { supportsManagerV4: true },
featureFlag: vi.fn()
} as any)
vi.mocked(useComfyManagerService).mockReturnValue({
isLegacyManagerUI: vi.fn().mockResolvedValue({})
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const store = useManagerStateStore()
await store.initializeManagerState()
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
})
it('should set LEGACY_UI state when server does not support v4', async () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: { system: { argv: ['python', 'main.py'] } }
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
supports_manager_v4_ui: true
})
vi.mocked(useFeatureFlags).mockReturnValue({
flags: { supportsManagerV4: false },
featureFlag: vi.fn()
} as any)
vi.mocked(useComfyManagerService).mockReturnValue({
isLegacyManagerUI: vi.fn().mockResolvedValue({})
} as any)
const store = useManagerStateStore()
await store.initializeManagerState()
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
})
it('should set LEGACY_UI state when isLegacyManagerUI route does not exist', async () => {
it('should return LEGACY_UI state when legacy manager extension exists', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: { system: { argv: ['python', 'main.py'] } }
} as any)
@@ -139,40 +120,52 @@ describe('useManagerStateStore', () => {
flags: { supportsManagerV4: false },
featureFlag: vi.fn()
} as any)
vi.mocked(useComfyManagerService).mockReturnValue({
isLegacyManagerUI: vi.fn().mockRejectedValue(new Error('404'))
vi.mocked(useExtensionStore).mockReturnValue({
extensions: [{ name: 'Comfy.CustomNodesManager' }]
} as any)
const store = useManagerStateStore()
await store.initializeManagerState()
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
})
it('should not re-initialize if already initialized', async () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: {
system: { argv: ['python', 'main.py', '--disable-manager'] }
}
} as any)
const store = useManagerStateStore()
await store.initializeManagerState()
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
// Change the mock to return different value
it('should return DISABLED state when feature flags are undefined', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: { system: { argv: ['python', 'main.py'] } }
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(useFeatureFlags).mockReturnValue({
flags: { supportsManagerV4: undefined },
featureFlag: vi.fn()
} as any)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
// Try to initialize again
await store.initializeManagerState()
const store = useManagerStateStore()
// Should still be DISABLED from first initialization
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
})
it('should handle null systemStats gracefully', async () => {
it('should return DISABLED state when no manager is available', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: { system: { argv: ['python', 'main.py'] } }
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(useFeatureFlags).mockReturnValue({
flags: { supportsManagerV4: false },
featureFlag: vi.fn()
} as any)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const store = useManagerStateStore()
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
})
it('should handle null systemStats gracefully', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: null
} as any)
@@ -183,12 +176,11 @@ describe('useManagerStateStore', () => {
flags: { supportsManagerV4: true },
featureFlag: vi.fn()
} as any)
vi.mocked(useComfyManagerService).mockReturnValue({
isLegacyManagerUI: vi.fn().mockResolvedValue({})
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const store = useManagerStateStore()
await store.initializeManagerState()
expect(store.managerUIState).toBe(ManagerUIState.NEW_UI)
})