mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-03 04:31:58 +00:00
feat: template publishing dialog, stepper, and step components
Rename template marketplace to template publishing throughout: - Replace useTemplateMarketplaceDialog with useTemplatePublishingDialog - Update core command from Comfy.ShowTemplateMarketplace to Comfy.ShowTemplatePublishing - Update workflow actions menu label and command reference Add multi-step publishing dialog infrastructure: - TemplatePublishingDialog.vue with step-based navigation - TemplatePublishingStepperNav.vue for step progress indicator - useTemplatePublishingStepper composable managing step state, navigation, and validation - Step components for each phase: landing, metadata, description, preview generation, category/tagging, preview, submission, complete - StepTemplatePublishingMetadata with form fields for title, category, tags, difficulty, and license selection - StepTemplatePublishingDescription with markdown editor and live preview via vue-i18n Add comprehensive i18n entries for all publishing steps, form labels, difficulty levels, license types, and category names. Add tests for dialog lifecycle, stepper navigation/validation, metadata form interaction, and description editing. Bump version to 1.42.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock(
|
||||
'@/platform/workflow/templates/composables/useTemplatePublishStorage',
|
||||
() => ({
|
||||
loadTemplateUnderway: vi.fn(() => null),
|
||||
saveTemplateUnderway: vi.fn()
|
||||
})
|
||||
)
|
||||
|
||||
import TemplatePublishingDialog from './TemplatePublishingDialog.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
templatePublishing: {
|
||||
dialogTitle: 'Template Publishing',
|
||||
next: 'Next',
|
||||
previous: 'Previous',
|
||||
saveDraft: 'Save Draft',
|
||||
stepProgress: 'Step {current} of {total}',
|
||||
steps: {
|
||||
landing: {
|
||||
title: 'Getting Started',
|
||||
description: 'Overview of the publishing process'
|
||||
},
|
||||
metadata: {
|
||||
title: 'Metadata',
|
||||
description: 'Title, description, and author info'
|
||||
},
|
||||
description: {
|
||||
title: 'Description',
|
||||
description: 'Write a detailed description of your template'
|
||||
},
|
||||
previewGeneration: {
|
||||
title: 'Preview',
|
||||
description: 'Generate preview images and videos'
|
||||
},
|
||||
categoryAndTagging: {
|
||||
title: 'Categories & Tags',
|
||||
description: 'Categorize and tag your template'
|
||||
},
|
||||
preview: {
|
||||
title: 'Preview',
|
||||
description: 'Review your template before submitting'
|
||||
},
|
||||
submissionForReview: {
|
||||
title: 'Submit',
|
||||
description: 'Submit your template for review'
|
||||
},
|
||||
complete: {
|
||||
title: 'Complete',
|
||||
description: 'Your template has been submitted'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function mountDialog(props?: { initialPage?: string }) {
|
||||
return mount(TemplatePublishingDialog, {
|
||||
props: {
|
||||
onClose: vi.fn(),
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
stubs: {
|
||||
BaseModalLayout: {
|
||||
template: `
|
||||
<div data-testid="modal">
|
||||
<div data-testid="left-panel"><slot name="leftPanel" /></div>
|
||||
<div data-testid="header"><slot name="header" /></div>
|
||||
<div data-testid="header-right"><slot name="header-right-area" /></div>
|
||||
<div data-testid="content"><slot name="content" /></div>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
TemplatePublishingStepperNav: {
|
||||
template: '<div data-testid="stepper-nav" />',
|
||||
props: ['currentStep', 'stepDefinitions']
|
||||
},
|
||||
StepTemplatePublishingLanding: {
|
||||
template: '<div data-testid="step-landing" />'
|
||||
},
|
||||
StepTemplatePublishingMetadata: {
|
||||
template: '<div data-testid="step-metadata" />'
|
||||
},
|
||||
StepTemplatePublishingDescription: {
|
||||
template: '<div data-testid="step-description" />'
|
||||
},
|
||||
StepTemplatePublishingPreviewGeneration: {
|
||||
template: '<div data-testid="step-preview-generation" />'
|
||||
},
|
||||
StepTemplatePublishingCategoryAndTagging: {
|
||||
template: '<div data-testid="step-category" />'
|
||||
},
|
||||
StepTemplatePublishingPreview: {
|
||||
template: '<div data-testid="step-preview" />'
|
||||
},
|
||||
StepTemplatePublishingSubmissionForReview: {
|
||||
template: '<div data-testid="step-submission" />'
|
||||
},
|
||||
StepTemplatePublishingComplete: {
|
||||
template: '<div data-testid="step-complete" />'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('TemplatePublishingDialog', () => {
|
||||
it('renders the dialog with the first step by default', () => {
|
||||
const wrapper = mountDialog()
|
||||
expect(wrapper.find('[data-testid="modal"]').exists()).toBe(true)
|
||||
expect(wrapper.find('[data-testid="step-landing"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('renders the stepper nav in the left panel', () => {
|
||||
const wrapper = mountDialog()
|
||||
const leftPanel = wrapper.find('[data-testid="left-panel"]')
|
||||
expect(leftPanel.find('[data-testid="stepper-nav"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('maps initialPage to the correct starting step', () => {
|
||||
const wrapper = mountDialog({ initialPage: 'metadata' })
|
||||
expect(wrapper.find('[data-testid="step-metadata"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('defaults to step 1 for unknown initialPage', () => {
|
||||
const wrapper = mountDialog({ initialPage: 'nonexistent' })
|
||||
expect(wrapper.find('[data-testid="step-landing"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('shows Previous button when not on first step', () => {
|
||||
const wrapper = mountDialog({ initialPage: 'metadata' })
|
||||
const headerRight = wrapper.find('[data-testid="header-right"]')
|
||||
|
||||
const buttons = headerRight.findAll('button')
|
||||
const buttonTexts = buttons.map((b) => b.text())
|
||||
expect(buttonTexts.some((text) => text.includes('Previous'))).toBe(true)
|
||||
})
|
||||
|
||||
it('disables Previous button on first step', () => {
|
||||
const wrapper = mountDialog()
|
||||
const headerRight = wrapper.find('[data-testid="header-right"]')
|
||||
|
||||
const prevButton = headerRight
|
||||
.findAll('button')
|
||||
.find((b) => b.text().includes('Previous'))
|
||||
expect(prevButton?.attributes('disabled')).toBeDefined()
|
||||
})
|
||||
|
||||
it('disables Next button on last step', () => {
|
||||
const wrapper = mountDialog({
|
||||
initialPage: 'complete'
|
||||
})
|
||||
const headerRight = wrapper.find('[data-testid="header-right"]')
|
||||
|
||||
const nextButton = headerRight
|
||||
.findAll('button')
|
||||
.find((b) => b.text().includes('Next'))
|
||||
expect(nextButton?.attributes('disabled')).toBeDefined()
|
||||
})
|
||||
|
||||
it('disables Next button on submit step', () => {
|
||||
const wrapper = mountDialog({
|
||||
initialPage: 'submissionForReview'
|
||||
})
|
||||
const headerRight = wrapper.find('[data-testid="header-right"]')
|
||||
|
||||
const nextButton = headerRight
|
||||
.findAll('button')
|
||||
.find((b) => b.text().includes('Next'))
|
||||
expect(nextButton?.attributes('disabled')).toBeDefined()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user