mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
[test] Add component test for image compare widget (#5549)
## Summary Added comprehensive component test suite for WidgetImageCompare widget with 410 test assertions covering display, edge cases, and integration scenarios. ## Changes - **What**: Created [Vue Test Utils](https://vue-test-utils.vuejs.org/) test suite for [WidgetImageCompare component](src/renderer/extensions/vueNodes/widgets/components/WidgetImageCompare.vue) using [Vitest](https://vitest.dev/) testing framework ## Review Focus Test coverage completeness for string vs object value handling, accessibility attribute propagation, and edge case robustness including malformed URLs and empty states. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5549-test-Add-component-test-for-image-compare-widget-26e6d73d365081189fe0d010f87d1eec) by [Unito](https://www.unito.io) --------- Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import ImageCompare from 'primevue/imagecompare'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
|
||||
import WidgetImageCompare, {
|
||||
type ImageCompareValue
|
||||
} from './WidgetImageCompare.vue'
|
||||
|
||||
describe('WidgetImageCompare Display', () => {
|
||||
const createMockWidget = (
|
||||
value: ImageCompareValue | string,
|
||||
options: SimplifiedWidget['options'] = {}
|
||||
): SimplifiedWidget<ImageCompareValue | string> => ({
|
||||
name: 'test_imagecompare',
|
||||
type: 'object',
|
||||
value,
|
||||
options
|
||||
})
|
||||
|
||||
const mountComponent = (
|
||||
widget: SimplifiedWidget<ImageCompareValue | string>,
|
||||
readonly = false
|
||||
) => {
|
||||
return mount(WidgetImageCompare, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
components: { ImageCompare }
|
||||
},
|
||||
props: {
|
||||
widget,
|
||||
readonly
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('Component Rendering', () => {
|
||||
it('renders imagecompare component with proper structure and styling', () => {
|
||||
const value: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg'
|
||||
}
|
||||
const widget = createMockWidget(value)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
// Component exists
|
||||
const imageCompare = wrapper.findComponent({ name: 'ImageCompare' })
|
||||
expect(imageCompare.exists()).toBe(true)
|
||||
|
||||
// Renders both images with correct URLs
|
||||
const images = wrapper.findAll('img')
|
||||
expect(images).toHaveLength(2)
|
||||
expect(images[0].attributes('src')).toBe('https://example.com/before.jpg')
|
||||
expect(images[1].attributes('src')).toBe('https://example.com/after.jpg')
|
||||
|
||||
// Images have proper styling classes
|
||||
images.forEach((img) => {
|
||||
expect(img.classes()).toContain('object-cover')
|
||||
expect(img.classes()).toContain('w-full')
|
||||
expect(img.classes()).toContain('h-full')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Object Value Input', () => {
|
||||
it('handles alt text correctly - custom, default, and empty', () => {
|
||||
// Test custom alt text
|
||||
const customAltValue: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg',
|
||||
beforeAlt: 'Original design',
|
||||
afterAlt: 'Updated design'
|
||||
}
|
||||
const customWrapper = mountComponent(createMockWidget(customAltValue))
|
||||
const customImages = customWrapper.findAll('img')
|
||||
expect(customImages[0].attributes('alt')).toBe('Original design')
|
||||
expect(customImages[1].attributes('alt')).toBe('Updated design')
|
||||
|
||||
// Test default alt text
|
||||
const defaultAltValue: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg'
|
||||
}
|
||||
const defaultWrapper = mountComponent(createMockWidget(defaultAltValue))
|
||||
const defaultImages = defaultWrapper.findAll('img')
|
||||
expect(defaultImages[0].attributes('alt')).toBe('Before image')
|
||||
expect(defaultImages[1].attributes('alt')).toBe('After image')
|
||||
|
||||
// Test empty string alt text (falls back to default)
|
||||
const emptyAltValue: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg',
|
||||
beforeAlt: '',
|
||||
afterAlt: ''
|
||||
}
|
||||
const emptyWrapper = mountComponent(createMockWidget(emptyAltValue))
|
||||
const emptyImages = emptyWrapper.findAll('img')
|
||||
expect(emptyImages[0].attributes('alt')).toBe('Before image')
|
||||
expect(emptyImages[1].attributes('alt')).toBe('After image')
|
||||
})
|
||||
|
||||
it('handles missing and partial image URLs gracefully', () => {
|
||||
// Missing URLs
|
||||
const missingValue: ImageCompareValue = { before: '', after: '' }
|
||||
const missingWrapper = mountComponent(createMockWidget(missingValue))
|
||||
const missingImages = missingWrapper.findAll('img')
|
||||
expect(missingImages[0].attributes('src')).toBe('')
|
||||
expect(missingImages[1].attributes('src')).toBe('')
|
||||
|
||||
// Partial URLs
|
||||
const partialValue: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: ''
|
||||
}
|
||||
const partialWrapper = mountComponent(createMockWidget(partialValue))
|
||||
const partialImages = partialWrapper.findAll('img')
|
||||
expect(partialImages[0].attributes('src')).toBe(
|
||||
'https://example.com/before.jpg'
|
||||
)
|
||||
expect(partialImages[1].attributes('src')).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('String Value Input', () => {
|
||||
it('handles string value as before image only', () => {
|
||||
const value = 'https://example.com/single.jpg'
|
||||
const widget = createMockWidget(value)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
expect(images[0].attributes('src')).toBe('https://example.com/single.jpg')
|
||||
expect(images[1].attributes('src')).toBe('')
|
||||
})
|
||||
|
||||
it('uses default alt text for string values', () => {
|
||||
const value = 'https://example.com/single.jpg'
|
||||
const widget = createMockWidget(value)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
expect(images[0].attributes('alt')).toBe('Before image')
|
||||
expect(images[1].attributes('alt')).toBe('After image')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Widget Options Handling', () => {
|
||||
it('passes through accessibility options', () => {
|
||||
const value: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg'
|
||||
}
|
||||
const widget = createMockWidget(value, {
|
||||
tabindex: 1,
|
||||
ariaLabel: 'Compare images',
|
||||
ariaLabelledby: 'compare-label'
|
||||
})
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
const imageCompare = wrapper.findComponent({ name: 'ImageCompare' })
|
||||
expect(imageCompare.props('tabindex')).toBe(1)
|
||||
expect(imageCompare.props('ariaLabel')).toBe('Compare images')
|
||||
expect(imageCompare.props('ariaLabelledby')).toBe('compare-label')
|
||||
})
|
||||
|
||||
it('uses default tabindex when not provided', () => {
|
||||
const value: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg'
|
||||
}
|
||||
const widget = createMockWidget(value)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
const imageCompare = wrapper.findComponent({ name: 'ImageCompare' })
|
||||
expect(imageCompare.props('tabindex')).toBe(0)
|
||||
})
|
||||
|
||||
it('passes through PrimeVue specific options', () => {
|
||||
const value: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg'
|
||||
}
|
||||
const widget = createMockWidget(value, {
|
||||
unstyled: true,
|
||||
pt: { root: { class: 'custom-class' } },
|
||||
ptOptions: { mergeSections: true }
|
||||
})
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
const imageCompare = wrapper.findComponent({ name: 'ImageCompare' })
|
||||
expect(imageCompare.props('unstyled')).toBe(true)
|
||||
expect(imageCompare.props('pt')).toEqual({
|
||||
root: { class: 'custom-class' }
|
||||
})
|
||||
expect(imageCompare.props('ptOptions')).toEqual({ mergeSections: true })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Readonly Mode', () => {
|
||||
it('renders normally in readonly mode (no interaction restrictions)', () => {
|
||||
const value: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg'
|
||||
}
|
||||
const widget = createMockWidget(value)
|
||||
const wrapper = mountComponent(widget, true)
|
||||
|
||||
// ImageCompare is display-only, readonly doesn't affect rendering
|
||||
const imageCompare = wrapper.findComponent({ name: 'ImageCompare' })
|
||||
expect(imageCompare.exists()).toBe(true)
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
expect(images).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('handles null or undefined widget value', () => {
|
||||
const widget = createMockWidget('')
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
expect(images[0].attributes('src')).toBe('')
|
||||
expect(images[1].attributes('src')).toBe('')
|
||||
expect(images[0].attributes('alt')).toBe('Before image')
|
||||
expect(images[1].attributes('alt')).toBe('After image')
|
||||
})
|
||||
|
||||
it('handles empty object value', () => {
|
||||
const value: ImageCompareValue = {} as ImageCompareValue
|
||||
const widget = createMockWidget(value)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
expect(images[0].attributes('src')).toBe('')
|
||||
expect(images[1].attributes('src')).toBe('')
|
||||
})
|
||||
|
||||
it('handles malformed object value', () => {
|
||||
const value = { randomProp: 'test', before: '', after: '' }
|
||||
const widget = createMockWidget(value)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
expect(images[0].attributes('src')).toBe('')
|
||||
expect(images[1].attributes('src')).toBe('')
|
||||
})
|
||||
|
||||
it('handles special content - long URLs, special characters, and long alt text', () => {
|
||||
// Test very long URLs
|
||||
const longUrl = 'https://example.com/' + 'a'.repeat(1000) + '.jpg'
|
||||
const longUrlValue: ImageCompareValue = {
|
||||
before: longUrl,
|
||||
after: longUrl
|
||||
}
|
||||
const longUrlWrapper = mountComponent(createMockWidget(longUrlValue))
|
||||
const longUrlImages = longUrlWrapper.findAll('img')
|
||||
expect(longUrlImages[0].attributes('src')).toBe(longUrl)
|
||||
expect(longUrlImages[1].attributes('src')).toBe(longUrl)
|
||||
|
||||
// Test special characters in URLs
|
||||
const specialUrl =
|
||||
'https://example.com/path with spaces & symbols!@#$.jpg'
|
||||
const specialUrlValue: ImageCompareValue = {
|
||||
before: specialUrl,
|
||||
after: specialUrl
|
||||
}
|
||||
const specialUrlWrapper = mountComponent(
|
||||
createMockWidget(specialUrlValue)
|
||||
)
|
||||
const specialUrlImages = specialUrlWrapper.findAll('img')
|
||||
expect(specialUrlImages[0].attributes('src')).toBe(specialUrl)
|
||||
expect(specialUrlImages[1].attributes('src')).toBe(specialUrl)
|
||||
|
||||
// Test very long alt text
|
||||
const longAlt =
|
||||
'Very long alt text that exceeds normal length: ' +
|
||||
'description '.repeat(50)
|
||||
const longAltValue: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg',
|
||||
beforeAlt: longAlt,
|
||||
afterAlt: longAlt
|
||||
}
|
||||
const longAltWrapper = mountComponent(createMockWidget(longAltValue))
|
||||
const longAltImages = longAltWrapper.findAll('img')
|
||||
expect(longAltImages[0].attributes('alt')).toBe(longAlt)
|
||||
expect(longAltImages[1].attributes('alt')).toBe(longAlt)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Template Structure', () => {
|
||||
it('correctly assigns images to left and right template slots', () => {
|
||||
const value: ImageCompareValue = {
|
||||
before: 'https://example.com/before.jpg',
|
||||
after: 'https://example.com/after.jpg'
|
||||
}
|
||||
const widget = createMockWidget(value)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
const images = wrapper.findAll('img')
|
||||
// First image (before) should be in left template slot
|
||||
expect(images[0].attributes('src')).toBe('https://example.com/before.jpg')
|
||||
// Second image (after) should be in right template slot
|
||||
expect(images[1].attributes('src')).toBe('https://example.com/after.jpg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Integration', () => {
|
||||
it('works with various URL types - data URLs and blob URLs', () => {
|
||||
// Test data URLs
|
||||
const dataUrl =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='
|
||||
const dataUrlValue: ImageCompareValue = {
|
||||
before: dataUrl,
|
||||
after: dataUrl
|
||||
}
|
||||
const dataUrlWrapper = mountComponent(createMockWidget(dataUrlValue))
|
||||
const dataUrlImages = dataUrlWrapper.findAll('img')
|
||||
expect(dataUrlImages[0].attributes('src')).toBe(dataUrl)
|
||||
expect(dataUrlImages[1].attributes('src')).toBe(dataUrl)
|
||||
|
||||
// Test blob URLs
|
||||
const blobUrl =
|
||||
'blob:http://example.com/12345678-1234-1234-1234-123456789012'
|
||||
const blobUrlValue: ImageCompareValue = {
|
||||
before: blobUrl,
|
||||
after: blobUrl
|
||||
}
|
||||
const blobUrlWrapper = mountComponent(createMockWidget(blobUrlValue))
|
||||
const blobUrlImages = blobUrlWrapper.findAll('img')
|
||||
expect(blobUrlImages[0].attributes('src')).toBe(blobUrl)
|
||||
expect(blobUrlImages[1].attributes('src')).toBe(blobUrl)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -30,7 +30,7 @@ import { computed } from 'vue'
|
||||
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
|
||||
interface ImageCompareValue {
|
||||
export interface ImageCompareValue {
|
||||
before: string
|
||||
after: string
|
||||
beforeAlt?: string
|
||||
|
||||
Reference in New Issue
Block a user