use testing pinia

This commit is contained in:
bymyself
2025-09-09 18:59:10 -07:00
parent 16a23e9081
commit e885a0c93c
2 changed files with 42 additions and 311 deletions

View File

@@ -1,265 +0,0 @@
import { mount } from '@vue/test-utils'
import { describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import ImagePreview from './ImagePreview.vue'
describe('ImagePreview', () => {
const defaultProps = {
imageUrls: [
'/api/view?filename=test1.png&type=output',
'/api/view?filename=test2.png&type=output'
],
dimensions: '512 x 512'
}
const mountImagePreview = (props = {}) => {
return mount(ImagePreview, {
props: { ...defaultProps, ...props }
})
}
it('renders image preview when imageUrls provided', () => {
const wrapper = mountImagePreview()
expect(wrapper.find('.image-preview').exists()).toBe(true)
expect(wrapper.find('img').exists()).toBe(true)
expect(wrapper.find('img').attributes('src')).toBe(
defaultProps.imageUrls[0]
)
})
it('does not render when no imageUrls provided', () => {
const wrapper = mountImagePreview({ imageUrls: [] })
expect(wrapper.find('.image-preview').exists()).toBe(false)
})
it('displays dimensions correctly', () => {
const wrapper = mountImagePreview({ dimensions: '1024 x 768' })
expect(wrapper.text()).toContain('1024 x 768')
})
it('shows navigation dots for multiple images', () => {
const wrapper = mountImagePreview()
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
expect(navigationDots).toHaveLength(2)
})
it('does not show navigation dots for single image', () => {
const wrapper = mountImagePreview({
imageUrls: [defaultProps.imageUrls[0]]
})
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
expect(navigationDots).toHaveLength(0)
})
it('shows action buttons on hover', async () => {
const wrapper = mountImagePreview()
// Initially buttons should not be visible
expect(wrapper.find('.actions').exists()).toBe(false)
// Trigger hover
await wrapper.trigger('mouseenter')
await nextTick()
// Action buttons should now be visible
expect(wrapper.find('.actions').exists()).toBe(true)
expect(wrapper.findAll('.action-btn')).toHaveLength(3) // mask, download, remove
})
it('hides action buttons when not hovering', async () => {
const wrapper = mountImagePreview()
// Trigger hover
await wrapper.trigger('mouseenter')
await nextTick()
expect(wrapper.find('.actions').exists()).toBe(true)
// Trigger mouse leave
await wrapper.trigger('mouseleave')
await nextTick()
expect(wrapper.find('.actions').exists()).toBe(false)
})
it('shows mask/edit button only for single images', async () => {
// Multiple images - should not show mask button
const multipleImagesWrapper = mountImagePreview()
await multipleImagesWrapper.trigger('mouseenter')
await nextTick()
const maskButtonMultiple = multipleImagesWrapper.find('[title="Edit/Mask"]')
expect(maskButtonMultiple.exists()).toBe(false)
// Single image - should show mask button
const singleImageWrapper = mountImagePreview({
imageUrls: [defaultProps.imageUrls[0]]
})
await singleImageWrapper.trigger('mouseenter')
await nextTick()
const maskButtonSingle = singleImageWrapper.find('[title="Edit/Mask"]')
expect(maskButtonSingle.exists()).toBe(true)
})
it('handles action button clicks', async () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
const wrapper = mountImagePreview({
imageUrls: [defaultProps.imageUrls[0]]
})
await wrapper.trigger('mouseenter')
await nextTick()
// Test Edit/Mask button
await wrapper.find('[title="Edit/Mask"]').trigger('click')
expect(consoleSpy).toHaveBeenCalledWith(
'Edit/Mask clicked for:',
defaultProps.imageUrls[0]
)
// Test Remove button
await wrapper.find('[title="Remove"]').trigger('click')
expect(consoleSpy).toHaveBeenCalledWith(
'Remove clicked for:',
defaultProps.imageUrls[0]
)
consoleSpy.mockRestore()
})
it('handles download button click', async () => {
// Mock DOM methods for download test
const mockLink = {
href: '',
download: '',
click: vi.fn()
}
const mockCreateElement = vi
.spyOn(document, 'createElement')
.mockReturnValue(mockLink as any)
const mockAppendChild = vi
.spyOn(document.body, 'appendChild')
.mockImplementation(() => mockLink as any)
const mockRemoveChild = vi
.spyOn(document.body, 'removeChild')
.mockImplementation(() => mockLink as any)
const wrapper = mountImagePreview({
imageUrls: [defaultProps.imageUrls[0]]
})
await wrapper.trigger('mouseenter')
await nextTick()
// Test Download button
await wrapper.find('[title="Download"]').trigger('click')
expect(mockCreateElement).toHaveBeenCalledWith('a')
expect(mockLink.href).toBe(defaultProps.imageUrls[0])
expect(mockLink.click).toHaveBeenCalled()
mockCreateElement.mockRestore()
mockAppendChild.mockRestore()
mockRemoveChild.mockRestore()
})
it('switches images when navigation dots are clicked', async () => {
const wrapper = mountImagePreview()
// Initially shows first image
expect(wrapper.find('img').attributes('src')).toBe(
defaultProps.imageUrls[0]
)
// Click second navigation dot
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
await navigationDots[1].trigger('click')
await nextTick()
// Should now show second image
expect(wrapper.find('img').attributes('src')).toBe(
defaultProps.imageUrls[1]
)
})
it('applies correct classes to navigation dots based on current image', async () => {
const wrapper = mountImagePreview()
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
// First dot should be active (has bg-white class)
expect(navigationDots[0].classes()).toContain('bg-white')
expect(navigationDots[1].classes()).toContain('bg-white/50')
// Switch to second image
await navigationDots[1].trigger('click')
await nextTick()
// Second dot should now be active
expect(navigationDots[0].classes()).toContain('bg-white/50')
expect(navigationDots[1].classes()).toContain('bg-white')
})
it('updates dimensions when image loads', async () => {
const wrapper = mountImagePreview({ dimensions: undefined })
// Initially shows "Loading..."
expect(wrapper.text()).toContain('Loading...')
// Simulate image load event
const img = wrapper.find('img')
const mockLoadEvent = {
target: {
naturalWidth: 1024,
naturalHeight: 768
}
}
await img.trigger('load', mockLoadEvent)
await nextTick()
// Should now show actual dimensions
expect(wrapper.text()).toContain('1024 x 768')
})
it('handles image load errors gracefully', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
const wrapper = mountImagePreview()
const img = wrapper.find('img')
await img.trigger('error')
expect(consoleSpy).toHaveBeenCalledWith(
'Failed to load image:',
defaultProps.imageUrls[0]
)
consoleSpy.mockRestore()
})
it('has proper accessibility attributes', () => {
const wrapper = mountImagePreview()
const img = wrapper.find('img')
expect(img.attributes('alt')).toBe('Node output 1')
})
it('updates alt text when switching images', async () => {
const wrapper = mountImagePreview()
// Initially first image
expect(wrapper.find('img').attributes('alt')).toBe('Node output 1')
// Switch to second image
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
await navigationDots[1].trigger('click')
await nextTick()
// Alt text should update
expect(wrapper.find('img').attributes('alt')).toBe('Node output 2')
})
})

View File

@@ -1,8 +1,29 @@
import { describe, expect, it, vi } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useImagePreview } from '@/renderer/extensions/vueNodes/composables/useImagePreview'
vi.mock('@/stores/commandStore', () => ({
useCommandStore: vi.fn(() => ({
execute: vi.fn().mockResolvedValue(undefined)
}))
}))
vi.mock('@/stores/imagePreviewStore', () => ({
useNodeOutputStore: vi.fn(() => ({
removeNodeOutputs: vi.fn().mockReturnValue(true)
}))
}))
describe('useImagePreview', () => {
beforeEach(() => {
vi.clearAllMocks()
setActivePinia(createPinia())
})
afterEach(() => {
vi.restoreAllMocks()
})
const mockImageUrls = [
'/api/view?filename=test1.png&type=output',
'/api/view?filename=test2.png&type=output',
@@ -14,13 +35,26 @@ describe('useImagePreview', () => {
naturalWidth: number,
naturalHeight: number
): Event => {
const mockImg = {
naturalWidth,
naturalHeight,
// Create a mock that satisfies HTMLImageElement type checking
const mockImg = Object.create(HTMLImageElement.prototype)
// Define naturalWidth and naturalHeight as properties since they're readonly on HTMLImageElement
Object.defineProperty(mockImg, 'naturalWidth', {
value: naturalWidth,
writable: false,
configurable: true
})
Object.defineProperty(mockImg, 'naturalHeight', {
value: naturalHeight,
writable: false,
configurable: true
})
Object.assign(mockImg, {
addEventListener: vi.fn(),
dispatchEvent: vi.fn(),
removeEventListener: vi.fn()
} satisfies EventTarget & { naturalWidth: number; naturalHeight: number }
})
return {
target: mockImg,
@@ -100,19 +134,6 @@ describe('useImagePreview', () => {
expect(currentIndex.value).toBe(2) // Should remain unchanged
})
it('handles edit mask action', () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
const { handleEditMask } = useImagePreview(mockImageUrls)
handleEditMask()
expect(consoleSpy).toHaveBeenCalledWith(
'Edit/Mask clicked for:',
mockImageUrls[0]
)
consoleSpy.mockRestore()
})
it('handles download action', () => {
// Mock DOM methods
const mockLink = {
@@ -122,13 +143,13 @@ describe('useImagePreview', () => {
}
const mockCreateElement = vi
.spyOn(document, 'createElement')
.mockReturnValue(mockLink as any)
.mockReturnValue(mockLink as unknown as HTMLAnchorElement)
const mockAppendChild = vi
.spyOn(document.body, 'appendChild')
.mockImplementation(() => mockLink as any)
.mockImplementation(() => mockLink as unknown as HTMLAnchorElement)
const mockRemoveChild = vi
.spyOn(document.body, 'removeChild')
.mockImplementation(() => mockLink as any)
.mockImplementation(() => mockLink as unknown as HTMLAnchorElement)
const { handleDownload } = useImagePreview(mockImageUrls)
handleDownload()
@@ -144,19 +165,6 @@ describe('useImagePreview', () => {
mockRemoveChild.mockRestore()
})
it('handles remove action', () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
const { handleRemove } = useImagePreview(mockImageUrls)
handleRemove()
expect(consoleSpy).toHaveBeenCalledWith(
'Remove clicked for:',
mockImageUrls[0]
)
consoleSpy.mockRestore()
})
it('handles image load event correctly', () => {
const { handleImageLoad, actualDimensions } = useImagePreview(mockImageUrls)
@@ -188,16 +196,4 @@ describe('useImagePreview', () => {
setCurrentIndex(1)
expect(actualDimensions.value).toBeNull()
})
it('composable functions are properly defined', () => {
const { handleEditMask, handleDownload, handleRemove } =
useImagePreview(mockImageUrls)
expect(handleEditMask).toBeDefined()
expect(typeof handleEditMask).toBe('function')
expect(handleDownload).toBeDefined()
expect(typeof handleDownload).toBe('function')
expect(handleRemove).toBeDefined()
expect(typeof handleRemove).toBe('function')
})
})