Skip to content

Commit

Permalink
feat: provide form values as context for yup closes #4753
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed May 28, 2024
1 parent cf1e1a6 commit 27fe5c8
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-cobras-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vee-validate": minor
---

feat: provide form values as context for yup closes #4753
6 changes: 5 additions & 1 deletion packages/vee-validate/src/types/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ export interface TypedSchemaPathDescription {
exists: boolean;
}

export interface TypedSchemaContext {
formData: GenericObject;
}

export interface TypedSchema<TInput = any, TOutput = TInput> {
__type: 'VVTypedSchema';
parse(values: TInput): Promise<{ value?: TOutput; errors: TypedSchemaError[] }>;
parse(values: TInput, context?: TypedSchemaContext): Promise<{ value?: TOutput; errors: TypedSchemaError[] }>;
cast?(values: Partial<TInput>): TInput;
describe?(path?: Path<TInput>): Partial<TypedSchemaPathDescription>;
}
Expand Down
25 changes: 15 additions & 10 deletions packages/vee-validate/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
YupSchema,
TypedSchemaError,
Path,
TypedSchemaContext,
} from './types';
import { isCallable, FieldValidationMetaInfo } from '../../shared';

Expand Down Expand Up @@ -72,12 +73,13 @@ export async function validate<TValue = unknown>(
* Starts the validation process.
*/
async function _validate<TValue = unknown>(field: FieldValidationContext<TValue>, value: TValue) {
if (isTypedSchema(field.rules) || isYupValidator(field.rules)) {
return validateFieldWithTypedSchema(value, field.rules);
const rules = field.rules;
if (isTypedSchema(rules) || isYupValidator(rules)) {
return validateFieldWithTypedSchema(value, { ...field, rules });
}

// if a generic function or chain of generic functions
if (isCallable(field.rules) || Array.isArray(field.rules)) {
if (isCallable(rules) || Array.isArray(rules)) {
const ctx = {
field: field.label || field.name,
name: field.name,
Expand All @@ -87,7 +89,7 @@ async function _validate<TValue = unknown>(field: FieldValidationContext<TValue>
};

// Normalize the pipeline
const pipeline = Array.isArray(field.rules) ? field.rules : [field.rules];
const pipeline = Array.isArray(rules) ? rules : [rules];
const length = pipeline.length;
const errors: ReturnType<typeof _generateFieldError>[] = [];

Expand Down Expand Up @@ -120,7 +122,7 @@ async function _validate<TValue = unknown>(field: FieldValidationContext<TValue>

const normalizedContext = {
...field,
rules: normalizeRules(field.rules),
rules: normalizeRules(rules),
};
const errors: ReturnType<typeof _generateFieldError>[] = [];
const rulesKeys = Object.keys(normalizedContext.rules);
Expand Down Expand Up @@ -161,9 +163,9 @@ function isYupError(err: unknown): err is YupError {
function yupToTypedSchema(yupSchema: YupSchema): TypedSchema {
const schema: TypedSchema = {
__type: 'VVTypedSchema',
async parse(values: any) {
async parse(values: any, context?: TypedSchemaContext) {
try {
const output = await yupSchema.validate(values, { abortEarly: false });
const output = await yupSchema.validate(values, { abortEarly: false, context: context?.formData || {} });

return {
output,
Expand Down Expand Up @@ -205,9 +207,12 @@ function yupToTypedSchema(yupSchema: YupSchema): TypedSchema {
/**
* Handles yup validation
*/
async function validateFieldWithTypedSchema(value: unknown, schema: TypedSchema | YupSchema) {
const typedSchema = isTypedSchema(schema) ? schema : yupToTypedSchema(schema);
const result = await typedSchema.parse(value);
async function validateFieldWithTypedSchema(
value: unknown,
context: FieldValidationContext<any> & { rules: TypedSchema | YupSchema },
) {
const typedSchema = isTypedSchema(context.rules) ? context.rules : yupToTypedSchema(context.rules);
const result = await typedSchema.parse(value, { formData: context.formData });

const messages: string[] = [];
for (const error of result.errors) {
Expand Down
44 changes: 44 additions & 0 deletions packages/vee-validate/tests/Form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3178,3 +3178,47 @@ test('removes proper pathState when field is unmounting', async () => {
expect(form.meta.value.valid).toBe(true);
expect(form.getAllPathStates()).toMatchObject([{ id: 0, path: 'foo' }]);
});

test('provides form values as yup context refs', async () => {
mountWithHoc({
setup() {
const pw = yup.string().required().min(3).label('Password');
const cpw = yup
.string()
.required()
.oneOf([yup.ref('$password')])
.label('Confirm Password');

return {
pw,
cpw,
};
},
template: `
<VForm v-slot="{ errors }">
<Field id="password" name="password" type="password" :rules="pw" />
<span id="passwordErr">{{ errors.password }}</span>
<Field id="confirmPassword" name="confirmPassword" type="password" :rules="cpw" />
<span id="confirmPasswordErr">{{ errors.confirmPassword }}</span>
<button>Validate</button>
</VForm>
`,
});

const pwError = document.querySelector('#passwordErr');
const cpwError = document.querySelector('#confirmPasswordErr');

await flushPromises();
setValue(document.querySelector('#password') as HTMLInputElement, '123');
setValue(document.querySelector('#confirmPassword') as HTMLInputElement, '12');
await flushPromises();

expect(pwError?.textContent).toBeFalsy();
expect(cpwError?.textContent).toBeTruthy();
setValue(document.querySelector('#confirmPassword') as HTMLInputElement, '123');
await flushPromises();
expect(pwError?.textContent).toBeFalsy();
expect(cpwError?.textContent).toBeFalsy();
});

0 comments on commit 27fe5c8

Please sign in to comment.