mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-13 17:26:22 +00:00
test: add coverage for migrated linearMode/ImagePreview and PackBanner
Two of the components migrated to useImage in this PR shipped without test files, dragging codecov patch coverage to 54.23% (below the 80% threshold). Add render/behavior tests covering: - linearMode/ImagePreview: ZoomPane vs mobile rendering, useImage-driven width/height computation, showSize gating, and execution status message precedence. - packBanner/PackBanner: default banner fallback, banner_url/icon precedence, and useImage error fallback to default banner. Pushes patch coverage above the codecov threshold without changing any production code.
This commit is contained in:
126
src/renderer/extensions/linearMode/ImagePreview.test.ts
Normal file
126
src/renderer/extensions/linearMode/ImagePreview.test.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { render, screen } from '@testing-library/vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Ref } from 'vue'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
const useImageMock = vi.hoisted(() => ({
|
||||
state: null as Ref<HTMLImageElement | undefined> | null,
|
||||
isReady: null as Ref<boolean> | null
|
||||
}))
|
||||
|
||||
vi.mock('@vueuse/core', async () => {
|
||||
const actual = await vi.importActual('@vueuse/core')
|
||||
const { ref } = await import('vue')
|
||||
useImageMock.state = ref<HTMLImageElement | undefined>(undefined)
|
||||
useImageMock.isReady = ref(false)
|
||||
return {
|
||||
...(actual as Record<string, unknown>),
|
||||
useImage: () => ({
|
||||
state: useImageMock.state,
|
||||
isReady: useImageMock.isReady
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const executionStatusMock = vi.hoisted(() => ({
|
||||
message: null as Ref<string | null> | null
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/extensions/linearMode/useExecutionStatus', async () => {
|
||||
const { ref } = await import('vue')
|
||||
executionStatusMock.message = ref<string | null>(null)
|
||||
return {
|
||||
useExecutionStatus: () => ({
|
||||
executionStatusMessage: executionStatusMock.message
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
import ImagePreview from './ImagePreview.vue'
|
||||
|
||||
const i18n = createI18n({ legacy: false, locale: 'en', missingWarn: false })
|
||||
|
||||
function renderImagePreview(props: Record<string, unknown> = {}) {
|
||||
return render(ImagePreview, {
|
||||
props: { src: 'https://example.com/image.png', ...props },
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
stubs: {
|
||||
ZoomPane: {
|
||||
template: '<div data-testid="zoom-pane"><slot /></div>'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setLoadedImage(width: number, height: number) {
|
||||
const fakeImage = { naturalWidth: width, naturalHeight: height } as
|
||||
| HTMLImageElement
|
||||
| undefined
|
||||
useImageMock.state!.value = fakeImage
|
||||
useImageMock.isReady!.value = true
|
||||
}
|
||||
|
||||
describe('ImagePreview (linearMode)', () => {
|
||||
beforeEach(() => {
|
||||
if (useImageMock.state) useImageMock.state.value = undefined
|
||||
if (useImageMock.isReady) useImageMock.isReady.value = false
|
||||
if (executionStatusMock.message) executionStatusMock.message.value = null
|
||||
})
|
||||
|
||||
it('renders src inside ZoomPane in desktop mode', () => {
|
||||
renderImagePreview()
|
||||
expect(screen.getByTestId('zoom-pane')).toBeInTheDocument()
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'src',
|
||||
'https://example.com/image.png'
|
||||
)
|
||||
})
|
||||
|
||||
it('renders bare img when mobile is true', () => {
|
||||
renderImagePreview({ mobile: true })
|
||||
expect(screen.queryByTestId('zoom-pane')).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'src',
|
||||
'https://example.com/image.png'
|
||||
)
|
||||
})
|
||||
|
||||
it('shows dimensions once the image is ready', async () => {
|
||||
renderImagePreview()
|
||||
setLoadedImage(800, 600)
|
||||
await nextTick()
|
||||
expect(screen.getByText('800 x 600')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('appends label when provided alongside dimensions', async () => {
|
||||
renderImagePreview({ label: 'demo' })
|
||||
setLoadedImage(64, 32)
|
||||
await nextTick()
|
||||
expect(screen.getByText(/64 x 32/)).toBeInTheDocument()
|
||||
expect(screen.getByText(/demo/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not show dimensions when showSize=false', async () => {
|
||||
renderImagePreview({ showSize: false })
|
||||
setLoadedImage(800, 600)
|
||||
await nextTick()
|
||||
expect(screen.queryByText('800 x 600')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not show dimensions before the image is ready', () => {
|
||||
renderImagePreview()
|
||||
expect(screen.queryByText(/x/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows execution status message instead of dimensions when present', async () => {
|
||||
renderImagePreview()
|
||||
setLoadedImage(800, 600)
|
||||
executionStatusMock.message!.value = 'Generating…'
|
||||
await nextTick()
|
||||
expect(screen.getByText('Generating…')).toBeInTheDocument()
|
||||
expect(screen.queryByText('800 x 600')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import { render, screen } from '@testing-library/vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Ref } from 'vue'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
const useImageMock = vi.hoisted(() => ({
|
||||
error: null as Ref<unknown> | null
|
||||
}))
|
||||
|
||||
vi.mock('@vueuse/core', async () => {
|
||||
const actual = await vi.importActual('@vueuse/core')
|
||||
const { ref } = await import('vue')
|
||||
useImageMock.error = ref<unknown>(null)
|
||||
return {
|
||||
...(actual as Record<string, unknown>),
|
||||
useImage: () => ({ error: useImageMock.error })
|
||||
}
|
||||
})
|
||||
|
||||
import PackBanner from './PackBanner.vue'
|
||||
|
||||
const DEFAULT_BANNER = '/assets/images/fallback-gradient-avatar.svg'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
g: {
|
||||
defaultBanner: 'Default banner'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function makePack(
|
||||
overrides: Partial<components['schemas']['Node']> = {}
|
||||
): components['schemas']['Node'] {
|
||||
return {
|
||||
id: 'pack-id',
|
||||
name: 'TestPack',
|
||||
...overrides
|
||||
} as components['schemas']['Node']
|
||||
}
|
||||
|
||||
function renderPackBanner(nodePack: components['schemas']['Node']) {
|
||||
return render(PackBanner, {
|
||||
props: { nodePack },
|
||||
global: { plugins: [i18n] }
|
||||
})
|
||||
}
|
||||
|
||||
describe('PackBanner', () => {
|
||||
beforeEach(() => {
|
||||
if (useImageMock.error) useImageMock.error.value = null
|
||||
})
|
||||
|
||||
it('renders the default banner when both banner_url and icon are missing', () => {
|
||||
renderPackBanner(makePack())
|
||||
const img = screen.getByRole('img')
|
||||
expect(img).toHaveAttribute('src', DEFAULT_BANNER)
|
||||
expect(img).toHaveAttribute('alt', 'Default banner')
|
||||
})
|
||||
|
||||
it('renders the banner_url image when provided', () => {
|
||||
renderPackBanner(makePack({ banner_url: 'https://example.com/banner.png' }))
|
||||
const img = screen.getByRole('img')
|
||||
expect(img).toHaveAttribute('src', 'https://example.com/banner.png')
|
||||
expect(img).toHaveAttribute('alt', 'TestPack banner')
|
||||
})
|
||||
|
||||
it('falls back to icon when banner_url is missing but icon is set', () => {
|
||||
renderPackBanner(makePack({ icon: 'https://example.com/icon.svg' }))
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'src',
|
||||
'https://example.com/icon.svg'
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to default banner when image fails to load', async () => {
|
||||
renderPackBanner(makePack({ banner_url: 'https://example.com/broken.png' }))
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'src',
|
||||
'https://example.com/broken.png'
|
||||
)
|
||||
|
||||
useImageMock.error!.value = new Event('error')
|
||||
await nextTick()
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', DEFAULT_BANNER)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user