diff --git a/src/component/props_validation.ts b/src/component/props_validation.ts index 5aebdd62..bae01d94 100644 --- a/src/component/props_validation.ts +++ b/src/component/props_validation.ts @@ -19,6 +19,14 @@ export function applyDefaultProps(props: { [key: string]: any }, ComponentClass: //------------------------------------------------------------------------------ // Prop validation helper //------------------------------------------------------------------------------ +function getPropDescription(staticProps: any) { + if (staticProps instanceof Array) { + return Object.fromEntries( + staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])) + ); + } + return staticProps || { "*": true }; +} /** * Validate the component props (or next props) against the (static) props @@ -33,46 +41,34 @@ export const validateProps = function (name: string | typeof Component, props: a applyDefaultProps(props, ComponentClass); - const propsDef = ComponentClass.props; - if (propsDef instanceof Array) { - // list of strings (prop names) - for (const propName of propsDef) { - if (propName[propName.length - 1] === "?") { - // optional prop - break; - } - if (!(propName in props)) { + let propsDef = getPropDescription(ComponentClass.props); + const allowAdditionalProps = "*" in propsDef; + + for (let propName in propsDef) { + if (propName === "*") { + continue; + } + if (props[propName] === undefined) { + if (propsDef[propName] && !propsDef[propName].optional) { throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`); + } else { + continue; } } - for (let key in props) { - if (!propsDef.includes(key) && !propsDef.includes(key + "?")) { - throw new Error(`Unknown prop '${key}' given to component '${ComponentClass.name}'`); - } + let isValid; + try { + isValid = isValidProp(props[propName], propsDef[propName]); + } catch (e) { + (e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${ + (e as Error).message + })`; + throw e; } - } else if (propsDef) { - // propsDef is an object now - for (let propName in propsDef) { - if (props[propName] === undefined) { - if (propsDef[propName] && !propsDef[propName].optional) { - throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`); - } else { - continue; - } - } - let isValid; - try { - isValid = isValidProp(props[propName], propsDef[propName]); - } catch (e) { - (e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${ - (e as Error).message - })`; - throw e; - } - if (!isValid) { - throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`); - } + if (!isValid) { + throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`); } + } + if (!allowAdditionalProps) { for (let propName in props) { if (!(propName in propsDef)) { throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`); diff --git a/tests/components/__snapshots__/props_validation.test.ts.snap b/tests/components/__snapshots__/props_validation.test.ts.snap index 82dfe1ec..75ef0ec8 100644 --- a/tests/components/__snapshots__/props_validation.test.ts.snap +++ b/tests/components/__snapshots__/props_validation.test.ts.snap @@ -96,6 +96,58 @@ exports[`default props default values are also set whenever component is updated }" `; +exports[`props validation can specify that additional props are allowed (array) 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + + return function template(ctx, node, key = \\"\\") { + const props1 = {message: 'm',otherProp: 'o'} + helpers.validateProps(\`Child\`, props1, ctx) + return component(\`Child\`, props1, key + \`__2\`, node, ctx); + } +}" +`; + +exports[`props validation can specify that additional props are allowed (array) 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + + let block1 = createBlock(\`