diff --git a/browser_tests/tests/templates.spec.ts b/browser_tests/tests/templates.spec.ts index 9141e9135..16f9b24a6 100644 --- a/browser_tests/tests/templates.spec.ts +++ b/browser_tests/tests/templates.spec.ts @@ -80,6 +80,12 @@ test.describe('Templates', () => { // Load a template await comfyPage.executeCommand('Comfy.BrowseTemplates') await expect(comfyPage.templates.content).toBeVisible() + + await comfyPage.page + .locator( + 'nav > div:nth-child(2) > div > span:has-text("Getting Started")' + ) + .click() await comfyPage.templates.loadTemplate('default') await expect(comfyPage.templates.content).toBeHidden() @@ -102,48 +108,72 @@ test.describe('Templates', () => { expect(await comfyPage.templates.content.isVisible()).toBe(true) }) - test('Uses title field as fallback when the key is not found in locales', async ({ + test('Uses proper locale files for templates', async ({ comfyPage }) => { + // Set locale to French before opening templates + await comfyPage.setSetting('Comfy.Locale', 'fr') + + // Load the templates dialog and wait for the French index file request + const requestPromise = comfyPage.page.waitForRequest( + '**/templates/index.fr.json' + ) + + await comfyPage.executeCommand('Comfy.BrowseTemplates') + + const request = await requestPromise + + // Verify French index was requested + expect(request.url()).toContain('templates/index.fr.json') + + await expect(comfyPage.templates.content).toBeVisible() + }) + + test('Falls back to English templates when locale file not found', async ({ comfyPage }) => { - // Capture request for the index.json - await comfyPage.page.route('**/templates/index.json', async (route, _) => { - // Add a new template that won't have a translation pre-generated - const response = [ - { - moduleName: 'default', - title: 'FALLBACK CATEGORY', - type: 'image', - templates: [ - { - name: 'unknown_key_has_no_translation_available', - title: 'FALLBACK TEMPLATE NAME', - mediaType: 'image', - mediaSubtype: 'webp', - description: 'No translations found' - } - ] - } - ] + // Set locale to a language that doesn't have a template file + await comfyPage.setSetting('Comfy.Locale', 'de') // German - no index.de.json exists + + // Wait for the German request (expected to 404) + const germanRequestPromise = comfyPage.page.waitForRequest( + '**/templates/index.de.json' + ) + + // Wait for the fallback English request + const englishRequestPromise = comfyPage.page.waitForRequest( + '**/templates/index.json' + ) + + // Intercept the German file to simulate a 404 + await comfyPage.page.route('**/templates/index.de.json', async (route) => { await route.fulfill({ - status: 200, - body: JSON.stringify(response), - headers: { - 'Content-Type': 'application/json', - 'Cache-Control': 'no-store' - } + status: 404, + headers: { 'Content-Type': 'text/plain' }, + body: 'Not Found' }) }) + // Allow the English index to load normally + await comfyPage.page.route('**/templates/index.json', (route) => + route.continue() + ) + // Load the templates dialog await comfyPage.executeCommand('Comfy.BrowseTemplates') + await expect(comfyPage.templates.content).toBeVisible() - // Expect the title to be used as fallback for template cards + // Verify German was requested first, then English as fallback + const germanRequest = await germanRequestPromise + const englishRequest = await englishRequestPromise + + expect(germanRequest.url()).toContain('templates/index.de.json') + expect(englishRequest.url()).toContain('templates/index.json') + + // Verify English titles are shown as fallback await expect( - comfyPage.templates.content.getByText('FALLBACK TEMPLATE NAME') + comfyPage.templates.content.getByRole('heading', { + name: 'Image Generation' + }) ).toBeVisible() - - // 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 ({ @@ -153,25 +183,43 @@ test.describe('Templates', () => { 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 - }) + const firstCard = comfyPage.page + .locator('[data-testid^="template-workflow-"]') + .first() + await expect(firstCard).toBeVisible({ timeout: 5000 }) - // Take snapshot of the template grid - const templateGrid = comfyPage.templates.content.locator('.grid').first() + // Get the template grid + const templateGrid = comfyPage.page.locator( + '[data-testid="template-workflows-content"]' + ) await expect(templateGrid).toBeVisible() - await expect(templateGrid).toHaveScreenshot('template-grid-desktop.png') + + // Check grid layout at desktop size (default) + const desktopGridClass = await templateGrid.getAttribute('class') + expect(desktopGridClass).toContain('grid') + expect(desktopGridClass).toContain( + 'grid-cols-[repeat(auto-fill,minmax(16rem,1fr))]' + ) + + // Count visible cards at desktop size + const desktopCardCount = await comfyPage.page + .locator('[data-testid^="template-workflow-"]') + .count() + expect(desktopCardCount).toBeGreaterThan(0) // 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') + // Grid should still be responsive at mobile size + const mobileGridClass = await templateGrid.getAttribute('class') + expect(mobileGridClass).toContain('grid') // 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') + // Grid should still be responsive at tablet size + const tabletGridClass = await templateGrid.getAttribute('class') + expect(tabletGridClass).toContain('grid') }) test('hover effects work on template cards', async ({ comfyPage }) => { @@ -179,10 +227,13 @@ test.describe('Templates', () => { await comfyPage.executeCommand('Comfy.BrowseTemplates') await expect(comfyPage.templates.content).toBeVisible() - // Get a template card - const firstCard = comfyPage.page.locator('.template-card').first() + // Get a template card using data-testid + const firstCard = comfyPage.page + .locator('[data-testid^="template-workflow-"]') + .first() await expect(firstCard).toBeVisible({ timeout: 5000 }) + // Check initial state - card should have transition classes // Take snapshot before hover await expect(firstCard).toHaveScreenshot('template-card-before-hover.png') @@ -257,21 +308,42 @@ test.describe('Templates', () => { await comfyPage.executeCommand('Comfy.BrowseTemplates') await expect(comfyPage.templates.content).toBeVisible() - // Verify cards are visible with varying content lengths + // Wait for cards to load 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') + comfyPage.page.locator( + '[data-testid="template-workflow-short-description"]' + ) ).toBeVisible({ timeout: 5000 }) - // Take snapshot of a grid with specific cards - const templateGrid = comfyPage.templates.content - .locator('.grid:has-text("Short Description")') - .first() + // Verify all three cards with different descriptions are visible + const shortDescCard = comfyPage.page.locator( + '[data-testid="template-workflow-short-description"]' + ) + const mediumDescCard = comfyPage.page.locator( + '[data-testid="template-workflow-medium-description"]' + ) + const longDescCard = comfyPage.page.locator( + '[data-testid="template-workflow-long-description"]' + ) + + await expect(shortDescCard).toBeVisible() + await expect(mediumDescCard).toBeVisible() + await expect(longDescCard).toBeVisible() + + // Verify descriptions are visible and have line-clamp class + // The description is in a p tag with text-muted class + const shortDesc = shortDescCard.locator('p.text-muted.line-clamp-2') + const mediumDesc = mediumDescCard.locator('p.text-muted.line-clamp-2') + const longDesc = longDescCard.locator('p.text-muted.line-clamp-2') + + await expect(shortDesc).toContainText('short description') + await expect(mediumDesc).toContainText('medium length description') + await expect(longDesc).toContainText('much longer description') + + // Verify grid layout maintains consistency + const templateGrid = comfyPage.page.locator( + '[data-testid="template-workflows-content"]' + ) 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 index ce88325aa..6ca212261 100644 Binary files a/browser_tests/tests/templates.spec.ts-snapshots/template-card-after-hover-chromium-linux.png 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 index bf1d18934..3af64e790 100644 Binary files a/browser_tests/tests/templates.spec.ts-snapshots/template-card-before-hover-chromium-linux.png 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-varying-content-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png index 6c330c2f4..a3635bdaf 100644 Binary files a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png and b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-varying-content-chromium-linux.png differ diff --git a/package.json b/package.json index 1f5eac1e7..70f728756 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "@tiptap/extension-table-row": "^2.10.4", "@tiptap/starter-kit": "^2.10.4", "@vueuse/core": "^11.0.0", + "@vueuse/integrations": "^13.9.0", "@xterm/addon-fit": "^0.10.0", "@xterm/addon-serialize": "^0.13.0", "@xterm/xterm": "^5.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 508abfc17..69032f4ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: '@vueuse/core': specifier: ^11.0.0 version: 11.0.0(vue@3.5.13(typescript@5.9.2)) + '@vueuse/integrations': + specifier: ^13.9.0 + version: 13.9.0(axios@1.11.0)(fuse.js@7.0.0)(vue@3.5.13(typescript@5.9.2)) '@xterm/addon-fit': specifier: ^0.10.0 version: 0.10.0(@xterm/xterm@5.5.0) @@ -2905,18 +2908,73 @@ packages: '@vueuse/core@12.8.2': resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + '@vueuse/core@13.9.0': + resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/integrations@13.9.0': + resolution: {integrity: sha512-SDobKBbPIOe0cVL7QxMzGkuUGHvWTdihi9zOrrWaWUgFKe15cwEcwfWmgrcNzjT6kHnNmWuTajPHoIzUjYNYYQ==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 || ^8 + vue: ^3.5.0 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + '@vueuse/metadata@11.0.0': resolution: {integrity: sha512-0TKsAVT0iUOAPWyc9N79xWYfovJVPATiOPVKByG6jmAYdDiwvMVm9xXJ5hp4I8nZDxpCcYlLq/Rg9w1Z/jrGcg==} '@vueuse/metadata@12.8.2': resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + '@vueuse/metadata@13.9.0': + resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==} + '@vueuse/shared@11.0.0': resolution: {integrity: sha512-i4ZmOrIEjSsL94uAEt3hz88UCz93fMyP/fba9S+vypX90fKg3uYX9cThqvWc9aXxuTzR0UGhOKOTQd//Goh1nQ==} '@vueuse/shared@12.8.2': resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + '@vueuse/shared@13.9.0': + resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==} + peerDependencies: + vue: ^3.5.0 + '@webgpu/types@0.1.51': resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==} @@ -9675,10 +9733,28 @@ snapshots: transitivePeerDependencies: - typescript + '@vueuse/core@13.9.0(vue@3.5.13(typescript@5.9.2))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 13.9.0 + '@vueuse/shared': 13.9.0(vue@3.5.13(typescript@5.9.2)) + vue: 3.5.13(typescript@5.9.2) + + '@vueuse/integrations@13.9.0(axios@1.11.0)(fuse.js@7.0.0)(vue@3.5.13(typescript@5.9.2))': + dependencies: + '@vueuse/core': 13.9.0(vue@3.5.13(typescript@5.9.2)) + '@vueuse/shared': 13.9.0(vue@3.5.13(typescript@5.9.2)) + vue: 3.5.13(typescript@5.9.2) + optionalDependencies: + axios: 1.11.0 + fuse.js: 7.0.0 + '@vueuse/metadata@11.0.0': {} '@vueuse/metadata@12.8.2': {} + '@vueuse/metadata@13.9.0': {} + '@vueuse/shared@11.0.0(vue@3.5.13(typescript@5.9.2))': dependencies: vue-demi: 0.14.10(vue@3.5.13(typescript@5.9.2)) @@ -9692,6 +9768,10 @@ snapshots: transitivePeerDependencies: - typescript + '@vueuse/shared@13.9.0(vue@3.5.13(typescript@5.9.2))': + dependencies: + vue: 3.5.13(typescript@5.9.2) + '@webgpu/types@0.1.51': {} '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': diff --git a/public/assets/images/default-template.png b/public/assets/images/default-template.png new file mode 100644 index 000000000..8aeab89cc Binary files /dev/null and b/public/assets/images/default-template.png differ diff --git a/src/assets/icons/custom/dark-info.svg b/src/assets/icons/custom/dark-info.svg new file mode 100644 index 000000000..26c05560f --- /dev/null +++ b/src/assets/icons/custom/dark-info.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/components/card/CardBottom.vue b/src/components/card/CardBottom.vue index 7f35754e6..4a0ae1047 100644 --- a/src/components/card/CardBottom.vue +++ b/src/components/card/CardBottom.vue @@ -1,7 +1,19 @@ - + diff --git a/src/components/card/CardContainer.vue b/src/components/card/CardContainer.vue index 1a17d5659..390e441a0 100644 --- a/src/components/card/CardContainer.vue +++ b/src/components/card/CardContainer.vue @@ -8,15 +8,21 @@ + + diff --git a/src/components/input/MultiSelect.vue b/src/components/input/MultiSelect.vue index d687c583b..28111ff6e 100644 --- a/src/components/input/MultiSelect.vue +++ b/src/components/input/MultiSelect.vue @@ -9,7 +9,7 @@ --> diff --git a/src/components/templates/TemplateWorkflowCard.spec.ts b/src/components/templates/TemplateWorkflowCard.spec.ts deleted file mode 100644 index 733b723d6..000000000 --- a/src/components/templates/TemplateWorkflowCard.spec.ts +++ /dev/null @@ -1,273 +0,0 @@ -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 type { TemplateInfo } from '@/platform/workflow/templates/types/template' - -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}`, - addEventListener: vi.fn(), - removeEventListener: vi.fn() - } -})) - -vi.mock('@/scripts/app', () => ({ - app: { - loadGraphData: vi.fn() - } -})) - -vi.mock('@/stores/dialogStore', () => ({ - useDialogStore: () => ({ - closeDialog: vi.fn() - }) -})) - -vi.mock( - '@/platform/workflow/templates/repositories/workflowTemplatesStore', - () => ({ - useWorkflowTemplatesStore: () => ({ - isLoaded: true, - loadWorkflowTemplates: vi.fn().mockResolvedValue(true), - groupedTemplates: [] - }) - }) -) - -vi.mock('vue-i18n', () => ({ - useI18n: () => ({ - t: (key: string, fallback: string) => fallback || key - }) -})) - -vi.mock( - '@/platform/workflow/templates/composables/useTemplateWorkflows', - () => ({ - useTemplateWorkflows: () => ({ - getTemplateThumbnailUrl: ( - template: TemplateInfo, - sourceModule: string, - index = '' - ) => { - const basePath = - sourceModule === 'default' - ? `/fileURL/templates/${template.name}` - : `/apiURL/workflow_templates/${sourceModule}/${template.name}` - const indexSuffix = - sourceModule === 'default' && index ? `-${index}` : '' - return `${basePath}${indexSuffix}.${template.mediaSubtype}` - }, - getTemplateTitle: (template: TemplateInfo, sourceModule: string) => { - const fallback = - template.title ?? template.name ?? `${sourceModule} Template` - return sourceModule === 'default' - ? template.localizedTitle ?? fallback - : fallback - }, - getTemplateDescription: ( - template: TemplateInfo, - sourceModule: string - ) => { - return sourceModule === 'default' - ? template.localizedDescription ?? '' - : template.description?.replace(/[-_]/g, ' ').trim() ?? '' - }, - loadWorkflowTemplate: vi.fn() - }) - }) -) - -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: - '
', - props: ['dataTestid', 'pt'] - }, - ProgressSpinner: { - template: '
' - } - } - } - }) - } - - it('emits loadWorkflow event when clicked', async () => { - const wrapper = mountCard({ - template: createTemplate({ name: 'test-workflow' }) - }) - await wrapper.find('.card').trigger('click') - expect(wrapper.emitted('loadWorkflow')).toBeTruthy() - expect(wrapper.emitted('loadWorkflow')?.[0]).toEqual(['test-workflow']) - }) - - it('shows loading spinner when loading is true', () => { - const wrapper = mountCard({ loading: true }) - expect(wrapper.find('.progress-spinner').exists()).toBe(true) - }) - - it('renders audio thumbnail for audio media type', () => { - const wrapper = mountCard({ - template: createTemplate({ mediaType: 'audio' }) - }) - expect(wrapper.find('.mock-audio-thumbnail').exists()).toBe(true) - }) - - it('renders compare slider thumbnail for compareSlider variant', () => { - const wrapper = mountCard({ - template: createTemplate({ thumbnailVariant: 'compareSlider' }) - }) - expect(wrapper.find('.mock-compare-slider').exists()).toBe(true) - }) - - it('renders hover dissolve thumbnail for hoverDissolve variant', () => { - const wrapper = mountCard({ - template: createTemplate({ thumbnailVariant: 'hoverDissolve' }) - }) - expect(wrapper.find('.mock-hover-dissolve').exists()).toBe(true) - }) - - it('renders default thumbnail by default', () => { - const wrapper = mountCard() - expect(wrapper.find('.mock-default-thumbnail').exists()).toBe(true) - }) - - it('passes correct props to default thumbnail for video', () => { - const wrapper = mountCard({ - template: createTemplate({ mediaType: 'video' }) - }) - const thumbnail = wrapper.find('.mock-default-thumbnail') - expect(thumbnail.exists()).toBe(true) - }) - - it('uses zoomHover scale when variant is zoomHover', () => { - const wrapper = mountCard({ - template: createTemplate({ thumbnailVariant: 'zoomHover' }) - }) - expect(wrapper.find('.mock-default-thumbnail').exists()).toBe(true) - }) - - it('displays localized title for default source module', () => { - const wrapper = mountCard({ - sourceModule: 'default', - template: createTemplate({ localizedTitle: 'My Localized Title' }) - }) - expect(wrapper.text()).toContain('My Localized Title') - }) - - it('displays template name as title for non-default source modules', () => { - const wrapper = mountCard({ - sourceModule: 'custom', - template: createTemplate({ name: 'custom-template' }) - }) - expect(wrapper.text()).toContain('custom-template') - }) - - it('displays localized description for default source module', () => { - const wrapper = mountCard({ - sourceModule: 'default', - template: createTemplate({ - localizedDescription: 'My Localized Description' - }) - }) - expect(wrapper.text()).toContain('My Localized Description') - }) - - it('processes description for non-default source modules', () => { - const wrapper = mountCard({ - sourceModule: 'custom', - template: createTemplate({ description: 'custom_module-description' }) - }) - expect(wrapper.text()).toContain('custom module description') - }) - - it('generates correct thumbnail URLs for default source module', () => { - const wrapper = mountCard({ - sourceModule: 'default', - template: createTemplate({ - name: 'my-template', - mediaSubtype: 'jpg' - }) - }) - const vm = wrapper.vm as any - expect(vm.baseThumbnailSrc).toBe('/fileURL/templates/my-template-1.jpg') - expect(vm.overlayThumbnailSrc).toBe('/fileURL/templates/my-template-2.jpg') - }) - - it('generates correct thumbnail URLs for custom source module', () => { - const wrapper = mountCard({ - sourceModule: 'custom-module', - template: createTemplate({ - name: 'my-template', - mediaSubtype: 'png' - }) - }) - const vm = wrapper.vm as any - expect(vm.baseThumbnailSrc).toBe( - '/apiURL/workflow_templates/custom-module/my-template.png' - ) - expect(vm.overlayThumbnailSrc).toBe( - '/apiURL/workflow_templates/custom-module/my-template.png' - ) - }) -}) diff --git a/src/components/templates/TemplateWorkflowCard.vue b/src/components/templates/TemplateWorkflowCard.vue deleted file mode 100644 index eb6a93038..000000000 --- a/src/components/templates/TemplateWorkflowCard.vue +++ /dev/null @@ -1,139 +0,0 @@ - - - diff --git a/src/components/templates/TemplateWorkflowCardSkeleton.vue b/src/components/templates/TemplateWorkflowCardSkeleton.vue deleted file mode 100644 index 00bf73839..000000000 --- a/src/components/templates/TemplateWorkflowCardSkeleton.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/src/components/templates/TemplateWorkflowList.vue b/src/components/templates/TemplateWorkflowList.vue deleted file mode 100644 index b6ac99c5e..000000000 --- a/src/components/templates/TemplateWorkflowList.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - diff --git a/src/components/templates/TemplateWorkflowView.spec.ts b/src/components/templates/TemplateWorkflowView.spec.ts deleted file mode 100644 index d9633a7e0..000000000 --- a/src/components/templates/TemplateWorkflowView.spec.ts +++ /dev/null @@ -1,185 +0,0 @@ -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 type { TemplateInfo } from '@/platform/workflow/templates/types/template' - -vi.mock('primevue/dataview', () => ({ - default: { - name: 'DataView', - template: ` -
-
-
- -
-
- `, - props: ['value', 'layout', 'lazy', 'pt'] - } -})) - -vi.mock('primevue/selectbutton', () => ({ - default: { - name: 'SelectButton', - template: - '
', - props: ['modelValue', 'options', 'allowEmpty'] - } -})) - -vi.mock('@/components/templates/TemplateWorkflowCard.vue', () => ({ - default: { - template: ` -
- `, - props: ['sourceModule', 'categoryTitle', 'loading', 'template'], - emits: ['loadWorkflow'] - } -})) - -vi.mock('@/components/templates/TemplateWorkflowList.vue', () => ({ - default: { - template: '
', - props: ['sourceModule', 'categoryTitle', 'loading', 'templates'], - emits: ['loadWorkflow'] - } -})) - -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, - mediaType: 'image', - mediaSubtype: 'png', - thumbnailVariant: 'default', - description: `Description for ${name}` - }) - - const mountView = (props = {}) => { - const i18n = createI18n({ - legacy: false, - locale: 'en', - messages: { - en: { - templateWorkflows: { - loadingMore: 'Loading more...' - } - } - } - }) - - return mount(TemplateWorkflowView, { - props: { - title: 'Test Templates', - sourceModule: 'default', - categoryTitle: 'Test Category', - templates: [ - createTemplate('template-1'), - createTemplate('template-2'), - createTemplate('template-3') - ], - loading: null, - ...props - }, - global: { - plugins: [i18n] - } - }) - } - - it('renders template cards for each template', () => { - const wrapper = mountView() - const cards = wrapper.findAll('.mock-template-card') - - expect(cards.length).toBe(3) - expect(cards[0].attributes('data-name')).toBe('template-1') - expect(cards[1].attributes('data-name')).toBe('template-2') - expect(cards[2].attributes('data-name')).toBe('template-3') - }) - - it('emits loadWorkflow event when clicked', async () => { - const wrapper = mountView() - const card = wrapper.find('.mock-template-card') - - await card.trigger('click') - - expect(wrapper.emitted()).toHaveProperty('loadWorkflow') - // Check that the emitted event contains the template name - const emitted = wrapper.emitted('loadWorkflow') - expect(emitted).toBeTruthy() - expect(emitted?.[0][0]).toBe('template-1') - }) - - it('passes correct props to template cards', () => { - const wrapper = mountView({ - sourceModule: 'custom', - categoryTitle: 'Custom Category' - }) - - const card = wrapper.find('.mock-template-card') - expect(card.exists()).toBe(true) - expect(card.attributes('data-source-module')).toBe('custom') - expect(card.attributes('data-category-title')).toBe('Custom Category') - }) - - it('applies loading state correctly to cards', () => { - const wrapper = mountView({ - loading: 'template-2' - }) - - const cards = wrapper.findAll('.mock-template-card') - - // Only the second card should have loading=true since loading="template-2" - expect(cards[0].attributes('data-loading')).toBe('false') - expect(cards[1].attributes('data-loading')).toBe('true') - expect(cards[2].attributes('data-loading')).toBe('false') - }) -}) diff --git a/src/components/templates/TemplateWorkflowView.vue b/src/components/templates/TemplateWorkflowView.vue deleted file mode 100644 index 9c73ddc86..000000000 --- a/src/components/templates/TemplateWorkflowView.vue +++ /dev/null @@ -1,168 +0,0 @@ - - - diff --git a/src/components/templates/TemplateWorkflowsContent.vue b/src/components/templates/TemplateWorkflowsContent.vue deleted file mode 100644 index 38925b9e4..000000000 --- a/src/components/templates/TemplateWorkflowsContent.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - diff --git a/src/components/templates/TemplateWorkflowsDialogHeader.vue b/src/components/templates/TemplateWorkflowsDialogHeader.vue deleted file mode 100644 index 9313ab104..000000000 --- a/src/components/templates/TemplateWorkflowsDialogHeader.vue +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/src/components/templates/TemplateWorkflowsSideNav.vue b/src/components/templates/TemplateWorkflowsSideNav.vue deleted file mode 100644 index 07ff87990..000000000 --- a/src/components/templates/TemplateWorkflowsSideNav.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/src/components/templates/thumbnails/AudioThumbnail.vue b/src/components/templates/thumbnails/AudioThumbnail.vue index dda6e79a5..49333d93e 100644 --- a/src/components/templates/thumbnails/AudioThumbnail.vue +++ b/src/components/templates/thumbnails/AudioThumbnail.vue @@ -1,6 +1,12 @@