From 573eb8c7e89f7c47f64164a1bc6581b87882ab72 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Fri, 23 Jan 2026 13:36:52 -0800 Subject: [PATCH] test: cover inline queue progress UI --- src/components/TopMenuSection.test.ts | 103 ++++++++++++++++-- .../queue/QueueInlineProgress.test.ts | 51 +++++++++ 2 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 src/components/queue/QueueInlineProgress.test.ts diff --git a/src/components/TopMenuSection.test.ts b/src/components/TopMenuSection.test.ts index f40bef39bf..65001e8abc 100644 --- a/src/components/TopMenuSection.test.ts +++ b/src/components/TopMenuSection.test.ts @@ -2,7 +2,8 @@ import { createTestingPinia } from '@pinia/testing' import { mount } from '@vue/test-utils' import type { MenuItem } from 'primevue/menuitem' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { computed, nextTick } from 'vue' +import { computed, defineComponent, h, nextTick } from 'vue' +import type { Component } from 'vue' import { createI18n } from 'vue-i18n' import TopMenuSection from '@/components/TopMenuSection.vue' @@ -36,7 +37,17 @@ vi.mock('@/stores/firebaseAuthStore', () => ({ })) })) -function createWrapper(pinia = createTestingPinia({ createSpy: vi.fn })) { +type WrapperOptions = { + pinia?: ReturnType + stubs?: Record + attachTo?: HTMLElement +} + +function createWrapper({ + pinia = createTestingPinia({ createSpy: vi.fn }), + stubs = {}, + attachTo +}: WrapperOptions = {}) { const i18n = createI18n({ legacy: false, locale: 'en', @@ -55,6 +66,7 @@ function createWrapper(pinia = createTestingPinia({ createSpy: vi.fn })) { }) return mount(TopMenuSection, { + attachTo, global: { plugins: [pinia, i18n], stubs: { @@ -67,7 +79,8 @@ function createWrapper(pinia = createTestingPinia({ createSpy: vi.fn })) { name: 'ContextMenu', props: ['model'], template: '
' - } + }, + ...stubs }, directives: { tooltip: () => {} @@ -92,6 +105,7 @@ function createTask(id: string, status: JobStatus): TaskItemImpl { describe('TopMenuSection', () => { beforeEach(() => { vi.resetAllMocks() + localStorage.clear() }) describe('authentication state', () => { @@ -152,7 +166,7 @@ describe('TopMenuSection', () => { vi.mocked(settingStore.get).mockImplementation((key) => key === 'Comfy.Queue.QPOV2' ? true : undefined ) - const wrapper = createWrapper(pinia) + const wrapper = createWrapper({ pinia }) await nextTick() @@ -170,7 +184,7 @@ describe('TopMenuSection', () => { vi.mocked(settingStore.get).mockImplementation((key) => key === 'Comfy.Queue.QPOV2' ? false : undefined ) - const wrapper = createWrapper(pinia) + const wrapper = createWrapper({ pinia }) const commandStore = useCommandStore(pinia) await wrapper.find('[data-testid="queue-overlay-toggle"]').trigger('click') @@ -186,7 +200,7 @@ describe('TopMenuSection', () => { vi.mocked(settingStore.get).mockImplementation((key) => key === 'Comfy.Queue.QPOV2' ? true : undefined ) - const wrapper = createWrapper(pinia) + const wrapper = createWrapper({ pinia }) const sidebarTabStore = useSidebarTabStore(pinia) await wrapper.find('[data-testid="queue-overlay-toggle"]').trigger('click') @@ -200,7 +214,7 @@ describe('TopMenuSection', () => { vi.mocked(settingStore.get).mockImplementation((key) => key === 'Comfy.Queue.QPOV2' ? true : undefined ) - const wrapper = createWrapper(pinia) + const wrapper = createWrapper({ pinia }) const sidebarTabStore = useSidebarTabStore(pinia) const toggleButton = wrapper.find('[data-testid="queue-overlay-toggle"]') @@ -211,6 +225,81 @@ describe('TopMenuSection', () => { expect(sidebarTabStore.activeSidebarTabId).toBe(null) }) + describe('inline progress summary', () => { + const configureSettings = ( + pinia: ReturnType, + qpoV2Enabled: boolean + ) => { + const settingStore = useSettingStore(pinia) + vi.mocked(settingStore.get).mockImplementation((key) => { + if (key === 'Comfy.Queue.QPOV2') return qpoV2Enabled + if (key === 'Comfy.UseNewMenu') return 'Top' + return undefined + }) + } + + it('renders inline progress summary when QPO V2 is enabled', async () => { + const pinia = createTestingPinia({ createSpy: vi.fn }) + configureSettings(pinia, true) + + const wrapper = createWrapper({ pinia }) + + await nextTick() + + expect( + wrapper.findComponent({ name: 'QueueInlineProgressSummary' }).exists() + ).toBe(true) + }) + + it('does not render inline progress summary when QPO V2 is disabled', async () => { + const pinia = createTestingPinia({ createSpy: vi.fn }) + configureSettings(pinia, false) + + const wrapper = createWrapper({ pinia }) + + await nextTick() + + expect( + wrapper.findComponent({ name: 'QueueInlineProgressSummary' }).exists() + ).toBe(false) + }) + + it('teleports inline progress summary when actionbar is floating', async () => { + localStorage.setItem('Comfy.MenuPosition.Docked', 'false') + const actionbarTarget = document.createElement('div') + document.body.appendChild(actionbarTarget) + const pinia = createTestingPinia({ createSpy: vi.fn }) + configureSettings(pinia, true) + + const ComfyActionbarStub = defineComponent({ + name: 'ComfyActionbar', + setup(_, { expose }) { + expose({ panelElement: actionbarTarget }) + return () => h('div') + } + }) + + const wrapper = createWrapper({ + pinia, + attachTo: document.body, + stubs: { + ComfyActionbar: ComfyActionbarStub + } + }) + + try { + await nextTick() + + expect( + actionbarTarget.querySelector('queue-inline-progress-summary-stub') + ).not.toBeNull() + } finally { + wrapper.unmount() + actionbarTarget.remove() + } + }) + }) + it('disables the clear queue context menu item when no queued jobs exist', () => { const wrapper = createWrapper() const menu = wrapper.findComponent({ name: 'ContextMenu' }) diff --git a/src/components/queue/QueueInlineProgress.test.ts b/src/components/queue/QueueInlineProgress.test.ts new file mode 100644 index 0000000000..aa330744d4 --- /dev/null +++ b/src/components/queue/QueueInlineProgress.test.ts @@ -0,0 +1,51 @@ +import { mount } from '@vue/test-utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { ref } from 'vue' + +import QueueInlineProgress from '@/components/queue/QueueInlineProgress.vue' + +const mockProgress = vi.hoisted(() => ({ + totalPercent: ref(0), + currentNodePercent: ref(0) +})) + +vi.mock('@/composables/queue/useQueueProgress', () => ({ + useQueueProgress: () => ({ + totalPercent: mockProgress.totalPercent, + currentNodePercent: mockProgress.currentNodePercent + }) +})) + +const createWrapper = (props: { hidden?: boolean } = {}) => + mount(QueueInlineProgress, { props }) + +describe('QueueInlineProgress', () => { + beforeEach(() => { + mockProgress.totalPercent.value = 0 + mockProgress.currentNodePercent.value = 0 + }) + + it('renders when total progress is non-zero', () => { + mockProgress.totalPercent.value = 12 + + const wrapper = createWrapper() + + expect(wrapper.find('[aria-hidden="true"]').exists()).toBe(true) + }) + + it('renders when current node progress is non-zero', () => { + mockProgress.currentNodePercent.value = 33 + + const wrapper = createWrapper() + + expect(wrapper.find('[aria-hidden="true"]').exists()).toBe(true) + }) + + it('does not render when hidden', () => { + mockProgress.totalPercent.value = 45 + + const wrapper = createWrapper({ hidden: true }) + + expect(wrapper.find('[aria-hidden="true"]').exists()).toBe(false) + }) +})