diff --git a/src/renderer/extensions/linearMode/ImagePreview.test.ts b/src/renderer/extensions/linearMode/ImagePreview.test.ts new file mode 100644 index 0000000000..5c95c5cb3f --- /dev/null +++ b/src/renderer/extensions/linearMode/ImagePreview.test.ts @@ -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 | null, + isReady: null as Ref | null +})) + +vi.mock('@vueuse/core', async () => { + const actual = await vi.importActual('@vueuse/core') + const { ref } = await import('vue') + useImageMock.state = ref(undefined) + useImageMock.isReady = ref(false) + return { + ...(actual as Record), + useImage: () => ({ + state: useImageMock.state, + isReady: useImageMock.isReady + }) + } +}) + +const executionStatusMock = vi.hoisted(() => ({ + message: null as Ref | null +})) + +vi.mock('@/renderer/extensions/linearMode/useExecutionStatus', async () => { + const { ref } = await import('vue') + executionStatusMock.message = ref(null) + return { + useExecutionStatus: () => ({ + executionStatusMessage: executionStatusMock.message + }) + } +}) + +import ImagePreview from './ImagePreview.vue' + +const i18n = createI18n({ legacy: false, locale: 'en', missingWarn: false }) + +function renderImagePreview(props: Record = {}) { + return render(ImagePreview, { + props: { src: 'https://example.com/image.png', ...props }, + global: { + plugins: [i18n], + stubs: { + ZoomPane: { + template: '
' + } + } + } + }) +} + +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() + }) +}) diff --git a/src/workbench/extensions/manager/components/manager/packBanner/PackBanner.test.ts b/src/workbench/extensions/manager/components/manager/packBanner/PackBanner.test.ts new file mode 100644 index 0000000000..39c4b047ab --- /dev/null +++ b/src/workbench/extensions/manager/components/manager/packBanner/PackBanner.test.ts @@ -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 | null +})) + +vi.mock('@vueuse/core', async () => { + const actual = await vi.importActual('@vueuse/core') + const { ref } = await import('vue') + useImageMock.error = ref(null) + return { + ...(actual as Record), + 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'] { + 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) + }) +})