diff --git a/README.md b/README.md index b566a3dd..80ae0f97 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,6 @@ Here is a short example to illustrate interactive widgets: ```javascript class ClickCounter extends owl.Component { - inlineTemplate = ` - `; - state = { value: 0 }; increment() { @@ -48,7 +43,12 @@ class ClickCounter extends owl.Component { } } -const qweb = new owl.QWeb(); +const TEMPLATES = ` + `; + +const qweb = new owl.QWeb(TEMPLATES); const counter = new ClickCounter({ qweb }); counter.mount(document.body); ``` diff --git a/doc/component.md b/doc/component.md index 2f65e86d..386213b9 100644 --- a/doc/component.md +++ b/doc/component.md @@ -30,7 +30,7 @@ OWL components are the building blocks for user interface. They are designed to and follow the QWeb specification. This is a requirement for Odoo. OWL components are defined as a subclass of Component. The rendering is -exclusively done by a [QWeb](qweb.md) template (either defined inline or preloaded in QWeb). +exclusively done by a [QWeb](qweb.md) template (which needs to be preloaded in QWeb). Rendering a component generates a virtual dom representation of the widget, which is then patched to the DOM, in order to apply the changes in an efficient way. @@ -61,7 +61,7 @@ Note that this code is written in ESNext style, so it will only run on the latest browsers without a transpilation step. This example show how a component should be defined: it simply subclasses the -Component class. If no `template` key (or `inlineTemplate`), is defined, then +Component class. If no `template` key is defined, then Owl will use the component's name as template name. Here, a state object is defined. It is not mandatory to use the state object, but it is certainly encouraged. The state object is [observed](observer.md), and any @@ -112,7 +112,7 @@ between a valid css class added by the component, or some custom code, and a class that need to be removed. This is why we only support the explicit syntax with a class object: -```js +```xml ``` @@ -124,7 +124,7 @@ parent to its children. The environment needs to have a QWeb instance, which will be used to render the component template. Be aware that the name of the component may be significant: if a component does -not define a `template` or `inlineTemplate` key, then Owl will lookup in QWeb to +not define a `template` key, then Owl will lookup in QWeb to find a template with the component name (or one of its ancestor). ### Properties @@ -137,9 +137,6 @@ find a template with the component name (or one of its ancestor). - **`template`** (string, optional): if given, this is the name of the QWeb template that will render the component. -- **`inlineTemplate`** (string, optional): a string that represents a xml template. If set, - this will be loaded into QWeb and used instead of the `template` property. - - **`state`** (Object): this is the location of the component's state, if there is any. After the willStart method, the `state` property is observed, and each change will cause the widget to rerender itself. diff --git a/src/component.ts b/src/component.ts index ab2d3b8a..3614f015 100644 --- a/src/component.ts +++ b/src/component.ts @@ -43,7 +43,7 @@ export interface Meta { mountedHandlers: { [key: number]: Function }; } -// If a component does not define explicitely a template (or inlineTemplate) +// If a component does not define explicitely a template // key, it needs to find a template with its name (or a parent's). This is // qweb dependant, so we need a place to store this information indexed by // qweb instances. @@ -61,7 +61,6 @@ export class Component< > extends EventBus { readonly __owl__: Meta; template?: string; - inlineTemplate?: string; get el(): HTMLElement | null { return this.__owl__.vnode ? (this).__owl__.vnode.elm : null; @@ -388,41 +387,29 @@ export class Component< const qweb = this.env.qweb; if (!this.template) { - if (this.inlineTemplate) { - this.env.qweb.addTemplate( - this.inlineTemplate, - this.inlineTemplate, - true - ); - - // we write on the proto, so any new component of this class will get - // automatically the template key properly setup. - (this).__proto__.template = this.inlineTemplate; + let tmap = TEMPLATE_MAP[qweb.id]; + if (!tmap) { + tmap = {}; + TEMPLATE_MAP[qweb.id] = tmap; + } + let p = (this).constructor; + let name: string = p.name; + let template = tmap[name]; + if (template) { + this.template = template; } else { - let tmap = TEMPLATE_MAP[qweb.id]; - if (!tmap) { - tmap = {}; - TEMPLATE_MAP[qweb.id] = tmap; + while ( + (template = p.name) && + !(template in qweb.templates) && + p !== Component + ) { + p = p.__proto__; } - let p = (this).constructor; - let name: string = p.name; - let template = tmap[name]; - if (template) { - this.template = template; + if (p === Component) { + this.template = "default"; } else { - while ( - (template = p.name) && - !(template in qweb.templates) && - p !== Component - ) { - p = p.__proto__; - } - if (p === Component) { - this.template = "default"; - } else { - tmap[name] = template; - this.template = template; - } + tmap[name] = template; + this.template = template; } } } diff --git a/src/qweb_core.ts b/src/qweb_core.ts index 3d65c736..11d63fe4 100644 --- a/src/qweb_core.ts +++ b/src/qweb_core.ts @@ -152,14 +152,7 @@ export class QWeb { * Add a template to the internal template map. Note that it is not * immediately compiled. */ - addTemplate( - name: string, - xmlString: string, - allowDuplicates: boolean = false - ) { - if (name in this.templates && allowDuplicates) { - return; - } + addTemplate(name: string, xmlString: string) { const doc = parseXML(xmlString); if (!doc.firstChild) { throw new Error("Invalid template (should not be empty)"); diff --git a/src/store.ts b/src/store.ts index 78d98894..35d1e462 100644 --- a/src/store.ts +++ b/src/store.ts @@ -72,7 +72,7 @@ export class Store extends EventBus { const func: (...any) => any = entry[1]; this.getters[name] = payload => { return func({ state: this.state, getters: this.getters }, payload); - } + }; } } @@ -154,6 +154,8 @@ interface EnvWithStore extends Env { store: Store; } +let nextID = 1; + export function connect(mapStateToProps, options: any = {}) { let hashFunction = options.hashFunction || null; @@ -184,7 +186,7 @@ export function connect(mapStateToProps, options: any = {}) { return function( Comp: Constructor> ) { - return class extends Comp { + const Result = class extends Comp { constructor(parent, props?: any) { const env = parent instanceof Component ? parent.env : parent; const ownProps = Object.assign({}, props || {}); @@ -269,5 +271,13 @@ export function connect(mapStateToProps, options: any = {}) { return super._updateProps(mergedProps, forceUpdate, patchQueue); } }; + + // we assign here a unique name to the resulting anonymous class. + // this is necessary for Owl to be able to properly deduce templates. + // Otherwise, all connected components would have the same name, and then + // each component after the first will necessarily have the same template. + let name = `ConnectedComponent${nextID++}`; + Object.defineProperty(Result, "name", { value: name }); + return Result; }; } diff --git a/tests/component.test.ts b/tests/component.test.ts index e84ffda8..cabb4713 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -24,11 +24,11 @@ beforeEach(() => { fixture = makeTestFixture(); env = makeTestWEnv(); env.qweb.addTemplate( - "counter", + "Counter", `
` ); - env.qweb.addTemplate("widgetA", `
Hello
`); - env.qweb.addTemplate("widgetB", `
world
`); + env.qweb.addTemplate("WidgetA", `
Hello
`); + env.qweb.addTemplate("WidgetB", `
world
`); }); afterEach(() => { @@ -44,7 +44,6 @@ function children(w: Widget): Widget[] { // Test widgets class Counter extends Widget { - template = "counter"; state = { counter: 0 }; @@ -55,13 +54,10 @@ class Counter extends Widget { } class WidgetA extends Widget { - template = "widgetA"; widgets = { b: WidgetB }; } -class WidgetB extends Widget { - template = "widgetB"; -} +class WidgetB extends Widget {} //------------------------------------------------------------------------------ // Tests @@ -108,9 +104,11 @@ describe("basic widget properties", () => { }); test("widget style and classname", async () => { - class StyledWidget extends Widget { - inlineTemplate = `
world
`; - } + env.qweb.addTemplate( + "StyledWidget", + `
world
` + ); + class StyledWidget extends Widget {} const widget = new StyledWidget(env); await widget.mount(fixture); expect(fixture.innerHTML).toBe( @@ -191,8 +189,8 @@ describe("lifecycle hooks", () => { test("willStart hook is called on subwidget", async () => { let ok = false; + env.qweb.addTemplate("ParentWidget", `
`); class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { child: ChildWidget }; } class ChildWidget extends Widget { @@ -208,8 +206,9 @@ describe("lifecycle hooks", () => { test("mounted hook is called on subwidgets, in proper order", async () => { const steps: any[] = []; + env.qweb.addTemplate("ParentWidget", `
`); + class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { child: ChildWidget }; mounted() { steps.push("parent:mounted"); @@ -229,8 +228,16 @@ describe("lifecycle hooks", () => { test("mounted hook is called on subsubwidgets, in proper order", async () => { const steps: any[] = []; + env.qweb.addTemplate( + "ParentWidget", + `
` + ); + env.qweb.addTemplate( + "ChildWidget", + `
` + ); + class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { child: ChildWidget }; state = { flag: false }; mounted() { @@ -241,7 +248,6 @@ describe("lifecycle hooks", () => { } } class ChildWidget extends Widget { - inlineTemplate = `
`; widgets = { childchild: ChildChildWidget }; mounted() { steps.push("child:mounted"); @@ -277,8 +283,11 @@ describe("lifecycle hooks", () => { test("willPatch, patched hook are called on subsubwidgets, in proper order", async () => { const steps: any[] = []; + env.qweb.addTemplate( + "ParentWidget", + `
>
` + ); class ParentWidget extends Widget { - inlineTemplate = `
>
`; widgets = { child: ChildWidget }; state = { n: 1 }; willPatch() { @@ -288,8 +297,11 @@ describe("lifecycle hooks", () => { steps.push("parent:patched"); } } + env.qweb.addTemplate( + "ChildWidget", + `
` + ); class ChildWidget extends Widget { - inlineTemplate = `
`; widgets = { childchild: ChildChildWidget }; willPatch() { steps.push("child:willPatch"); @@ -298,8 +310,9 @@ describe("lifecycle hooks", () => { steps.push("child:patched"); } } + env.qweb.addTemplate("ChildChildWidget", `
`); + class ChildChildWidget extends Widget { - inlineTemplate = `
`; willPatch() { steps.push("childchild:willPatch"); } @@ -329,8 +342,9 @@ describe("lifecycle hooks", () => { // the t-else part in the template is important. This is // necessary to have a situation that could confuse the vdom // patching algorithm - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
@@ -338,7 +352,9 @@ describe("lifecycle hooks", () => {
-
`; +
` + ); + class ParentWidget extends Widget { state = { ok: false }; widgets = { child: ChildWidget }; } @@ -385,11 +401,14 @@ describe("lifecycle hooks", () => { test("widgets are unmounted and destroyed if no longer in DOM", async () => { let steps: string[] = []; + env.qweb.addTemplate( + "ParentWidget", + `
+ +
` + ); class ParentWidget extends Widget { state = { ok: true }; - inlineTemplate = `
- -
`; widgets = { child: ChildWidget }; } @@ -418,8 +437,8 @@ describe("lifecycle hooks", () => { test("widgets are unmounted and destroyed if no longer in DOM, even after updateprops", async () => { let childUnmounted = false; + env.qweb.addTemplate("ChildWidget", ``); class ChildWidget extends Widget { - inlineTemplate = ``; willUnmount() { childUnmounted = true; } @@ -428,14 +447,17 @@ describe("lifecycle hooks", () => { } } - class ParentWidget extends Widget { - widgets = { ChildWidget }; - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
-
`; + ` + ); + class ParentWidget extends Widget { + widgets = { ChildWidget }; state = { n: 0, flag: true }; increment() { this.state.n += 1; @@ -459,8 +481,8 @@ describe("lifecycle hooks", () => { test("hooks are called in proper order in widget creation/destruction", async () => { let steps: string[] = []; + env.qweb.addTemplate("ParentWidget", `
`); class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { child: ChildWidget }; constructor(parent) { super(parent); @@ -509,15 +531,16 @@ describe("lifecycle hooks", () => { test("willUpdateProps hook is called", async () => { let def = makeDeferred(); + env.qweb.addTemplate( + "Parent", + '' + ); class Parent extends Widget { - inlineTemplate = - ''; state = { n: 1 }; widgets = { Child: HookWidget }; } + env.qweb.addTemplate("HookWidget", ''); class HookWidget extends Widget { - inlineTemplate = ''; - willUpdateProps(nextProps) { expect(nextProps.n).toBe(2); return def; @@ -560,8 +583,11 @@ describe("lifecycle hooks", () => { test("patched hook is called after updateProps", async () => { let n = 0; + env.qweb.addTemplate( + "Parent", + '
' + ); class Parent extends Widget { - inlineTemplate = '
'; state = { a: 1 }; widgets = { Child: TestWidget }; } @@ -600,13 +626,16 @@ describe("lifecycle hooks", () => { test("shouldUpdate hook prevent rerendering", async () => { let shouldUpdate = false; + env.qweb.addTemplate( + "Parent", + `
` + ); class Parent extends Widget { - inlineTemplate = `
`; state = { val: 42 }; widgets = { Child: TestWidget }; } + env.qweb.addTemplate("TestWidget", `
`); class TestWidget extends Widget { - inlineTemplate = `
`; shouldUpdate() { return shouldUpdate; } @@ -626,13 +655,16 @@ describe("lifecycle hooks", () => { test("sub widget (inside sub node): hooks are correctly called", async () => { let created = false; let mounted = false; - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
-
`; + ` + ); + class ParentWidget extends Widget { widgets = { child: ChildWidget }; state = { flag: false }; } @@ -659,11 +691,14 @@ describe("lifecycle hooks", () => { test("willPatch/patched hook", async () => { const steps: string[] = []; - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
-
`; + ` + ); + class ParentWidget extends Widget { widgets = { child: ChildWidget }; state = { n: 1 }; willPatch() { @@ -703,11 +738,14 @@ describe("lifecycle hooks", () => { // we make sure here that willPatch/patched is only called if widget is in // dom, mounted const steps: string[] = []; - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
-
`; + ` + ); + class ParentWidget extends Widget { widgets = { child: ChildWidget }; state = { n: 1, flag: true }; } @@ -815,8 +853,11 @@ describe("composition", () => { }); test("t-refs on widget are widgets", async () => { + env.qweb.addTemplate( + "WidgetC", + `
Hello
` + ); class WidgetC extends Widget { - inlineTemplate = `
Hello
`; widgets = { b: WidgetB }; } const widget = new WidgetC(env); @@ -826,11 +867,14 @@ describe("composition", () => { test("t-refs are bound at proper timing", async () => { expect.assertions(2); - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
-
`; + ` + ); + class ParentWidget extends Widget { widgets = { Widget }; state = { list: [] }; willPatch() { @@ -849,12 +893,15 @@ describe("composition", () => { test("t-refs are bound at proper timing (2)", async () => { expect.assertions(10); - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
-
`; + ` + ); + class ParentWidget extends Widget { widgets = { Widget }; state = { child1: true, child2: false }; count = 0; @@ -894,8 +941,8 @@ describe("composition", () => { }); test("modifying a sub widget", async () => { + env.qweb.addTemplate("ParentWidget", `
`); class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { Counter }; } const widget = new ParentWidget(env); @@ -912,12 +959,15 @@ describe("composition", () => { }); test("refs in a loop", async () => { - class ParentWidget extends Widget { - inlineTemplate = `
+ env.qweb.addTemplate( + "ParentWidget", + `
-
`; +
` + ); + class ParentWidget extends Widget { state = { items: [1, 2, 3] }; widgets = { Child: Widget }; } @@ -947,8 +997,8 @@ describe("composition", () => { }); test("rerendering a widget with a sub widget", async () => { + env.qweb.addTemplate("ParentWidget", `
`); class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { Counter }; } const widget = new ParentWidget(env); @@ -966,9 +1016,12 @@ describe("composition", () => { }); test("sub widgets are destroyed if no longer in dom, then recreated", async () => { + env.qweb.addTemplate( + "ParentWidget", + `
` + ); class ParentWidget extends Widget { state = { ok: true }; - inlineTemplate = `
`; widgets = { counter: Counter }; } const widget = new ParentWidget(env); @@ -991,9 +1044,12 @@ describe("composition", () => { }); test("sub widgets with t-keepalive are not destroyed if no longer in dom", async () => { + env.qweb.addTemplate( + "ParentWidget", + `
` + ); class ParentWidget extends Widget { state = { ok: true }; - inlineTemplate = `
`; widgets = { counter: Counter }; } const widget = new ParentWidget(env); @@ -1021,14 +1077,16 @@ describe("composition", () => { }); test("sub widgets dom state with t-keepalive is preserved", async () => { + env.qweb.addTemplate( + "ParentWidget", + `
` + ); class ParentWidget extends Widget { state = { ok: true }; - inlineTemplate = `
`; widgets = { InputWidget }; } - class InputWidget extends Widget { - inlineTemplate = ""; - } + env.qweb.addTemplate("InputWidget", ""); + class InputWidget extends Widget {} const widget = new ParentWidget(env); await widget.mount(fixture); const input = fixture.getElementsByTagName("input")[0]; @@ -1046,17 +1104,19 @@ describe("composition", () => { }); test("sub widgets rendered in a loop", async () => { - class ChildWidget extends Widget { - inlineTemplate = ``; - } + env.qweb.addTemplate("ChildWidget", ``); + class ChildWidget extends Widget {} - class Parent extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "Parent", + `
-
`; + ` + ); + class Parent extends Widget { state = { numbers: [1, 2, 3] }; @@ -1077,8 +1137,8 @@ describe("composition", () => { test("sub widgets with some state rendered in a loop", async () => { let n = 1; + env.qweb.addTemplate("ChildWidget", ``); class ChildWidget extends Widget { - inlineTemplate = ``; constructor(parent) { super(parent); this.state = { n }; @@ -1118,17 +1178,20 @@ describe("composition", () => { test("sub widgets between t-ifs", async () => { // this confuses the patching algorithm... - class ChildWidget extends Widget { - inlineTemplate = `child`; - } + env.qweb.addTemplate("ChildWidget", `child`); + class ChildWidget extends Widget {} - class Parent extends Widget { - inlineTemplate = `
+ env.qweb.addTemplate( + "Parent", + `

hey

noo

test -
`; +
` + ); + + class Parent extends Widget { state = { flag: false }; widgets = { ChildWidget }; } @@ -1154,14 +1217,17 @@ describe("composition", () => { describe("props evaluation (with t-props directive)", () => { test("explicit object prop", async () => { + env.qweb.addTemplate( + "Parent", + `
` + ); class Parent extends Widget { - inlineTemplate = `
`; widgets = { child: Child }; state = { val: 42 }; } + env.qweb.addTemplate("Child", ``); class Child extends Widget { - inlineTemplate = ``; state: { someval: number }; constructor(parent: Parent, props: { value: number }) { super(parent); @@ -1175,14 +1241,17 @@ describe("props evaluation (with t-props directive)", () => { }); test("object prop value", async () => { + env.qweb.addTemplate( + "Parent", + `
` + ); class Parent extends Widget { - inlineTemplate = `
`; widgets = { child: Child }; state = { val: 42 }; } + env.qweb.addTemplate("Child", ``); class Child extends Widget { - inlineTemplate = ``; state: { someval: number }; constructor(parent: Parent, props: { val: number }) { super(parent); @@ -1196,12 +1265,14 @@ describe("props evaluation (with t-props directive)", () => { }); test("accept ES6-like syntax for props (with getters)", async () => { - class Child extends Widget { - inlineTemplate = ``; - } + env.qweb.addTemplate("Child", ``); + class Child extends Widget {} + env.qweb.addTemplate( + "Parent", + `
` + ); class Parent extends Widget { - inlineTemplate = `
`; widgets = { child: Child }; get greetings() { return `hello ${this.props.name}`; @@ -1213,20 +1284,25 @@ describe("props evaluation (with t-props directive)", () => { }); test("t-set works with t-props", async () => { - class Parent extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "Parent", + `
-
`; + ` + ); + class Parent extends Widget { widgets = { child: Child }; } - class Child extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "Child", + ` - `; - } + ` + ); + class Child extends Widget {} const widget = new Parent(env); await widget.mount(fixture); @@ -1236,16 +1312,18 @@ describe("props evaluation (with t-props directive)", () => { describe("class and style attributes with t-widget", () => { test("class is properly added on widget root el", async () => { - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
-
`; + ` + ); + class ParentWidget extends Widget { widgets = { child: Child }; } - class Child extends Widget { - inlineTemplate = `
`; - } + env.qweb.addTemplate("Child", `
`); + class Child extends Widget {} const widget = new ParentWidget(env); await widget.mount(fixture); expect(fixture.innerHTML).toBe(`
`); @@ -1253,23 +1331,21 @@ describe("class and style attributes with t-widget", () => { test("t-att-class is properly added/removed on widget root el", async () => { env.qweb.addTemplate( - "parent", + "ParentWidget", `
` ); class ParentWidget extends Widget { - template = "parent"; widgets = { child: Child }; state = { a: true, b: false }; } - class Child extends Widget { - inlineTemplate = `
`; - } + env.qweb.addTemplate("Child", `
`); + class Child extends Widget {} const widget = new ParentWidget(env); await widget.mount(fixture); expect(fixture.innerHTML).toBe(`
`); - expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot(); + expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot(); widget.state.a = false; widget.state.b = true; @@ -1278,11 +1354,14 @@ describe("class and style attributes with t-widget", () => { }); test("style is properly added on widget root el", async () => { - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
-
`; +
` + ); + class ParentWidget extends Widget { widgets = { child: Widget }; } const widget = new ParentWidget(env); @@ -1294,21 +1373,20 @@ describe("class and style attributes with t-widget", () => { test("dynamic t-att-style is properly added and updated on widget root el", async () => { env.qweb.addTemplate( - "parent", + "ParentWidget", `
` ); class ParentWidget extends Widget { - template = "parent"; widgets = { child: Widget }; state = { style: "font-size: 20px" }; } const widget = new ParentWidget(env); await widget.mount(fixture); - expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot(); + expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot(); expect(fixture.innerHTML).toBe( `
` @@ -1326,8 +1404,11 @@ describe("class and style attributes with t-widget", () => { describe("other directives with t-widget", () => { test("t-on works as expected", async () => { let n = 0; + env.qweb.addTemplate( + "ParentWidget", + `
` + ); class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { child: Child }; someMethod(arg) { expect(arg).toBe(43); @@ -1347,14 +1428,16 @@ describe("other directives with t-widget", () => { }); test("t-if works with t-widget", async () => { + env.qweb.addTemplate( + "ParentWidget", + `
` + ); class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { child: Child }; state = { flag: true }; } - class Child extends Widget { - inlineTemplate = "hey"; - } + env.qweb.addTemplate("Child", "hey"); + class Child extends Widget {} const widget = new ParentWidget(env); await widget.mount(fixture); @@ -1371,18 +1454,20 @@ describe("other directives with t-widget", () => { }); test("t-else works with t-widget", async () => { - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
somediv
-
`; +
` + ); + class ParentWidget extends Widget { widgets = { child: Child }; state = { flag: true }; } - class Child extends Widget { - inlineTemplate = "hey"; - } + env.qweb.addTemplate("Child", "hey"); + class Child extends Widget {} const widget = new ParentWidget(env); await widget.mount(fixture); @@ -1395,18 +1480,20 @@ describe("other directives with t-widget", () => { }); test("t-elif works with t-widget", async () => { - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
somediv
-
`; +
` + ); + class ParentWidget extends Widget { widgets = { child: Child }; state = { flag: true }; } - class Child extends Widget { - inlineTemplate = "hey"; - } + env.qweb.addTemplate("Child", "hey"); + class Child extends Widget {} const widget = new ParentWidget(env); await widget.mount(fixture); @@ -1419,18 +1506,20 @@ describe("other directives with t-widget", () => { }); test("t-else with empty string works with t-widget", async () => { - class ParentWidget extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "ParentWidget", + `
somediv
-
`; +
` + ); + class ParentWidget extends Widget { widgets = { child: Child }; state = { flag: true }; } - class Child extends Widget { - inlineTemplate = "hey"; - } + env.qweb.addTemplate("Child", "hey"); + class Child extends Widget {} const widget = new ParentWidget(env); await widget.mount(fixture); @@ -1448,8 +1537,11 @@ describe("random stuff/miscellaneous", () => { // this test makes sure that the foreach directive does not pollute sub // context with the inLoop variable, which is then used in the t-widget // directive as a key + env.qweb.addTemplate( + "Test", + `
txt
` + ); class Test extends Widget { - inlineTemplate = `
txt
`; widgets = { widget: Widget }; } const widget = new Test(env); @@ -1461,15 +1553,20 @@ describe("random stuff/miscellaneous", () => { // in this situation, we protect against a bug that occurred: because of the // interplay between widgets and vnodes, a sub widget vnode was patched // twice. + env.qweb.addTemplate( + "Parent", + `
` + ); class Parent extends Widget { - inlineTemplate = `
`; widgets = { child: Child }; state = { flag: false }; } - class Child extends Widget { - inlineTemplate = `abcdef`; - } + env.qweb.addTemplate( + "Child", + `abcdef` + ); + class Child extends Widget {} const widget = new Parent(env); await widget.mount(fixture); @@ -1481,38 +1578,37 @@ describe("random stuff/miscellaneous", () => { test("snapshotting compiled code", async () => { env.qweb.addTemplate( - "parent", + "Parent", `
` ); class Parent extends Widget { - inlineTemplate = "parent"; widgets = { child: Child }; state = { flag: false }; } - class Child extends Widget { - inlineTemplate = `abcdef`; - } + env.qweb.addTemplate( + "Child", + `abcdef` + ); + class Child extends Widget {} const widget = new Parent(env); await widget.mount(fixture); - expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot(); + expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot(); }); test("t-props should not be undefined (snapshotting)", async () => { - env.qweb.addTemplate("parent", `
`); + env.qweb.addTemplate("Parent", `
`); class Parent extends Widget { - inlineTemplate = "parent"; widgets = { child: Child }; } - class Child extends Widget { - inlineTemplate = `abc`; - } + env.qweb.addTemplate("Child", `abc`); + class Child extends Widget {} const widget = new Parent(env); await widget.mount(fixture); - expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot(); + expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot(); }); test("component semantics", async () => { @@ -1556,25 +1652,28 @@ describe("random stuff/miscellaneous", () => { steps.push(`${this.name}:destroy`); } } + env.qweb.addTemplate("A", `
A
`); class A extends TestWidget { - inlineTemplate = `
A
`; widgets = { B, C }; name = "A"; } + env.qweb.addTemplate("B", `
B
`); class B extends TestWidget { - inlineTemplate = `
B
`; name = "B"; constructor(parent, props) { super(parent, props); steps.push("B:constructor"); } } - class C extends TestWidget { - inlineTemplate = ` + env.qweb.addTemplate( + "C", + `
C -
`; + ` + ); + class C extends TestWidget { widgets = { D, E, F }; name = "C"; state = { flag: true }; @@ -1586,16 +1685,16 @@ describe("random stuff/miscellaneous", () => { } } + env.qweb.addTemplate("D", `
D
`); class D extends TestWidget { - inlineTemplate = `
D
`; name = "D"; constructor(parent, props) { super(parent, props); steps.push("D:constructor"); } } + env.qweb.addTemplate("E", `
E
`); class E extends TestWidget { - inlineTemplate = `
E
`; name = "E"; constructor(parent, props) { super(parent, props); @@ -1603,8 +1702,8 @@ describe("random stuff/miscellaneous", () => { } } + env.qweb.addTemplate("F", `
F
`); class F extends TestWidget { - inlineTemplate = `
F
`; name = "F"; constructor(parent, props) { super(parent, props); @@ -1674,7 +1773,6 @@ describe("async rendering", () => { test("destroying a widget before start is over", async () => { let def = makeDeferred(); class W extends Widget { - inlineTemplate = "invalid><"; willStart(): Promise { return def; } @@ -1695,14 +1793,17 @@ describe("async rendering", () => { test("destroying/recreating a subwidget with different props (if start is not over)", async () => { let def = makeDeferred(); let n = 0; + env.qweb.addTemplate( + "W", + `
` + ); class W extends Widget { - inlineTemplate = `
`; widgets = { Child }; state = { val: 1 }; } + env.qweb.addTemplate("Child", `child:`); class Child extends Widget { - inlineTemplate = `child:`; constructor(parent, props) { super(parent, props); n++; @@ -1731,25 +1832,28 @@ describe("async rendering", () => { let defA = makeDeferred(); let defB = makeDeferred(); + env.qweb.addTemplate("ChildA", "a"); class ChildA extends Widget { - inlineTemplate = "a"; willStart(): Promise { return defA; } } + env.qweb.addTemplate("ChildB", "b"); class ChildB extends Widget { - inlineTemplate = "b"; willStart(): Promise { return defB; } } - class Parent extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "Parent", + `
-
`; + ` + ); + class Parent extends Widget { widgets = { ChildA, ChildB }; state = { flagA: false, flagB: false }; } @@ -1775,25 +1879,28 @@ describe("async rendering", () => { let defA = makeDeferred(); let defB = makeDeferred(); + env.qweb.addTemplate("ChildA", `a`); class ChildA extends Widget { - inlineTemplate = `a`; _updateProps(props, forceUpdate, fiber): Promise { return defA.then(() => super._updateProps(props, forceUpdate, fiber)); } } + env.qweb.addTemplate("ChildB", `b`); class ChildB extends Widget { - inlineTemplate = `b`; willStart(): Promise { return defB; } } - class Parent extends Widget { - inlineTemplate = ` + env.qweb.addTemplate( + "Parent", + `
-
`; + ` + ); + class Parent extends Widget { widgets = { ChildA, ChildB }; state = { valA: 1, valB: 2, flagB: false }; } @@ -1855,8 +1962,8 @@ describe("async rendering", () => { test("properly behave when destroyed/unmounted while rendering ", async () => { let def = Promise.resolve(); + env.qweb.addTemplate("Child", `
`); class Child extends Widget { - inlineTemplate = `
`; widgets = { SubChild }; mounted() { // from now on, each rendering in child widget will be delayed (see @@ -1879,9 +1986,12 @@ describe("async rendering", () => { } } + env.qweb.addTemplate( + "Parent", + ` +
` + ); class Parent extends Widget { - inlineTemplate = ` -
`; widgets = { Child }; state = { flag: true, val: "Framboise Lindemans" }; } @@ -1939,8 +2049,8 @@ describe("updating environment", () => { }); test("updating child env does not modify parent env", async () => { + env.qweb.addTemplate("ParentWidget", `
`); class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { child: Widget }; } const parent = new ParentWidget(env); @@ -1953,8 +2063,8 @@ describe("updating environment", () => { }); test("updating parent env does modify child env", async () => { + env.qweb.addTemplate("ParentWidget", `
`); class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { child: Widget }; } const parent = new ParentWidget(env); @@ -1966,8 +2076,8 @@ describe("updating environment", () => { }); test("updating parent env does modify child env, part 2", async () => { + env.qweb.addTemplate("ParentWidget", `
`); class ParentWidget extends Widget { - inlineTemplate = `
`; widgets = { child: Widget }; } const parent = new ParentWidget(env); @@ -1980,9 +2090,8 @@ describe("updating environment", () => { }); test("updating env force a rerender", async () => { - class TestWidget extends Widget { - inlineTemplate = `
`; - } + env.qweb.addTemplate("TestWidget", `
`); + class TestWidget extends Widget {} (env).someKey = "hey"; const widget = new TestWidget(env); await widget.mount(fixture); @@ -1992,13 +2101,12 @@ describe("updating environment", () => { }); test("updating env force rerendering children", async () => { + env.qweb.addTemplate("Parent", `
`); class Parent extends Widget { - inlineTemplate = `
`; widgets = { Child }; } - class Child extends Widget { - inlineTemplate = `
`; - } + env.qweb.addTemplate("Child", `
`); + class Child extends Widget {} (env).someKey = "hey"; const widget = new Parent(env); await widget.mount(fixture); @@ -2011,9 +2119,9 @@ describe("updating environment", () => { describe("widget and observable state", () => { test("widget is rerendered when its state is changed", async () => { + env.qweb.addTemplate("TestWidget", `
`); class TestWidget extends Widget { state = { drink: "water" }; - inlineTemplate = `
`; } const widget = new TestWidget(env); await widget.mount(fixture); @@ -2029,10 +2137,13 @@ describe("widget and observable state", () => { test("subwidgets cannot change observable state received from parent", async () => { expect.assertions(1); + env.qweb.addTemplate( + "Parent", + `
` + ); class Parent extends Widget { state = { obj: { coffee: 1 } }; widgets = { Child }; - inlineTemplate = `
`; } class Child extends Widget { constructor(parent, props) { @@ -2051,9 +2162,12 @@ describe("widget and observable state", () => { }); test("widget can add observed keys to its state", async () => { + env.qweb.addTemplate( + "TestWidget", + `
` + ); class TestWidget extends Widget { state: any = { a: 1 }; - inlineTemplate = `
`; } const widget = new TestWidget(env); await widget.mount(fixture); @@ -2068,8 +2182,8 @@ describe("widget and observable state", () => { describe("t-mounted directive", () => { test("callback is not called when not in DOM", async () => { + env.qweb.addTemplate("TestWidget", `
`); class TestWidget extends Widget { - inlineTemplate = `
`; f() {} } const widget = new TestWidget(env); @@ -2079,8 +2193,8 @@ describe("t-mounted directive", () => { }); test("callback is called when in DOM", async () => { + env.qweb.addTemplate("TestWidget", `
`); class TestWidget extends Widget { - inlineTemplate = `
`; f() {} } const widget = new TestWidget(env); @@ -2090,8 +2204,8 @@ describe("t-mounted directive", () => { }); test("callback with args is called when in DOM", async () => { + env.qweb.addTemplate("TestWidget", `
`); class TestWidget extends Widget { - inlineTemplate = `
`; f() {} } const widget = new TestWidget(env); @@ -2102,8 +2216,11 @@ describe("t-mounted directive", () => { }); test("combined with a t-if", async () => { + env.qweb.addTemplate( + "TestWidget", + `
` + ); class TestWidget extends Widget { - inlineTemplate = `
`; state = { flag: false }; f() {} } @@ -2118,8 +2235,11 @@ describe("t-mounted directive", () => { }); test("combined with a t-ref", async () => { + env.qweb.addTemplate( + "TestWidget", + `
` + ); class TestWidget extends Widget { - inlineTemplate = `
`; f() {} } const widget = new TestWidget(env); @@ -2130,8 +2250,11 @@ describe("t-mounted directive", () => { }); test("combined with a t-transition", async () => { + env.qweb.addTemplate( + "TestWidget", + `
blue
` + ); class TestWidget extends Widget { - inlineTemplate = `
blue
`; state = { flag: false }; f() {} } @@ -2175,16 +2298,6 @@ describe("can deduce template from name", () => { expect(fixture.innerHTML).toBe("Orval"); }); - test("can find template of parent component, defined by inlinetemplate key", async () => { - class ABC extends Widget { - inlineTemplate = "Orval"; - } - class DEF extends ABC {} - const def = new DEF(env); - await def.mount(fixture); - expect(fixture.innerHTML).toBe("Orval"); - }); - test("templates are found in proper qweb instance", async () => { const env2 = makeTestWEnv(); env.qweb.addTemplate("ABC", "Rochefort 8"); diff --git a/tests/store.test.ts b/tests/store.test.ts index 70f2a801..46f2ad03 100644 --- a/tests/store.test.ts +++ b/tests/store.test.ts @@ -176,15 +176,15 @@ describe("basic use", () => { 1: { id: 1, name: "bertinchamps", - tasterID: 1, - }, + tasterID: 1 + } }, tasters: { 1: { id: 1, - name: 'aaron', + name: "aaron" } - }, + } }; const getters = { beerTasterName({ state }, beerID) { @@ -207,15 +207,15 @@ describe("basic use", () => { 1: { id: 1, name: "bertinchamps", - tasterID: 1, - }, + tasterID: 1 + } }, tasters: { 1: { id: 1, - name: 'aaron', + name: "aaron" } - }, + } }; const getters = { beerTasterName({ state }, beerID) { @@ -243,15 +243,15 @@ describe("basic use", () => { 1: { id: 1, name: "bertinchamps", - tasterID: 1, - }, + tasterID: 1 + } }, tasters: { 1: { id: 1, - name: 'aaron', + name: "aaron" } - }, + } }; const getters = { beerTasterName({ state }, beerID) { @@ -278,15 +278,15 @@ describe("basic use", () => { return `${getters.b()}${getters.c(1)}`; }, b() { - return 'b'; + return "b"; }, c({}, i) { return `c${i}`; - }, + } }; const store = new Store({ getters }); - expect(store.getters.a()).toBe('bc1'); + expect(store.getters.a()).toBe("bc1"); }); }); @@ -408,20 +408,21 @@ describe("connecting a component to store", () => { fixture.remove(); }); - class App extends Component { - inlineTemplate = ` + test("connecting a component works", async () => { + env.qweb.addTemplate( + "App", + `
-
`; - widgets = { Todo }; - } - class Todo extends Component { - inlineTemplate = ``; - } - - test("connecting a component works", async () => { + ` + ); + env.qweb.addTemplate("Todo", ``); + class App extends Component { + widgets = { Todo }; + } + class Todo extends Component {} const state = { todos: [] }; const mutations = { addTodo({ state }, msg) { @@ -456,14 +457,16 @@ describe("connecting a component to store", () => { } const store = new Store({ state, mutations }); - class App extends Component { - inlineTemplate = ` + env.qweb.addTemplate( + "App", + `
-
`; - } + ` + ); + class App extends Component {} const DeepTodoApp = connect( mapStateToProps, @@ -493,8 +496,8 @@ describe("connecting a component to store", () => { test("connected child components with custom hooks", async () => { let steps: any = []; + env.qweb.addTemplate("Child", `
`); class Child extends Component { - inlineTemplate = `
`; mounted() { steps.push("child:mounted"); } @@ -505,11 +508,14 @@ describe("connecting a component to store", () => { const ConnectedChild = connect(s => s)(Child); - class Parent extends Component { - inlineTemplate = ` + env.qweb.addTemplate( + "Parent", + `
-
`; +
` + ); + class Parent extends Component { widgets = { ConnectedChild }; constructor(env: Env) { @@ -540,20 +546,22 @@ describe("connecting a component to store", () => { }; const store = new Store({ state, mutations }); - class TodoItem extends Component { - inlineTemplate = ``; - } + env.qweb.addTemplate("TodoItem", ``); + class TodoItem extends Component {} const ConnectedTodo = connect((state, props) => { const todo = state.todos.find(t => t.id === props.id); return todo; })(TodoItem); - class TodoList extends Component { - inlineTemplate = `
+ env.qweb.addTemplate( + "TodoList", + `
-
`; +
` + ); + class TodoList extends Component { widgets = { ConnectedTodo }; } @@ -578,10 +586,7 @@ describe("connecting a component to store", () => { test("connect receives store getters as third argument", async () => { const state = { importantID: 1, - todos: [ - { id: 1, text: "jupiler" }, - { id: 2, text: "bertinchamps" }, - ], + todos: [{ id: 1, text: "jupiler" }, { id: 2, text: "bertinchamps" }] }; const getters = { importantTodoText({ state }) { @@ -589,30 +594,35 @@ describe("connecting a component to store", () => { }, text({ state }, id) { return state.todos.find(todo => todo.id === id).text; - }, + } }; const store = new Store({ state, getters }); - class TodoItem extends Component { - inlineTemplate = `
+ env.qweb.addTemplate( + "TodoItem", + `
-
`; - } +
` + ); + class TodoItem extends Component {} const ConnectedTodo = connect((state, props, getters) => { const todo = state.todos.find(t => t.id === props.id); return { activeTodoText: getters.text(todo.id), - importantTodoText: getters.importantTodoText(), + importantTodoText: getters.importantTodoText() }; })(TodoItem); - class TodoList extends Component { - inlineTemplate = `
+ env.qweb.addTemplate( + "TodoList", + `
-
`; +
` + ); + class TodoList extends Component { widgets = { ConnectedTodo }; } @@ -625,21 +635,25 @@ describe("connecting a component to store", () => { const app = new ConnectedTodoList(env); await app.mount(fixture); - expect(fixture.innerHTML).toBe("
jupilerjupiler
bertinchampsjupiler
"); + expect(fixture.innerHTML).toBe( + "
jupilerjupiler
bertinchampsjupiler
" + ); }); test("connected component is updated when props are updated", async () => { - class Beer extends Component { - inlineTemplate = ``; - } + env.qweb.addTemplate("Beer", ``); + class Beer extends Component {} const ConnectedBeer = connect((state, props) => { return state.beers[props.id]; })(Beer); + env.qweb.addTemplate( + "App", + `
+ +
` + ); class App extends Component { - inlineTemplate = `
- -
`; widgets = { ConnectedBeer }; state = { beerId: 1 }; } @@ -658,12 +672,14 @@ describe("connecting a component to store", () => { }); test("connected component is updated when store is changed", async () => { - class App extends Component { - inlineTemplate = ` + env.qweb.addTemplate( + "App", + `
-
`; - } +
` + ); + class App extends Component {} const mutations = { addBeer({ state }, name) { @@ -692,13 +708,15 @@ describe("connecting a component to store", () => { }); test("connected component with undefined, null and string props", async () => { - class Beer extends Component { - inlineTemplate = `
+ env.qweb.addTemplate( + "Beer", + `
taster: selected: consumed: -
`; - } +
` + ); + class Beer extends Component {} const ConnectedBeer = connect((state, props) => { return { selected: state.beers[props.id], @@ -707,10 +725,13 @@ describe("connecting a component to store", () => { }; })(Beer); - class App extends Component { - inlineTemplate = `
+ env.qweb.addTemplate( + "App", + `
-
`; +
` + ); + class App extends Component { widgets = { ConnectedBeer }; state = { beerId: 0 }; } @@ -756,13 +777,15 @@ describe("connecting a component to store", () => { }); test("connected component deeply reactive with undefined, null and string props", async () => { - class Beer extends Component { - inlineTemplate = `
+ env.qweb.addTemplate( + "Beer", + `
taster: selected: consumed: -
`; - } +
` + ); + class Beer extends Component {} const ConnectedBeer = connect((state, props) => { return { selected: state.beers[props.id], @@ -771,10 +794,13 @@ describe("connecting a component to store", () => { }; })(Beer); - class App extends Component { - inlineTemplate = `
+ env.qweb.addTemplate( + "App", + `
-
`; +
` + ); + class App extends Component { widgets = { ConnectedBeer }; state = { beerId: 0 }; } @@ -845,12 +871,16 @@ describe("connecting a component to store", () => { test("correct update order when parent/children are connected", async () => { const steps: string[] = []; - class Parent extends Component { - inlineTemplate = ` + + env.qweb.addTemplate( + "Parent", + `
- `; + ` + ); + class Parent extends Component { widgets = { Child: ConnectedChild }; } const ConnectedParent = connect(function(s) { @@ -858,9 +888,8 @@ describe("connecting a component to store", () => { return { current: s.current, isvisible: s.isvisible }; })(Parent); - class Child extends Component { - inlineTemplate = ``; - } + env.qweb.addTemplate("Child", ``); + class Child extends Component {} const ConnectedChild = connect(function(s, props) { steps.push("child"); @@ -889,8 +918,8 @@ describe("connecting a component to store", () => { test("connected component willpatch/patch hooks are called on store updates", async () => { const steps: string[] = []; + env.qweb.addTemplate("App", `
`); class App extends Component { - inlineTemplate = `
`; willPatch() { steps.push("willpatch"); }