diff --git a/packages/builder/src/userInterface/propsDefinitionParsing/types.js b/packages/builder/src/userInterface/propsDefinitionParsing/types.js index ee7232dada..43fceea17f 100644 --- a/packages/builder/src/userInterface/propsDefinitionParsing/types.js +++ b/packages/builder/src/userInterface/propsDefinitionParsing/types.js @@ -11,13 +11,29 @@ const defaultDef = typeName => () => ({ required:false, default:types[typeName].default, options: typeName === "options" ? [] : undefined, - elementDefinition: typeName === "array" ? "string" : undefined + elementDefinition: typeName === "array" ? {} : undefined }); const propType = (defaultValue, isOfType, defaultDefinition) => ({ isOfType, default:defaultValue, defaultDefinition }); +/*export const expandPropDef = propDef => { + const p = isString(propDef) + ? types[propDef].defaultDefinition() + : propDef; + + if(!isString(propDef)) { + const def = types[propDef.type].defaultDefinition(); + assign(propDef, def); + } + + if(p.type === "array" && isString(p.elementDefinition)) { + p.elementDefinition = types[p.elementDefinition].defaultDefinition() + } + return p; +}*/ + const isComponent = isObjectLike; export const types = { @@ -26,5 +42,6 @@ export const types = { number: propType(() => 0, isNumber, defaultDef("number")), array: propType(() => [], isArray, defaultDef("array")), options: propType(() => "", isString, defaultDef("options")), - component: propType(() => ({_component:""}), isComponent, defaultDef("component")) + component: propType(() => ({_component:""}), isComponent, defaultDef("component")), + asset: propType(() => "", isString, defaultDef("asset")), }; \ No newline at end of file diff --git a/packages/builder/src/userInterface/propsDefinitionParsing/validatePages.js b/packages/builder/src/userInterface/propsDefinitionParsing/validatePages.js new file mode 100644 index 0000000000..7d48c58b6c --- /dev/null +++ b/packages/builder/src/userInterface/propsDefinitionParsing/validatePages.js @@ -0,0 +1,66 @@ +import { recursivelyValidate } from "./validateProps"; +import { + isString, + keys, + flatten, + isArray, + map, + filter +} from "lodash/fp"; +import { common } from "budibase-core"; +const pipe = common.$; + +export const validatePage = (page, getComponent) => { + const errors = []; + const error = message => errors.push(message); + + const noIndex = !page.index || !page.index._component; + if(noIndex) { + error("Must choose a component for your index.html"); + } + + if(!page.appBody + || !isString(page.appBody) + || !page.appBody.endsWith(".json")) { + error("App body must be set toa valid JSON file"); + } + + const indexHtmlErrors = noIndex + ? [] + : pipe( + recursivelyValidate(page.index, getComponent), [ + map(e => `Index.html: ${e.error}`) + ]); + + return [...errors, ...indexHtmlErrors]; +} + +export const validatePages = (pages, getComponent) => { + + let errors = []; + const error = message => errors.push(message); + + if(!pages.main) { + error("must have a 'main' page"); + } + + if(!pages.unauthenticated) { + error("must have a 'unauthenticated' (login) page"); + } + + if(!pages.componentLibraries + || !isArray(pages.componentLibraries) + || pages.componentLibraries.length === 0) { + + error("componentLibraries must be set to a non-empty array of strings"); + } + + const pageErrors = pipe(pages, [ + keys, + filter(k => k !== "componentLibraries"), + map(k => validatePage(pages[k], getComponent)), + flatten + ]); + + return [...errors, ...pageErrors]; +} \ No newline at end of file diff --git a/packages/builder/src/userInterface/propsDefinitionParsing/validateProps.js b/packages/builder/src/userInterface/propsDefinitionParsing/validateProps.js index 2078a5297b..dedfd17ca8 100644 --- a/packages/builder/src/userInterface/propsDefinitionParsing/validateProps.js +++ b/packages/builder/src/userInterface/propsDefinitionParsing/validateProps.js @@ -30,6 +30,13 @@ export const recursivelyValidate = (rootProps, getComponent, stack=[]) => { return getComponent(componentName); } + if(!rootProps._component) { + const errs = []; + makeError(errs, "_component", stack)("Component is not set"); + return errs; + // this would break everything else anyway + } + const propsDef = getComponentPropsDefinition( rootProps._component); @@ -80,18 +87,26 @@ export const recursivelyValidate = (rootProps, getComponent, stack=[]) => { return flattenDeep([errors, ...childErrors, ...childArrayErrors]); } -const expandPropDef = propDef => - isString(propDef) - ? types[propDef].defaultDefinition() - : propDef; +const expandPropDef = propDef => { + const p = isString(propDef) + ? types[propDef].defaultDefinition() + : propDef; + if(p.type === "array" && isString(p.elementDefinition)) { + p.elementDefinition = types[p.elementDefinition].defaultDefinition() + } + return p; +} export const validateProps = (propsDefinition, props, stack=[], isFinal=true) => { const errors = []; - if(!props._component) + if(!props._component) { makeError(errors, "_component", stack)("Component is not set"); + return errors; + // this would break everything else anyway + } for(let propDefName in propsDefinition) { diff --git a/packages/builder/tests/validatePages.spec.js b/packages/builder/tests/validatePages.spec.js new file mode 100644 index 0000000000..3305a6bf01 --- /dev/null +++ b/packages/builder/tests/validatePages.spec.js @@ -0,0 +1,92 @@ +import { + validatePages, + validatePage +} from "../src/userInterface/propsDefinitionParsing/validatePages"; + +const validPages = () => ({ + "main" : { + "index" : { + "_component": "testIndexHtml", + "title": "My Cool App", + "customScripts": [ + {"url": "MyCustomComponents.js"} + ] + }, + "appBody" : "./main.app.json" + }, + "unauthenticated" : { + "index" : { + "_component": "testIndexHtml", + "title": "My Cool App - Login", + "customScripts": [ + {"url": "MyCustomComponents.js"} + ] + }, + "appBody" : "./unauthenticated.app.json" + }, + "componentLibraries": ["./myComponents"] +}); + +const getComponent = name => ({ + testIndexHtml : { + title: "string", + customScripts: { + type:"array", + elementDefinition: { + url: "string" + } + } + } +}[name]) + +describe("validate single page", () => { + + it("should return no errors when page is valid", () => { + const errors = validatePage( + validPages().main, + getComponent); + + expect(errors).toEqual([]); + }); + + it("should return error when index is not set, or set incorrectly", () => { + let page = validPages().main; + delete page.index; + expect(validatePage(page, getComponent).length).toEqual(1); + + page.index = {title:"something"}; // no _component + const noComponent = validatePage(page, getComponent); + expect(noComponent.length).toEqual(1); + + }); + + it("should return error when appBody is not set, or set incorrectly", () => { + let page = validPages().main; + delete page.appBody; + expect(validatePage(page, getComponent).length).toEqual(1); + + page.appBody = true; // not a string + expect(validatePage(page, getComponent).length).toEqual(1); + + page.appBody = "something.js"; // not a json + expect(validatePage(page, getComponent).length).toEqual(1); + + }); + +}); + +describe("validate pages", () => { + + it("should return no errors when pages are valid", () => { + const errors = validatePages( + validPages(), + getComponent); + + expect(errors).toEqual([]); + }); + + it("should return error when index is not set, or set incorrectly", () => { + + }); + +});