mirror of https://github.com/Budibase/budibase.git
5 changed files with 296 additions and 15 deletions
@ -0,0 +1,26 @@ |
|||||
|
import { |
||||
|
isString, |
||||
|
isBoolean, |
||||
|
isNumber, |
||||
|
isArray |
||||
|
} from "lodash/fp"; |
||||
|
|
||||
|
const defaultDef = typeName => () => ({ |
||||
|
type: typeName, |
||||
|
required:false, |
||||
|
default:types[typeName].default, |
||||
|
options: typeName === "options" ? [] : undefined, |
||||
|
itemPropsDefinition: typeName === "array" ? "string" : undefined |
||||
|
}); |
||||
|
|
||||
|
const propType = (defaultValue, isOfType, defaultDefinition) => ({ |
||||
|
isOfType, default:defaultValue, defaultDefinition |
||||
|
}); |
||||
|
|
||||
|
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")) |
||||
|
}; |
||||
@ -0,0 +1,106 @@ |
|||||
|
import { types } from "./types"; |
||||
|
import { createDefaultProps } from "./createDefaultProps"; |
||||
|
import { isString } from "util"; |
||||
|
import { |
||||
|
includes, |
||||
|
filter, |
||||
|
map, |
||||
|
keys, |
||||
|
flatten, |
||||
|
each } from "lodash/fp"; |
||||
|
import { common } from "budibase-core"; |
||||
|
|
||||
|
const pipe = common.$; |
||||
|
|
||||
|
const makeError = (errors, propName) => (message) => |
||||
|
errors.push({ |
||||
|
propName, |
||||
|
error:message}); |
||||
|
|
||||
|
export const validateProps = (propsDefinition, props, isFinal=true) => { |
||||
|
|
||||
|
const errors = []; |
||||
|
|
||||
|
for(let propDefName in propsDefinition) { |
||||
|
|
||||
|
let propDef = propsDefinition[propDefName]; |
||||
|
|
||||
|
if(isString(propDef)) |
||||
|
propDef = types[propDef].defaultDefinition(); |
||||
|
|
||||
|
const type = types[propDef.type]; |
||||
|
|
||||
|
const error = makeError(errors, propDefName); |
||||
|
|
||||
|
const propValue = props[propDefName]; |
||||
|
if(isFinal && propDef.required && propValue) { |
||||
|
error(`Property ${propDefName} is required`); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if(!type.isOfType(propValue)) { |
||||
|
error(`Property ${propDefName} is not of type ${propDef.type}. Actual value ${propValue}`) |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if(propDef.type === "array") { |
||||
|
for(let arrayItem of propValue) { |
||||
|
const arrayErrs = validateProps( |
||||
|
propDef.itemPropsDefinition, |
||||
|
arrayItem, |
||||
|
isFinal |
||||
|
) |
||||
|
for(let arrErr of arrayErrs) { |
||||
|
errors.push(arrErr); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if(propDef.type === "options" |
||||
|
&& propValue |
||||
|
&& !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); |
||||
|
|
||||
|
|
||||
|
// arrar props without itemPropsDefinition
|
||||
|
pipe(propsDefinition, [ |
||||
|
keys, |
||||
|
map(k => ({ |
||||
|
propDef:propsDefinition[k], |
||||
|
propName:k |
||||
|
})), |
||||
|
filter(d => d.propDef.type === "array" && !d.propDef.itemPropsDefinition), |
||||
|
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)), |
||||
|
flatten |
||||
|
]); |
||||
|
|
||||
|
pipe(propsDefinition, [ |
||||
|
keys, |
||||
|
map(k => ({ |
||||
|
propDef:propsDefinition[k], |
||||
|
propName:k |
||||
|
})), |
||||
|
filter(d => d.propDef.type === "options" |
||||
|
&& (!d.propDef.options || d.propDef.options.length === 0)), |
||||
|
each(d => makeError(errors, d.propName)(`${d.propName} does not have any options`)) |
||||
|
]); |
||||
|
|
||||
|
return [...errors, ...arrayPropValidationErrors] |
||||
|
|
||||
|
} |
||||
|
|
||||
@ -0,0 +1,156 @@ |
|||||
|
import { |
||||
|
validatePropsDefinition, |
||||
|
validateProps |
||||
|
} from "../src/userInterface/propsDefinitionParsing/validateProps"; |
||||
|
import { createDefaultProps } from "../src/userInterface/propsDefinitionParsing/createDefaultProps"; |
||||
|
import { |
||||
|
keys, some |
||||
|
} from "lodash/fp"; |
||||
|
|
||||
|
// not that allot of this functionality is covered
|
||||
|
// in createDefaultProps - as validate props uses that.
|
||||
|
|
||||
|
describe("validatePropsDefinition", () => { |
||||
|
|
||||
|
it("should recursively validate array props and return no errors when valid", () => { |
||||
|
|
||||
|
const propsDef = { |
||||
|
columns : { |
||||
|
type: "array", |
||||
|
itemPropsDefinition: { |
||||
|
width: "number", |
||||
|
units: { |
||||
|
type: "string", |
||||
|
default: "px" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const errors = validatePropsDefinition(propsDef); |
||||
|
|
||||
|
expect(errors).toEqual([]); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
it("should recursively validate array props and return errors when invalid", () => { |
||||
|
|
||||
|
const propsDef = { |
||||
|
columns : { |
||||
|
type: "array", |
||||
|
itemPropsDefinition: { |
||||
|
width: "invlid type", |
||||
|
units: { |
||||
|
type: "string", |
||||
|
default: "px" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const errors = validatePropsDefinition(propsDef); |
||||
|
|
||||
|
expect(errors.length).toEqual(1); |
||||
|
expect(errors[0].propName).toBe("width"); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
it("should return error when no options for options field", () => { |
||||
|
|
||||
|
const propsDef = { |
||||
|
size: { |
||||
|
type: "options", |
||||
|
options: [] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const errors = validatePropsDefinition(propsDef); |
||||
|
|
||||
|
expect(errors.length).toEqual(1); |
||||
|
expect(errors[0].propName).toBe("size"); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
it("should not return error when options field has options", () => { |
||||
|
|
||||
|
const propsDef = { |
||||
|
size: { |
||||
|
type: "options", |
||||
|
options: ["small", "medium", "large"] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const errors = validatePropsDefinition(propsDef); |
||||
|
|
||||
|
expect(errors).toEqual([]); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
const validPropDef = { |
||||
|
size: { |
||||
|
type: "options", |
||||
|
options: ["small", "medium", "large"], |
||||
|
default:"medium" |
||||
|
}, |
||||
|
rowCount : "number", |
||||
|
columns : { |
||||
|
type: "array", |
||||
|
itemPropsDefinition: { |
||||
|
width: "number", |
||||
|
units: { |
||||
|
type: "string", |
||||
|
default: "px" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
const validProps = () => { |
||||
|
const { props } = createDefaultProps(validPropDef); |
||||
|
props.columns.push( |
||||
|
createDefaultProps(validPropDef.columns.itemPropsDefinition).props); |
||||
|
return props; |
||||
|
} |
||||
|
|
||||
|
describe("validateProps", () => { |
||||
|
|
||||
|
it("should have no errors with a big list of valid props", () => { |
||||
|
|
||||
|
const errors = validateProps(validPropDef, validProps(), true); |
||||
|
expect(errors).toEqual([]); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
it("should return error with invalid value", () => { |
||||
|
|
||||
|
const props = validProps(); |
||||
|
props.rowCount = "1"; |
||||
|
const errors = validateProps(validPropDef, props, true); |
||||
|
expect(errors.length).toEqual(1); |
||||
|
expect(errors[0].propName).toBe("rowCount"); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
it("should return error with invalid option", () => { |
||||
|
|
||||
|
const props = validProps(); |
||||
|
props.size = "really_small"; |
||||
|
const errors = validateProps(validPropDef, props, true); |
||||
|
expect(errors.length).toEqual(1); |
||||
|
expect(errors[0].propName).toBe("size"); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
it("should return error with invalid array item", () => { |
||||
|
|
||||
|
const props = validProps(); |
||||
|
props.columns[0].width = "seven"; |
||||
|
const errors = validateProps(validPropDef, props, true); |
||||
|
expect(errors.length).toEqual(1); |
||||
|
expect(errors[0].propName).toBe("width"); |
||||
|
|
||||
|
}); |
||||
|
}) |
||||
Loading…
Reference in new issue