diff --git a/MANAGER_MIGRATION_BACKUPS.md b/MANAGER_MIGRATION_BACKUPS.md
index 538b34154..7ac32acc7 100644
--- a/MANAGER_MIGRATION_BACKUPS.md
+++ b/MANAGER_MIGRATION_BACKUPS.md
@@ -18,6 +18,18 @@ This document tracks backup branches created during the manager migration recove
- Updated type definitions and store interfaces
- Resolved merge conflicts and formatting fixes
+### `manager-migration-clean-tested`
+- **Created**: 2025-08-30
+- **Source Branch**: `manager-migration-clean`
+- **Source Commit**: `380f335bf` - "feat: Integrate ComfyUI Manager migration with v2 API and enhanced UI"
+- **Purpose**: Backup before manual testing via dev server
+- **Contains**:
+ - Single squashed commit with complete manager migration
+ - All recovered functionality from PR #3367
+ - v2 API integration and enhanced UI components
+ - Resolved TypeScript issues and quality checks passed
+ - Clean, production-ready state ready for manual testing
+
### `manager-migration-upstream-backup`
- **Created**: Earlier in recovery process
- **Purpose**: Backup of upstream state before major changes
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7c92c2381..b3ce933a9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -198,6 +198,9 @@ importers:
'@types/fs-extra':
specifier: ^11.0.4
version: 11.0.4
+ '@types/lodash':
+ specifier: ^4.17.20
+ version: 4.17.20
'@types/node':
specifier: ^20.14.8
version: 20.14.10
@@ -232,8 +235,8 @@ importers:
specifier: ^5.2.6
version: 5.2.6(eslint-config-prettier@10.1.2(eslint@9.12.0(jiti@2.5.1)))(eslint@9.12.0(jiti@2.5.1))(prettier@3.3.2)
eslint-plugin-storybook:
- specifier: ^9.1.1
- version: 9.1.1(eslint@9.12.0(jiti@2.5.1))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2)))(typescript@5.9.2)
+ specifier: ^9.1.3
+ version: 9.1.3(eslint@9.12.0(jiti@2.5.1))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2)))(typescript@5.9.2)
eslint-plugin-unused-imports:
specifier: ^4.1.4
version: 4.1.4(@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.12.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.12.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.12.0(jiti@2.5.1))
@@ -1953,6 +1956,9 @@ packages:
'@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
+ '@types/lodash@4.17.20':
+ resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
+
'@types/markdown-it@13.0.9':
resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==}
@@ -3085,12 +3091,12 @@ packages:
eslint-config-prettier:
optional: true
- eslint-plugin-storybook@9.1.1:
- resolution: {integrity: sha512-g4/i9yW6cl4TCEMzYyALNvO3d/jB6TDvSs/Pmye7dHDrra2B7dgZJGzmEWILD62brVrLVHNoXgy2dNPtx80kmw==}
+ eslint-plugin-storybook@9.1.3:
+ resolution: {integrity: sha512-CR576JrlvxLY2ebJIyR6z/YWy6+iyVsB7ORjPrwM3a9SshlRnAntdEn6hyMYbQmFoPIv7kYcRiDznDXBQ/jitA==}
engines: {node: '>=20.0.0'}
peerDependencies:
eslint: '>=8'
- storybook: ^9.1.1
+ storybook: ^9.1.3
eslint-plugin-unused-imports@4.1.4:
resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==}
@@ -7670,8 +7676,8 @@ snapshots:
dependencies:
'@mdx-js/react': 3.1.0(@types/react@19.1.9)(react@19.1.1)
'@storybook/csf-plugin': 9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2)))
- '@storybook/icons': 1.4.0(react-dom@19.1.1(react@18.3.1))(react@19.1.1)
- '@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@18.3.1))(react@19.1.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2)))
+ '@storybook/icons': 1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2)))
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2))
@@ -7693,12 +7699,12 @@ snapshots:
'@storybook/global@5.0.0': {}
- '@storybook/icons@1.4.0(react-dom@19.1.1(react@18.3.1))(react@19.1.1)':
+ '@storybook/icons@1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- '@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@18.3.1))(react@19.1.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2)))':
+ '@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2)))':
dependencies:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
@@ -7964,6 +7970,8 @@ snapshots:
'@types/linkify-it@5.0.0': {}
+ '@types/lodash@4.17.20': {}
+
'@types/markdown-it@13.0.9':
dependencies:
'@types/linkify-it': 3.0.5
@@ -9212,7 +9220,7 @@ snapshots:
optionalDependencies:
eslint-config-prettier: 10.1.2(eslint@9.12.0(jiti@2.5.1))
- eslint-plugin-storybook@9.1.1(eslint@9.12.0(jiti@2.5.1))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2)))(typescript@5.9.2):
+ eslint-plugin-storybook@9.1.3(eslint@9.12.0(jiti@2.5.1))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(terser@5.39.2)))(typescript@5.9.2):
dependencies:
'@typescript-eslint/utils': 8.39.0(eslint@9.12.0(jiti@2.5.1))(typescript@5.9.2)
eslint: 9.12.0(jiti@2.5.1)
diff --git a/src/components/dialog/content/ManagerProgressDialogContent.vue b/src/components/dialog/content/ManagerProgressDialogContent.vue
index b1b4272d8..b5256025d 100644
--- a/src/components/dialog/content/ManagerProgressDialogContent.vue
+++ b/src/components/dialog/content/ManagerProgressDialogContent.vue
@@ -18,7 +18,7 @@
'max-h-0': !isExpanded
}"
>
-
+
-
{{ log.taskName }}
+
{{ panel.taskName }}
{{
isInProgress(index)
@@ -52,24 +52,24 @@
-
{{ logLine }}
+
{{ log }}
@@ -90,23 +90,17 @@ import {
useManagerProgressDialogStore
} from '@/stores/comfyManagerStore'
-const comfyManagerStore = useComfyManagerStore()
+const { taskLogs } = useComfyManagerStore()
const progressDialogContent = useManagerProgressDialogStore()
+const managerStore = useComfyManagerStore()
const isInProgress = (index: number) =>
- index === comfyManagerStore.managerQueue.historyCount - 1 &&
- comfyManagerStore.isLoading
+ index === taskPanels.value.length - 1 && managerStore.uncompletedCount > 0
+const taskPanels = computed(() => taskLogs)
const isExpanded = computed(() => progressDialogContent.isExpanded)
const isCollapsed = computed(() => !isExpanded.value)
-const focusedLogs = computed(() => {
- if (progressDialogContent.getActiveTabIndex() === 0) {
- return comfyManagerStore.succeededTasksLogs
- }
- return comfyManagerStore.failedTasksLogs
-})
-
const collapsedPanels = ref>({})
const togglePanel = (index: number) => {
collapsedPanels.value[index] = !collapsedPanels.value[index]
@@ -121,7 +115,7 @@ const { y: scrollY } = useScroll(sectionsContainerRef, {
const lastPanelRef = ref(null)
const isUserScrolling = ref(false)
-const lastPanelLogs = computed(() => focusedLogs.value?.at(-1)?.logs)
+const lastPanelLogs = computed(() => taskPanels.value?.at(-1)?.logs)
const isAtBottom = (el: HTMLElement | null) => {
if (!el) return false
diff --git a/src/components/dialog/header/ManagerProgressHeader.vue b/src/components/dialog/header/ManagerProgressHeader.vue
index f07eff7f5..80f349308 100644
--- a/src/components/dialog/header/ManagerProgressHeader.vue
+++ b/src/components/dialog/header/ManagerProgressHeader.vue
@@ -18,40 +18,16 @@
diff --git a/src/composables/useServerLogs.ts b/src/composables/useServerLogs.ts
index deb0204a7..a802c7b8f 100644
--- a/src/composables/useServerLogs.ts
+++ b/src/composables/useServerLogs.ts
@@ -1,34 +1,24 @@
import { useEventListener } from '@vueuse/core'
-import { ref } from 'vue'
+import { onUnmounted, ref } from 'vue'
import { LogsWsMessage } from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
-import { components } from '@/types/generatedManagerTypes'
const LOGS_MESSAGE_TYPE = 'logs'
-const MANAGER_WS_TASK_DONE_NAME = 'cm-task-completed'
-const MANAGER_WS_TASK_STARTED_NAME = 'cm-task-started'
-
-type ManagerWsTaskDoneMsg = components['schemas']['MessageTaskDone']
-type ManagerWsTaskStartedMsg = components['schemas']['MessageTaskStarted']
interface UseServerLogsOptions {
- ui_id: string
immediate?: boolean
messageFilter?: (message: string) => boolean
}
-export const useServerLogs = (options: UseServerLogsOptions) => {
+export const useServerLogs = (options: UseServerLogsOptions = {}) => {
const {
immediate = false,
messageFilter = (msg: string) => Boolean(msg.trim())
} = options
const logs = ref([])
- const isTaskStarted = ref(false)
- let stopLogs: ReturnType | null = null
- let stopTaskDone: ReturnType | null = null
- let stopTaskStarted: ReturnType | null = null
+ let stop: ReturnType | null = null
const isValidLogEvent = (event: CustomEvent) =>
event?.type === LOGS_MESSAGE_TYPE && event.detail?.entries?.length > 0
@@ -37,81 +27,34 @@ export const useServerLogs = (options: UseServerLogsOptions) => {
event.detail.entries.map((e) => e.m).filter(messageFilter)
const handleLogMessage = (event: CustomEvent) => {
- // Only capture logs if this task has started
- if (!isTaskStarted.value) return
-
if (isValidLogEvent(event)) {
- const messages = parseLogMessage(event)
- if (messages.length > 0) {
- logs.value.push(...messages)
- }
+ logs.value.push(...parseLogMessage(event))
}
}
- const handleTaskStarted = (event: CustomEvent) => {
- if (event?.type === MANAGER_WS_TASK_STARTED_NAME) {
- // Check if this is our task starting
- const isOurTask = event.detail.ui_id === options.ui_id
- if (isOurTask) {
- isTaskStarted.value = true
- void stopTaskStarted?.()
- }
- }
- }
-
- const handleTaskDone = (event: CustomEvent) => {
- if (event?.type === MANAGER_WS_TASK_DONE_NAME) {
- const { state } = event.detail
- // Check if our task is now in the history (completed)
- const isOurTaskDone = state.history[options.ui_id]
- if (isOurTaskDone) {
- isTaskStarted.value = false
- void stopListening()
- }
- }
- }
-
- const startListening = async () => {
+ const start = async () => {
await api.subscribeLogs(true)
- stopLogs = useEventListener(api, LOGS_MESSAGE_TYPE, handleLogMessage)
- stopTaskStarted = useEventListener(
- api,
- MANAGER_WS_TASK_STARTED_NAME,
- handleTaskStarted
- )
- stopTaskDone = useEventListener(
- api,
- MANAGER_WS_TASK_DONE_NAME,
- handleTaskDone
- )
+ stop = useEventListener(api, LOGS_MESSAGE_TYPE, handleLogMessage)
}
const stopListening = async () => {
- stopLogs?.()
- stopTaskStarted?.()
- stopTaskDone?.()
- stopLogs = null
- stopTaskStarted = null
- stopTaskDone = null
- // TODO: move subscribe/unsubscribe logs to useManagerQueue. Subscribe when task starts if not already subscribed.
- // Unsubscribe ONLY when there are no tasks running or queued up and the only remaining task finishes.
- // await api.subscribeLogs(false)
+ stop?.()
+ stop = null
+ await api.subscribeLogs(false)
}
if (immediate) {
- void startListening()
+ void start()
}
- const cleanup = async () => {
+ onUnmounted(async () => {
await stopListening()
logs.value = []
- isTaskStarted.value = false
- }
+ })
return {
logs,
- startListening,
- stopListening,
- cleanup
+ startListening: start,
+ stopListening
}
}
diff --git a/src/constants/coreSettings.ts b/src/constants/coreSettings.ts
index 787efc7e4..280ec0df9 100644
--- a/src/constants/coreSettings.ts
+++ b/src/constants/coreSettings.ts
@@ -935,12 +935,5 @@ export const CORE_SETTINGS: SettingParams[] = [
name: 'Release seen timestamp',
type: 'hidden',
defaultValue: 0
- },
- {
- id: 'Comfy.Memory.AllowManualUnload',
- name: 'Allow manual unload of models and execution cache via user command',
- type: 'hidden',
- defaultValue: true,
- versionAdded: '1.18.0'
}
]
diff --git a/src/services/comfyManagerService.ts b/src/services/comfyManagerService.ts
index cff405c57..330d5dd0f 100644
--- a/src/services/comfyManagerService.ts
+++ b/src/services/comfyManagerService.ts
@@ -1,18 +1,17 @@
import axios, { AxiosError, AxiosResponse } from 'axios'
-import { v4 as uuidv4 } from 'uuid'
import { ref } from 'vue'
import { api } from '@/scripts/api'
-import { components } from '@/types/generatedManagerTypes'
+import {
+ type InstallPackParams,
+ type InstalledPacksResponse,
+ type ManagerPackInfo,
+ type ManagerQueueStatus,
+ SelectedVersion,
+ type UpdateAllPacksParams
+} from '@/types/comfyManagerTypes'
import { isAbortError } from '@/utils/typeGuardUtil'
-type ManagerQueueStatus = components['schemas']['QueueStatus']
-type InstallPackParams = components['schemas']['InstallPackParams']
-type InstalledPacksResponse = components['schemas']['InstalledPacksResponse']
-type UpdateAllPacksParams = components['schemas']['UpdateAllPacksParams']
-type ManagerTaskHistory = components['schemas']['HistoryResponse']
-type QueueTaskItem = components['schemas']['QueueTaskItem']
-
const GENERIC_SECURITY_ERR_MSG =
'Forbidden: A security error has occurred. Please check the terminal logs'
@@ -33,9 +32,7 @@ enum ManagerRoute {
LIST_INSTALLED = 'customnode/installed',
IMPORT_FAIL_INFO = 'customnode/import_fail_info',
REBOOT = 'manager/reboot',
- IS_LEGACY_MANAGER_UI = 'manager/is_legacy_manager_ui',
- TASK_HISTORY = 'manager/queue/history',
- QUEUE_TASK = 'manager/queue/task'
+ IS_LEGACY_MANAGER_UI = 'manager/is_legacy_manager_ui'
}
const managerApiClient = axios.create({
@@ -52,6 +49,7 @@ const managerApiClient = axios.create({
export const useComfyManagerService = () => {
const isLoading = ref(false)
const error = ref(null)
+ const didStartQueue = ref(false)
const handleRequestError = (
err: unknown,
@@ -112,21 +110,19 @@ export const useComfyManagerService = () => {
201: 'Created: ComfyUI-Manager job queue is already running'
}
+ didStartQueue.value = true
+
return executeRequest(
() => managerApiClient.get(ManagerRoute.START_QUEUE, { signal }),
{ errorContext, routeSpecificErrors }
)
}
- const getQueueStatus = async (client_id?: string, signal?: AbortSignal) => {
+ const getQueueStatus = async (signal?: AbortSignal) => {
const errorContext = 'Getting ComfyUI-Manager queue status'
return executeRequest(
- () =>
- managerApiClient.get(ManagerRoute.QUEUE_STATUS, {
- params: client_id ? { client_id } : undefined,
- signal
- }),
+ () => managerApiClient.get(ManagerRoute.QUEUE_STATUS, { signal }),
{ errorContext }
)
}
@@ -158,66 +154,73 @@ export const useComfyManagerService = () => {
)
}
- const queueTask = async (
- kind: QueueTaskItem['kind'],
- params: QueueTaskItem['params'],
- ui_id?: string,
+ const installPack = async (
+ params: InstallPackParams,
signal?: AbortSignal
) => {
- const task: QueueTaskItem = {
- kind,
- params,
- ui_id: ui_id || uuidv4(),
- client_id: api.clientId ?? api.initialClientId ?? 'unknown'
- }
-
- const errorContext = `Queueing ${task.kind} task`
+ const errorContext = `Installing pack ${params.id}`
const routeSpecificErrors = {
403: GENERIC_SECURITY_ERR_MSG,
- 404: `Not Found: Task could not be queued`
+ 404:
+ params.selected_version === SelectedVersion.NIGHTLY
+ ? `Not Found: Node pack ${params.id} does not provide nightly version`
+ : GENERIC_SECURITY_ERR_MSG
}
return executeRequest(
- () => managerApiClient.post(ManagerRoute.QUEUE_TASK, task, { signal }),
+ () => managerApiClient.post(ManagerRoute.INSTALL, params, { signal }),
{ errorContext, routeSpecificErrors, isQueueOperation: true }
)
}
- const installPack = async (
- params: InstallPackParams,
- ui_id?: string,
- signal?: AbortSignal
- ) => {
- return queueTask('install', params, ui_id, signal)
- }
-
const uninstallPack = async (
- params: components['schemas']['UninstallPackParams'],
- ui_id?: string,
+ params: ManagerPackInfo,
signal?: AbortSignal
) => {
- return queueTask('uninstall', params, ui_id, signal)
+ const errorContext = `Uninstalling pack ${params.id}`
+ const routeSpecificErrors = {
+ 403: GENERIC_SECURITY_ERR_MSG
+ }
+
+ return executeRequest(
+ () => managerApiClient.post(ManagerRoute.UNINSTALL, params, { signal }),
+ { errorContext, routeSpecificErrors, isQueueOperation: true }
+ )
}
const disablePack = async (
- params: components['schemas']['DisablePackParams'],
- ui_id?: string,
+ params: ManagerPackInfo,
signal?: AbortSignal
): Promise => {
- return queueTask('disable', params, ui_id, signal)
+ const errorContext = `Disabling pack ${params.id}`
+ const routeSpecificErrors = {
+ 404: `Pack ${params.id} not found or not installed`,
+ 409: `Pack ${params.id} is already disabled`
+ }
+
+ return executeRequest(
+ () => managerApiClient.post(ManagerRoute.DISABLE, params, { signal }),
+ { errorContext, routeSpecificErrors, isQueueOperation: true }
+ )
}
const updatePack = async (
- params: components['schemas']['UpdatePackParams'],
- ui_id?: string,
+ params: ManagerPackInfo,
signal?: AbortSignal
): Promise => {
- return queueTask('update', params, ui_id, signal)
+ const errorContext = `Updating pack ${params.id}`
+ const routeSpecificErrors = {
+ 403: GENERIC_SECURITY_ERR_MSG
+ }
+
+ return executeRequest(
+ () => managerApiClient.post(ManagerRoute.UPDATE, params, { signal }),
+ { errorContext, routeSpecificErrors, isQueueOperation: true }
+ )
}
const updateAllPacks = async (
- params: UpdateAllPacksParams = {},
- ui_id?: string,
+ params?: UpdateAllPacksParams,
signal?: AbortSignal
) => {
const errorContext = 'Updating all packs'
@@ -226,18 +229,8 @@ export const useComfyManagerService = () => {
401: 'Unauthorized: ComfyUI-Manager job queue is busy'
}
- const queryParams = {
- mode: params.mode,
- client_id: api.clientId ?? api.initialClientId ?? 'unknown',
- ui_id: ui_id || uuidv4()
- }
-
return executeRequest(
- () =>
- managerApiClient.get(ManagerRoute.UPDATE_ALL, {
- params: queryParams,
- signal
- }),
+ () => managerApiClient.get(ManagerRoute.UPDATE_ALL, { params, signal }),
{ errorContext, routeSpecificErrors, isQueueOperation: true }
)
}
@@ -263,27 +256,6 @@ export const useComfyManagerService = () => {
)
}
- const getTaskHistory = async (
- options: {
- ui_id?: string
- max_items?: number
- client_id?: string
- offset?: number
- } = {},
- signal?: AbortSignal
- ) => {
- const errorContext = 'Getting ComfyUI-Manager task history'
-
- return executeRequest(
- () =>
- managerApiClient.get(ManagerRoute.TASK_HISTORY, {
- params: options,
- signal
- }),
- { errorContext }
- )
- }
-
return {
// State
isLoading,
@@ -293,7 +265,6 @@ export const useComfyManagerService = () => {
startQueue,
resetQueue,
getQueueStatus,
- getTaskHistory,
// Pack management
listInstalledPacks,
diff --git a/src/stores/comfyManagerStore.ts b/src/stores/comfyManagerStore.ts
index b6896438b..b9840f421 100644
--- a/src/stores/comfyManagerStore.ts
+++ b/src/stores/comfyManagerStore.ts
@@ -1,6 +1,6 @@
import { whenever } from '@vueuse/core'
import { defineStore } from 'pinia'
-import { computed, ref, watch } from 'vue'
+import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useCachedRequest } from '@/composables/useCachedRequest'
@@ -33,9 +33,8 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
const isStale = ref(true)
const taskLogs = ref([])
- const managerQueue = useManagerQueue()
const { statusMessage, allTasksDone, enqueueTask, uncompletedCount } =
- managerQueue
+ useManagerQueue()
const setStale = () => {
isStale.value = true
@@ -213,11 +212,6 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
taskLogs.value = []
}
- // Computed properties for UI components
- const succeededTasksLogs = computed(() => taskLogs.value)
- const failedTasksLogs = computed(() => [])
- const failedTasksIds = computed(() => [])
-
return {
// Manager state
isLoading: managerService.isLoading,
@@ -244,15 +238,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
updatePack,
updateAllPacks,
disablePack,
- enablePack: installPack, // Enable is done via install endpoint with a disabled pack
-
- // Manager queue
- managerQueue,
-
- // UI properties for progress dialog
- succeededTasksLogs,
- failedTasksLogs,
- failedTasksIds
+ enablePack: installPack // Enable is done via install endpoint with a disabled pack
}
})
@@ -265,7 +251,6 @@ export const useManagerProgressDialogStore = defineStore(
'managerProgressDialog',
() => {
const isExpanded = ref(false)
- const activeTabIndex = ref(0)
const toggle = () => {
isExpanded.value = !isExpanded.value
@@ -278,19 +263,11 @@ export const useManagerProgressDialogStore = defineStore(
const expand = () => {
isExpanded.value = true
}
-
- const getActiveTabIndex = () => activeTabIndex.value
- const setActiveTabIndex = (index: number) => {
- activeTabIndex.value = index
- }
-
return {
isExpanded,
toggle,
collapse,
- expand,
- getActiveTabIndex,
- setActiveTabIndex
+ expand
}
}
)
diff --git a/tests-ui/tests/composables/useServerLogs.test.ts b/tests-ui/tests/composables/useServerLogs.test.ts
index 97558e9cd..3af4f0faa 100644
--- a/tests-ui/tests/composables/useServerLogs.test.ts
+++ b/tests-ui/tests/composables/useServerLogs.test.ts
@@ -24,22 +24,22 @@ describe('useServerLogs', () => {
})
it('should initialize with empty logs array', () => {
- const { logs } = useServerLogs({ ui_id: 'test-ui-id' })
+ const { logs } = useServerLogs()
expect(logs.value).toEqual([])
})
it('should not subscribe to logs by default', () => {
- useServerLogs({ ui_id: 'test-ui-id' })
+ useServerLogs()
expect(api.subscribeLogs).not.toHaveBeenCalled()
})
it('should subscribe to logs when immediate is true', () => {
- useServerLogs({ ui_id: 'test-ui-id', immediate: true })
+ useServerLogs({ immediate: true })
expect(api.subscribeLogs).toHaveBeenCalledWith(true)
})
it('should start listening when startListening is called', async () => {
- const { startListening } = useServerLogs({ ui_id: 'test-ui-id' })
+ const { startListening } = useServerLogs()
await startListening()
@@ -47,21 +47,16 @@ describe('useServerLogs', () => {
})
it('should stop listening when stopListening is called', async () => {
- const { startListening, stopListening } = useServerLogs({
- ui_id: 'test-ui-id'
- })
+ const { startListening, stopListening } = useServerLogs()
await startListening()
await stopListening()
- // TODO: Update this test when subscribeLogs(false) is re-enabled
- // Currently commented out in useServerLogs to prevent logs from stopping
- // after 1st of multiple queue tasks
- expect(api.subscribeLogs).toHaveBeenCalledWith(true)
+ expect(api.subscribeLogs).toHaveBeenCalledWith(false)
})
it('should register event listener when starting', async () => {
- const { startListening } = useServerLogs({ ui_id: 'test-ui-id' })
+ const { startListening } = useServerLogs()
await startListening()
@@ -73,30 +68,16 @@ describe('useServerLogs', () => {
})
it('should handle log messages correctly', async () => {
- const { logs, startListening } = useServerLogs({ ui_id: 'test-ui-id' })
+ const { logs, startListening } = useServerLogs()
await startListening()
- // Get the callbacks that were registered with useEventListener
- const mockCalls = vi.mocked(useEventListener).mock.calls
- const logsCallback = mockCalls.find((call) => call[1] === 'logs')?.[2] as (
+ // Get the callback that was registered with useEventListener
+ const eventCallback = vi.mocked(useEventListener).mock.calls[0][2] as (
event: CustomEvent
) => void
- const taskStartedCallback = mockCalls.find(
- (call) => call[1] === 'cm-task-started'
- )?.[2] as (event: CustomEvent) => void
- // First, simulate task started event
- const taskStartedEvent = new CustomEvent('cm-task-started', {
- detail: {
- type: 'cm-task-started',
- ui_id: 'test-ui-id'
- }
- })
- taskStartedCallback(taskStartedEvent)
- await nextTick()
-
- // Now simulate receiving a log event
+ // Simulate receiving a log event
const mockEvent = new CustomEvent('logs', {
detail: {
type: 'logs',
@@ -104,7 +85,7 @@ describe('useServerLogs', () => {
} as unknown as LogsWsMessage
}) as CustomEvent
- logsCallback(mockEvent)
+ eventCallback(mockEvent)
await nextTick()
expect(logs.value).toEqual(['Log message 1', 'Log message 2'])
@@ -112,32 +93,15 @@ describe('useServerLogs', () => {
it('should use the message filter if provided', async () => {
const { logs, startListening } = useServerLogs({
- ui_id: 'test-ui-id',
messageFilter: (msg) => msg !== 'remove me'
})
await startListening()
- // Get the callbacks that were registered with useEventListener
- const mockCalls = vi.mocked(useEventListener).mock.calls
- const logsCallback = mockCalls.find((call) => call[1] === 'logs')?.[2] as (
+ const eventCallback = vi.mocked(useEventListener).mock.calls[0][2] as (
event: CustomEvent
) => void
- const taskStartedCallback = mockCalls.find(
- (call) => call[1] === 'cm-task-started'
- )?.[2] as (event: CustomEvent) => void
- // First, simulate task started event
- const taskStartedEvent = new CustomEvent('cm-task-started', {
- detail: {
- type: 'cm-task-started',
- ui_id: 'test-ui-id'
- }
- })
- taskStartedCallback(taskStartedEvent)
- await nextTick()
-
- // Now simulate receiving a log event
const mockEvent = new CustomEvent('logs', {
detail: {
type: 'logs',
@@ -149,7 +113,7 @@ describe('useServerLogs', () => {
} as unknown as LogsWsMessage
}) as CustomEvent
- logsCallback(mockEvent)
+ eventCallback(mockEvent)
await nextTick()
expect(logs.value).toEqual(['Log message 1 dont remove me', ''])
diff --git a/tests-ui/tests/composables/widgets/useManagerQueue.test.ts b/tests-ui/tests/composables/widgets/useManagerQueue.test.ts
index aa58d9250..7fce2b5e1 100644
--- a/tests-ui/tests/composables/widgets/useManagerQueue.test.ts
+++ b/tests-ui/tests/composables/widgets/useManagerQueue.test.ts
@@ -1,130 +1,32 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { nextTick, ref } from 'vue'
+import { nextTick } from 'vue'
import { useManagerQueue } from '@/composables/useManagerQueue'
-import { app } from '@/scripts/app'
+import { api } from '@/scripts/api'
-// 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/api', () => ({
+ api: {
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn()
}
-})
-
-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', () => {
- 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 createMockTask = (result: any = 'result') => ({
+ task: vi.fn().mockResolvedValue(result),
+ onComplete: vi.fn()
})
- const createMockHistoryItem = (
- clientId = 'test-client-id',
- result = 'success',
- additional = {}
- ) => ({
- client_id: clientId,
- result,
- ...additional
- })
-
- const createMockState = (overrides = {}) => ({
- running_queue: [],
- pending_queue: [],
- history: {},
- installed_packs: {},
- ...overrides
- })
-
- 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 }
- })
- }
- }
-
- const getEventHandler = (eventType: string) => {
- const mockEventListener = app.api.addEventListener as any
- const eventCall = mockEventListener.mock.calls.find(
- (call: any) => call[0] === eventType
- )
- return eventCall ? eventCall[1] : null
+ const createQueueWithMockTask = () => {
+ const queue = useManagerQueue()
+ const mockTask = createMockTask()
+ queue.enqueueTask(mockTask)
+ return { queue, mockTask }
}
const getEventListenerCallback = () =>
- vi.mocked(app.api.addEventListener).mock.calls[0][1]
+ vi.mocked(api.addEventListener).mock.calls[0][1]
const simulateServerStatus = async (status: 'all-done' | 'in_progress') => {
const event = new CustomEvent('cm-queue-status', {
@@ -134,27 +36,12 @@ describe('useManagerQueue', () => {
await nextTick()
}
- const createMockQueueTask = (result = 'success') => ({
- task: vi.fn().mockResolvedValue(result),
- onComplete: vi.fn()
- })
-
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', () => {
@@ -170,7 +57,7 @@ describe('useManagerQueue', () => {
describe('queue management', () => {
it('should add tasks to the queue', () => {
const queue = useManagerQueue()
- const mockTask = createMockQueueTask()
+ const mockTask = createMockTask()
queue.enqueueTask(mockTask)
@@ -182,8 +69,8 @@ describe('useManagerQueue', () => {
const queue = useManagerQueue()
// Add some tasks
- queue.enqueueTask(createMockQueueTask())
- queue.enqueueTask(createMockQueueTask())
+ queue.enqueueTask(createMockTask())
+ queue.enqueueTask(createMockTask())
expect(queue.queueLength.value).toBe(2)
@@ -193,50 +80,59 @@ describe('useManagerQueue', () => {
expect(queue.queueLength.value).toBe(0)
expect(queue.allTasksDone.value).toBe(true)
})
-
- it('should set up event listeners on creation', () => {
- useManagerQueue()
-
- expect(app.api.addEventListener).toHaveBeenCalled()
- })
})
- describe('processing state handling', () => {
- it('should update processing state based on queue length', async () => {
+ describe('server status handling', () => {
+ it('should update server status when receiving websocket events', async () => {
const queue = useManagerQueue()
- // Initially empty queue
- expect(queue.allTasksDone.value).toBe(true)
- expect(queue.statusMessage.value).toBe('all-done')
+ await simulateServerStatus('in_progress')
- // Add tasks to queue
- queue.enqueueTask(createMockTask())
- queue.enqueueTask(createMockTask())
-
- expect(queue.queueLength.value).toBe(2)
+ expect(queue.statusMessage.value).toBe('in_progress')
expect(queue.allTasksDone.value).toBe(false)
})
- it('should handle server status changes', async () => {
+ it('should handle invalid status values gracefully', async () => {
const queue = useManagerQueue()
- // Test status change to in_progress
- await simulateServerStatus('in_progress')
- expect(queue.statusMessage.value).toBe('in_progress')
+ // Simulate an invalid status
+ const event = new CustomEvent('cm-queue-status', {
+ detail: null as any
+ })
- // Test status change back to all-done
- await simulateServerStatus('all-done')
+ getEventListenerCallback()!(event)
+ await nextTick()
+
+ // Should maintain the default status
+ expect(queue.statusMessage.value).toBe('all-done')
+ })
+
+ it('should handle missing status property gracefully', async () => {
+ const queue = useManagerQueue()
+
+ // Simulate a detail object without status property
+ const event = new CustomEvent('cm-queue-status', {
+ detail: { someOtherProperty: 'value' } as any
+ })
+
+ getEventListenerCallback()!(event)
+ await nextTick()
+
+ // Should maintain the default status
expect(queue.statusMessage.value).toBe('all-done')
})
})
- describe('task state management', () => {
- const createQueueWithMockTask = () => {
- const queue = useManagerQueue()
- const mockTask = createMockTask()
- queue.enqueueTask(mockTask)
- return { queue, mockTask }
- }
+ describe('task execution', () => {
+ it('should start the next task when server is idle and queue has items', async () => {
+ const { queue, mockTask } = createQueueWithMockTask()
+
+ await simulateServerStatus('all-done')
+
+ // Task should have been started
+ expect(mockTask.task).toHaveBeenCalled()
+ expect(queue.queueLength.value).toBe(0)
+ })
it('should execute onComplete callback when task completes and server becomes idle', async () => {
const { mockTask } = createQueueWithMockTask()
@@ -341,7 +237,7 @@ describe('useManagerQueue', () => {
expect(mockTask.onComplete).toHaveBeenCalled()
})
- it('should handle multiple tasks enqueued at once while server busy', async () => {
+ it('should handle multiple multiple tasks enqueued at once while server busy', async () => {
const queue = useManagerQueue()
const mockTask1 = createMockTask()
const mockTask2 = createMockTask()
@@ -377,6 +273,10 @@ describe('useManagerQueue', () => {
expect(mockTask2.onComplete).toHaveBeenCalled()
expect(mockTask3.onComplete).not.toHaveBeenCalled()
+ // Verify state of queue
+ expect(queue.queueLength.value).toBe(1)
+ expect(queue.allTasksDone.value).toBe(false)
+
// Task 3
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
@@ -390,7 +290,30 @@ describe('useManagerQueue', () => {
expect(queue.allTasksDone.value).toBe(true)
})
- it('should handle server status changes with empty queue', async () => {
+ it('should handle adding tasks while processing is in progress', async () => {
+ const queue = useManagerQueue()
+ const mockTask1 = createMockTask()
+ const mockTask2 = createMockTask()
+
+ // Add first task and start processing
+ queue.enqueueTask(mockTask1)
+ await simulateServerStatus('all-done')
+ expect(mockTask1.task).toHaveBeenCalled()
+
+ // 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
@@ -399,387 +322,8 @@ describe('useManagerQueue', () => {
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
- expect(queue.queueLength.value).toBe(0)
+ // Should not cause any errors
expect(queue.allTasksDone.value).toBe(true)
})
})
-
- describe('legacy queue data management', () => {
- beforeEach(() => {
- taskHistory = ref({})
- taskQueue = ref({
- history: {},
- running_queue: [],
- pending_queue: [],
- installed_packs: {}
- })
- installedPacks = ref({})
- })
-
- it('should provide access to task queue state', async () => {
- const runningTasks = [createMockTask('task1')]
- const pendingTasks = [createMockTask('task2'), createMockTask('task3')]
-
- taskQueue.value.running_queue = runningTasks
- taskQueue.value.pending_queue = pendingTasks
-
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
- await nextTick()
-
- expect(queue.taskQueue.value.running_queue).toEqual(runningTasks)
- expect(queue.taskQueue.value.pending_queue).toEqual(pendingTasks)
- expect(queue.queueLength.value).toBe(3)
- })
-
- it('should provide access to task history', async () => {
- const mockHistory = {
- task1: createMockHistoryItem(),
- task2: createMockHistoryItem('test-client-id', 'error')
- }
- taskHistory.value = mockHistory
-
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
- await nextTick()
-
- expect(queue.taskHistory.value).toEqual(mockHistory)
- expect(queue.historyCount.value).toBe(2)
- })
-
- it('should handle empty state gracefully', async () => {
- taskQueue.value.running_queue = []
- taskQueue.value.pending_queue = []
- taskHistory.value = {}
-
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
- await nextTick()
-
- expect(queue.queueLength.value).toBe(0)
- expect(queue.historyCount.value).toBe(0)
- })
- })
-
- describe('state management', () => {
- it('should provide reactive task history', async () => {
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- taskHistory.value = {
- task1: createMockHistoryItem(),
- task2: createMockHistoryItem('test-client-id', 'error')
- }
-
- await nextTick()
-
- expect(queue.taskHistory.value).toEqual(taskHistory.value)
- expect(queue.historyCount.value).toBe(2)
- })
-
- it('should provide reactive installed packs', async () => {
- installedPacks.value = {
- pack1: { version: '1.0' },
- pack2: { version: '2.0' }
- }
-
- await nextTick()
-
- // The composable should have access to installedPacks through the parameter
- expect(installedPacks.value).toEqual({
- pack1: { version: '1.0' },
- pack2: { version: '2.0' }
- })
- })
- })
-
- describe('computed properties', () => {
- it('should correctly compute allTasksDone', async () => {
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- // Empty queue = all done
- expect(queue.allTasksDone.value).toBe(true)
-
- // Add pending tasks
- taskQueue.value.pending_queue = [createMockTask('task1')]
-
- await nextTick()
-
- expect(queue.allTasksDone.value).toBe(false)
-
- // Clear queue
- taskQueue.value.running_queue = []
- taskQueue.value.pending_queue = []
-
- await nextTick()
-
- expect(queue.allTasksDone.value).toBe(true)
- })
-
- it('should correctly compute queueLength', async () => {
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- expect(queue.queueLength.value).toBe(0)
-
- taskQueue.value.running_queue = [createMockTask('task1')]
- taskQueue.value.pending_queue = [
- createMockTask('task2'),
- createMockTask('task3')
- ]
-
- await nextTick()
-
- expect(queue.queueLength.value).toBe(3)
- })
- })
-
- describe('client filtering functionality', () => {
- 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')]
- })
-
- 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(queue.queueLength.value).toBe(2)
- })
-
- it('should filter history by client ID in WebSocket events', async () => {
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- const mockState = createMockState({
- history: {
- task1: createMockHistoryItem(),
- task2: createMockHistoryItem('other-client-id'),
- task3: createMockHistoryItem()
- }
- })
-
- triggerWebSocketEvent('cm-task-completed', mockState)
- await nextTick()
-
- // Should only include history items from this client
- expect(Object.keys(taskHistory.value)).toHaveLength(2)
- expect(taskHistory.value).toHaveProperty('task1')
- expect(taskHistory.value).toHaveProperty('task3')
- expect(taskHistory.value).not.toHaveProperty('task2')
- expect(queue.historyCount.value).toBe(2)
- })
-
- it('should handle all tasks being from other clients', async () => {
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- const mockState = createMockState({
- running_queue: [
- createMockTask('task1', 'other-client-1'),
- createMockTask('task2', 'other-client-2')
- ],
- pending_queue: [createMockTask('task3', 'other-client-1')],
- history: {
- task4: createMockHistoryItem('other-client-1'),
- task5: createMockHistoryItem('other-client-2')
- }
- })
-
- triggerWebSocketEvent('cm-task-completed', mockState)
- await nextTick()
-
- // Should have no tasks or history
- expect(taskQueue.value.running_queue).toEqual([])
- expect(taskQueue.value.pending_queue).toEqual([])
- expect(taskHistory.value).toEqual({})
- expect(queue.queueLength.value).toBe(0)
- expect(queue.historyCount.value).toBe(0)
- })
- })
-
- 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 handle task started events', async () => {
- useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- const mockState = createMockState({
- running_queue: [createMockTask('task1')],
- pending_queue: [createMockTask('task2')],
- installed_packs: { pack1: { version: '1.0' } }
- })
-
- triggerWebSocketEvent('cm-task-started', mockState)
- await nextTick()
-
- expect(taskQueue.value.running_queue).toEqual([createMockTask('task1')])
- expect(taskQueue.value.pending_queue).toEqual([createMockTask('task2')])
- expect(installedPacks.value).toEqual({ pack1: { version: '1.0' } })
- })
-
- it('should filter out tasks from other clients in WebSocket events', async () => {
- useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- const mockState = createMockState({
- running_queue: [
- createMockTask('task1'),
- createMockTask('task2', 'other-client-id')
- ],
- pending_queue: [createMockTask('task3', 'other-client-id')],
- 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([])
- expect(taskHistory.value).toEqual({
- task1: createMockHistoryItem()
- })
- })
-
- it('should ignore events with wrong type', async () => {
- useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- const handler = getEventHandler('cm-task-completed')
-
- // Send event with wrong type
- handler({
- type: 'wrong-event-type',
- detail: {
- state: createMockState({ running_queue: [createMockTask('task1')] })
- }
- })
- await nextTick()
-
- // Should not update state
- expect(taskQueue.value.running_queue).toEqual([])
- })
- })
-
- describe('cleanup functionality', () => {
- it('should clean up event listeners on stopListening', () => {
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
- const mockRemoveEventListener = app.api.removeEventListener as any
-
- queue.stopListening()
-
- expect(mockRemoveEventListener).toHaveBeenCalledTimes(2)
-
- // Check that both event types were called with the correct event names
- const calls = mockRemoveEventListener.mock.calls
- const eventTypes = calls.map((call: any) => call[0])
- expect(eventTypes).toContain('cm-task-completed')
- expect(eventTypes).toContain('cm-task-started')
-
- // Check that functions were passed as second parameter
- calls.forEach((call: any) => {
- expect(typeof call[1]).toBe('function')
- })
- })
-
- it('should handle multiple stopListening calls gracefully', () => {
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
- const mockRemoveEventListener = app.api.removeEventListener as any
-
- queue.stopListening()
- queue.stopListening()
-
- // Should still only be called twice (once per event type)
- expect(mockRemoveEventListener).toHaveBeenCalledTimes(4)
- })
- })
-
- describe('edge cases', () => {
- it('should handle undefined installed_packs in state update', async () => {
- useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- const mockState = createMockState({
- running_queue: [createMockTask('task1')],
- installed_packs: undefined
- })
-
- triggerWebSocketEvent('cm-task-completed', mockState)
- await nextTick()
-
- // Should not update installedPacks when undefined
- expect(installedPacks.value).toEqual({})
- })
-
- it('should handle rapid successive events', async () => {
- const queue = useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- // Send multiple events rapidly
- for (let i = 0; i < 10; i++) {
- triggerWebSocketEvent(
- 'cm-task-completed',
- createMockState({
- running_queue: [createMockTask(`task${i}`)],
- history: { [`task${i}`]: createMockHistoryItem() }
- })
- )
- }
-
- await nextTick()
-
- // Should have the last state
- expect(taskQueue.value.running_queue).toEqual([createMockTask('task9')])
- expect(queue.queueLength.value).toBe(1)
- })
-
- it('should maintain consistency when mixing event types', async () => {
- useManagerQueue(taskHistory, taskQueue, installedPacks)
-
- // Send alternating event types
- triggerWebSocketEvent(
- 'cm-task-started',
- createMockState({
- running_queue: [createMockTask('task1')],
- pending_queue: [createMockTask('task2')]
- })
- )
-
- triggerWebSocketEvent(
- 'cm-task-completed',
- createMockState({
- running_queue: [],
- pending_queue: [createMockTask('task2')],
- history: { task1: createMockHistoryItem() }
- })
- )
-
- await nextTick()
-
- expect(taskQueue.value.running_queue).toEqual([])
- expect(taskQueue.value.pending_queue).toEqual([createMockTask('task2')])
- expect(taskHistory.value).toHaveProperty('task1')
- })
- })
})