Skip to content

Commit

Permalink
[IMP] runtime: allow using any class as a type in props validation
Browse files Browse the repository at this point in the history
Previously, we had a fixed whitelist for types that were allowed during
props validation. The implementation however supports using arbitrary
classes, and in practice it's desirable to do so, and already done when
not using typescript (when using typescript, it will error if the class
is not whitelisted), eg in Odoo, we use "Element" for the arch in the
standard view props, but this causes all view controllers to fail type
checking because it's not whitelisted.

This commit simply replaces existing constructors by a generic
constructor type, and adds a test with a validation success and a test
with a validation failure.
  • Loading branch information
sdegueldre authored and ged-odoo committed Mar 26, 2024
1 parent 33dfeb1 commit 97b69f1
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 10 deletions.
11 changes: 1 addition & 10 deletions src/runtime/validation.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import { OwlError } from "../common/owl_error";
import { toRaw } from "./reactivity";

type BaseType =
| typeof String
| typeof Boolean
| typeof Number
| typeof Date
| typeof Object
| typeof Array
| typeof Function
| true
| "*";
type BaseType = { new (...args: any[]): any } | true | "*";

interface TypeInfo {
type?: TypeDescription;
Expand Down
39 changes: 39 additions & 0 deletions tests/components/__snapshots__/props_validation.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,45 @@ exports[`props validation can specify that additional props are allowed (object)
}"
`;

exports[`props validation can use custom class as type 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"customObj\\"]);

return function template(ctx, node, key = \\"\\") {
const props1 = {customObj: ctx['customObj']};
helpers.validateProps(\`Child\`, props1, this);
return comp1(props1, key + \`__1\`, node, this, null);
}
}"
`;

exports[`props validation can use custom class as type 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;

return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].customObj.val);
}
}"
`;

exports[`props validation can use custom class as type: validation failure 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"customObj\\"]);

return function template(ctx, node, key = \\"\\") {
const props1 = {customObj: ctx['customObj']};
helpers.validateProps(\`Child\`, props1, this);
return comp1(props1, key + \`__1\`, node, this, null);
}
}"
`;

exports[`props validation can validate a prop with multiple types 1`] = `
"function anonymous(app, bdom, helpers
) {
Expand Down
46 changes: 46 additions & 0 deletions tests/components/props_validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,52 @@ describe("props validation", () => {
expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'Child': 'message' is missing");
});

test("can use custom class as type", async () => {
class CustomClass {
val = "hey";
}
class Child extends Component {
static props = { customObj: CustomClass };
static template = xml`<t t-esc="props.customObj.val"/>`;
}

class Parent extends Component {
static components = { Child };
static template = xml`<Child customObj="customObj" />`;
customObj = new CustomClass();
}

const app = new App(Parent, { test: true });
await app.mount(fixture);
expect(fixture.innerHTML).toBe("hey");
});

test("can use custom class as type: validation failure", async () => {
class CustomClass {}
class Child extends Component {
static props = { customObj: CustomClass };
static template = xml`<div>hey</div>`;
}

class Parent extends Component {
static components = { Child };
static template = xml`<Child customObj="customObj" />`;
customObj = {};
}

const app = new App(Parent, { test: true });
let error: OwlError | undefined;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
"Invalid props for component 'Child': 'customObj' is not a customclass"
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"Invalid props for component 'Child': 'customObj' is not a customclass"
);
});
});

//------------------------------------------------------------------------------
Expand Down

0 comments on commit 97b69f1

Please sign in to comment.