diff --git a/src/components/templates/TemplateWorkflowView.spec.ts b/src/components/templates/TemplateWorkflowView.spec.ts index a70e828a5..6860797c6 100644 --- a/src/components/templates/TemplateWorkflowView.spec.ts +++ b/src/components/templates/TemplateWorkflowView.spec.ts @@ -1,5 +1,6 @@ import { mount } from '@vue/test-utils' import { describe, expect, it, vi } from 'vitest' +import { createI18n } from 'vue-i18n' import TemplateWorkflowView from '@/components/templates/TemplateWorkflowView.vue' import { TemplateInfo } from '@/types/workflowTemplateTypes' @@ -53,10 +54,46 @@ vi.mock('@/components/templates/TemplateWorkflowList.vue', () => ({ } })) +vi.mock('@/components/templates/TemplateSearchBar.vue', () => ({ + default: { + template: '', + props: ['searchQuery', 'filteredCount'], + emits: ['update:searchQuery', 'clearFilters'] + } +})) + +vi.mock('@/components/templates/TemplateWorkflowCardSkeleton.vue', () => ({ + default: { + template: '
' + } +})) + vi.mock('@vueuse/core', () => ({ useLocalStorage: () => 'grid' })) +vi.mock('@/composables/useIntersectionObserver', () => ({ + useIntersectionObserver: vi.fn() +})) + +vi.mock('@/composables/useLazyPagination', () => ({ + useLazyPagination: (items: any) => ({ + paginatedItems: items, + isLoading: { value: false }, + hasMoreItems: { value: false }, + loadNextPage: vi.fn(), + reset: vi.fn() + }) +})) + +vi.mock('@/composables/useTemplateFiltering', () => ({ + useTemplateFiltering: (templates: any) => ({ + searchQuery: { value: '' }, + filteredTemplates: templates, + filteredCount: { value: templates.value?.length || 0 } + }) +})) + describe('TemplateWorkflowView', () => { const createTemplate = (name: string): TemplateInfo => ({ name, @@ -67,6 +104,18 @@ describe('TemplateWorkflowView', () => { }) const mountView = (props = {}) => { + const i18n = createI18n({ + legacy: false, + locale: 'en', + messages: { + en: { + templateWorkflows: { + loadingMore: 'Loading more...' + } + } + } + }) + return mount(TemplateWorkflowView, { props: { title: 'Test Templates', @@ -79,6 +128,9 @@ describe('TemplateWorkflowView', () => { ], loading: null, ...props + }, + global: { + plugins: [i18n] } }) } diff --git a/src/components/templates/thumbnails/CompareSliderThumbnail.spec.ts b/src/components/templates/thumbnails/CompareSliderThumbnail.spec.ts index 7d0fcc9c9..681d81238 100644 --- a/src/components/templates/thumbnails/CompareSliderThumbnail.spec.ts +++ b/src/components/templates/thumbnails/CompareSliderThumbnail.spec.ts @@ -12,6 +12,15 @@ vi.mock('@/components/templates/thumbnails/BaseThumbnail.vue', () => ({ } })) +vi.mock('@/components/common/LazyImage.vue', () => ({ + default: { + name: 'LazyImage', + template: + '', + props: ['src', 'alt', 'imageClass', 'imageStyle'] + } +})) + vi.mock('@vueuse/core', () => ({ useMouseInElement: () => ({ elementX: ref(50), @@ -35,23 +44,24 @@ describe('CompareSliderThumbnail', () => { it('renders both base and overlay images', () => { const wrapper = mountThumbnail() - const images = wrapper.findAll('img') - expect(images.length).toBe(2) - expect(images[0].attributes('src')).toBe('/base-image.jpg') - expect(images[1].attributes('src')).toBe('/overlay-image.jpg') + const lazyImages = wrapper.findAllComponents({ name: 'LazyImage' }) + expect(lazyImages.length).toBe(2) + expect(lazyImages[0].props('src')).toBe('/base-image.jpg') + expect(lazyImages[1].props('src')).toBe('/overlay-image.jpg') }) it('applies correct alt text to both images', () => { const wrapper = mountThumbnail({ alt: 'Custom Alt Text' }) - const images = wrapper.findAll('img') - expect(images[0].attributes('alt')).toBe('Custom Alt Text') - expect(images[1].attributes('alt')).toBe('Custom Alt Text') + const lazyImages = wrapper.findAllComponents({ name: 'LazyImage' }) + expect(lazyImages[0].props('alt')).toBe('Custom Alt Text') + expect(lazyImages[1].props('alt')).toBe('Custom Alt Text') }) it('applies clip-path style to overlay image', () => { const wrapper = mountThumbnail() - const overlay = wrapper.findAll('img')[1] - expect(overlay.attributes('style')).toContain('clip-path') + const overlayLazyImage = wrapper.findAllComponents({ name: 'LazyImage' })[1] + const imageStyle = overlayLazyImage.props('imageStyle') + expect(imageStyle.clipPath).toContain('inset') }) it('renders slider divider', () => { diff --git a/src/components/templates/thumbnails/DefaultThumbnail.spec.ts b/src/components/templates/thumbnails/DefaultThumbnail.spec.ts index bb754c0dd..ebe138a9e 100644 --- a/src/components/templates/thumbnails/DefaultThumbnail.spec.ts +++ b/src/components/templates/thumbnails/DefaultThumbnail.spec.ts @@ -11,6 +11,15 @@ vi.mock('@/components/templates/thumbnails/BaseThumbnail.vue', () => ({ } })) +vi.mock('@/components/common/LazyImage.vue', () => ({ + default: { + name: 'LazyImage', + template: + '', + props: ['src', 'alt', 'imageClass', 'imageStyle'] + } +})) + describe('DefaultThumbnail', () => { const mountThumbnail = (props = {}) => { return mount(DefaultThumbnail, { @@ -25,9 +34,9 @@ describe('DefaultThumbnail', () => { it('renders image with correct src and alt', () => { const wrapper = mountThumbnail() - const img = wrapper.find('img') - expect(img.attributes('src')).toBe('/test-image.jpg') - expect(img.attributes('alt')).toBe('Test Image') + const lazyImage = wrapper.findComponent({ name: 'LazyImage' }) + expect(lazyImage.props('src')).toBe('/test-image.jpg') + expect(lazyImage.props('alt')).toBe('Test Image') }) it('applies scale transform when hovered', () => { @@ -35,35 +44,43 @@ describe('DefaultThumbnail', () => { isHovered: true, hoverZoom: 10 }) - const img = wrapper.find('img') - expect(img.attributes('style')).toContain('scale(1.1)') + const lazyImage = wrapper.findComponent({ name: 'LazyImage' }) + expect(lazyImage.props('imageStyle')).toEqual({ transform: 'scale(1.1)' }) }) it('does not apply scale transform when not hovered', () => { const wrapper = mountThumbnail({ isHovered: false }) - const img = wrapper.find('img') - expect(img.attributes('style')).toBeUndefined() + const lazyImage = wrapper.findComponent({ name: 'LazyImage' }) + expect(lazyImage.props('imageStyle')).toBeUndefined() }) it('applies video styling for video type', () => { const wrapper = mountThumbnail({ isVideo: true }) - const img = wrapper.find('img') - expect(img.classes()).toContain('w-full') - expect(img.classes()).toContain('h-full') - expect(img.classes()).toContain('object-cover') + const lazyImage = wrapper.findComponent({ name: 'LazyImage' }) + const imageClass = lazyImage.props('imageClass') + const classString = Array.isArray(imageClass) + ? imageClass.join(' ') + : imageClass + expect(classString).toContain('w-full') + expect(classString).toContain('h-full') + expect(classString).toContain('object-cover') }) it('applies image styling for non-video type', () => { const wrapper = mountThumbnail({ isVideo: false }) - const img = wrapper.find('img') - expect(img.classes()).toContain('max-w-full') - expect(img.classes()).toContain('object-contain') + const lazyImage = wrapper.findComponent({ name: 'LazyImage' }) + const imageClass = lazyImage.props('imageClass') + const classString = Array.isArray(imageClass) + ? imageClass.join(' ') + : imageClass + expect(classString).toContain('max-w-full') + expect(classString).toContain('object-contain') }) it('applies correct styling for webp images', () => { @@ -71,8 +88,12 @@ describe('DefaultThumbnail', () => { src: '/test-video.webp', isVideo: true }) - const img = wrapper.find('img') - expect(img.classes()).toContain('object-cover') + const lazyImage = wrapper.findComponent({ name: 'LazyImage' }) + const imageClass = lazyImage.props('imageClass') + const classString = Array.isArray(imageClass) + ? imageClass.join(' ') + : imageClass + expect(classString).toContain('object-cover') }) it('image is not draggable', () => { @@ -83,11 +104,15 @@ describe('DefaultThumbnail', () => { it('applies transition classes', () => { const wrapper = mountThumbnail() - const img = wrapper.find('img') - expect(img.classes()).toContain('transform-gpu') - expect(img.classes()).toContain('transition-transform') - expect(img.classes()).toContain('duration-300') - expect(img.classes()).toContain('ease-out') + const lazyImage = wrapper.findComponent({ name: 'LazyImage' }) + const imageClass = lazyImage.props('imageClass') + const classString = Array.isArray(imageClass) + ? imageClass.join(' ') + : imageClass + expect(classString).toContain('transform-gpu') + expect(classString).toContain('transition-transform') + expect(classString).toContain('duration-300') + expect(classString).toContain('ease-out') }) it('passes correct props to BaseThumbnail', () => { diff --git a/src/components/templates/thumbnails/HoverDissolveThumbnail.spec.ts b/src/components/templates/thumbnails/HoverDissolveThumbnail.spec.ts index b8b8b77d7..4ca00c668 100644 --- a/src/components/templates/thumbnails/HoverDissolveThumbnail.spec.ts +++ b/src/components/templates/thumbnails/HoverDissolveThumbnail.spec.ts @@ -11,6 +11,15 @@ vi.mock('@/components/templates/thumbnails/BaseThumbnail.vue', () => ({ } })) +vi.mock('@/components/common/LazyImage.vue', () => ({ + default: { + name: 'LazyImage', + template: + '', + props: ['src', 'alt', 'imageClass', 'imageStyle'] + } +})) + describe('HoverDissolveThumbnail', () => { const mountThumbnail = (props = {}) => { return mount(HoverDissolveThumbnail, { @@ -27,31 +36,39 @@ describe('HoverDissolveThumbnail', () => { it('renders both base and overlay images', () => { const wrapper = mountThumbnail() - const images = wrapper.findAll('img') - expect(images.length).toBe(2) - expect(images[0].attributes('src')).toBe('/base-image.jpg') - expect(images[1].attributes('src')).toBe('/overlay-image.jpg') + const lazyImages = wrapper.findAllComponents({ name: 'LazyImage' }) + expect(lazyImages.length).toBe(2) + expect(lazyImages[0].props('src')).toBe('/base-image.jpg') + expect(lazyImages[1].props('src')).toBe('/overlay-image.jpg') }) it('applies correct alt text to both images', () => { const wrapper = mountThumbnail({ alt: 'Custom Alt Text' }) - const images = wrapper.findAll('img') - expect(images[0].attributes('alt')).toBe('Custom Alt Text') - expect(images[1].attributes('alt')).toBe('Custom Alt Text') + const lazyImages = wrapper.findAllComponents({ name: 'LazyImage' }) + expect(lazyImages[0].props('alt')).toBe('Custom Alt Text') + expect(lazyImages[1].props('alt')).toBe('Custom Alt Text') }) it('makes overlay image visible when hovered', () => { const wrapper = mountThumbnail({ isHovered: true }) - const overlayImage = wrapper.findAll('img')[1] - expect(overlayImage.classes()).toContain('opacity-100') - expect(overlayImage.classes()).not.toContain('opacity-0') + const overlayLazyImage = wrapper.findAllComponents({ name: 'LazyImage' })[1] + const imageClass = overlayLazyImage.props('imageClass') + const classString = Array.isArray(imageClass) + ? imageClass.join(' ') + : imageClass + expect(classString).toContain('opacity-100') + expect(classString).not.toContain('opacity-0') }) it('makes overlay image hidden when not hovered', () => { const wrapper = mountThumbnail({ isHovered: false }) - const overlayImage = wrapper.findAll('img')[1] - expect(overlayImage.classes()).toContain('opacity-0') - expect(overlayImage.classes()).not.toContain('opacity-100') + const overlayLazyImage = wrapper.findAllComponents({ name: 'LazyImage' })[1] + const imageClass = overlayLazyImage.props('imageClass') + const classString = Array.isArray(imageClass) + ? imageClass.join(' ') + : imageClass + expect(classString).toContain('opacity-0') + expect(classString).not.toContain('opacity-100') }) it('passes isHovered prop to BaseThumbnail', () => { @@ -62,21 +79,33 @@ describe('HoverDissolveThumbnail', () => { it('applies transition classes to overlay image', () => { const wrapper = mountThumbnail() - const overlayImage = wrapper.findAll('img')[1] - expect(overlayImage.classes()).toContain('transition-opacity') - expect(overlayImage.classes()).toContain('duration-300') + const overlayLazyImage = wrapper.findAllComponents({ name: 'LazyImage' })[1] + const imageClass = overlayLazyImage.props('imageClass') + const classString = Array.isArray(imageClass) + ? imageClass.join(' ') + : imageClass + expect(classString).toContain('transition-opacity') + expect(classString).toContain('duration-300') }) it('applies correct positioning to both images', () => { const wrapper = mountThumbnail() - const images = wrapper.findAll('img') + const lazyImages = wrapper.findAllComponents({ name: 'LazyImage' }) // Check base image - expect(images[0].classes()).toContain('absolute') - expect(images[0].classes()).toContain('inset-0') + const baseImageClass = lazyImages[0].props('imageClass') + const baseClassString = Array.isArray(baseImageClass) + ? baseImageClass.join(' ') + : baseImageClass + expect(baseClassString).toContain('absolute') + expect(baseClassString).toContain('inset-0') // Check overlay image - expect(images[1].classes()).toContain('absolute') - expect(images[1].classes()).toContain('inset-0') + const overlayImageClass = lazyImages[1].props('imageClass') + const overlayClassString = Array.isArray(overlayImageClass) + ? overlayImageClass.join(' ') + : overlayImageClass + expect(overlayClassString).toContain('absolute') + expect(overlayClassString).toContain('inset-0') }) })