diff --git a/src/components/common/DotSpinner.vue b/src/components/common/DotSpinner.vue
new file mode 100644
index 0000000000..548737bdf5
--- /dev/null
+++ b/src/components/common/DotSpinner.vue
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/dialog/content/manager/button/PackInstallButton.vue b/src/components/dialog/content/manager/button/PackInstallButton.vue
index 7bba01ef0a..2f9751d0fd 100644
--- a/src/components/dialog/content/manager/button/PackInstallButton.vue
+++ b/src/components/dialog/content/manager/button/PackInstallButton.vue
@@ -10,7 +10,6 @@
:loading="isInstalling"
:loading-message="$t('g.installing')"
@action="installAllPacks"
- @click="onClick"
/>
@@ -37,10 +36,6 @@ const { nodePacks, variant, label } = defineProps<{
const isInstalling = inject(IsInstallingKey, ref(false))
-const onClick = (): void => {
- isInstalling.value = true
-}
-
const managerStore = useComfyManagerStore()
const createPayload = (installItem: NodePack) => {
@@ -65,8 +60,6 @@ const installPack = (item: NodePack) =>
const installAllPacks = async () => {
if (!nodePacks?.length) return
- isInstalling.value = true
-
const uninstalledPacks = nodePacks.filter(
(pack) => !managerStore.isPackInstalled(pack.id)
)
diff --git a/src/components/dialog/content/manager/packCard/PackCard.vue b/src/components/dialog/content/manager/packCard/PackCard.vue
index 08caeb29cb..ee2cef92de 100644
--- a/src/components/dialog/content/manager/packCard/PackCard.vue
+++ b/src/components/dialog/content/manager/packCard/PackCard.vue
@@ -84,10 +84,9 @@
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index f036d9dbd6..b3871790e1 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -161,6 +161,12 @@
"inWorkflow": "In Workflow",
"infoPanelEmpty": "Click an item to see the info",
"restartToApplyChanges": "To apply changes, please restart ComfyUI",
+ "clickToFinishSetup": "Click",
+ "toFinishSetup": "to finish setup",
+ "applyChanges": "Apply Changes",
+ "restartingBackend": "Restarting backend to apply changes...",
+ "extensionsSuccessfullyInstalled": "Extension(s) successfully installed and are ready to use!",
+ "installingDependencies": "Installing dependencies...",
"loadingVersions": "Loading versions...",
"selectVersion": "Select Version",
"downloads": "Downloads",
diff --git a/src/stores/comfyManagerStore.ts b/src/stores/comfyManagerStore.ts
index 0568711f77..b9840f4212 100644
--- a/src/stores/comfyManagerStore.ts
+++ b/src/stores/comfyManagerStore.ts
@@ -29,6 +29,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
const enabledPacksIds = ref>(new Set())
const disabledPacksIds = ref>(new Set())
const installedPacksIds = ref>(new Set())
+ const installingPacksIds = ref>(new Set())
const isStale = ref(true)
const taskLogs = ref([])
@@ -49,6 +50,9 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
isInstalledPackId(packName) &&
enabledPacksIds.value.has(packName)
+ const isInstallingPackId = (packName: string | undefined): boolean =>
+ !!packName && installingPacksIds.value.has(packName)
+
const packsToIdSet = (packs: ManagerPackInstalled[]) =>
packs.reduce((acc, pack) => {
const id = pack.cnr_id || pack.aux_id
@@ -117,7 +121,11 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
whenever(isStale, refreshInstalledList, { immediate: true })
whenever(uncompletedCount, () => showManagerProgressDialog())
- const withLogs = (task: () => Promise, taskName: string) => {
+ const withLogs = (
+ task: () => Promise,
+ taskName: string,
+ packId?: string
+ ) => {
const { startListening, stopListening, logs } = useServerLogs()
const loggedTask = async () => {
@@ -128,6 +136,9 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
const onComplete = async () => {
await stopListening()
+ if (packId) {
+ installingPacksIds.value.delete(packId)
+ }
setStale()
}
@@ -152,8 +163,11 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
}
}
+ installingPacksIds.value.add(params.id)
const task = () => managerService.installPack(params, signal)
- enqueueTask(withLogs(task, `${actionDescription} ${params.id}`))
+ enqueueTask(
+ withLogs(task, `${actionDescription} ${params.id}`, params.id)
+ )
},
{ maxSize: 1 }
)
@@ -162,14 +176,16 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
installPack.clear()
installPack.cancel()
const task = () => managerService.uninstallPack(params, signal)
- enqueueTask(withLogs(task, t('manager.uninstalling', { id: params.id })))
+ enqueueTask(
+ withLogs(task, t('manager.uninstalling', { id: params.id }), params.id)
+ )
}
const updatePack = useCachedRequest(
async (params: ManagerPackInfo, signal?: AbortSignal) => {
updateAllPacks.cancel()
const task = () => managerService.updatePack(params, signal)
- enqueueTask(withLogs(task, t('g.updating', { id: params.id })))
+ enqueueTask(withLogs(task, t('g.updating', { id: params.id }), params.id))
},
{ maxSize: 1 }
)
@@ -184,7 +200,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
const disablePack = (params: ManagerPackInfo, signal?: AbortSignal) => {
const task = () => managerService.disablePack(params, signal)
- enqueueTask(withLogs(task, t('g.disabling', { id: params.id })))
+ enqueueTask(withLogs(task, t('g.disabling', { id: params.id }), params.id))
}
const getInstalledPackVersion = (packId: string) => {
@@ -212,6 +228,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
installedPacksIds,
isPackInstalled: isInstalledPackId,
isPackEnabled: isEnabledPackId,
+ isPackInstalling: isInstallingPackId,
getInstalledPackVersion,
refreshInstalledList,
diff --git a/tests-ui/tests/components/dialog/footer/ManagerProgressFooter.test.ts b/tests-ui/tests/components/dialog/footer/ManagerProgressFooter.test.ts
new file mode 100644
index 0000000000..c92d4c89ad
--- /dev/null
+++ b/tests-ui/tests/components/dialog/footer/ManagerProgressFooter.test.ts
@@ -0,0 +1,440 @@
+import { mount } from '@vue/test-utils'
+import PrimeVue from 'primevue/config'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { nextTick } from 'vue'
+import { createI18n } from 'vue-i18n'
+
+import ManagerProgressFooter from '@/components/dialog/footer/ManagerProgressFooter.vue'
+import { useComfyManagerService } from '@/services/comfyManagerService'
+import {
+ useComfyManagerStore,
+ useManagerProgressDialogStore
+} from '@/stores/comfyManagerStore'
+import { useCommandStore } from '@/stores/commandStore'
+import { useDialogStore } from '@/stores/dialogStore'
+import { useSettingStore } from '@/stores/settingStore'
+import { TaskLog } from '@/types/comfyManagerTypes'
+
+// Mock modules
+vi.mock('@/stores/comfyManagerStore')
+vi.mock('@/stores/dialogStore')
+vi.mock('@/stores/settingStore')
+vi.mock('@/stores/commandStore')
+vi.mock('@/services/comfyManagerService')
+
+// Mock useEventListener to capture the event handler
+let reconnectHandler: (() => void) | null = null
+vi.mock('@vueuse/core', async () => {
+ const actual = await vi.importActual('@vueuse/core')
+ return {
+ ...actual,
+ useEventListener: vi.fn(
+ (_target: any, event: string, handler: any, _options: any) => {
+ if (event === 'reconnected') {
+ reconnectHandler = handler
+ }
+ }
+ )
+ }
+})
+vi.mock('@/services/workflowService', () => ({
+ useWorkflowService: vi.fn(() => ({
+ reloadCurrentWorkflow: vi.fn().mockResolvedValue(undefined)
+ }))
+}))
+vi.mock('@/stores/workspace/colorPaletteStore', () => ({
+ useColorPaletteStore: vi.fn(() => ({
+ completedActivePalette: {
+ light_theme: false
+ }
+ }))
+}))
+
+// Helper function to mount component with required setup
+const mountComponent = (options: { captureError?: boolean } = {}) => {
+ const i18n = createI18n({
+ legacy: false,
+ locale: 'en',
+ messages: {
+ en: {}
+ }
+ })
+
+ const config: any = {
+ global: {
+ plugins: [PrimeVue, i18n],
+ mocks: {
+ $t: (key: string) => key // Mock i18n translation
+ }
+ }
+ }
+
+ // Add error handler for tests that expect errors
+ if (options.captureError) {
+ config.global.config = {
+ errorHandler: () => {
+ // Suppress error in test
+ }
+ }
+ }
+
+ return mount(ManagerProgressFooter, config)
+}
+
+describe('ManagerProgressFooter', () => {
+ const mockTaskLogs: TaskLog[] = []
+
+ const mockComfyManagerStore = {
+ uncompletedCount: 0,
+ taskLogs: mockTaskLogs,
+ allTasksDone: true,
+ clearLogs: vi.fn(),
+ setStale: vi.fn(),
+ // Add other required properties
+ isLoading: { value: false },
+ error: { value: null },
+ statusMessage: { value: 'DONE' },
+ installedPacks: {},
+ installedPacksIds: new Set(),
+ isPackInstalled: vi.fn(),
+ isPackEnabled: vi.fn(),
+ getInstalledPackVersion: vi.fn(),
+ refreshInstalledList: vi.fn(),
+ installPack: vi.fn(),
+ uninstallPack: vi.fn(),
+ updatePack: vi.fn(),
+ updateAllPacks: vi.fn(),
+ disablePack: vi.fn(),
+ enablePack: vi.fn()
+ }
+
+ const mockDialogStore = {
+ closeDialog: vi.fn(),
+ // Add other required properties
+ dialogStack: { value: [] },
+ showDialog: vi.fn(),
+ $id: 'dialog',
+ $state: {} as any,
+ $patch: vi.fn(),
+ $reset: vi.fn(),
+ $subscribe: vi.fn(),
+ $dispose: vi.fn(),
+ $onAction: vi.fn()
+ }
+
+ const mockSettingStore = {
+ get: vi.fn().mockReturnValue(false),
+ set: vi.fn(),
+ // Add other required properties
+ settingValues: { value: {} },
+ settingsById: { value: {} },
+ exists: vi.fn(),
+ getDefaultValue: vi.fn(),
+ loadSettingValues: vi.fn(),
+ updateValue: vi.fn(),
+ $id: 'setting',
+ $state: {} as any,
+ $patch: vi.fn(),
+ $reset: vi.fn(),
+ $subscribe: vi.fn(),
+ $dispose: vi.fn(),
+ $onAction: vi.fn()
+ }
+
+ const mockProgressDialogStore = {
+ isExpanded: false,
+ toggle: vi.fn(),
+ collapse: vi.fn(),
+ expand: vi.fn()
+ }
+
+ const mockCommandStore = {
+ execute: vi.fn().mockResolvedValue(undefined)
+ }
+
+ const mockComfyManagerService = {
+ rebootComfyUI: vi.fn().mockResolvedValue(null)
+ }
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ // Reset task logs
+ mockTaskLogs.length = 0
+ mockComfyManagerStore.taskLogs = mockTaskLogs
+ // Reset event handler
+ reconnectHandler = null
+
+ vi.mocked(useComfyManagerStore).mockReturnValue(
+ mockComfyManagerStore as any
+ )
+ vi.mocked(useDialogStore).mockReturnValue(mockDialogStore as any)
+ vi.mocked(useSettingStore).mockReturnValue(mockSettingStore as any)
+ vi.mocked(useManagerProgressDialogStore).mockReturnValue(
+ mockProgressDialogStore as any
+ )
+ vi.mocked(useCommandStore).mockReturnValue(mockCommandStore as any)
+ vi.mocked(useComfyManagerService).mockReturnValue(
+ mockComfyManagerService as any
+ )
+ })
+
+ describe('State 1: Queue Running', () => {
+ it('should display loading spinner and progress counter when queue is running', async () => {
+ // Setup queue running state
+ mockComfyManagerStore.uncompletedCount = 3
+ mockTaskLogs.push(
+ { taskName: 'Installing pack1', logs: [] },
+ { taskName: 'Installing pack2', logs: [] },
+ { taskName: 'Installing pack3', logs: [] }
+ )
+
+ const wrapper = mountComponent()
+
+ // Check loading spinner exists (DotSpinner component)
+ expect(wrapper.find('.inline-flex').exists()).toBe(true)
+
+ // Check current task name is displayed
+ expect(wrapper.text()).toContain('Installing pack3')
+
+ // Check progress counter (completed: 2 of 3)
+ expect(wrapper.text()).toMatch(/2.*3/)
+
+ // Check expand/collapse button exists
+ const expandButton = wrapper.find('[aria-label="Expand"]')
+ expect(expandButton.exists()).toBe(true)
+
+ // Check Apply Changes button is NOT shown
+ expect(wrapper.text()).not.toContain('manager.applyChanges')
+ })
+
+ it('should toggle expansion when expand button is clicked', async () => {
+ mockComfyManagerStore.uncompletedCount = 1
+ mockTaskLogs.push({ taskName: 'Installing', logs: [] })
+
+ const wrapper = mountComponent()
+
+ const expandButton = wrapper.find('[aria-label="Expand"]')
+ await expandButton.trigger('click')
+
+ expect(mockProgressDialogStore.toggle).toHaveBeenCalled()
+ })
+ })
+
+ describe('State 2: Tasks Completed (Waiting for Restart)', () => {
+ it('should display check mark and Apply Changes button when all tasks are done', async () => {
+ // Setup tasks completed state
+ mockComfyManagerStore.uncompletedCount = 0
+ mockTaskLogs.push(
+ { taskName: 'Installed pack1', logs: [] },
+ { taskName: 'Installed pack2', logs: [] }
+ )
+ mockComfyManagerStore.allTasksDone = true
+
+ const wrapper = mountComponent()
+
+ // Check check mark emoji
+ expect(wrapper.text()).toContain('✅')
+
+ // Check restart message (split into 3 parts)
+ expect(wrapper.text()).toContain('manager.clickToFinishSetup')
+ expect(wrapper.text()).toContain('manager.applyChanges')
+ expect(wrapper.text()).toContain('manager.toFinishSetup')
+
+ // Check Apply Changes button exists
+ const applyButton = wrapper
+ .findAll('button')
+ .find((btn) => btn.text().includes('manager.applyChanges'))
+ expect(applyButton).toBeTruthy()
+
+ // Check no progress counter
+ expect(wrapper.text()).not.toMatch(/\d+.*of.*\d+/)
+ })
+ })
+
+ describe('State 3: Restarting', () => {
+ it('should display restarting message and spinner during restart', async () => {
+ // Setup completed state first
+ mockComfyManagerStore.uncompletedCount = 0
+ mockComfyManagerStore.allTasksDone = true
+
+ const wrapper = mountComponent()
+
+ // Click Apply Changes to trigger restart
+ const applyButton = wrapper
+ .findAll('button')
+ .find((btn) => btn.text().includes('manager.applyChanges'))
+ await applyButton?.trigger('click')
+
+ // Wait for state update
+ await nextTick()
+
+ // Check restarting message
+ expect(wrapper.text()).toContain('manager.restartingBackend')
+
+ // Check loading spinner during restart
+ expect(wrapper.find('.inline-flex').exists()).toBe(true)
+
+ // Check Apply Changes button is hidden
+ expect(wrapper.text()).not.toContain('manager.applyChanges')
+ })
+ })
+
+ describe('State 4: Restart Completed', () => {
+ it('should display success message and auto-close after 3 seconds', async () => {
+ vi.useFakeTimers()
+
+ // Setup completed state
+ mockComfyManagerStore.uncompletedCount = 0
+ mockComfyManagerStore.allTasksDone = true
+
+ const wrapper = mountComponent()
+
+ // Trigger restart
+ const applyButton = wrapper
+ .findAll('button')
+ .find((btn) => btn.text().includes('manager.applyChanges'))
+ await applyButton?.trigger('click')
+
+ // Wait for event listener to be set up
+ await nextTick()
+
+ // Trigger the reconnect handler directly
+ if (reconnectHandler) {
+ await reconnectHandler()
+ }
+
+ // Wait for restart completed state
+ await nextTick()
+
+ // Check success message
+ expect(wrapper.text()).toContain('🎉')
+ expect(wrapper.text()).toContain(
+ 'manager.extensionsSuccessfullyInstalled'
+ )
+
+ // Check dialog closes after 3 seconds
+ vi.advanceTimersByTime(3000)
+
+ await nextTick()
+
+ expect(mockDialogStore.closeDialog).toHaveBeenCalledWith({
+ key: 'global-manager-progress-dialog'
+ })
+ expect(mockComfyManagerStore.clearLogs).toHaveBeenCalled()
+
+ vi.useRealTimers()
+ })
+ })
+
+ describe('Common Features', () => {
+ it('should always display close button', async () => {
+ const wrapper = mountComponent()
+
+ const closeButton = wrapper.find('[aria-label="Close"]')
+ expect(closeButton.exists()).toBe(true)
+ })
+
+ it('should close dialog when close button is clicked', async () => {
+ const wrapper = mountComponent()
+
+ const closeButton = wrapper.find('[aria-label="Close"]')
+ await closeButton.trigger('click')
+
+ expect(mockDialogStore.closeDialog).toHaveBeenCalledWith({
+ key: 'global-manager-progress-dialog'
+ })
+ })
+ })
+
+ describe('Toast Management', () => {
+ it('should suppress reconnection toasts during restart', async () => {
+ mockComfyManagerStore.uncompletedCount = 0
+ mockComfyManagerStore.allTasksDone = true
+ mockSettingStore.get.mockReturnValue(false) // Original setting
+
+ const wrapper = mountComponent()
+
+ // Click Apply Changes
+ const applyButton = wrapper
+ .findAll('button')
+ .find((btn) => btn.text().includes('manager.applyChanges'))
+ await applyButton?.trigger('click')
+
+ // Check toast setting was disabled
+ expect(mockSettingStore.set).toHaveBeenCalledWith(
+ 'Comfy.Toast.DisableReconnectingToast',
+ true
+ )
+ })
+
+ it('should restore toast settings after restart completes', async () => {
+ mockComfyManagerStore.uncompletedCount = 0
+ mockComfyManagerStore.allTasksDone = true
+ mockSettingStore.get.mockReturnValue(false) // Original setting
+
+ const wrapper = mountComponent()
+
+ // Click Apply Changes
+ const applyButton = wrapper
+ .findAll('button')
+ .find((btn) => btn.text().includes('manager.applyChanges'))
+ await applyButton?.trigger('click')
+
+ // Wait for event listener to be set up
+ await nextTick()
+
+ // Trigger the reconnect handler directly
+ if (reconnectHandler) {
+ await reconnectHandler()
+ }
+
+ // Wait for settings restoration
+ await nextTick()
+
+ expect(mockSettingStore.set).toHaveBeenCalledWith(
+ 'Comfy.Toast.DisableReconnectingToast',
+ false // Restored to original
+ )
+ })
+ })
+
+ describe('Error Handling', () => {
+ it('should restore state and close dialog on restart error', async () => {
+ mockComfyManagerStore.uncompletedCount = 0
+ mockComfyManagerStore.allTasksDone = true
+
+ // Mock restart to throw error
+ mockComfyManagerService.rebootComfyUI.mockRejectedValue(
+ new Error('Restart failed')
+ )
+
+ const wrapper = mountComponent({ captureError: true })
+
+ // Click Apply Changes
+ const applyButton = wrapper
+ .findAll('button')
+ .find((btn) => btn.text().includes('manager.applyChanges'))
+
+ expect(applyButton).toBeTruthy()
+
+ // The component throws the error but Vue Test Utils catches it
+ // We need to check if the error handling logic was executed
+ await applyButton!.trigger('click').catch(() => {
+ // Error is expected, ignore it
+ })
+
+ // Wait for error handling
+ await nextTick()
+
+ // Check dialog was closed on error
+ expect(mockDialogStore.closeDialog).toHaveBeenCalled()
+ // Check toast settings were restored
+ expect(mockSettingStore.set).toHaveBeenCalledWith(
+ 'Comfy.Toast.DisableReconnectingToast',
+ false
+ )
+ // Check that the error handler was called
+ expect(mockComfyManagerService.rebootComfyUI).toHaveBeenCalled()
+ })
+ })
+})
diff --git a/tests-ui/tests/store/comfyManagerStore.test.ts b/tests-ui/tests/store/comfyManagerStore.test.ts
index 41ead35327..f99d2b3632 100644
--- a/tests-ui/tests/store/comfyManagerStore.test.ts
+++ b/tests-ui/tests/store/comfyManagerStore.test.ts
@@ -6,6 +6,8 @@ import { useComfyManagerService } from '@/services/comfyManagerService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import {
InstalledPacksResponse,
+ ManagerChannel,
+ ManagerDatabaseSource,
ManagerPackInstalled
} from '@/types/comfyManagerTypes'
@@ -13,6 +15,34 @@ vi.mock('@/services/comfyManagerService', () => ({
useComfyManagerService: vi.fn()
}))
+vi.mock('@/services/dialogService', () => ({
+ useDialogService: () => ({
+ showManagerProgressDialog: vi.fn()
+ })
+}))
+
+vi.mock('@/composables/useManagerQueue', () => {
+ const enqueueTaskMock = vi.fn()
+
+ return {
+ useManagerQueue: () => ({
+ statusMessage: ref(''),
+ allTasksDone: ref(false),
+ enqueueTask: enqueueTaskMock,
+ uncompletedCount: ref(0)
+ }),
+ enqueueTask: enqueueTaskMock
+ }
+})
+
+vi.mock('@/composables/useServerLogs', () => ({
+ useServerLogs: () => ({
+ startListening: vi.fn(),
+ stopListening: vi.fn(),
+ logs: ref([])
+ })
+}))
+
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: vi.fn((key) => key)
@@ -33,11 +63,7 @@ interface EnabledDisabledTestCase {
}
describe('useComfyManagerStore', () => {
- let mockManagerService: {
- isLoading: ReturnType>
- error: ReturnType>
- listInstalledPacks: ReturnType
- }
+ let mockManagerService: ReturnType
const triggerPacksChange = async (
installedPacks: InstalledPacksResponse,
@@ -55,10 +81,21 @@ describe('useComfyManagerStore', () => {
mockManagerService = {
isLoading: ref(false),
error: ref(null),
- listInstalledPacks: vi.fn().mockResolvedValue({})
+ startQueue: vi.fn().mockResolvedValue(null),
+ resetQueue: vi.fn().mockResolvedValue(null),
+ getQueueStatus: vi.fn().mockResolvedValue(null),
+ listInstalledPacks: vi.fn().mockResolvedValue({}),
+ getImportFailInfo: vi.fn().mockResolvedValue(null),
+ installPack: vi.fn().mockResolvedValue(null),
+ uninstallPack: vi.fn().mockResolvedValue(null),
+ enablePack: vi.fn().mockResolvedValue(null),
+ disablePack: vi.fn().mockResolvedValue(null),
+ updatePack: vi.fn().mockResolvedValue(null),
+ updateAllPacks: vi.fn().mockResolvedValue(null),
+ rebootComfyUI: vi.fn().mockResolvedValue(null),
+ isLegacyManagerUI: vi.fn().mockResolvedValue(false)
}
- // @ts-expect-error Mocking the return type of useComfyManagerService
vi.mocked(useComfyManagerService).mockReturnValue(mockManagerService)
})
@@ -313,4 +350,90 @@ describe('useComfyManagerStore', () => {
}
)
})
+
+ describe('isPackInstalling', () => {
+ it('should return false for packs not being installed', () => {
+ const store = useComfyManagerStore()
+ expect(store.isPackInstalling('test-pack')).toBe(false)
+ expect(store.isPackInstalling(undefined)).toBe(false)
+ expect(store.isPackInstalling('')).toBe(false)
+ })
+
+ it('should track pack as installing when installPack is called', async () => {
+ const store = useComfyManagerStore()
+
+ // Call installPack
+ await store.installPack.call({
+ id: 'test-pack',
+ repository: 'https://github.com/test/test-pack',
+ channel: ManagerChannel.DEV,
+ mode: ManagerDatabaseSource.CACHE,
+ selected_version: 'latest',
+ version: 'latest'
+ })
+
+ // Check that the pack is marked as installing
+ expect(store.isPackInstalling('test-pack')).toBe(true)
+ })
+
+ it('should remove pack from installing list when explicitly removed', async () => {
+ const store = useComfyManagerStore()
+
+ // Call installPack
+ await store.installPack.call({
+ id: 'test-pack',
+ repository: 'https://github.com/test/test-pack',
+ channel: ManagerChannel.DEV,
+ mode: ManagerDatabaseSource.CACHE,
+ selected_version: 'latest',
+ version: 'latest'
+ })
+
+ // Verify pack is installing
+ expect(store.isPackInstalling('test-pack')).toBe(true)
+
+ // Call installPack again for another pack to demonstrate multiple installs
+ await store.installPack.call({
+ id: 'another-pack',
+ repository: 'https://github.com/test/another-pack',
+ channel: ManagerChannel.DEV,
+ mode: ManagerDatabaseSource.CACHE,
+ selected_version: 'latest',
+ version: 'latest'
+ })
+
+ // Both should be installing
+ expect(store.isPackInstalling('test-pack')).toBe(true)
+ expect(store.isPackInstalling('another-pack')).toBe(true)
+ })
+
+ it('should track multiple packs installing independently', async () => {
+ const store = useComfyManagerStore()
+
+ // Install pack 1
+ await store.installPack.call({
+ id: 'pack-1',
+ repository: 'https://github.com/test/pack-1',
+ channel: ManagerChannel.DEV,
+ mode: ManagerDatabaseSource.CACHE,
+ selected_version: 'latest',
+ version: 'latest'
+ })
+
+ // Install pack 2
+ await store.installPack.call({
+ id: 'pack-2',
+ repository: 'https://github.com/test/pack-2',
+ channel: ManagerChannel.DEV,
+ mode: ManagerDatabaseSource.CACHE,
+ selected_version: 'latest',
+ version: 'latest'
+ })
+
+ // Both should be installing
+ expect(store.isPackInstalling('pack-1')).toBe(true)
+ expect(store.isPackInstalling('pack-2')).toBe(true)
+ expect(store.isPackInstalling('pack-3')).toBe(false)
+ })
+ })
})