mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-20 14:54:12 +00:00
feat(queue): introduce queue notification banners and remove completion summary flow (#8740)
## Summary Replace the old completion-summary overlay path with queue notification banners for queueing/completed/failed lifecycle feedback. ## Key changes - Added `QueueNotificationBanner`, `QueueNotificationBannerHost`, stories, and tests. - Added `useQueueNotificationBanners` to handle: - immediate `queuedPending` on `promptQueueing` - transition to `queued` on `promptQueued` (request-id aware) - completed/failed notification sequencing from finished batch history - timed notification queueing/dismissal - Removed completion-summary implementation: - `useCompletionSummary` - `CompletionSummaryBanner` - `QueueOverlayEmpty` - Simplified `QueueProgressOverlay` to `hidden | active | expanded` states. - Top menu behavior: - restored `QueueInlineProgressSummary` as separate UI - ordering is inline summary first, notification banner below - notification banner remains under the top menu section (not teleported to floating actionbar target) - Kept established API-event signaling pattern (`promptQueueing`/`promptQueued`) instead of introducing a separate bus. - Updated tests for top-menu visibility/ordering and notification behavior across QPOV2 enabled/disabled. ## Notes - Completion notifications now support stacked thumbnails (cap: 3). - https://www.figma.com/design/LVilZgHGk5RwWOkVN6yCEK/Queue-Progress-Modal?node-id=3843-20314&m=dev ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8740-feat-Queue-Notification-Toasts-3016d73d3650814c8a50d9567a40f44d) by [Unito](https://www.unito.io)
This commit is contained in:
136
src/components/queue/QueueNotificationBanner.test.ts
Normal file
136
src/components/queue/QueueNotificationBanner.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import QueueNotificationBanner from '@/components/queue/QueueNotificationBanner.vue'
|
||||
import type { QueueNotificationBanner as QueueNotificationBannerItem } from '@/composables/queue/useQueueNotificationBanners'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
queue: {
|
||||
jobAddedToQueue: 'Job added to queue',
|
||||
jobQueueing: 'Job queueing'
|
||||
},
|
||||
sideToolbar: {
|
||||
queueProgressOverlay: {
|
||||
preview: 'Preview',
|
||||
jobCompleted: 'Job completed',
|
||||
jobFailed: 'Job failed',
|
||||
jobsAddedToQueue:
|
||||
'{count} job added to queue | {count} jobs added to queue',
|
||||
jobsCompleted: '{count} job completed | {count} jobs completed',
|
||||
jobsFailed: '{count} job failed | {count} jobs failed'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const mountComponent = (notification: QueueNotificationBannerItem) =>
|
||||
mount(QueueNotificationBanner, {
|
||||
props: { notification },
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
|
||||
describe(QueueNotificationBanner, () => {
|
||||
it('renders singular queued message without count prefix', () => {
|
||||
const wrapper = mountComponent({
|
||||
type: 'queued',
|
||||
count: 1
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('Job added to queue')
|
||||
expect(wrapper.text()).not.toContain('1 job')
|
||||
})
|
||||
|
||||
it('renders queued message with pluralization', () => {
|
||||
const wrapper = mountComponent({
|
||||
type: 'queued',
|
||||
count: 2
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('2 jobs added to queue')
|
||||
expect(wrapper.html()).toContain('icon-[lucide--check]')
|
||||
})
|
||||
|
||||
it('renders queued pending message with spinner icon', () => {
|
||||
const wrapper = mountComponent({
|
||||
type: 'queuedPending',
|
||||
count: 1
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('Job queueing')
|
||||
expect(wrapper.html()).toContain('icon-[lucide--loader-circle]')
|
||||
expect(wrapper.html()).toContain('animate-spin')
|
||||
})
|
||||
|
||||
it('renders failed message and alert icon', () => {
|
||||
const wrapper = mountComponent({
|
||||
type: 'failed',
|
||||
count: 1
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('Job failed')
|
||||
expect(wrapper.html()).toContain('icon-[lucide--circle-alert]')
|
||||
})
|
||||
|
||||
it('renders completed message with thumbnail preview when provided', () => {
|
||||
const wrapper = mountComponent({
|
||||
type: 'completed',
|
||||
count: 3,
|
||||
thumbnailUrls: ['https://example.com/preview.png']
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('3 jobs completed')
|
||||
const image = wrapper.get('img')
|
||||
expect(image.attributes('src')).toBe('https://example.com/preview.png')
|
||||
expect(image.attributes('alt')).toBe('Preview')
|
||||
})
|
||||
|
||||
it('renders two completion thumbnail previews', () => {
|
||||
const wrapper = mountComponent({
|
||||
type: 'completed',
|
||||
count: 4,
|
||||
thumbnailUrls: [
|
||||
'https://example.com/preview-1.png',
|
||||
'https://example.com/preview-2.png'
|
||||
]
|
||||
})
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
expect(images.length).toBe(2)
|
||||
expect(images[0].attributes('src')).toBe(
|
||||
'https://example.com/preview-1.png'
|
||||
)
|
||||
expect(images[1].attributes('src')).toBe(
|
||||
'https://example.com/preview-2.png'
|
||||
)
|
||||
})
|
||||
|
||||
it('caps completion thumbnail previews at two', () => {
|
||||
const wrapper = mountComponent({
|
||||
type: 'completed',
|
||||
count: 4,
|
||||
thumbnailUrls: [
|
||||
'https://example.com/preview-1.png',
|
||||
'https://example.com/preview-2.png',
|
||||
'https://example.com/preview-3.png',
|
||||
'https://example.com/preview-4.png'
|
||||
]
|
||||
})
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
expect(images.length).toBe(2)
|
||||
expect(images[0].attributes('src')).toBe(
|
||||
'https://example.com/preview-1.png'
|
||||
)
|
||||
expect(images[1].attributes('src')).toBe(
|
||||
'https://example.com/preview-2.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user