Skip to content

Commit

Permalink
convert the return to reactive, this will allow the render to not req…
Browse files Browse the repository at this point in the history
…uired `.value` at the end

vuejs/core#738
  • Loading branch information
pikax committed Feb 20, 2020
1 parent aa517ca commit 9cc313f
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 65 deletions.
16 changes: 8 additions & 8 deletions examples/vue-composable-example/src/components/Validation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
<div class="about">
<h1>Form validation</h1>
<form @submit="onSubmit">
<input v-model="form.firstName.$value.value" placeholder="firstName" />
<input v-model="form.lastName.$value.value" placeholder="lastName" />
<input v-model="form.password.$value.value" placeholder="password" />
<input v-model="form.samePassword.$value.value" placeholder="password2" />
<input v-model="form.firstName.$value" placeholder="firstName" />
<input v-model="form.lastName.$value" placeholder="lastName" />
<input v-model="form.password.$value" placeholder="password" />
<input v-model="form.samePassword.$value" placeholder="password2" />
<p
v-if="
form.samePassword.$dirty.value &&
form.samePassword.match.$invalid.value
form.samePassword.$dirty &&
form.samePassword.match.$invalid
"
>
{{ form.samePassword.match.$message.value }}
{{ form.samePassword.match.$message }}
</p>
</form>
{{ form }}
Expand Down Expand Up @@ -65,7 +65,7 @@ export default createComponent({
return {
onSubmit,
form
form: ref(form)
};
}
});
Expand Down
44 changes: 22 additions & 22 deletions packages/core/__tests__/validation/validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ describe("validation", () => {
});

expect(validation).toMatchObject({
$anyDirty: { value: false },
$anyInvalid: { value: true },
$anyDirty: false,
$anyInvalid: true,

test: {
$dirty: { value: false },
$value,
$dirty: false,
$value: $value.value,
required: {
$invalid: { value: true }
$invalid: true
}
},
test1: {
$dirty: { value: false },
$value: $value1,
$dirty: false,
$value: $value1.value,
required: {
$invalid: { value: true }
$invalid: true
}
}
});
Expand All @@ -46,47 +46,47 @@ describe("validation", () => {

await nextTick();

expect(validation.test.$value).toBe($value);
expect(validation.test.$value).toBe($value.value);
expect(validation).toMatchObject({
$anyDirty: { value: true },
$anyInvalid: { value: true },
$anyDirty: true,
$anyInvalid: true,

test: {
$dirty: { value: true },
$value,
$dirty: true,
$value: $value.value,
required: {
$invalid: { value: false }
$invalid: false
}
},
test1: {
$dirty: { value: false },
$value: $value1,
$dirty: false,
$value: $value1.value,
required: {
$invalid: { value: true }
$invalid: true
}
}
});
});

it("validator should run if dependent of other ref", async () => {
const password = ref('');
const password = ref("");
const form = useValidation({
password: {
$value: password
},
password2: {
$value: ref(""),
samePassword(r: string, ctx: any) {
return r === ctx.password.$value.value;
return r === ctx.password.$value;
}
}
});

expect(form.password2.samePassword.$invalid.value).toBe(false);
expect(form.password2.samePassword.$invalid).toBe(false);

form.password.$value.value = 'test';
form.password.$value = "test";
await nextTick();

expect(form.password2.samePassword.$invalid.value).toBe(true);
expect(form.password2.samePassword.$invalid).toBe(true);
});
});
4 changes: 2 additions & 2 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Ref, isRef, ref } from "@vue/composition-api";
export type RefTyped<T> = T | Ref<T>;
export type RefElement = Element | Ref<Element | undefined>;

export type UnwrapType<T> = T extends Ref<infer R> ? R : T;
export type WrapType<T> = T extends Ref<any> ? T : Ref<T>;
export type UnwrapRef<T> = T extends Ref<infer R> ? R : T;
export type WrapRef<T> = T extends Ref<any> ? T : Ref<T>;

export function unwrap(o: RefElement): Element;
export function unwrap<T>(o: RefTyped<T>): T;
Expand Down
64 changes: 31 additions & 33 deletions packages/core/src/validation/validation.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import {
UnwrapType,
RefTyped,
isObject,
isPromise,
wrap,
isBoolean
} from "../utils";
import { ref, Ref, watch, computed } from "@vue/composition-api";
import { UnwrapRef, isObject, isPromise, wrap, isBoolean } from "../utils";
import { ref, Ref, watch, computed, reactive } from "@vue/composition-api";

type ValidatorFunc<T, TContext = any> = (
model: T,
Expand All @@ -15,40 +8,40 @@ type ValidatorFunc<T, TContext = any> = (

type ValidatorObject<TValidator extends ValidatorFunc<any>> = {
$validator: TValidator;
$message: RefTyped<string>;
$message: string;
};

type Validator<T> = ValidatorFunc<T> | ValidatorObject<ValidatorFunc<T>>;

interface ValidationValue<T> {
$value: T extends Ref<any> ? T : Ref<T>;
$dirty: Ref<boolean>;
$value: UnwrapRef<T>;
$dirty: boolean;
}

interface ValidatorResult {
$error: Ref<any>;
$invalid: Ref<boolean>;
$error: any;
$invalid: boolean;
}

interface ValidationGroupResult {
$anyDirty: Ref<boolean>;
$errors: Ref<Array<any>>;
$anyInvalid: Ref<boolean>;
$anyDirty: boolean;
$errors: Array<any>;
$anyInvalid: boolean;
}

interface ValidatorResultPromise {
$pending: Ref<boolean>;
$promise: Ref<Promise<boolean> | null>;
$pending: boolean;
$promise: Promise<boolean> | null;
}

interface ValidatorResultMessage {
$message: Ref<string>;
$message: string;
}

/* Input */
type ValidationInputType<T, TValue> = Record<
Exclude<keyof T, "$value">,
Validator<UnwrapType<TValue>>
Validator<UnwrapRef<TValue>>
> & { $value: TValue };

type ValidationInput<T> = T extends { $value: infer TValue }
Expand Down Expand Up @@ -181,7 +174,7 @@ const buildValidationValue = (
$promise,
$invalid,
$message
};
} as any;
};

const buildValidation = <T>(
Expand Down Expand Up @@ -238,28 +231,26 @@ const buildValidation = <T>(
.filter(x => x[0] !== "$")
.map(x => (validation[x] as any) as ValidatorResult);
$errors = computed(() => {
return validations.map(x => x.$error.value).filter(Boolean);
return validations.map(x => x.$error).filter(Boolean);
}) as Ref<[]>;
// $anyDirty = computed(() => validations.some(x => !!x));
$anyInvalid = computed(() => validations.some(x => !!x.$invalid.value));
$anyInvalid = computed(() => validations.some(x => !!x.$invalid));
} else {
const validations = Object.keys(validation).map(
x => (validation[x] as any) as ValidationGroupResult
);
$errors = computed(() => {
return validations.map(x => x.$errors.value).filter(Boolean);
return validations.map(x => x.$errors).filter(Boolean);
}) as Ref<[]>;
$anyDirty = computed(() =>
validations.some(
x =>
(x.$anyDirty && x.$anyDirty.value) ||
(isBoolean((x as any).$dirty && (x as any).$dirty.value) &&
(x as any).$dirty.value)
(x.$anyDirty && x.$anyDirty) ||
(isBoolean((x as any).$dirty && (x as any).$dirty) &&
(x as any).$dirty)
)
);
$anyInvalid = computed(() =>
validations.some(x => !!x.$anyInvalid.value)
);
$anyInvalid = computed(() => validations.some(x => !!x.$anyInvalid));
}

r[k] = {
Expand All @@ -281,6 +272,13 @@ export function useValidation<T extends UseValidation<E>, E = any>(
): ValidationOutput<E> & ValidationGroupResult {
const handlers: Array<Function> = [];
const validation = buildValidation({ input }, handlers);
handlers.forEach(x => x(validation.input));
return validation.input as any;
// convert to reactive, this will make it annoying to deconstruct, but
// allows to use it directly on the render template without `.value`
// https://github.com/vuejs/vue-next/pull/738
const validationInput = reactive(validation.input);
// set the context, this will allow to use this object as the second
// argument when calling validators
handlers.forEach(x => x(validationInput));

return validationInput as any;
}

0 comments on commit 9cc313f

Please sign in to comment.