recursive validation of props
This commit is contained in:
parent
ca74258c04
commit
0b94346104
|
@ -5,15 +5,24 @@ import {
|
|||
import { types } from "./types";
|
||||
import { assign } from "lodash";
|
||||
|
||||
export const createDefaultProps = (propsDefinition, derivedFromProps) => {
|
||||
export const createProps = (componentName, propsDefinition, derivedFromProps) => {
|
||||
|
||||
const error = (propName, error) =>
|
||||
errors.push({propName, error});
|
||||
|
||||
const props = {
|
||||
_component: componentName
|
||||
};
|
||||
|
||||
const props = {};
|
||||
const errors = [];
|
||||
|
||||
if(!componentName)
|
||||
error("_component", "Component name not supplied");
|
||||
|
||||
for(let propDef in propsDefinition) {
|
||||
const parsedPropDef = parsePropDef(propsDefinition[propDef]);
|
||||
if(parsedPropDef.error)
|
||||
errors.push({propName:propDef, error:parsedPropDef.error});
|
||||
error(propDef, parsedPropDef.error);
|
||||
else
|
||||
props[propDef] = parsedPropDef;
|
||||
}
|
|
@ -2,7 +2,8 @@ import {
|
|||
isString,
|
||||
isBoolean,
|
||||
isNumber,
|
||||
isArray
|
||||
isArray,
|
||||
isObjectLike
|
||||
} from "lodash/fp";
|
||||
|
||||
const defaultDef = typeName => () => ({
|
||||
|
@ -10,17 +11,20 @@ const defaultDef = typeName => () => ({
|
|||
required:false,
|
||||
default:types[typeName].default,
|
||||
options: typeName === "options" ? [] : undefined,
|
||||
itemPropsDefinition: typeName === "array" ? "string" : undefined
|
||||
elementDefinition: typeName === "array" ? "string" : undefined
|
||||
});
|
||||
|
||||
const propType = (defaultValue, isOfType, defaultDefinition) => ({
|
||||
isOfType, default:defaultValue, defaultDefinition
|
||||
});
|
||||
|
||||
const isComponent = isObjectLike;
|
||||
|
||||
export const types = {
|
||||
string: propType(() => "", isString, defaultDef("string")),
|
||||
bool: propType(() => false, isBoolean, defaultDef("bool")),
|
||||
number: propType(() => 0, isNumber, defaultDef("number")),
|
||||
array: propType(() => [], isArray, defaultDef("array")),
|
||||
options: propType(() => "", isString, defaultDef("options"))
|
||||
options: propType(() => "", isString, defaultDef("options")),
|
||||
component: propType(() => ({_component:""}), isComponent, defaultDef("component"))
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import { types } from "./types";
|
||||
import { createDefaultProps } from "./createDefaultProps";
|
||||
import { createProps } from "./createProps";
|
||||
import { isString } from "util";
|
||||
import {
|
||||
includes,
|
||||
|
@ -17,12 +17,41 @@ const makeError = (errors, propName) => (message) =>
|
|||
propName,
|
||||
error:message});
|
||||
|
||||
export const recursivelyValidate = (rootProps, getComponentPropsDefinition, stack=[]) => {
|
||||
|
||||
const propsDef = getComponentPropsDefinition(
|
||||
rootProps._component);
|
||||
|
||||
const errors = validateProps(
|
||||
propsDef,
|
||||
rootProps);
|
||||
|
||||
// adding name to object.... for ease
|
||||
const childErrors = pipe(propsDef, [
|
||||
keys,
|
||||
map(k => ({...propsDef[k], name:k })),
|
||||
filter(d => d.type === "component"),
|
||||
map(d => ({
|
||||
errs:recursivelyValidate(
|
||||
rootProps[d.name],
|
||||
d.def,
|
||||
[...stack, propsDef]),
|
||||
def:d
|
||||
}))
|
||||
]);
|
||||
}
|
||||
|
||||
export const validateProps = (propsDefinition, props, isFinal=true) => {
|
||||
|
||||
const errors = [];
|
||||
|
||||
if(!props._component)
|
||||
makeError(errors, "_component")("Component is not set");
|
||||
|
||||
for(let propDefName in propsDefinition) {
|
||||
|
||||
if(propDefName === "_component") continue;
|
||||
|
||||
let propDef = propsDefinition[propDefName];
|
||||
|
||||
if(isString(propDef))
|
||||
|
@ -46,7 +75,7 @@ export const validateProps = (propsDefinition, props, isFinal=true) => {
|
|||
if(propDef.type === "array") {
|
||||
for(let arrayItem of propValue) {
|
||||
const arrayErrs = validateProps(
|
||||
propDef.itemPropsDefinition,
|
||||
propDef.elementDefinition,
|
||||
arrayItem,
|
||||
isFinal
|
||||
)
|
||||
|
@ -61,31 +90,32 @@ export const validateProps = (propsDefinition, props, isFinal=true) => {
|
|||
&& !includes(propValue)(propDef.options)) {
|
||||
error(`Property ${propDefName} is not one of allowed options. Acutal value is ${propValue}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export const validatePropsDefinition = (propsDefinition) => {
|
||||
const { errors } = createDefaultProps(propsDefinition);
|
||||
const { errors } = createProps("dummy_component_name", propsDefinition);
|
||||
|
||||
|
||||
// arrar props without itemPropsDefinition
|
||||
// arrar props without elementDefinition
|
||||
pipe(propsDefinition, [
|
||||
keys,
|
||||
map(k => ({
|
||||
propDef:propsDefinition[k],
|
||||
propName:k
|
||||
})),
|
||||
filter(d => d.propDef.type === "array" && !d.propDef.itemPropsDefinition),
|
||||
filter(d => d.propDef.type === "array" && !d.propDef.elementDefinition),
|
||||
each(d => makeError(errors, d.propName)(`${d.propName} does not have a definition for it's item props`))
|
||||
]);
|
||||
|
||||
const arrayPropValidationErrors = pipe(propsDefinition, [
|
||||
keys,
|
||||
map(k => propsDefinition[k]),
|
||||
filter(d => d.type === "array" && d.itemPropsDefinition),
|
||||
map(d => validatePropsDefinition(d.itemPropsDefinition)),
|
||||
filter(d => d.type === "array" && d.elementDefinition),
|
||||
map(d => validatePropsDefinition(d.elementDefinition)),
|
||||
flatten
|
||||
]);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createDefaultProps } from "../src/userInterface/propsDefinitionParsing/createDefaultProps";
|
||||
import { createProps } from "../src/userInterface/propsDefinitionParsing/createProps";
|
||||
import {
|
||||
keys, some
|
||||
} from "lodash/fp";
|
||||
|
@ -10,12 +10,33 @@ describe("createDefaultProps", () => {
|
|||
fieldName: {type:"string", default:"something"}
|
||||
};
|
||||
|
||||
const { props, errors } = createDefaultProps(propDef);
|
||||
const { props, errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(props.fieldName).toBeDefined();
|
||||
expect(props.fieldName).toBe("something");
|
||||
expect(keys(props).length).toBe(1);
|
||||
expect(keys(props).length).toBe(2);
|
||||
});
|
||||
|
||||
it("should set component name", () => {
|
||||
const propDef = {
|
||||
fieldName: {type:"string", default:"something"}
|
||||
};
|
||||
|
||||
const { props, errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(props._component).toBe("some_component");
|
||||
});
|
||||
|
||||
it("should return error when component name not supplied", () => {
|
||||
const propDef = {
|
||||
fieldName: {type:"string", default:"something"}
|
||||
};
|
||||
|
||||
const { errors } = createProps("",propDef);
|
||||
|
||||
expect(errors.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should create a object with single blank string value, when no default", () => {
|
||||
|
@ -23,7 +44,7 @@ describe("createDefaultProps", () => {
|
|||
fieldName: {type:"string"}
|
||||
};
|
||||
|
||||
const { props, errors } = createDefaultProps(propDef);
|
||||
const { props, errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(props.fieldName).toBeDefined();
|
||||
|
@ -35,7 +56,7 @@ describe("createDefaultProps", () => {
|
|||
fieldName: "string"
|
||||
};
|
||||
|
||||
const { props, errors } = createDefaultProps(propDef);
|
||||
const { props, errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(props.fieldName).toBeDefined();
|
||||
|
@ -44,38 +65,50 @@ describe("createDefaultProps", () => {
|
|||
|
||||
it("should create a object with single fals value, when prop definition is 'bool' ", () => {
|
||||
const propDef = {
|
||||
fieldName: "bool"
|
||||
isVisible: "bool"
|
||||
};
|
||||
|
||||
const { props, errors } = createDefaultProps(propDef);
|
||||
const { props, errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(props.fieldName).toBeDefined();
|
||||
expect(props.fieldName).toBe(false);
|
||||
expect(props.isVisible).toBeDefined();
|
||||
expect(props.isVisible).toBe(false);
|
||||
});
|
||||
|
||||
it("should create a object with single 0 value, when prop definition is 'number' ", () => {
|
||||
const propDef = {
|
||||
fieldName: "number"
|
||||
width: "number"
|
||||
};
|
||||
|
||||
const { props, errors } = createDefaultProps(propDef);
|
||||
const { props, errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(props.fieldName).toBeDefined();
|
||||
expect(props.fieldName).toBe(0);
|
||||
expect(props.width).toBeDefined();
|
||||
expect(props.width).toBe(0);
|
||||
});
|
||||
|
||||
it("should create a object with single 0 value, when prop definition is 'array' ", () => {
|
||||
it("should create a object with single empty array, when prop definition is 'array' ", () => {
|
||||
const propDef = {
|
||||
fieldName: "array"
|
||||
columns: "array"
|
||||
};
|
||||
|
||||
const { props, errors } = createDefaultProps(propDef);
|
||||
const { props, errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(props.fieldName).toBeDefined();
|
||||
expect(props.fieldName).toEqual([]);
|
||||
expect(props.columns).toBeDefined();
|
||||
expect(props.columns).toEqual([]);
|
||||
});
|
||||
|
||||
it("should create a object with single empty component props, when prop definition is 'component' ", () => {
|
||||
const propDef = {
|
||||
content: "component"
|
||||
};
|
||||
|
||||
const { props, errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(props.content).toBeDefined();
|
||||
expect(props.content).toEqual({_component:""});
|
||||
});
|
||||
|
||||
it("should create an object with multiple prop names", () => {
|
||||
|
@ -84,14 +117,14 @@ describe("createDefaultProps", () => {
|
|||
fieldLength: { type: "number", default: 500 }
|
||||
};
|
||||
|
||||
const { props, errors } = createDefaultProps(propDef);
|
||||
const { props, errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
expect(props.fieldName).toBeDefined();
|
||||
expect(props.fieldName).toBe("");
|
||||
expect(props.fieldLength).toBeDefined();
|
||||
expect(props.fieldLength).toBe(500);
|
||||
expect(keys(props).length).toBe(2);
|
||||
expect(keys(props).length).toBe(3);
|
||||
})
|
||||
|
||||
it("should return error when invalid type", () => {
|
||||
|
@ -100,7 +133,7 @@ describe("createDefaultProps", () => {
|
|||
fieldLength: { type: "invalid type name "}
|
||||
};
|
||||
|
||||
const { errors } = createDefaultProps(propDef);
|
||||
const { errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors.length).toBe(2);
|
||||
expect(some(e => e.propName === "fieldName")(errors)).toBeTruthy();
|
||||
|
@ -112,7 +145,7 @@ describe("createDefaultProps", () => {
|
|||
fieldName: {type:"string", default: 1}
|
||||
};
|
||||
|
||||
const { errors } = createDefaultProps(propDef);
|
||||
const { errors } = createProps("some_component",propDef);
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(some(e => e.propName === "fieldName")(errors)).toBeTruthy();
|
||||
|
@ -125,10 +158,11 @@ describe("createDefaultProps", () => {
|
|||
};
|
||||
|
||||
const derivedFrom = {
|
||||
_component:"root",
|
||||
fieldName: "surname"
|
||||
};
|
||||
|
||||
const { props, errors } = createDefaultProps(propDef, [derivedFrom]);
|
||||
const { props, errors } = createProps("some_component",propDef, [derivedFrom]);
|
||||
|
||||
expect(errors.length).toBe(0);
|
||||
expect(props.fieldName).toBe("surname");
|
||||
|
@ -139,23 +173,31 @@ describe("createDefaultProps", () => {
|
|||
it("should merge in derived props, last in list taking priority", () => {
|
||||
const propDef = {
|
||||
fieldName: "string",
|
||||
fieldLength: { type: "number", default: 500}
|
||||
fieldLength: { type: "number", default: 500},
|
||||
header: "component",
|
||||
content: {
|
||||
type: "component",
|
||||
default: { _component: "childcomponent", wdith: 500 }
|
||||
}
|
||||
};
|
||||
|
||||
const derivedFrom1 = {
|
||||
_component:"root",
|
||||
fieldName: "surname",
|
||||
fieldLength: 200
|
||||
};
|
||||
|
||||
const derivedFrom2 = {
|
||||
_component:"child",
|
||||
fieldName: "forename"
|
||||
};
|
||||
|
||||
const { props, errors } = createDefaultProps(propDef, [derivedFrom1, derivedFrom2]);
|
||||
const { props, errors } = createProps("some_component",propDef, [derivedFrom1, derivedFrom2]);
|
||||
|
||||
expect(errors.length).toBe(0);
|
||||
expect(props.fieldName).toBe("forename");
|
||||
expect(props.fieldLength).toBe(200);
|
||||
expect(props._component).toBe("child");
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -2,10 +2,7 @@ import {
|
|||
validatePropsDefinition,
|
||||
validateProps
|
||||
} from "../src/userInterface/propsDefinitionParsing/validateProps";
|
||||
import { createDefaultProps } from "../src/userInterface/propsDefinitionParsing/createDefaultProps";
|
||||
import {
|
||||
keys, some
|
||||
} from "lodash/fp";
|
||||
import { createProps } from "../src/userInterface/propsDefinitionParsing/createProps";
|
||||
|
||||
// not that allot of this functionality is covered
|
||||
// in createDefaultProps - as validate props uses that.
|
||||
|
@ -17,7 +14,7 @@ describe("validatePropsDefinition", () => {
|
|||
const propsDef = {
|
||||
columns : {
|
||||
type: "array",
|
||||
itemPropsDefinition: {
|
||||
elementDefinition: {
|
||||
width: "number",
|
||||
units: {
|
||||
type: "string",
|
||||
|
@ -38,7 +35,7 @@ describe("validatePropsDefinition", () => {
|
|||
const propsDef = {
|
||||
columns : {
|
||||
type: "array",
|
||||
itemPropsDefinition: {
|
||||
elementDefinition: {
|
||||
width: "invlid type",
|
||||
units: {
|
||||
type: "string",
|
||||
|
@ -97,7 +94,7 @@ const validPropDef = {
|
|||
rowCount : "number",
|
||||
columns : {
|
||||
type: "array",
|
||||
itemPropsDefinition: {
|
||||
elementDefinition: {
|
||||
width: "number",
|
||||
units: {
|
||||
type: "string",
|
||||
|
@ -109,9 +106,9 @@ const validPropDef = {
|
|||
};
|
||||
|
||||
const validProps = () => {
|
||||
const { props } = createDefaultProps(validPropDef);
|
||||
const { props } = createProps("some_component", validPropDef);
|
||||
props.columns.push(
|
||||
createDefaultProps(validPropDef.columns.itemPropsDefinition).props);
|
||||
createProps("childcomponent", validPropDef.columns.elementDefinition).props);
|
||||
return props;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue