mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-03 11:09:10 +00:00
Compare commits
1 Commits
fix/codera
...
fix/codera
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
140e0a128d |
@@ -255,6 +255,13 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
||||
return workflow
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the workflow data has a stable `id` field for sharing.
|
||||
* If the provided (or default) workflow data does not contain an `id`,
|
||||
* a new UUID is generated and injected into the returned copy.
|
||||
*
|
||||
* @returns A deep copy of the workflow data with a guaranteed `id` field.
|
||||
*/
|
||||
const ensureWorkflowId = (
|
||||
workflowData?: ComfyWorkflowJSON
|
||||
): ComfyWorkflowJSON => {
|
||||
@@ -270,7 +277,11 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a new temporary workflow
|
||||
* Helper to create a new temporary workflow.
|
||||
*
|
||||
* Calls {@link ensureWorkflowId} to guarantee the workflow data contains
|
||||
* a UUID `id` field. If the provided data has no `id`, one is generated
|
||||
* and injected into the serialized content.
|
||||
*/
|
||||
const createNewWorkflow = (
|
||||
path: string,
|
||||
@@ -291,7 +302,13 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a temporary workflow, attempting to reuse an existing workflow if conditions match
|
||||
* Create a temporary workflow, attempting to reuse an existing workflow
|
||||
* if conditions match.
|
||||
*
|
||||
* Note: A UUID `id` field is injected into the workflow data via
|
||||
* {@link ensureWorkflowId} if one is not already present. The serialized
|
||||
* workflow content will always contain an `id` field, even if none was
|
||||
* provided in the input.
|
||||
*/
|
||||
const createTemporary = (path?: string, workflowData?: ComfyWorkflowJSON) => {
|
||||
const fullPath = getUnconflictedPath(
|
||||
@@ -323,7 +340,13 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new temporary workflow without attempting to reuse existing workflows
|
||||
* Create a new temporary workflow without attempting to reuse existing
|
||||
* workflows.
|
||||
*
|
||||
* Note: A UUID `id` field is injected into the workflow data via
|
||||
* {@link ensureWorkflowId} if one is not already present. The serialized
|
||||
* workflow content will always contain an `id` field, even if none was
|
||||
* provided in the input.
|
||||
*/
|
||||
const createNewTemporary = (
|
||||
path?: string,
|
||||
|
||||
@@ -1,79 +1,59 @@
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import ComfyHubPublishDialog from '@/platform/workflow/sharing/components/publish/ComfyHubPublishDialog.vue'
|
||||
import type { ComfyHubProfile } from '@/schemas/apiSchema'
|
||||
|
||||
const mockFetchApi = vi.hoisted(() => vi.fn())
|
||||
const mockToastErrorHandler = vi.hoisted(() => vi.fn())
|
||||
const mockResolvedUserInfo = vi.hoisted(() => ({
|
||||
value: { id: 'user-a' }
|
||||
}))
|
||||
const mockFetchProfile = vi.hoisted(() => vi.fn())
|
||||
const mockGoToStep = vi.hoisted(() => vi.fn())
|
||||
const mockGoNext = vi.hoisted(() => vi.fn())
|
||||
const mockGoBack = vi.hoisted(() => vi.fn())
|
||||
const mockOpenProfileCreationStep = vi.hoisted(() => vi.fn())
|
||||
const mockCloseProfileCreationStep = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
fetchApi: mockFetchApi
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/auth/useCurrentUser', () => ({
|
||||
useCurrentUser: () => ({
|
||||
resolvedUserInfo: mockResolvedUserInfo
|
||||
vi.mock(
|
||||
'@/platform/workflow/sharing/composables/useComfyHubProfileGate',
|
||||
() => ({
|
||||
useComfyHubProfileGate: () => ({
|
||||
fetchProfile: mockFetchProfile
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
|
||||
vi.mock('@/composables/useErrorHandling', () => ({
|
||||
useErrorHandling: () => ({
|
||||
toastErrorHandler: mockToastErrorHandler
|
||||
vi.mock(
|
||||
'@/platform/workflow/sharing/composables/useComfyHubPublishWizard',
|
||||
() => ({
|
||||
useComfyHubPublishWizard: () => ({
|
||||
currentStep: ref('finish'),
|
||||
formData: ref({
|
||||
name: '',
|
||||
description: '',
|
||||
workflowType: '',
|
||||
tags: [],
|
||||
thumbnailType: 'image',
|
||||
thumbnailFile: null,
|
||||
comparisonBeforeFile: null,
|
||||
comparisonAfterFile: null,
|
||||
exampleImages: [],
|
||||
selectedExampleIds: []
|
||||
}),
|
||||
isFirstStep: ref(false),
|
||||
isLastStep: ref(true),
|
||||
goToStep: mockGoToStep,
|
||||
goNext: mockGoNext,
|
||||
goBack: mockGoBack,
|
||||
openProfileCreationStep: mockOpenProfileCreationStep,
|
||||
closeProfileCreationStep: mockCloseProfileCreationStep
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: () => ({
|
||||
activeWorkflow: { filename: 'test-workflow' }
|
||||
})
|
||||
}))
|
||||
|
||||
const mockProfile: ComfyHubProfile = {
|
||||
username: 'testuser',
|
||||
name: 'Test User',
|
||||
description: 'A test profile'
|
||||
}
|
||||
|
||||
function mockSuccessResponse(data?: unknown) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => data ?? mockProfile
|
||||
} as Response
|
||||
}
|
||||
|
||||
function mockErrorResponse(status = 404) {
|
||||
return {
|
||||
ok: false,
|
||||
status,
|
||||
json: async () => ({ message: 'Not found' })
|
||||
} as Response
|
||||
}
|
||||
|
||||
// Reset module-level singleton state in useComfyHubProfileGate between tests
|
||||
async function resetProfileGateSingleton() {
|
||||
const { useComfyHubProfileGate } =
|
||||
await import('@/platform/workflow/sharing/composables/useComfyHubProfileGate')
|
||||
const gate = useComfyHubProfileGate()
|
||||
gate.hasProfile.value = null
|
||||
gate.profile.value = null
|
||||
gate.isCheckingProfile.value = false
|
||||
gate.isFetchingProfile.value = false
|
||||
}
|
||||
)
|
||||
|
||||
describe('ComfyHubPublishDialog', () => {
|
||||
const onClose = vi.fn()
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockResolvedUserInfo.value = { id: 'user-a' }
|
||||
mockFetchApi.mockResolvedValue(mockErrorResponse())
|
||||
await resetProfileGateSingleton()
|
||||
mockFetchProfile.mockResolvedValue(null)
|
||||
})
|
||||
|
||||
function createWrapper() {
|
||||
@@ -98,7 +78,7 @@ describe('ComfyHubPublishDialog', () => {
|
||||
},
|
||||
ComfyHubPublishWizardContent: {
|
||||
template:
|
||||
'<div><button data-testid="require-profile" @click="$props.onRequireProfile()" /><button data-testid="gate-complete" @click="$props.onGateComplete()" /><button data-testid="gate-close" @click="$props.onGateClose()" /><span data-testid="current-step">{{ $props.currentStep }}</span></div>',
|
||||
'<div><button data-testid="require-profile" @click="$props.onRequireProfile()" /><button data-testid="gate-complete" @click="$props.onGateComplete()" /><button data-testid="gate-close" @click="$props.onGateClose()" /></div>',
|
||||
props: [
|
||||
'currentStep',
|
||||
'formData',
|
||||
@@ -108,9 +88,7 @@ describe('ComfyHubPublishDialog', () => {
|
||||
'onGoBack',
|
||||
'onRequireProfile',
|
||||
'onGateComplete',
|
||||
'onGateClose',
|
||||
'onUpdateFormData',
|
||||
'onPublish'
|
||||
'onGateClose'
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -118,62 +96,44 @@ describe('ComfyHubPublishDialog', () => {
|
||||
})
|
||||
}
|
||||
|
||||
it('prefetches profile on mount via real composable', async () => {
|
||||
it('starts in publish wizard mode and prefetches profile asynchronously', async () => {
|
||||
createWrapper()
|
||||
await flushPromises()
|
||||
|
||||
expect(mockFetchApi).toHaveBeenCalledWith('/hub/profile')
|
||||
expect(mockFetchProfile).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('starts on the describe step with real wizard composable', async () => {
|
||||
const wrapper = createWrapper()
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('[data-testid="current-step"]').text()).toBe('describe')
|
||||
})
|
||||
|
||||
it('switches to profileCreation step when require-profile is triggered', async () => {
|
||||
it('switches to profile creation step when final-step publish requires profile', async () => {
|
||||
const wrapper = createWrapper()
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.find('[data-testid="require-profile"]').trigger('click')
|
||||
|
||||
expect(wrapper.find('[data-testid="current-step"]').text()).toBe(
|
||||
'profileCreation'
|
||||
)
|
||||
expect(mockOpenProfileCreationStep).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('returns to finish step and re-fetches profile after gate complete', async () => {
|
||||
mockFetchApi.mockResolvedValue(mockSuccessResponse())
|
||||
it('returns to finish state after gate complete and does not auto-close', async () => {
|
||||
const wrapper = createWrapper()
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.find('[data-testid="require-profile"]').trigger('click')
|
||||
expect(wrapper.find('[data-testid="current-step"]').text()).toBe(
|
||||
'profileCreation'
|
||||
)
|
||||
|
||||
await wrapper.find('[data-testid="gate-complete"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('[data-testid="current-step"]').text()).toBe('finish')
|
||||
// Initial prefetch + force re-fetch after gate complete
|
||||
expect(mockFetchApi).toHaveBeenCalledTimes(2)
|
||||
expect(mockOpenProfileCreationStep).toHaveBeenCalledOnce()
|
||||
expect(mockCloseProfileCreationStep).toHaveBeenCalledOnce()
|
||||
expect(mockFetchProfile).toHaveBeenCalledWith({ force: true })
|
||||
expect(onClose).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns to finish step when profile gate is closed', async () => {
|
||||
it('returns to finish state when profile gate is closed', async () => {
|
||||
const wrapper = createWrapper()
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.find('[data-testid="require-profile"]').trigger('click')
|
||||
expect(wrapper.find('[data-testid="current-step"]').text()).toBe(
|
||||
'profileCreation'
|
||||
)
|
||||
|
||||
await wrapper.find('[data-testid="gate-close"]').trigger('click')
|
||||
|
||||
expect(wrapper.find('[data-testid="current-step"]').text()).toBe('finish')
|
||||
expect(mockOpenProfileCreationStep).toHaveBeenCalledOnce()
|
||||
expect(mockCloseProfileCreationStep).toHaveBeenCalledOnce()
|
||||
expect(onClose).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user