Skip to content

Commit

Permalink
[FIX] component: properly validate optional types in objects
Browse files Browse the repository at this point in the history
closes #440
  • Loading branch information
ged-odoo committed Nov 13, 2019
1 parent 263f31f commit 749f006
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 15 deletions.
3 changes: 1 addition & 2 deletions doc/reference/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,6 @@ update a number whenever the change is done.

Note: the online playground has an example to show how it works.


### Semantics

We give here an informal description of the way components are created/updated
Expand Down Expand Up @@ -824,7 +823,7 @@ As an application becomes complex, it may be quite unsafe to define props in an
- hard to tell how a component should be used, by looking at its code.
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parents.

A props type system would solve both issues, by describing the types and shapes
A props type system solves both issues, by describing the types and shapes
of the props. Here is how it works in Owl:

- `props` key is a static key (so, different from `this.props` in a component instance)
Expand Down
19 changes: 9 additions & 10 deletions doc/reference/qweb_templating_language.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ For reference, here is a list of all standard QWeb directives:
The component system in Owl requires additional directives, to express various
needs. Here is a list of all Owl specific directives:

| Name | Description |
| ------------------------ | ----------------------------------------------------------------------------------- |
| `t-component`, `t-props` | [Defining a sub component](component.md#composition) |
| `t-ref` | [Setting a reference to a dom node or a sub component](component.md#references) |
| `t-key` | [Defining a key (to help virtual dom reconciliation)](#loops) |
| `t-on-*` | [Event handling](component.md#event-handling) |
| `t-transition` | [Defining an animation](animations.md#css-transitions) |
| `t-slot` | [Rendering a slot](component.md#slots) |
| `t-model` | [Form input bindings](component.md#form-input-bindings) |
| Name | Description |
| ------------------------ | ------------------------------------------------------------------------------- |
| `t-component`, `t-props` | [Defining a sub component](component.md#composition) |
| `t-ref` | [Setting a reference to a dom node or a sub component](component.md#references) |
| `t-key` | [Defining a key (to help virtual dom reconciliation)](#loops) |
| `t-on-*` | [Event handling](component.md#event-handling) |
| `t-transition` | [Defining an animation](animations.md#css-transitions) |
| `t-slot` | [Rendering a slot](component.md#slots) |
| `t-model` | [Form input bindings](component.md#form-input-bindings) |

## Reference

Expand Down Expand Up @@ -388,7 +388,6 @@ into the global context.
<!-- new_variable undefined -->
```


Even though Owl tries to be as declarative as possible, the DOM does not fully
expose its state declaratively in the DOM tree. For example, the scrolling state,
the current user selection, the focused element or the state of an input are not
Expand Down
18 changes: 17 additions & 1 deletion src/component/props_validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ QWeb.utils.validateProps = function(Widget, props: Object) {
break;
}
}
let isValid = isValidProp(props[propName], propsDef[propName]);
let isValid;
try {
isValid = isValidProp(props[propName], propsDef[propName]);
} catch (e) {
e.message = `Invalid prop '${propName}' in component ${Widget.name} (${e.message})`;
throw e;
}
if (!isValid) {
throw new Error(`Props '${propName}' of invalid type in component '${Widget.name}'`);
}
Expand Down Expand Up @@ -80,6 +86,9 @@ function isValidProp(prop, propDef): boolean {
return result;
}
// propsDef is an object
if (propDef.optional && prop === undefined) {
return true;
}
let result = isValidProp(prop, propDef.type);
if (propDef.type === Array) {
for (let i = 0, iLen = prop.length; i < iLen; i++) {
Expand All @@ -91,6 +100,13 @@ function isValidProp(prop, propDef): boolean {
for (let key in shape) {
result = result && isValidProp(prop[key], shape[key]);
}
if (result) {
for (let propName in prop) {
if (!(propName in shape)) {
throw new Error(`unknown prop '${propName}'`);
}
}
}
}
return result;
}
38 changes: 36 additions & 2 deletions tests/component/props_validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ describe("props validation", () => {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe(`Props 'p' of invalid type in component 'TestWidget'`);
expect(error.message).toBe("Props 'p' of invalid type in component 'TestWidget'");
});

test("can validate an optional props", async () => {
Expand Down Expand Up @@ -425,7 +425,8 @@ describe("props validation", () => {
} catch (e) {
error = e;
}
expect(error).toBeUndefined();
expect(error).toBeDefined();
expect(error.message).toBe("Invalid prop 'p' in component TestWidget (unknown prop 'extra')");

try {
props = { p: { id: "1", url: "url" } };
Expand Down Expand Up @@ -501,6 +502,39 @@ describe("props validation", () => {
expect(error.message).toBe(`Props 'p' of invalid type in component 'TestWidget'`);
});

test("can validate optional attributes in nested sub props", () => {
class TestComponent extends Component<any, any> {
static props = {
myprop: {
type: Array,
element: {
type: Object,
shape: {
num: { type: Number, optional: true }
}
}
}
};
}
let error;
try {
QWeb.utils.validateProps(TestComponent, { myprop: [{}] });
} catch (e) {
error = e;
}
expect(error).toBeUndefined();

try {
QWeb.utils.validateProps(TestComponent, { myprop: [{ a: 1 }] });
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe(
"Invalid prop 'myprop' in component TestComponent (unknown prop 'a')"
);
});

test("props are validated in dev mode (code snapshot)", async () => {
env.qweb.addTemplates(`
<templates>
Expand Down

0 comments on commit 749f006

Please sign in to comment.