mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
make topbar badges responsive and fix server health badges showing on unrelated dialogs (#6291)
## Summary Implemented responsive topbar badges with three display modes (full, compact, icon-only) using Tailwind breakpoints and PrimeVue Popover interactions. https://github.com/user-attachments/assets/57912253-b1b5-4a68-953e-0be942ff09c4 ## Changes - **What**: Replaced hardcoded 880px breakpoint with [Tailwind breakpoints](https://tailwindcss.com/docs/responsive-design) via [@vueuse/core](https://vueuse.org/core/useBreakpoints/) - `xl (≥1280px)`: Full display (icon + label + text) - `lg (≥1024px)`: Compact (icon + label, click for popover) - `<lg (<1024px)`: Icon-only (icon/label/dot, click for popover) - **What**: Added `CloudBadge` component to isolate static "Comfy Cloud BETA" badge from runtime store badges - **What**: Added `backgroundColor` prop to support different contexts (topbar vs dialog backgrounds) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6291-make-topbar-badges-responsive-and-fix-server-health-badges-showing-on-unrelated-dialogs-2986d73d365081d294e5c9a7af1aafb2) by [Unito](https://www.unito.io)
This commit is contained in:
231
src/components/topbar/TopbarBadge.test.ts
Normal file
231
src/components/topbar/TopbarBadge.test.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Popover from 'primevue/popover'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Tooltip from 'primevue/tooltip'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import type { TopbarBadge as TopbarBadgeType } from '@/types/comfy'
|
||||
|
||||
import TopbarBadge from './TopbarBadge.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {}
|
||||
}
|
||||
})
|
||||
|
||||
describe('TopbarBadge', () => {
|
||||
const exampleBadge: TopbarBadgeType = {
|
||||
text: 'Test Badge',
|
||||
label: 'BETA',
|
||||
variant: 'info'
|
||||
}
|
||||
|
||||
const mountTopbarBadge = (
|
||||
badge: Partial<TopbarBadgeType> = {},
|
||||
displayMode: 'full' | 'compact' | 'icon-only' = 'full'
|
||||
) => {
|
||||
return mount(TopbarBadge, {
|
||||
global: {
|
||||
plugins: [PrimeVue, i18n],
|
||||
directives: { tooltip: Tooltip },
|
||||
components: { Popover }
|
||||
},
|
||||
props: {
|
||||
badge: { ...exampleBadge, ...badge },
|
||||
displayMode
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('full display mode', () => {
|
||||
it('renders all badge elements (icon, label, text)', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Comfy Cloud',
|
||||
label: 'BETA',
|
||||
icon: 'pi pi-cloud'
|
||||
},
|
||||
'full'
|
||||
)
|
||||
|
||||
expect(wrapper.find('.pi-cloud').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('BETA')
|
||||
expect(wrapper.text()).toContain('Comfy Cloud')
|
||||
})
|
||||
|
||||
it('renders without icon when not provided', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Test',
|
||||
label: 'NEW'
|
||||
},
|
||||
'full'
|
||||
)
|
||||
|
||||
expect(wrapper.find('i').exists()).toBe(false)
|
||||
expect(wrapper.text()).toContain('NEW')
|
||||
expect(wrapper.text()).toContain('Test')
|
||||
})
|
||||
})
|
||||
|
||||
describe('compact display mode', () => {
|
||||
it('renders icon and label but not text', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Hidden Text',
|
||||
label: 'BETA',
|
||||
icon: 'pi pi-cloud'
|
||||
},
|
||||
'compact'
|
||||
)
|
||||
|
||||
expect(wrapper.find('.pi-cloud').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('BETA')
|
||||
expect(wrapper.text()).not.toContain('Hidden Text')
|
||||
})
|
||||
|
||||
it('opens popover on click', async () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Full Text',
|
||||
label: 'ALERT'
|
||||
},
|
||||
'compact'
|
||||
)
|
||||
|
||||
const clickableArea = wrapper.find('[class*="flex h-full"]')
|
||||
await clickableArea.trigger('click')
|
||||
|
||||
const popover = wrapper.findComponent(Popover)
|
||||
expect(popover.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('icon-only display mode', () => {
|
||||
it('renders only icon', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Hidden Text',
|
||||
label: 'BETA',
|
||||
icon: 'pi pi-cloud'
|
||||
},
|
||||
'icon-only'
|
||||
)
|
||||
|
||||
expect(wrapper.find('.pi-cloud').exists()).toBe(true)
|
||||
expect(wrapper.text()).not.toContain('BETA')
|
||||
expect(wrapper.text()).not.toContain('Hidden Text')
|
||||
})
|
||||
|
||||
it('renders label when no icon provided', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Hidden Text',
|
||||
label: 'NEW'
|
||||
},
|
||||
'icon-only'
|
||||
)
|
||||
|
||||
expect(wrapper.text()).toContain('NEW')
|
||||
expect(wrapper.text()).not.toContain('Hidden Text')
|
||||
})
|
||||
})
|
||||
|
||||
describe('badge variants', () => {
|
||||
it('applies error variant styles', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Error Message',
|
||||
label: 'ERROR',
|
||||
variant: 'error'
|
||||
},
|
||||
'full'
|
||||
)
|
||||
|
||||
expect(wrapper.find('.bg-danger-100').exists()).toBe(true)
|
||||
expect(wrapper.find('.text-danger-100').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('applies warning variant styles', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Warning Message',
|
||||
label: 'WARN',
|
||||
variant: 'warning'
|
||||
},
|
||||
'full'
|
||||
)
|
||||
|
||||
expect(wrapper.find('.bg-warning-100').exists()).toBe(true)
|
||||
expect(wrapper.find('.text-warning-100').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('uses default error icon for error variant', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Error',
|
||||
variant: 'error'
|
||||
},
|
||||
'full'
|
||||
)
|
||||
|
||||
expect(wrapper.find('.pi-exclamation-circle').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('uses default warning icon for warning variant', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Warning',
|
||||
variant: 'warning'
|
||||
},
|
||||
'full'
|
||||
)
|
||||
|
||||
expect(wrapper.find('.pi-exclamation-triangle').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('popover', () => {
|
||||
it('includes popover component in compact and icon-only modes', () => {
|
||||
const compactWrapper = mountTopbarBadge({}, 'compact')
|
||||
const iconOnlyWrapper = mountTopbarBadge({}, 'icon-only')
|
||||
const fullWrapper = mountTopbarBadge({}, 'full')
|
||||
|
||||
expect(compactWrapper.findComponent(Popover).exists()).toBe(true)
|
||||
expect(iconOnlyWrapper.findComponent(Popover).exists()).toBe(true)
|
||||
expect(fullWrapper.findComponent(Popover).exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('handles badge with only text', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Simple Badge'
|
||||
},
|
||||
'full'
|
||||
)
|
||||
|
||||
expect(wrapper.text()).toContain('Simple Badge')
|
||||
expect(wrapper.find('i').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('handles custom icon override', () => {
|
||||
const wrapper = mountTopbarBadge(
|
||||
{
|
||||
text: 'Custom',
|
||||
variant: 'error',
|
||||
icon: 'pi pi-custom-icon'
|
||||
},
|
||||
'full'
|
||||
)
|
||||
|
||||
expect(wrapper.find('.pi-custom-icon').exists()).toBe(true)
|
||||
expect(wrapper.find('.pi-exclamation-circle').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user