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') - }) - }) })