mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
refactor: migrate consumers from PrimeVue Badge and StatusBadge to Badge
Replace PrimeVue Badge in TreeExplorerTreeNode, UsageLogsTable, and SearchFilterChip. Replace StatusBadge with Badge in all consumer components. Replace PrimeVue Chip with native HTML in SearchFilterChip. Update customerEventsService severity values to match Badge variants. Add E2E visual regression tests for badge rendering.
This commit is contained in:
@@ -85,7 +85,10 @@ export class ComfyNodeSearchBox {
|
||||
}
|
||||
|
||||
async removeFilter(index: number) {
|
||||
await this.filterChips.nth(index).locator('.p-chip-remove-icon').click()
|
||||
await this.filterChips
|
||||
.nth(index)
|
||||
.getByRole('button', { name: 'Remove' })
|
||||
.click()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
86
browser_tests/tests/badgeVisual.spec.ts
Normal file
86
browser_tests/tests/badgeVisual.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { expect } from '@playwright/test'
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe(
|
||||
'Badge visual regression',
|
||||
{ tag: ['@screenshot', '@ui'] },
|
||||
() => {
|
||||
async function dismissToasts(comfyPage: { page: Page }) {
|
||||
const toastCloseButtons = comfyPage.page.locator('.p-toast-close-button')
|
||||
const count = await toastCloseButtons.count()
|
||||
for (let i = 0; i < count; i++) {
|
||||
await toastCloseButtons
|
||||
.nth(i)
|
||||
.click()
|
||||
.catch(() => {})
|
||||
}
|
||||
if (count > 0) {
|
||||
await comfyPage.page
|
||||
.locator('.p-toast-message')
|
||||
.first()
|
||||
.waitFor({ state: 'hidden', timeout: 3000 })
|
||||
.catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
test.describe('SearchFilterChip badge', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.NodeSearchBoxImpl',
|
||||
'v1 (legacy)'
|
||||
)
|
||||
await dismissToasts(comfyPage)
|
||||
})
|
||||
|
||||
test('Single filter chip renders correctly', async ({ comfyPage }) => {
|
||||
await comfyPage.canvasOps.doubleClick()
|
||||
await comfyPage.searchBox.addFilter('CONDITIONING', 'Input Type')
|
||||
|
||||
const searchContainer = comfyPage.page.locator(
|
||||
'.comfy-vue-node-search-container'
|
||||
)
|
||||
await expect(searchContainer).toHaveScreenshot(
|
||||
'filter-chip-conditioning.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Multiple filter chips render correctly', async ({ comfyPage }) => {
|
||||
await comfyPage.canvasOps.doubleClick()
|
||||
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
|
||||
await comfyPage.searchBox.addFilter('CLIP', 'Output Type')
|
||||
|
||||
const searchContainer = comfyPage.page.locator(
|
||||
'.comfy-vue-node-search-container'
|
||||
)
|
||||
await expect(searchContainer).toHaveScreenshot(
|
||||
'filter-chips-multiple.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Node library tree badge', () => {
|
||||
test('Folder node count badge renders correctly', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await dismissToasts(comfyPage)
|
||||
|
||||
const sidebarButton = comfyPage.page.getByRole('button', {
|
||||
name: 'Node Library'
|
||||
})
|
||||
await sidebarButton.click()
|
||||
|
||||
const sidebar = comfyPage.page.getByRole('complementary', {
|
||||
name: 'Sidebar'
|
||||
})
|
||||
await sidebar
|
||||
.getByRole('treeitem')
|
||||
.first()
|
||||
.waitFor({ state: 'visible', timeout: 10000 })
|
||||
|
||||
await expect(sidebar).toHaveScreenshot('node-library-tree-badges.png')
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -110,7 +110,7 @@ test.describe('Node search box', { tag: '@node' }, () => {
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.canvasOps.disconnectEdge()
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
await comfyPage.page.locator('.p-chip-remove-icon').click()
|
||||
await comfyPage.searchBox.removeFilter(0)
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler', {
|
||||
exact: true
|
||||
})
|
||||
|
||||
@@ -44,7 +44,7 @@ const mountActionbar = (showRunProgressBar: boolean) => {
|
||||
name: 'Panel',
|
||||
template: '<div><slot /></div>'
|
||||
},
|
||||
StatusBadge: true,
|
||||
Badge: true,
|
||||
ComfyRunButton: {
|
||||
name: 'ComfyRunButton',
|
||||
template: '<button type="button">Run</button>'
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<span class="text-sm font-normal tabular-nums">
|
||||
{{ activeJobsLabel }}
|
||||
</span>
|
||||
<StatusBadge
|
||||
<Badge
|
||||
v-if="activeJobsCount > 0"
|
||||
data-testid="active-jobs-indicator"
|
||||
variant="dot"
|
||||
@@ -104,7 +104,7 @@ import { computed, nextTick, ref, watch } from 'vue'
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import StatusBadge from '@/components/common/StatusBadge.vue'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import QueueInlineProgress from '@/components/queue/QueueInlineProgress.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useQueueFeatureFlags } from '@/composables/queue/useQueueFeatureFlags'
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
<template>
|
||||
<Chip removable @remove="emit('remove', $event)">
|
||||
<Badge size="small" :class="semanticBadgeClass">
|
||||
{{ badge }}
|
||||
</Badge>
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-2xl bg-surface-700 py-0.5 pr-1 pl-2 text-xs"
|
||||
>
|
||||
<Badge :label="badge" :class="semanticBadgeClass" />
|
||||
{{ text }}
|
||||
</Chip>
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="$t('g.remove')"
|
||||
class="inline-flex cursor-pointer items-center justify-center rounded-full p-0.5 hover:bg-surface-600"
|
||||
@click="emit('remove', $event)"
|
||||
>
|
||||
<i
|
||||
class="icon-[lucide--x] size-3 text-muted-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Badge from 'primevue/badge'
|
||||
import Chip from 'primevue/chip'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
|
||||
export interface SearchFilter {
|
||||
text: string
|
||||
badge: string
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import { statusBadgeVariants } from './statusBadge.variants'
|
||||
import type { StatusBadgeVariants } from './statusBadge.variants'
|
||||
|
||||
const {
|
||||
label,
|
||||
severity = 'default',
|
||||
variant,
|
||||
class: className
|
||||
} = defineProps<{
|
||||
label?: string | number
|
||||
severity?: StatusBadgeVariants['severity']
|
||||
variant?: StatusBadgeVariants['variant']
|
||||
class?: string
|
||||
}>()
|
||||
|
||||
const badgeClass = computed(() =>
|
||||
cn(
|
||||
statusBadgeVariants({
|
||||
severity,
|
||||
variant: variant ?? (label == null ? 'dot' : 'label')
|
||||
}),
|
||||
className
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="badgeClass">
|
||||
{{ label }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Badge from 'primevue/badge'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
|
||||
@@ -61,8 +61,7 @@ describe('TreeExplorerTreeNode', () => {
|
||||
expect(wrapper.findComponent(EditableText).props('modelValue')).toBe(
|
||||
'Test Node'
|
||||
)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
expect(wrapper.findComponent(Badge).props()['value'].toString()).toBe('3')
|
||||
expect(wrapper.findComponent(Badge).props('label')).toBe('3')
|
||||
})
|
||||
|
||||
it('makes node label editable when renamingEditingNode matches', async () => {
|
||||
|
||||
@@ -23,9 +23,10 @@
|
||||
</span>
|
||||
<Badge
|
||||
v-if="showNodeBadgeText"
|
||||
:value="nodeBadgeText"
|
||||
:label="nodeBadgeText"
|
||||
severity="secondary"
|
||||
class="leaf-count-badge"
|
||||
:variant="nodeBadgeText.length > 1 ? 'label' : 'circle'"
|
||||
class="ml-2"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -38,7 +39,7 @@
|
||||
|
||||
<script setup lang="ts" generic="T">
|
||||
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview'
|
||||
import Badge from 'primevue/badge'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import { computed, inject, ref } from 'vue'
|
||||
|
||||
import EditableText from '@/components/common/EditableText.vue'
|
||||
@@ -146,9 +147,6 @@ if (props.node.droppable) {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.leaf-count-badge {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.node-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { VariantProps } from 'cva'
|
||||
import { cva } from 'cva'
|
||||
|
||||
export const statusBadgeVariants = cva({
|
||||
base: 'inline-flex items-center justify-center rounded-full',
|
||||
variants: {
|
||||
severity: {
|
||||
default: 'bg-primary-background text-base-foreground',
|
||||
secondary: 'bg-secondary-background text-base-foreground',
|
||||
warn: 'bg-warning-background text-base-background',
|
||||
danger: 'bg-destructive-background text-white',
|
||||
contrast: 'bg-base-foreground text-base-background'
|
||||
},
|
||||
variant: {
|
||||
label: 'h-3.5 px-1 text-3xs font-semibold uppercase',
|
||||
dot: 'size-2',
|
||||
circle: 'size-3.5 text-3xs font-semibold'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
severity: 'default',
|
||||
variant: 'label'
|
||||
}
|
||||
})
|
||||
|
||||
export type StatusBadgeVariants = VariantProps<typeof statusBadgeVariants>
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Badge from 'primevue/badge'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import Column from 'primevue/column'
|
||||
import PrimeVue from 'primevue/config'
|
||||
@@ -125,13 +125,13 @@ describe('UsageLogsTable', () => {
|
||||
mockCustomerEventsService.getEventSeverity.mockImplementation((type) => {
|
||||
switch (type) {
|
||||
case EventType.CREDIT_ADDED:
|
||||
return 'success'
|
||||
return 'default'
|
||||
case EventType.ACCOUNT_CREATED:
|
||||
return 'info'
|
||||
return 'secondary'
|
||||
case EventType.API_USAGE_COMPLETED:
|
||||
return 'warning'
|
||||
return 'warn'
|
||||
default:
|
||||
return 'info'
|
||||
return 'secondary'
|
||||
}
|
||||
})
|
||||
mockCustomerEventsService.formatAmount.mockImplementation((amount) => {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<Column field="event_type" :header="$t('credits.eventType')">
|
||||
<template #body="{ data }">
|
||||
<Badge
|
||||
:value="customerEventService.formatEventType(data.event_type)"
|
||||
:label="customerEventService.formatEventType(data.event_type)"
|
||||
:severity="customerEventService.getEventSeverity(data.event_type)"
|
||||
/>
|
||||
</template>
|
||||
@@ -91,7 +91,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Badge from 'primevue/badge'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import Column from 'primevue/column'
|
||||
import DataTable from 'primevue/datatable'
|
||||
import Message from 'primevue/message'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Loader from '@/components/loader/Loader.vue'
|
||||
import StatusBadge from '@/components/common/StatusBadge.vue'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import type { AssetDownload } from '@/stores/assetDownloadStore'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
@@ -40,11 +40,11 @@ const isPending = computed(() => job.status === 'created')
|
||||
<i
|
||||
class="icon-[lucide--circle-alert] size-4 text-destructive-background"
|
||||
/>
|
||||
<StatusBadge :label="t('progressToast.failed')" severity="danger" />
|
||||
<Badge :label="t('progressToast.failed')" severity="danger" />
|
||||
</template>
|
||||
|
||||
<template v-else-if="isCompleted">
|
||||
<StatusBadge :label="t('progressToast.finished')" severity="contrast" />
|
||||
<Badge :label="t('progressToast.finished')" severity="contrast" />
|
||||
</template>
|
||||
|
||||
<template v-else-if="isRunning">
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<span ref="textRef" class="min-w-0 truncate">
|
||||
<slot />
|
||||
</span>
|
||||
<StatusBadge
|
||||
<Badge
|
||||
v-if="badge !== undefined"
|
||||
:label="String(badge)"
|
||||
severity="contrast"
|
||||
@@ -33,7 +33,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import StatusBadge from '@/components/common/StatusBadge.vue'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import type { NavItemData } from '@/types/navTypes'
|
||||
|
||||
import NavIcon from './NavIcon.vue'
|
||||
|
||||
@@ -119,9 +119,10 @@
|
||||
@click.stop="handleSelect"
|
||||
>
|
||||
{{ $t('g.use') }}
|
||||
<StatusBadge
|
||||
<Badge
|
||||
v-if="isNewlyImported"
|
||||
severity="contrast"
|
||||
variant="dot"
|
||||
class="absolute -top-0.5 -right-0.5"
|
||||
/>
|
||||
</Button>
|
||||
@@ -137,7 +138,7 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconGroup from '@/components/button/IconGroup.vue'
|
||||
import MoreButton from '@/components/button/MoreButton.vue'
|
||||
import StatusBadge from '@/components/common/StatusBadge.vue'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import { showConfirmDialog } from '@/components/dialog/confirm/confirmDialog'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import AssetBadgeGroup from '@/platform/assets/components/AssetBadgeGroup.vue'
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"
|
||||
@click="$emit('stepClick', step.name)"
|
||||
>
|
||||
<StatusBadge
|
||||
<Badge
|
||||
:label="step.number"
|
||||
variant="circle"
|
||||
severity="contrast"
|
||||
@@ -67,7 +67,7 @@ import { computed } from 'vue'
|
||||
|
||||
import { vAutoAnimate } from '@formkit/auto-animate/vue'
|
||||
|
||||
import StatusBadge from '@/components/common/StatusBadge.vue'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type { ComfyHubPublishStep } from '@/platform/workflow/sharing/composables/useComfyHubPublishWizard'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<span class="text-sm font-bold text-text-primary">
|
||||
{{ subscriptionTierName }}
|
||||
</span>
|
||||
<StatusBadge
|
||||
<Badge
|
||||
v-if="isCancelled"
|
||||
:label="$t('subscription.canceled')"
|
||||
severity="warn"
|
||||
@@ -365,7 +365,7 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
|
||||
import StatusBadge from '@/components/common/StatusBadge.vue'
|
||||
import Badge from '@/components/common/Badge.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useBillingContext } from '@/composables/billing/useBillingContext'
|
||||
import { useBillingOperationStore } from '@/platform/workspace/stores/billingOperationStore'
|
||||
|
||||
@@ -216,15 +216,17 @@ describe('useCustomerEventsService', () => {
|
||||
|
||||
describe('getEventSeverity', () => {
|
||||
it('should return correct severity for known event types', () => {
|
||||
expect(service.getEventSeverity(EventType.CREDIT_ADDED)).toBe('success')
|
||||
expect(service.getEventSeverity(EventType.ACCOUNT_CREATED)).toBe('info')
|
||||
expect(service.getEventSeverity(EventType.CREDIT_ADDED)).toBe('default')
|
||||
expect(service.getEventSeverity(EventType.ACCOUNT_CREATED)).toBe(
|
||||
'secondary'
|
||||
)
|
||||
expect(service.getEventSeverity(EventType.API_USAGE_COMPLETED)).toBe(
|
||||
'warning'
|
||||
'warn'
|
||||
)
|
||||
})
|
||||
|
||||
it('should return default severity for unknown event types', () => {
|
||||
expect(service.getEventSeverity('unknown_event')).toBe('info')
|
||||
expect(service.getEventSeverity('unknown_event')).toBe('secondary')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { AxiosError, AxiosResponse } from 'axios'
|
||||
import axios from 'axios'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import type { BadgeVariants } from '@/components/common/badge.variants'
|
||||
import { getComfyApiBaseUrl } from '@/config/comfyApi'
|
||||
import { d } from '@/i18n'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
@@ -134,16 +135,18 @@ export const useCustomerEventsService = () => {
|
||||
return value
|
||||
}
|
||||
|
||||
function getEventSeverity(eventType: string) {
|
||||
function getEventSeverity(
|
||||
eventType: string
|
||||
): NonNullable<BadgeVariants['severity']> {
|
||||
switch (eventType) {
|
||||
case 'credit_added':
|
||||
return 'success'
|
||||
return 'default'
|
||||
case 'account_created':
|
||||
return 'info'
|
||||
return 'secondary'
|
||||
case 'api_usage_completed':
|
||||
return 'warning'
|
||||
return 'warn'
|
||||
default:
|
||||
return 'info'
|
||||
return 'secondary'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user