recursive validation of component heirarchy
This commit is contained in:
parent
0b94346104
commit
886e5c6b2d
|
@ -7,59 +7,101 @@ import {
|
|||
map,
|
||||
keys,
|
||||
flatten,
|
||||
each } from "lodash/fp";
|
||||
flattenDeep,
|
||||
each,
|
||||
indexOf } from "lodash/fp";
|
||||
import { common } from "budibase-core";
|
||||
|
||||
const pipe = common.$;
|
||||
|
||||
const makeError = (errors, propName) => (message) =>
|
||||
const makeError = (errors, propName, stack) => (message) =>
|
||||
errors.push({
|
||||
stack,
|
||||
propName,
|
||||
error:message});
|
||||
|
||||
export const recursivelyValidate = (rootProps, getComponentPropsDefinition, stack=[]) => {
|
||||
export const recursivelyValidate = (rootProps, getComponent, stack=[]) => {
|
||||
|
||||
const getComponentPropsDefinition = componentName => {
|
||||
if(componentName.includes(":")) {
|
||||
const [parentComponent, arrayProp] = componentName.split(":");
|
||||
return getComponent(parentComponent)[arrayProp].elementDefinition;
|
||||
}
|
||||
return getComponent(componentName);
|
||||
}
|
||||
|
||||
const propsDef = getComponentPropsDefinition(
|
||||
rootProps._component);
|
||||
|
||||
const getPropsDefArray = (def) => pipe(def, [
|
||||
keys,
|
||||
map(k => def[k].name
|
||||
? expandPropDef(def[k])
|
||||
: ({
|
||||
...expandPropDef(def[k]),
|
||||
name:k }))
|
||||
]);
|
||||
|
||||
const propsDefArray = getPropsDefArray(propsDef);
|
||||
|
||||
const errors = validateProps(
|
||||
propsDef,
|
||||
rootProps);
|
||||
rootProps,
|
||||
stack,
|
||||
true);
|
||||
|
||||
// adding name to object.... for ease
|
||||
const childErrors = pipe(propsDef, [
|
||||
keys,
|
||||
map(k => ({...propsDef[k], name:k })),
|
||||
const validateChildren = (_defArray, _props, _stack) => pipe(_defArray, [
|
||||
filter(d => d.type === "component"),
|
||||
map(d => ({
|
||||
errs:recursivelyValidate(
|
||||
rootProps[d.name],
|
||||
d.def,
|
||||
[...stack, propsDef]),
|
||||
def:d
|
||||
}))
|
||||
map(d => recursivelyValidate(
|
||||
_props[d.name],
|
||||
getComponentPropsDefinition,
|
||||
[..._stack, d.name])),
|
||||
flatten
|
||||
]);
|
||||
|
||||
const childErrors = validateChildren(
|
||||
propsDefArray, rootProps, stack);
|
||||
|
||||
const childArrayErrors = pipe(propsDefArray, [
|
||||
filter(d => d.type === "array"),
|
||||
map(d => pipe(rootProps[d.name], [
|
||||
map(elementProps => pipe(elementProps._component, [
|
||||
getComponentPropsDefinition,
|
||||
getPropsDefArray,
|
||||
arr => validateChildren(
|
||||
arr,
|
||||
elementProps,
|
||||
[...stack,
|
||||
`${d.name}[${indexOf(elementProps)(rootProps[d.name])}]`])
|
||||
]))
|
||||
]))
|
||||
]);
|
||||
|
||||
return flattenDeep([errors, ...childErrors, ...childArrayErrors]);
|
||||
}
|
||||
|
||||
export const validateProps = (propsDefinition, props, isFinal=true) => {
|
||||
const expandPropDef = propDef =>
|
||||
isString(propDef)
|
||||
? types[propDef].defaultDefinition()
|
||||
: propDef;
|
||||
|
||||
|
||||
export const validateProps = (propsDefinition, props, stack=[], isFinal=true) => {
|
||||
|
||||
const errors = [];
|
||||
|
||||
if(!props._component)
|
||||
makeError(errors, "_component")("Component is not set");
|
||||
makeError(errors, "_component", stack)("Component is not set");
|
||||
|
||||
for(let propDefName in propsDefinition) {
|
||||
|
||||
if(propDefName === "_component") continue;
|
||||
|
||||
let propDef = propsDefinition[propDefName];
|
||||
|
||||
if(isString(propDef))
|
||||
propDef = types[propDef].defaultDefinition();
|
||||
const propDef = expandPropDef(propsDefinition[propDefName]);
|
||||
|
||||
const type = types[propDef.type];
|
||||
|
||||
const error = makeError(errors, propDefName);
|
||||
const error = makeError(errors, propDefName, stack);
|
||||
|
||||
const propValue = props[propDefName];
|
||||
if(isFinal && propDef.required && propValue) {
|
||||
|
@ -73,15 +115,19 @@ export const validateProps = (propsDefinition, props, isFinal=true) => {
|
|||
}
|
||||
|
||||
if(propDef.type === "array") {
|
||||
let index = 0;
|
||||
for(let arrayItem of propValue) {
|
||||
arrayItem._component = `${props._component}:${propDefName}`
|
||||
const arrayErrs = validateProps(
|
||||
propDef.elementDefinition,
|
||||
arrayItem,
|
||||
[...stack, `${propDefName}[${index}]`],
|
||||
isFinal
|
||||
)
|
||||
for(let arrErr of arrayErrs) {
|
||||
errors.push(arrErr);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
validatePropsDefinition,
|
||||
validateProps
|
||||
validateProps,
|
||||
recursivelyValidate
|
||||
} from "../src/userInterface/propsDefinitionParsing/validateProps";
|
||||
import { createProps } from "../src/userInterface/propsDefinitionParsing/createProps";
|
||||
|
||||
|
@ -116,7 +117,7 @@ describe("validateProps", () => {
|
|||
|
||||
it("should have no errors with a big list of valid props", () => {
|
||||
|
||||
const errors = validateProps(validPropDef, validProps(), true);
|
||||
const errors = validateProps(validPropDef, validProps(), [], true);
|
||||
expect(errors).toEqual([]);
|
||||
|
||||
});
|
||||
|
@ -125,7 +126,7 @@ describe("validateProps", () => {
|
|||
|
||||
const props = validProps();
|
||||
props.rowCount = "1";
|
||||
const errors = validateProps(validPropDef, props, true);
|
||||
const errors = validateProps(validPropDef, props, [], true);
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(errors[0].propName).toBe("rowCount");
|
||||
|
||||
|
@ -135,7 +136,7 @@ describe("validateProps", () => {
|
|||
|
||||
const props = validProps();
|
||||
props.size = "really_small";
|
||||
const errors = validateProps(validPropDef, props, true);
|
||||
const errors = validateProps(validPropDef, props, [], true);
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(errors[0].propName).toBe("size");
|
||||
|
||||
|
@ -145,9 +146,126 @@ describe("validateProps", () => {
|
|||
|
||||
const props = validProps();
|
||||
props.columns[0].width = "seven";
|
||||
const errors = validateProps(validPropDef, props, true);
|
||||
const errors = validateProps(validPropDef, props, [], true);
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(errors[0].propName).toBe("width");
|
||||
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe("recursivelyValidateProps", () => {
|
||||
|
||||
const rootComponent = {
|
||||
width: "number",
|
||||
child: "component",
|
||||
navitems: {
|
||||
type: "array",
|
||||
elementDefinition: {
|
||||
name: "string",
|
||||
icon: "component"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const todoListComponent = {
|
||||
showTitle: "bool",
|
||||
header: "component"
|
||||
};
|
||||
|
||||
const headerComponent = {
|
||||
text: "string"
|
||||
}
|
||||
|
||||
const iconComponent = {
|
||||
iconName: "string"
|
||||
}
|
||||
|
||||
const getComponent = name => ({
|
||||
rootComponent,
|
||||
todoListComponent,
|
||||
headerComponent,
|
||||
iconComponent
|
||||
})[name];
|
||||
|
||||
const rootProps = () => ({
|
||||
_component: "rootComponent",
|
||||
width: 100,
|
||||
child: {
|
||||
_component: "todoListComponent",
|
||||
showTitle: true,
|
||||
header: {
|
||||
_component: "headerComponent",
|
||||
text: "Your todo list"
|
||||
}
|
||||
},
|
||||
navitems: [
|
||||
{
|
||||
name: "Main",
|
||||
icon: {
|
||||
_component: "iconComponent",
|
||||
iconName:"fa fa-list"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
icon: {
|
||||
_component: "iconComponent",
|
||||
iconName:"fa fa-cog"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
it("should return no errors for valid structure", () => {
|
||||
const result = recursivelyValidate(
|
||||
rootProps(),
|
||||
getComponent);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return error on root component", () => {
|
||||
const root = rootProps();
|
||||
root.width = "yeeeoooo";
|
||||
const result = recursivelyValidate(root, getComponent);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].propName).toBe("width");
|
||||
});
|
||||
|
||||
it("should return error on first nested child component", () => {
|
||||
const root = rootProps();
|
||||
root.child.showTitle = "yeeeoooo";
|
||||
const result = recursivelyValidate(root, getComponent);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].stack).toEqual(["child"]);
|
||||
expect(result[0].propName).toBe("showTitle");
|
||||
});
|
||||
|
||||
it("should return error on second nested child component", () => {
|
||||
const root = rootProps();
|
||||
root.child.header.text = false;
|
||||
const result = recursivelyValidate(root, getComponent);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].stack).toEqual(["child", "header"]);
|
||||
expect(result[0].propName).toBe("text");
|
||||
});
|
||||
|
||||
it("should return error on invalid array prop", () => {
|
||||
const root = rootProps();
|
||||
root.navitems[1].name = false;
|
||||
const result = recursivelyValidate(root, getComponent);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].propName).toBe("name");
|
||||
expect(result[0].stack).toEqual(["navitems[1]"]);
|
||||
});
|
||||
|
||||
it("should return error on invalid array child", () => {
|
||||
const root = rootProps();
|
||||
root.navitems[1].icon.iconName = false;
|
||||
const result = recursivelyValidate(root, getComponent);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].propName).toBe("iconName");
|
||||
expect(result[0].stack).toEqual(["navitems[1]", "icon"]);
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue