recursive validation of component heirarchy
This commit is contained in:
parent
0b94346104
commit
886e5c6b2d
|
@ -7,59 +7,101 @@ import {
|
||||||
map,
|
map,
|
||||||
keys,
|
keys,
|
||||||
flatten,
|
flatten,
|
||||||
each } from "lodash/fp";
|
flattenDeep,
|
||||||
|
each,
|
||||||
|
indexOf } from "lodash/fp";
|
||||||
import { common } from "budibase-core";
|
import { common } from "budibase-core";
|
||||||
|
|
||||||
const pipe = common.$;
|
const pipe = common.$;
|
||||||
|
|
||||||
const makeError = (errors, propName) => (message) =>
|
const makeError = (errors, propName, stack) => (message) =>
|
||||||
errors.push({
|
errors.push({
|
||||||
|
stack,
|
||||||
propName,
|
propName,
|
||||||
error:message});
|
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(
|
const propsDef = getComponentPropsDefinition(
|
||||||
rootProps._component);
|
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(
|
const errors = validateProps(
|
||||||
propsDef,
|
propsDef,
|
||||||
rootProps);
|
rootProps,
|
||||||
|
stack,
|
||||||
|
true);
|
||||||
|
|
||||||
// adding name to object.... for ease
|
const validateChildren = (_defArray, _props, _stack) => pipe(_defArray, [
|
||||||
const childErrors = pipe(propsDef, [
|
|
||||||
keys,
|
|
||||||
map(k => ({...propsDef[k], name:k })),
|
|
||||||
filter(d => d.type === "component"),
|
filter(d => d.type === "component"),
|
||||||
map(d => ({
|
map(d => recursivelyValidate(
|
||||||
errs:recursivelyValidate(
|
_props[d.name],
|
||||||
rootProps[d.name],
|
getComponentPropsDefinition,
|
||||||
d.def,
|
[..._stack, d.name])),
|
||||||
[...stack, propsDef]),
|
flatten
|
||||||
def:d
|
|
||||||
}))
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
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 = [];
|
const errors = [];
|
||||||
|
|
||||||
if(!props._component)
|
if(!props._component)
|
||||||
makeError(errors, "_component")("Component is not set");
|
makeError(errors, "_component", stack)("Component is not set");
|
||||||
|
|
||||||
for(let propDefName in propsDefinition) {
|
for(let propDefName in propsDefinition) {
|
||||||
|
|
||||||
if(propDefName === "_component") continue;
|
if(propDefName === "_component") continue;
|
||||||
|
|
||||||
let propDef = propsDefinition[propDefName];
|
const propDef = expandPropDef(propsDefinition[propDefName]);
|
||||||
|
|
||||||
if(isString(propDef))
|
|
||||||
propDef = types[propDef].defaultDefinition();
|
|
||||||
|
|
||||||
const type = types[propDef.type];
|
const type = types[propDef.type];
|
||||||
|
|
||||||
const error = makeError(errors, propDefName);
|
const error = makeError(errors, propDefName, stack);
|
||||||
|
|
||||||
const propValue = props[propDefName];
|
const propValue = props[propDefName];
|
||||||
if(isFinal && propDef.required && propValue) {
|
if(isFinal && propDef.required && propValue) {
|
||||||
|
@ -73,15 +115,19 @@ export const validateProps = (propsDefinition, props, isFinal=true) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(propDef.type === "array") {
|
if(propDef.type === "array") {
|
||||||
|
let index = 0;
|
||||||
for(let arrayItem of propValue) {
|
for(let arrayItem of propValue) {
|
||||||
|
arrayItem._component = `${props._component}:${propDefName}`
|
||||||
const arrayErrs = validateProps(
|
const arrayErrs = validateProps(
|
||||||
propDef.elementDefinition,
|
propDef.elementDefinition,
|
||||||
arrayItem,
|
arrayItem,
|
||||||
|
[...stack, `${propDefName}[${index}]`],
|
||||||
isFinal
|
isFinal
|
||||||
)
|
)
|
||||||
for(let arrErr of arrayErrs) {
|
for(let arrErr of arrayErrs) {
|
||||||
errors.push(arrErr);
|
errors.push(arrErr);
|
||||||
}
|
}
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
validatePropsDefinition,
|
validatePropsDefinition,
|
||||||
validateProps
|
validateProps,
|
||||||
|
recursivelyValidate
|
||||||
} from "../src/userInterface/propsDefinitionParsing/validateProps";
|
} from "../src/userInterface/propsDefinitionParsing/validateProps";
|
||||||
import { createProps } from "../src/userInterface/propsDefinitionParsing/createProps";
|
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", () => {
|
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([]);
|
expect(errors).toEqual([]);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -125,7 +126,7 @@ describe("validateProps", () => {
|
||||||
|
|
||||||
const props = validProps();
|
const props = validProps();
|
||||||
props.rowCount = "1";
|
props.rowCount = "1";
|
||||||
const errors = validateProps(validPropDef, props, true);
|
const errors = validateProps(validPropDef, props, [], true);
|
||||||
expect(errors.length).toEqual(1);
|
expect(errors.length).toEqual(1);
|
||||||
expect(errors[0].propName).toBe("rowCount");
|
expect(errors[0].propName).toBe("rowCount");
|
||||||
|
|
||||||
|
@ -135,7 +136,7 @@ describe("validateProps", () => {
|
||||||
|
|
||||||
const props = validProps();
|
const props = validProps();
|
||||||
props.size = "really_small";
|
props.size = "really_small";
|
||||||
const errors = validateProps(validPropDef, props, true);
|
const errors = validateProps(validPropDef, props, [], true);
|
||||||
expect(errors.length).toEqual(1);
|
expect(errors.length).toEqual(1);
|
||||||
expect(errors[0].propName).toBe("size");
|
expect(errors[0].propName).toBe("size");
|
||||||
|
|
||||||
|
@ -145,9 +146,126 @@ describe("validateProps", () => {
|
||||||
|
|
||||||
const props = validProps();
|
const props = validProps();
|
||||||
props.columns[0].width = "seven";
|
props.columns[0].width = "seven";
|
||||||
const errors = validateProps(validPropDef, props, true);
|
const errors = validateProps(validPropDef, props, [], true);
|
||||||
expect(errors.length).toEqual(1);
|
expect(errors.length).toEqual(1);
|
||||||
expect(errors[0].propName).toBe("width");
|
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