import { Component, Env } from "../src/component"; import { makeTestFixture, makeTestEnv } from "./helpers"; import { QWeb, UTILS } from "../src/qweb_core"; //------------------------------------------------------------------------------ // Setup and helpers //------------------------------------------------------------------------------ let fixture: HTMLElement; let env: Env; let dev: boolean = false; beforeEach(() => { fixture = makeTestFixture(); env = makeTestEnv(); dev = QWeb.dev; QWeb.dev = true; }); afterEach(() => { fixture.remove(); QWeb.dev = dev; }); class Widget extends Component {} //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("props validation", () => { test("validation is only done in dev mode", async () => { class TestWidget extends Widget { static props = ["message"]; } QWeb.dev = true; expect(() => { new TestWidget(env); }).toThrow(); QWeb.dev = false; expect(() => { new TestWidget(env); }).not.toThrow(); }); test("props: list of strings", async () => { class TestWidget extends Widget { static props = ["message"]; } expect(() => { new TestWidget(env); }).toThrow("Missing props 'message' (component 'TestWidget')"); }); test("validate simple types", async () => { const Tests = [ { type: Number, ok: 1, ko: "1" }, { type: Boolean, ok: true, ko: "1" }, { type: String, ok: "1", ko: 1 }, { type: Object, ok: {}, ko: "1" }, { type: Date, ok: new Date(), ko: "1" }, { type: Function, ok: () => {}, ko: "1" } ]; for (let test of Tests) { let TestWidget = class extends Widget { static props = { p: test.type }; }; expect(() => { new TestWidget(env); }).toThrow("Missing props 'p'"); expect(() => { new TestWidget(env, { p: test.ok }); }).not.toThrow(); expect(() => { new TestWidget(env, { p: test.ko }); }).toThrow("Props 'p' of invalid type in component"); } }); test("validate simple types, alternate form", async () => { const Tests = [ { type: Number, ok: 1, ko: "1" }, { type: Boolean, ok: true, ko: "1" }, { type: String, ok: "1", ko: 1 }, { type: Object, ok: {}, ko: "1" }, { type: Date, ok: new Date(), ko: "1" }, { type: Function, ok: () => {}, ko: "1" } ]; for (let test of Tests) { let TestWidget = class extends Widget { static props = { p: { type: test.type } }; }; expect(() => { new TestWidget(env); }).toThrow("Missing props 'p'"); expect(() => { new TestWidget(env, { p: test.ok }); }).not.toThrow(); expect(() => { new TestWidget(env, { p: test.ko }); }).toThrow("Props 'p' of invalid type in component"); } }); test("can validate a prop with multiple types", async () => { let TestWidget = class extends Widget { static props = { p: [String, Boolean] }; }; expect(() => { new TestWidget(env, { p: "string" }); new TestWidget(env, { p: true }); }).not.toThrow(); expect(() => { new TestWidget(env, { p: 1 }); }).toThrow("Props 'p' of invalid type in component"); }); test("can validate an optional props", async () => { let TestWidget = class extends Widget { static props = { p: { type: String, optional: true } }; }; expect(() => { new TestWidget(env, { p: "hey" }); new TestWidget(env, {}); }).not.toThrow(); expect(() => { new TestWidget(env, { p: 1 }); }).toThrow(); }); test("can validate an array with given primitive type", async () => { let TestWidget = class extends Widget { static props = { p: { type: Array, element: String } }; }; expect(() => { new TestWidget(env, { p: [] }); new TestWidget(env, { p: ["string"] }); }).not.toThrow(); expect(() => { new TestWidget(env, { p: [1] }); }).toThrow(); expect(() => { new TestWidget(env, { p: ["string", 1] }); }).toThrow(); }); test("can validate an array with multiple sub element types", async () => { let TestWidget = class extends Widget { static props = { p: { type: Array, element: [String, Boolean] } }; }; expect(() => { new TestWidget(env, { p: [] }); new TestWidget(env, { p: ["string"] }); new TestWidget(env, { p: [false, true, "string"] }); }).not.toThrow(); expect(() => { new TestWidget(env, { p: [true, 1] }); }).toThrow(); }); test("can validate an object with simple shape", async () => { let TestWidget = class extends Widget { static props = { p: { type: Object, shape: { id: Number, url: String } } }; }; expect(() => { new TestWidget(env, { p: { id: 1, url: "url" } }); new TestWidget(env, { p: { id: 1, url: "url", extra: true } }); }).not.toThrow(); expect(() => { new TestWidget(env, { p: { id: "1", url: "url" } }); }).toThrow(); expect(() => { new TestWidget(env, { p: { id: 1 } }); }).toThrow(); }); test("can validate recursively complicated prop def", async () => { let TestWidget = class extends Widget { static props = { p: { type: Object, shape: { id: Number, url: [Boolean, { type: Array, element: Number }] } } }; }; expect(() => { new TestWidget(env, { p: { id: 1, url: true } }); new TestWidget(env, { p: { id: 1, url: [12] } }); }).not.toThrow(); expect(() => { new TestWidget(env, { p: { id: 1, url: [12, true] } }); }).toThrow(); }); test("props are validated in dev mode (code snapshot)", async () => { env.qweb.addTemplates(`
`); 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("
1
"); // 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", () => { test("can set default values", async () => { class TestWidget extends Widget { static defaultProps = { p: 4 }; } const w = new TestWidget(env, {}); expect(w.props.p).toBe(4); }); test("default values are also set whenever component is updated", async () => { class TestWidget extends Widget { static defaultProps = { p: 4 }; } const w = new TestWidget(env, { p: 1 }); await w.__updateProps({}); expect(w.props.p).toBe(4); }); });