[IMP] component: various prop validation improvements

- add optional form for list props: ['optionalField?']
- accept undefined values for optional props
- allow declaring props with only boolean true
- throw error if extra prop is given to component

closes #223
This commit is contained in:
Géry Debongnie
2019-06-28 15:48:33 +02:00
parent 1c8e1af86d
commit 7245ccf8f9
7 changed files with 277 additions and 127 deletions
+1 -1
View File
@@ -353,7 +353,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
let def3;
const ref5 = \`child\`;
let _6 = {'a':true};
Object.assign(_6, this.utils.toObj(context['state'].b?'b':''))
Object.assign(_6, utils.toObj(context['state'].b?'b':''))
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
let _2_index = c1.length;
c1.push(null);
@@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`props validation props are validated in dev mode (code snapshot) 1`] = `
"function anonymous(context,extra
) {
let utils = this.utils;
let QWeb = this.constructor;
let owner = context;
var h = this.utils.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
//COMPONENT
let def3;
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
let _2_index = c1.length;
c1.push(null);
let props4 = {message:1};
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
def3 = w4.__owl__.renderPromise;
} else {
w4.destroy();
w4 = false;
}
}
if (!w4) {
let componentKey4 = \`Child\`;
let W4 = context.components && context.components[componentKey4] || QWeb.components[componentKey4];
if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')}
utils.validateProps(W4, props4)
w4 = new W4(owner, props4);
context.__owl__.cmap[4] = w4.__owl__.id;
def3 = w4.__prepare();
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
} else {
utils.validateProps(w4.constructor, props4)
def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
}
extra.promises.push(def3);
return vn1;
}"
`;
+91 -14
View File
@@ -1,6 +1,6 @@
import { Component, Env } from "../src/component";
import { makeTestFixture, makeTestEnv } from "./helpers";
import { QWeb } from "../src";
import { QWeb, UTILS } from "../src/qweb_core";
//------------------------------------------------------------------------------
// Setup and helpers
@@ -45,19 +45,6 @@ describe("props validation", () => {
}).not.toThrow();
});
test("props validation is also done on update props", async () => {
expect.assertions(1);
class TestWidget extends Widget {
static props = ["message"];
}
const w = new TestWidget(env, { message: "bottle" });
try {
await w.__updateProps({});
} catch (e) {
expect(e.message).toBe("Missing props 'message' (component 'TestWidget')");
}
});
test("props: list of strings", async () => {
class TestWidget extends Widget {
static props = ["message"];
@@ -234,6 +221,96 @@ describe("props validation", () => {
new TestWidget(env, { p: { id: 1, url: [12, true] } });
}).toThrow();
});
test("props are validated in dev mode (code snapshot)", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="App">
<Child message="1"/>
</div>
<div t-name="Child"><t t-esc="props.message"/></div>
</templates>`);
class Child extends Widget {
static props = ["message"];
}
class App extends Widget {
components = { Child };
}
const app = new App(env);
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div>1</div></div>");
// need to make sure there are 2 call to update props. one at component
// creation, and one at update time.
expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot();
});
test("props: list of strings with optional props", async () => {
class TestWidget extends Widget {
static props = ["message", "someProp?"];
}
expect(() => {
UTILS.validateProps(TestWidget, { someProp: 1 });
}).toThrow();
expect(() => {
UTILS.validateProps(TestWidget, { message: 1 });
}).not.toThrow();
});
test("props: can be defined with a boolean", async () => {
class TestWidget extends Widget {
static props = { message: true };
}
expect(() => {
UTILS.validateProps(TestWidget, {});
}).toThrow();
});
test("props: extra props cause an error", async () => {
class TestWidget extends Widget {
static props = ["message"];
}
expect(() => {
UTILS.validateProps(TestWidget, { message: 1, flag: true });
}).toThrow();
});
test("props: extra props cause an error, part 2", async () => {
class TestWidget extends Widget {
static props = {message: true};
}
expect(() => {
UTILS.validateProps(TestWidget, { message: 1, flag: true });
}).toThrow();
});
test("props: optional prop do not cause an error", async () => {
class TestWidget extends Widget {
static props = ["message?"];
}
expect(() => {
UTILS.validateProps(TestWidget, { message: 1});
}).not.toThrow();
});
test("optional prop do not cause an error if value is undefined", async () => {
class TestWidget extends Widget {
static props = {message: {type: String, optional: true}};
}
expect(() => {
UTILS.validateProps(TestWidget, { message: undefined});
}).not.toThrow();
expect(() => {
UTILS.validateProps(TestWidget, { message: null});
}).toThrow();
});
});
describe("default props", () => {