diff --git a/browser_tests/tests/templates.spec.ts b/browser_tests/tests/templates.spec.ts index a891cc098..c40529740 100644 --- a/browser_tests/tests/templates.spec.ts +++ b/browser_tests/tests/templates.spec.ts @@ -142,4 +142,136 @@ test.describe('Templates', () => { // Expect the title to be used as fallback for the template categories await expect(comfyPage.page.getByLabel('FALLBACK CATEGORY')).toBeVisible() }) + + test('template cards are dynamically sized and responsive', async ({ + comfyPage + }) => { + // Open templates dialog + await comfyPage.executeCommand('Comfy.BrowseTemplates') + await expect(comfyPage.templates.content).toBeVisible() + + // Wait for at least one template card to appear + await expect(comfyPage.page.locator('.template-card').first()).toBeVisible({ + timeout: 5000 + }) + + // Take snapshot of the template grid + const templateGrid = comfyPage.templates.content.locator('.grid').first() + await expect(templateGrid).toBeVisible() + await expect(templateGrid).toHaveScreenshot('template-grid-desktop.png') + + // Check cards at mobile viewport size + await comfyPage.page.setViewportSize({ width: 640, height: 800 }) + await expect(templateGrid).toBeVisible() + await expect(templateGrid).toHaveScreenshot('template-grid-mobile.png') + + // Check cards at tablet size + await comfyPage.page.setViewportSize({ width: 1024, height: 800 }) + await expect(templateGrid).toBeVisible() + await expect(templateGrid).toHaveScreenshot('template-grid-tablet.png') + }) + + test('hover effects work on template cards', async ({ comfyPage }) => { + // Open templates dialog + await comfyPage.executeCommand('Comfy.BrowseTemplates') + await expect(comfyPage.templates.content).toBeVisible() + + // Get a template card + const firstCard = comfyPage.page.locator('.template-card').first() + await expect(firstCard).toBeVisible({ timeout: 5000 }) + + // Take snapshot before hover + await expect(firstCard).toHaveScreenshot('template-card-before-hover.png') + + // Hover over the card + await firstCard.hover() + + // Take snapshot after hover to verify hover effect + await expect(firstCard).toHaveScreenshot('template-card-after-hover.png') + }) + + test('template cards descriptions adjust height dynamically', async ({ + comfyPage + }) => { + // Setup test by intercepting templates response to inject cards with varying description lengths + await comfyPage.page.route('**/templates/index.json', async (route, _) => { + const response = [ + { + moduleName: 'default', + title: 'Test Templates', + type: 'image', + templates: [ + { + name: 'short-description', + title: 'Short Description', + mediaType: 'image', + mediaSubtype: 'webp', + description: 'This is a short description.' + }, + { + name: 'medium-description', + title: 'Medium Description', + mediaType: 'image', + mediaSubtype: 'webp', + description: + 'This is a medium length description that should take up two lines on most displays.' + }, + { + name: 'long-description', + title: 'Long Description', + mediaType: 'image', + mediaSubtype: 'webp', + description: + 'This is a much longer description that should definitely wrap to multiple lines. It contains enough text to demonstrate how the cards handle varying amounts of content while maintaining a consistent layout grid.' + } + ] + } + ] + await route.fulfill({ + status: 200, + body: JSON.stringify(response), + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store' + } + }) + }) + + // Mock the thumbnail images to avoid 404s + await comfyPage.page.route('**/templates/**.webp', async (route) => { + const headers = { + 'Content-Type': 'image/webp', + 'Cache-Control': 'no-store' + } + await route.fulfill({ + status: 200, + path: 'browser_tests/assets/example.webp', + headers + }) + }) + + // Open templates dialog + await comfyPage.executeCommand('Comfy.BrowseTemplates') + await expect(comfyPage.templates.content).toBeVisible() + + // Verify cards are visible with varying content lengths + await expect( + comfyPage.page.getByText('This is a short description.') + ).toBeVisible({ timeout: 5000 }) + await expect( + comfyPage.page.getByText('This is a medium length description') + ).toBeVisible({ timeout: 5000 }) + await expect( + comfyPage.page.getByText('This is a much longer description') + ).toBeVisible({ timeout: 5000 }) + + // Take snapshot of a grid with specific cards + const templateGrid = comfyPage.templates.content + .locator('.grid:has-text("Short Description")') + .first() + await expect(templateGrid).toBeVisible() + await expect(templateGrid).toHaveScreenshot( + 'template-grid-varying-content.png' + ) + }) }) diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-card-after-hover-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-card-after-hover-chromium-linux.png new file mode 100644 index 000000000..b767dbe20 Binary files /dev/null and b/browser_tests/tests/templates.spec.ts-snapshots/template-card-after-hover-chromium-linux.png differ diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-card-before-hover-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-card-before-hover-chromium-linux.png new file mode 100644 index 000000000..cfebed5da Binary files /dev/null and b/browser_tests/tests/templates.spec.ts-snapshots/template-card-before-hover-chromium-linux.png differ diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-desktop-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-desktop-chromium-linux.png new file mode 100644 index 000000000..92cb6b4f6 Binary files /dev/null and b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-desktop-chromium-linux.png differ diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-mobile-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-mobile-chromium-linux.png new file mode 100644 index 000000000..9bad33cd8 Binary files /dev/null and b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-mobile-chromium-linux.png differ diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-tablet-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-tablet-chromium-linux.png new file mode 100644 index 000000000..f4d2ead5c Binary files /dev/null and b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-tablet-chromium-linux.png differ diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png new file mode 100644 index 000000000..40452d452 Binary files /dev/null and b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png differ diff --git a/src/components/templates/TemplateWorkflowCard.spec.ts b/src/components/templates/TemplateWorkflowCard.spec.ts new file mode 100644 index 000000000..795bb66a7 --- /dev/null +++ b/src/components/templates/TemplateWorkflowCard.spec.ts @@ -0,0 +1,205 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' +import { ref } from 'vue' + +import TemplateWorkflowCard from '@/components/templates/TemplateWorkflowCard.vue' +import { TemplateInfo } from '@/types/workflowTemplateTypes' + +vi.mock('@/components/templates/thumbnails/AudioThumbnail.vue', () => ({ + default: { + name: 'AudioThumbnail', + template: '
', + props: ['src'] + } +})) + +vi.mock('@/components/templates/thumbnails/CompareSliderThumbnail.vue', () => ({ + default: { + name: 'CompareSliderThumbnail', + template: + '', + props: ['baseImageSrc', 'overlayImageSrc', 'alt', 'isHovered'] + } +})) + +vi.mock('@/components/templates/thumbnails/DefaultThumbnail.vue', () => ({ + default: { + name: 'DefaultThumbnail', + template: '', + props: ['src', 'alt', 'isHovered', 'isVideo', 'hoverZoom'] + } +})) + +vi.mock('@/components/templates/thumbnails/HoverDissolveThumbnail.vue', () => ({ + default: { + name: 'HoverDissolveThumbnail', + template: + '', + props: ['baseImageSrc', 'overlayImageSrc', 'alt', 'isHovered'] + } +})) + +vi.mock('@vueuse/core', () => ({ + useElementHover: () => ref(false) +})) + +vi.mock('@/scripts/api', () => ({ + api: { + fileURL: (path: string) => `/fileURL${path}`, + apiURL: (path: string) => `/apiURL${path}` + } +})) + +describe('TemplateWorkflowCard', () => { + const createTemplate = (overrides = {}): TemplateInfo => ({ + name: 'test-template', + mediaType: 'image', + mediaSubtype: 'png', + thumbnailVariant: 'default', + description: 'Test description', + ...overrides + }) + + const mountCard = (props = {}) => { + return mount(TemplateWorkflowCard, { + props: { + sourceModule: 'default', + categoryTitle: 'Test Category', + loading: false, + template: createTemplate(), + ...props + }, + global: { + stubs: { + Card: { + template: + '{{ description }}
',
+ ...slots
+ }
+ })
+ }
+
+ it('renders slot content', () => {
+ const wrapper = mountThumbnail()
+ expect(wrapper.find('img').exists()).toBe(true)
+ })
+
+ it('applies hover zoom with correct style', () => {
+ const wrapper = mountThumbnail({ isHovered: true })
+ const contentDiv = wrapper.find('.transform-gpu')
+ expect(contentDiv.attributes('style')).toContain('transform')
+ expect(contentDiv.attributes('style')).toContain('scale')
+ })
+
+ it('applies custom hover zoom value', () => {
+ const wrapper = mountThumbnail({ hoverZoom: 10, isHovered: true })
+ const contentDiv = wrapper.find('.transform-gpu')
+ expect(contentDiv.attributes('style')).toContain('scale(1.1)')
+ })
+
+ it('does not apply scale when not hovered', () => {
+ const wrapper = mountThumbnail({ isHovered: false })
+ const contentDiv = wrapper.find('.transform-gpu')
+ expect(contentDiv.attributes('style')).toBeUndefined()
+ })
+
+ it('shows error state when image fails to load', async () => {
+ const wrapper = mountThumbnail()
+ const vm = wrapper.vm as any
+
+ // Manually set error since useEventListener is mocked
+ vm.error = true
+ await nextTick()
+
+ expect(wrapper.find('.pi-file').exists()).toBe(true)
+ expect(wrapper.find('.transform-gpu').exists()).toBe(false)
+ })
+
+ it('applies transition classes to content', () => {
+ const wrapper = mountThumbnail()
+ const contentDiv = wrapper.find('.transform-gpu')
+ expect(contentDiv.classes()).toContain('transform-gpu')
+ expect(contentDiv.classes()).toContain('transition-transform')
+ expect(contentDiv.classes()).toContain('duration-1000')
+ expect(contentDiv.classes()).toContain('ease-out')
+ })
+})
diff --git a/src/components/templates/thumbnails/BaseThumbnail.vue b/src/components/templates/thumbnails/BaseThumbnail.vue
index da6ced350..ee4c0f3a4 100644
--- a/src/components/templates/thumbnails/BaseThumbnail.vue
+++ b/src/components/templates/thumbnails/BaseThumbnail.vue
@@ -3,7 +3,7 @@