mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
make Vue nodes' outputs/previews responsively sized and work with node resizing (#5970)
## Summary Added dedicated component for sampling previews and change all image outputs (outputs, videos, previews) to be responsive and respond to node resizing. https://github.com/user-attachments/assets/7e683d32-4914-460c-ba08-4573c40aef24 ## Changes - **What**: Implemented `LivePreview` component for mid-execution sampling visualization with responsive layout system - **Dependencies**: Added resize handle composable and transform state integration ## Review Focus Node resize interaction conflicts with canvas dragging, and image dimension calculation performance during rapid sampling updates. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5970-make-Vue-nodes-outputs-previews-responsively-sized-and-work-with-node-resizing-2866d73d365081508d53e6e286a9a3fe) by [Unito](https://www.unito.io) --------- Co-authored-by: DrJKL <DrJKL@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import LivePreview from '@/renderer/extensions/vueNodes/components/LivePreview.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
g: {
|
||||
liveSamplingPreview: 'Live sampling preview',
|
||||
imageFailedToLoad: 'Image failed to load',
|
||||
errorLoadingImage: 'Error loading image',
|
||||
calculatingDimensions: 'Calculating dimensions'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('LivePreview', () => {
|
||||
const defaultProps = {
|
||||
imageUrl: '/api/view?filename=test_sample.png&type=temp'
|
||||
}
|
||||
|
||||
const mountLivePreview = (props = {}) => {
|
||||
return mount(LivePreview, {
|
||||
props: { ...defaultProps, ...props },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
stubs: {
|
||||
'i-lucide:image-off': true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('renders preview when imageUrl provided', () => {
|
||||
const wrapper = mountLivePreview()
|
||||
|
||||
expect(wrapper.find('img').exists()).toBe(true)
|
||||
expect(wrapper.find('img').attributes('src')).toBe(defaultProps.imageUrl)
|
||||
})
|
||||
|
||||
it('does not render when no imageUrl provided', () => {
|
||||
const wrapper = mountLivePreview({ imageUrl: null })
|
||||
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
expect(wrapper.text()).toBe('')
|
||||
})
|
||||
|
||||
it('displays calculating dimensions text initially', () => {
|
||||
const wrapper = mountLivePreview()
|
||||
|
||||
expect(wrapper.text()).toContain('Calculating dimensions')
|
||||
})
|
||||
|
||||
it('has proper accessibility attributes', () => {
|
||||
const wrapper = mountLivePreview()
|
||||
|
||||
const img = wrapper.find('img')
|
||||
expect(img.attributes('alt')).toBe('Live sampling preview')
|
||||
})
|
||||
|
||||
it('handles image load event', async () => {
|
||||
const wrapper = mountLivePreview()
|
||||
const img = wrapper.find('img')
|
||||
|
||||
// Mock the naturalWidth and naturalHeight properties on the img element
|
||||
Object.defineProperty(img.element, 'naturalWidth', {
|
||||
writable: false,
|
||||
value: 512
|
||||
})
|
||||
Object.defineProperty(img.element, 'naturalHeight', {
|
||||
writable: false,
|
||||
value: 512
|
||||
})
|
||||
|
||||
// Trigger the load event
|
||||
await img.trigger('load')
|
||||
|
||||
expect(wrapper.text()).toContain('512 x 512')
|
||||
})
|
||||
|
||||
it('handles image error state', async () => {
|
||||
const wrapper = mountLivePreview()
|
||||
const img = wrapper.find('img')
|
||||
|
||||
// Trigger the error event
|
||||
await img.trigger('error')
|
||||
|
||||
// Check that the image is hidden and error content is shown
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
expect(wrapper.text()).toContain('Image failed to load')
|
||||
})
|
||||
|
||||
it('resets state when imageUrl changes', async () => {
|
||||
const wrapper = mountLivePreview()
|
||||
const img = wrapper.find('img')
|
||||
|
||||
// Set error state via event
|
||||
await img.trigger('error')
|
||||
expect(wrapper.text()).toContain('Error loading image')
|
||||
|
||||
// Change imageUrl prop
|
||||
await wrapper.setProps({ imageUrl: '/new-image.png' })
|
||||
await nextTick()
|
||||
|
||||
// State should be reset - dimensions text should show calculating
|
||||
expect(wrapper.text()).toContain('Calculating dimensions')
|
||||
expect(wrapper.text()).not.toContain('Error loading image')
|
||||
})
|
||||
|
||||
it('shows error state when image fails to load', async () => {
|
||||
const wrapper = mountLivePreview()
|
||||
const img = wrapper.find('img')
|
||||
|
||||
// Trigger error event
|
||||
await img.trigger('error')
|
||||
|
||||
// Should show error state instead of image
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
expect(wrapper.text()).toContain('Image failed to load')
|
||||
expect(wrapper.text()).toContain('Error loading image')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user