diff --git a/src/renderer/extensions/vueNodes/components/LivePreview.test.ts b/src/renderer/extensions/vueNodes/components/LivePreview.test.ts index 2b57f523be..71fce90302 100644 --- a/src/renderer/extensions/vueNodes/components/LivePreview.test.ts +++ b/src/renderer/extensions/vueNodes/components/LivePreview.test.ts @@ -1,9 +1,32 @@ import { createTestingPinia } from '@pinia/testing' -import { fireEvent, render, screen } from '@testing-library/vue' -import { describe, expect, it, vi } from 'vitest' +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, + error: 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) + useImageMock.error = ref(null) + return { + ...(actual as Record), + useImage: () => ({ + state: useImageMock.state, + isReady: useImageMock.isReady, + error: useImageMock.error + }) + } +}) + import LivePreview from '@/renderer/extensions/vueNodes/components/LivePreview.vue' const i18n = createI18n({ @@ -21,6 +44,19 @@ const i18n = createI18n({ } }) +function makeFakeLoadedImage(width: number, height: number): HTMLImageElement { + const img = new Image() + Object.defineProperty(img, 'naturalWidth', { + configurable: true, + value: width + }) + Object.defineProperty(img, 'naturalHeight', { + configurable: true, + value: height + }) + return img +} + describe('LivePreview', () => { const defaultProps = { imageUrl: '/api/view?filename=test_sample.png&type=temp' @@ -43,6 +79,12 @@ describe('LivePreview', () => { }) } + beforeEach(() => { + useImageMock.state!.value = undefined + useImageMock.isReady!.value = false + useImageMock.error!.value = null + }) + it('renders preview when imageUrl provided', () => { renderLivePreview() @@ -74,54 +116,62 @@ describe('LivePreview', () => { it('handles image load event', async () => { const { container } = renderLivePreview() - const img = screen.getByRole('img') - Object.defineProperty(img, 'naturalWidth', { - writable: false, - value: 512 - }) - Object.defineProperty(img, 'naturalHeight', { - writable: false, - value: 512 - }) - - await fireEvent.load(img) + useImageMock.state!.value = makeFakeLoadedImage(512, 512) + useImageMock.isReady!.value = true await nextTick() expect(container.textContent).toContain('512 x 512') }) + it('keeps last good dimensions when imageUrl changes (no flicker)', async () => { + const { container, rerender } = renderLivePreview() + + useImageMock.state!.value = makeFakeLoadedImage(800, 600) + useImageMock.isReady!.value = true + await nextTick() + expect(container.textContent).toContain('800 x 600') + + // Simulate the source changing during live preview streaming. useImage + // would normally reset isReady to false until the next image is ready. + useImageMock.isReady!.value = false + await rerender({ + imageUrl: '/api/view?filename=test_sample_2.png&type=temp' + }) + await nextTick() + + // Dimensions should still display, not flicker back to "Calculating". + expect(container.textContent).toContain('800 x 600') + expect(container.textContent).not.toContain('Calculating dimensions') + }) + it('handles image error state', async () => { renderLivePreview() - const img = screen.getByRole('img') - - await fireEvent.error(img) + useImageMock.error!.value = new Event('error') await nextTick() expect(screen.queryByRole('img')).not.toBeInTheDocument() screen.getByText('Image failed to load') }) - it('resets state when imageUrl changes', async () => { + it('resets error state when imageUrl changes', async () => { const { container, rerender } = renderLivePreview() - const img = screen.getByRole('img') - await fireEvent.error(img) + useImageMock.error!.value = new Event('error') await nextTick() expect(container.textContent).toContain('Error loading image') + // useImage resets error automatically when src changes. + useImageMock.error!.value = null await rerender({ imageUrl: '/new-image.png' }) await nextTick() - expect(container.textContent).toContain('Calculating dimensions') expect(container.textContent).not.toContain('Error loading image') }) it('shows error state when image fails to load', async () => { const { container } = renderLivePreview() - const img = screen.getByRole('img') - - await fireEvent.error(img) + useImageMock.error!.value = new Event('error') await nextTick() expect(screen.queryByRole('img')).not.toBeInTheDocument() diff --git a/src/renderer/extensions/vueNodes/components/LivePreview.vue b/src/renderer/extensions/vueNodes/components/LivePreview.vue index 546d758217..a074110045 100644 --- a/src/renderer/extensions/vueNodes/components/LivePreview.vue +++ b/src/renderer/extensions/vueNodes/components/LivePreview.vue @@ -12,8 +12,6 @@ :src="imageUrl" :alt="$t('g.liveSamplingPreview')" class="pointer-events-none min-h-55 w-full flex-1 object-contain contain-size" - @load="handleImageLoad" - @error="handleImageError" />
{{ @@ -26,7 +24,9 @@