mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
fix: upgrade vee-validate to v5 for native Zod 4 support
- Upgrade vee-validate from v4.15.1 to v5.0.0-beta.0 (Standard Schema) - Remove custom toTypedSchema adapter (veeValidateZod.ts) - Pass Zod schemas directly to validationSchema - Fix SignInForm tests: use text-based selectors, remove try/catch anti-pattern Amp-Thread-ID: https://ampcode.com/threads/T-019c7ec7-2a88-7165-9db4-ef656336407b Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@@ -271,8 +271,8 @@ catalogs:
|
||||
specifier: ^30.0.0
|
||||
version: 30.0.0
|
||||
vee-validate:
|
||||
specifier: ^4.15.1
|
||||
version: 4.15.1
|
||||
specifier: 5.0.0-beta.0
|
||||
version: 5.0.0-beta.0
|
||||
vite-plugin-dts:
|
||||
specifier: ^4.5.4
|
||||
version: 4.5.4
|
||||
@@ -486,7 +486,7 @@ importers:
|
||||
version: 0.8.2
|
||||
vee-validate:
|
||||
specifier: 'catalog:'
|
||||
version: 4.15.1(vue@3.5.13(typescript@5.9.3))
|
||||
version: 5.0.0-beta.0(vue@3.5.13(typescript@5.9.3))
|
||||
vue:
|
||||
specifier: 'catalog:'
|
||||
version: 3.5.13(typescript@5.9.3)
|
||||
@@ -3212,6 +3212,9 @@ packages:
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@standard-schema/utils@0.3.0':
|
||||
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
|
||||
|
||||
'@storybook/addon-docs@10.1.9':
|
||||
resolution: {integrity: sha512-SvwEZ32lyk5p3PRmE3pmfAhs4HMiVo5zxjTBVmK9kgz9zGgWCTlikb56tJ998hVe52CFyCvt3I9rkHeYMCKPww==}
|
||||
peerDependencies:
|
||||
@@ -8115,8 +8118,8 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
vee-validate@4.15.1:
|
||||
resolution: {integrity: sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==}
|
||||
vee-validate@5.0.0-beta.0:
|
||||
resolution: {integrity: sha512-uGIRnODDMM0A8Weu8AJcZFFJceUpgbSX6G4UYZgWhBc90VcXDK+v7yO16G+sj+6vU1eML11M2BH4HxwoPE62rw==}
|
||||
peerDependencies:
|
||||
vue: ^3.4.26
|
||||
|
||||
@@ -11206,6 +11209,8 @@ snapshots:
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@standard-schema/utils@0.3.0': {}
|
||||
|
||||
'@storybook/addon-docs@10.1.9(@types/react@19.1.9)(esbuild@0.27.1)(rollup@4.53.5)(storybook@10.1.9(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.0-beta.13(@types/node@24.10.4)(esbuild@0.27.1)(jiti@2.6.1)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@mdx-js/react': 3.1.1(@types/react@19.1.9)(react@19.2.3)
|
||||
@@ -17080,8 +17085,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
vee-validate@4.15.1(vue@3.5.13(typescript@5.9.3)):
|
||||
vee-validate@5.0.0-beta.0(vue@3.5.13(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@standard-schema/utils': 0.3.0
|
||||
'@vue/devtools-api': 7.7.9
|
||||
type-fest: 4.41.0
|
||||
vue: 3.5.13(typescript@5.9.3)
|
||||
|
||||
@@ -91,7 +91,7 @@ catalog:
|
||||
unplugin-icons: ^22.5.0
|
||||
unplugin-typegpu: 0.8.0
|
||||
unplugin-vue-components: ^30.0.0
|
||||
vee-validate: ^4.15.1
|
||||
vee-validate: 5.0.0-beta.0
|
||||
vite: 8.0.0-beta.13
|
||||
vite-plugin-dts: ^4.5.4
|
||||
vite-plugin-html: ^3.2.2
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toTypedSchema } from '@/utils/veeValidateZod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { ref } from 'vue'
|
||||
|
||||
@@ -31,7 +30,7 @@ const { handleSubmit } = useForm({
|
||||
confirmPassword: '',
|
||||
password: ''
|
||||
},
|
||||
validationSchema: toTypedSchema(updatePasswordSchema)
|
||||
validationSchema: updatePasswordSchema
|
||||
})
|
||||
|
||||
const onSubmit = handleSubmit(async (submittedValues) => {
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toTypedSchema } from '@/utils/veeValidateZod'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Message from 'primevue/message'
|
||||
import { useForm } from 'vee-validate'
|
||||
@@ -119,7 +118,7 @@ const { defineField, errors, validate } = useForm({
|
||||
initialValues: {
|
||||
apiKey: ''
|
||||
},
|
||||
validationSchema: toTypedSchema(apiKeySchema)
|
||||
validationSchema: apiKeySchema
|
||||
})
|
||||
|
||||
const [apiKey, apiKeyAttrs] = defineField('apiKey')
|
||||
|
||||
@@ -84,48 +84,45 @@ describe('SignInForm', () => {
|
||||
}
|
||||
|
||||
describe('Forgot Password Link', () => {
|
||||
function findForgotPasswordButton(wrapper: VueWrapper<ComponentInstance>) {
|
||||
return wrapper
|
||||
.findAll('button[type="button"]')
|
||||
.find((btn) =>
|
||||
btn.text().includes(enMessages.auth.login.forgotPassword)
|
||||
)!
|
||||
}
|
||||
|
||||
it('shows disabled style when email is empty', async () => {
|
||||
const wrapper = mountComponent()
|
||||
await nextTick()
|
||||
|
||||
const forgotPasswordSpan = wrapper.find(
|
||||
'span.text-muted.text-base.font-medium.cursor-pointer'
|
||||
)
|
||||
|
||||
expect(forgotPasswordSpan.classes()).toContain('text-link-disabled')
|
||||
const forgotBtn = findForgotPasswordButton(wrapper)
|
||||
expect(forgotBtn.classes()).toContain('text-link-disabled')
|
||||
})
|
||||
|
||||
it('shows toast and focuses email input when clicked while disabled', async () => {
|
||||
const wrapper = mountComponent()
|
||||
const forgotPasswordSpan = wrapper.find(
|
||||
'span.text-muted.text-base.font-medium.cursor-pointer'
|
||||
)
|
||||
const forgotBtn = findForgotPasswordButton(wrapper)
|
||||
|
||||
// Mock getElementById to track focus
|
||||
const mockFocus = vi.fn()
|
||||
const mockElement: Partial<HTMLElement> = { focus: mockFocus }
|
||||
vi.spyOn(document, 'getElementById').mockReturnValue(
|
||||
mockElement as HTMLElement
|
||||
)
|
||||
|
||||
// Click forgot password link while email is empty
|
||||
await forgotPasswordSpan.trigger('click')
|
||||
await forgotBtn.trigger('click')
|
||||
await nextTick()
|
||||
|
||||
// Should show toast warning
|
||||
expect(mockToastAdd).toHaveBeenCalledWith({
|
||||
severity: 'warn',
|
||||
summary: enMessages.auth.login.emailPlaceholder,
|
||||
life: 5000
|
||||
})
|
||||
|
||||
// Should focus email input
|
||||
expect(document.getElementById).toHaveBeenCalledWith(
|
||||
'comfy-org-sign-in-email'
|
||||
)
|
||||
expect(mockFocus).toHaveBeenCalled()
|
||||
|
||||
// Should NOT call sendPasswordReset
|
||||
expect(mockSendPasswordReset).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -135,11 +132,8 @@ describe('SignInForm', () => {
|
||||
.find('#comfy-org-sign-in-email')
|
||||
.setValue('test@example.com')
|
||||
|
||||
const forgotPasswordSpan = wrapper.find(
|
||||
'span.text-muted.text-base.font-medium.cursor-pointer'
|
||||
)
|
||||
|
||||
await forgotPasswordSpan.trigger('click')
|
||||
const forgotBtn = findForgotPasswordButton(wrapper)
|
||||
await forgotBtn.trigger('click')
|
||||
expect(mockSendPasswordReset).toHaveBeenCalledWith('test@example.com')
|
||||
})
|
||||
})
|
||||
@@ -157,28 +151,37 @@ describe('SignInForm', () => {
|
||||
describe('Loading State', () => {
|
||||
it('shows spinner when loading', async () => {
|
||||
mockLoading = true
|
||||
const wrapper = mountComponent(
|
||||
{},
|
||||
{
|
||||
global: {
|
||||
plugins: [
|
||||
PrimeVue,
|
||||
createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: { en: enMessages }
|
||||
}),
|
||||
ToastService
|
||||
],
|
||||
stubs: {
|
||||
ProgressSpinner: { template: '<div data-testid="spinner" />' }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
await nextTick()
|
||||
|
||||
try {
|
||||
const wrapper = mountComponent()
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.findComponent(ProgressSpinner).exists()).toBe(true)
|
||||
expect(wrapper.find('button').exists()).toBe(false)
|
||||
} catch (error) {
|
||||
mockLoading = true
|
||||
const wrapper = mountComponent()
|
||||
expect(wrapper.html()).toContain('p-progressspinner')
|
||||
expect(wrapper.html()).not.toContain('<button')
|
||||
}
|
||||
expect(wrapper.find('[data-testid="spinner"]').exists()).toBe(true)
|
||||
expect(wrapper.find('button[type="submit"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('shows button when not loading', () => {
|
||||
it('shows submit button when not loading', () => {
|
||||
mockLoading = false
|
||||
|
||||
const wrapper = mountComponent()
|
||||
|
||||
expect(wrapper.findComponent(ProgressSpinner).exists()).toBe(false)
|
||||
expect(wrapper.find('button').exists()).toBe(true)
|
||||
expect(wrapper.find('button[type="submit"]').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthAction
|
||||
import { signInSchema } from '@/schemas/signInSchema'
|
||||
import type { SignInData } from '@/schemas/signInSchema'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import { toTypedSchema } from '@/utils/veeValidateZod'
|
||||
|
||||
const authStore = useFirebaseAuthStore()
|
||||
const firebaseAuthActions = useFirebaseAuthActions()
|
||||
@@ -114,7 +113,7 @@ const { errors, meta, validate, values } = useForm<SignInData>({
|
||||
password: ''
|
||||
},
|
||||
validateOnMount: true,
|
||||
validationSchema: toTypedSchema(signInSchema)
|
||||
validationSchema: signInSchema
|
||||
})
|
||||
|
||||
const onSubmit = useThrottleFn(async (event: Event) => {
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toTypedSchema } from '@/utils/veeValidateZod'
|
||||
import { useThrottleFn } from '@vueuse/core'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
@@ -71,7 +70,7 @@ const { errors, handleSubmit, meta } = useForm<SignUpData>({
|
||||
password: ''
|
||||
},
|
||||
validateOnMount: true,
|
||||
validationSchema: toTypedSchema(signUpSchema)
|
||||
validationSchema: signUpSchema
|
||||
})
|
||||
|
||||
const onSubmit = useThrottleFn(
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toTypedSchema } from '@/utils/veeValidateZod'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Message from 'primevue/message'
|
||||
import Password from 'primevue/password'
|
||||
@@ -106,7 +105,7 @@ const { defineField, errors, handleSubmit, meta } = useForm<SignInData>({
|
||||
password: ''
|
||||
},
|
||||
validateOnMount: true,
|
||||
validationSchema: toTypedSchema(signInSchema)
|
||||
validationSchema: signInSchema
|
||||
})
|
||||
|
||||
const [email, emailAttrs] = defineField('email')
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import type { TypedSchema, TypedSchemaError } from 'vee-validate'
|
||||
import type { z } from 'zod'
|
||||
|
||||
const buildTypedSchemaErrors = (issues: z.ZodIssue[]): TypedSchemaError[] => {
|
||||
const groupedErrors = new Map<string, TypedSchemaError>()
|
||||
|
||||
for (const issue of issues) {
|
||||
const path = issue.path.length > 0 ? issue.path.join('.') : ''
|
||||
const existingError = groupedErrors.get(path)
|
||||
|
||||
if (existingError) {
|
||||
existingError.errors.push(issue.message)
|
||||
continue
|
||||
}
|
||||
|
||||
groupedErrors.set(path, {
|
||||
path: path || undefined,
|
||||
errors: [issue.message]
|
||||
})
|
||||
}
|
||||
|
||||
return [...groupedErrors.values()]
|
||||
}
|
||||
|
||||
export const toTypedSchema = <TSchema extends z.ZodType>(
|
||||
schema: TSchema
|
||||
): TypedSchema<z.input<TSchema>, z.output<TSchema>> => ({
|
||||
__type: 'VVTypedSchema',
|
||||
parse: async (values) => {
|
||||
const result = await schema.safeParseAsync(values)
|
||||
|
||||
if (result.success) {
|
||||
return {
|
||||
value: result.data,
|
||||
errors: []
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errors: buildTypedSchemaErrors(result.error.issues)
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user