From 9c4c3e3b83bb36a977bb7b3ec1c8aa2e2524a8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 14 Jun 2022 18:05:58 +0200 Subject: [PATCH] [IMP] validation: add support for value types This commit add supports for value types in prop validation. To describe a value V type, one has to simply write {value: V}. Note that this commit also improves the validation typing. closes #1198 closes #910 --- doc/reference/props.md | 2 ++ src/runtime/validation.ts | 14 ++++++++++---- tests/validation.test.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/doc/reference/props.md b/doc/reference/props.md index 4d699bab7..4fcfbd9b4 100644 --- a/doc/reference/props.md +++ b/doc/reference/props.md @@ -159,6 +159,7 @@ For each key, a `prop` definition is either a boolean, a constructor, a list of - a boolean: indicate that the props exists, and is mandatory. - a constructor: this should describe the type, for example: `id: Number` describe the props `id` as a number +- an object describing a value as type. This is done by using the `value` key. For example, `{value: false}` specifies that the corresponding value should be equal to false. - a list of constructors. In that case, this means that we allow more than one type. For example, `id: [Number, String]` means that `id` can be either a string or a number. @@ -240,6 +241,7 @@ class ComponentB extends owl.Component { size: { validate: e => ["small", "medium", "large"].includes(e) }, + someId: [Number, {value: false}], // either a number or false }; ``` diff --git a/src/runtime/validation.ts b/src/runtime/validation.ts index 1015ed32f..02d473fbe 100644 --- a/src/runtime/validation.ts +++ b/src/runtime/validation.ts @@ -8,9 +8,6 @@ type BaseType = | true | "*"; -type UnionType = (TypeInfo | BaseType)[]; -type TypeDescription = BaseType | UnionType | TypeInfo; - interface TypeInfo { type?: TypeDescription; optional?: boolean; @@ -19,6 +16,9 @@ interface TypeInfo { element?: TypeDescription; } +type ValueType = { value: any }; + +type TypeDescription = BaseType | TypeInfo | ValueType | TypeDescription[]; type SimplifiedSchema = string[]; type NormalizedSchema = { [key: string]: TypeDescription }; export type Schema = SimplifiedSchema | NormalizedSchema; @@ -26,8 +26,10 @@ export type Schema = SimplifiedSchema | NormalizedSchema; // ----------------------------------------------------------------------------- // helpers // ----------------------------------------------------------------------------- -const isUnionType = (t: TypeDescription): t is UnionType => Array.isArray(t); +const isUnionType = (t: TypeDescription): t is TypeDescription[] => Array.isArray(t); const isBaseType = (t: TypeDescription): t is BaseType => typeof t !== "object"; +const isValueType = (t: TypeDescription): t is ValueType => + typeof t === "object" && t && "value" in t; export function isOptional(t: TypeDescription): Boolean { return typeof t === "object" && "optional" in t ? t.optional || false : false; @@ -42,6 +44,8 @@ function describe(info: TypeDescription): string { return describeType(info); } else if (isUnionType(info)) { return info.map(describe).join(" or "); + } else if (isValueType(info)) { + return String(info.value); } if ("element" in info) { return `list of ${describe({ type: info.element, optional: false })}s`; @@ -134,6 +138,8 @@ function validateType(key: string, value: any, descr: TypeDescription): string | return isOptional(descr) ? null : `'${key}' is undefined (should be a ${describe(descr)})`; } else if (isBaseType(descr)) { return validateBaseType(key, value, descr); + } else if (isValueType(descr)) { + return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`; } else if (isUnionType(descr)) { let validDescr = descr.find((p) => !validateType(key, value, p)); return validDescr ? null : `'${key}' is not a ${describe(descr)}`; diff --git a/tests/validation.test.ts b/tests/validation.test.ts index 9428375bc..a8d5c9834 100644 --- a/tests/validation.test.ts +++ b/tests/validation.test.ts @@ -248,4 +248,30 @@ describe("validateSchema", () => { expect(validateSchema({ size: "sall" }, schema)).toEqual(["'size' is not valid"]); expect(validateSchema({ size: 1 }, schema)).toEqual(["'size' is not a string"]); }); + + test("value as type", () => { + expect(validateSchema({ a: false }, { a: { value: false } })).toEqual([]); + expect(validateSchema({ a: true }, { a: { value: false } })).toEqual([ + "'a' is not equal to 'false'", + ]); + }); + + test("value as type (some other values)", () => { + expect(validateSchema({ a: null }, { a: { value: null } })).toEqual([]); + expect(validateSchema({ a: false }, { a: { value: null } })).toEqual([ + "'a' is not equal to 'null'", + ]); + expect(validateSchema({ a: "hey" }, { a: { value: "hey" } })).toEqual([]); + expect(validateSchema({ a: true }, { a: { value: "hey" } })).toEqual([ + "'a' is not equal to 'hey'", + ]); + }); + + test("value as type work in union type", () => { + expect(validateSchema({ a: false }, { a: [String, { value: false }] })).toEqual([]); + expect(validateSchema({ a: true }, { a: [String, { value: false }] })).toEqual([ + "'a' is not a string or false", + ]); + expect(validateSchema({ a: "string" }, { a: [String, { value: false }] })).toEqual([]); + }); });