2019-07-14 22:18:00 +02:00
|
|
|
import { Component, Env } from "../../src/component/component";
|
|
|
|
|
import { QWeb } from "../../src/qweb/qweb";
|
2019-09-12 09:45:32 +02:00
|
|
|
import { xml } from "../../src/tags";
|
2019-09-26 21:56:01 +02:00
|
|
|
import { useState, useRef } from "../../src/hooks";
|
2019-07-14 22:18:00 +02:00
|
|
|
import { EventBus } from "../../src/core/event_bus";
|
2019-02-06 13:26:21 +01:00
|
|
|
import {
|
2019-02-07 12:10:44 +01:00
|
|
|
makeDeferred,
|
2019-02-06 13:26:21 +01:00
|
|
|
makeTestFixture,
|
2019-05-17 15:52:16 +02:00
|
|
|
makeTestEnv,
|
2019-02-06 13:26:21 +01:00
|
|
|
nextMicroTick,
|
2019-02-07 12:10:44 +01:00
|
|
|
nextTick,
|
2019-06-13 16:27:12 +02:00
|
|
|
normalize,
|
|
|
|
|
editInput
|
2019-07-14 22:18:00 +02:00
|
|
|
} from "../helpers";
|
2019-01-16 13:19:58 +01:00
|
|
|
|
2019-01-25 15:28:21 +01:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// Setup and helpers
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// We create before each test:
|
|
|
|
|
// - fixture: a div, appended to the DOM, intended to be the target of dom
|
|
|
|
|
// manipulations. Note that it is removed after each test.
|
2019-06-20 09:42:33 +02:00
|
|
|
// - env: a WEnv, necessary to create new components
|
2019-01-23 14:28:41 +01:00
|
|
|
|
2019-01-25 15:28:21 +01:00
|
|
|
let fixture: HTMLElement;
|
2019-03-14 15:53:31 +01:00
|
|
|
let env: Env;
|
2019-01-23 11:12:54 +01:00
|
|
|
|
2019-01-25 15:28:21 +01:00
|
|
|
beforeEach(() => {
|
2019-01-29 10:46:01 +01:00
|
|
|
fixture = makeTestFixture();
|
2019-05-17 15:52:16 +02:00
|
|
|
env = makeTestEnv();
|
2019-06-14 23:33:45 +02:00
|
|
|
env.qweb.addTemplate("Widget", "<div></div>");
|
2019-02-02 11:41:19 +01:00
|
|
|
env.qweb.addTemplate(
|
2019-05-15 11:15:08 +02:00
|
|
|
"Counter",
|
2019-02-02 11:41:19 +01:00
|
|
|
`<div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div>`
|
|
|
|
|
);
|
2019-06-20 09:42:33 +02:00
|
|
|
env.qweb.addTemplate("WidgetA", `<div>Hello<t t-component="b"/></div>`);
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("WidgetB", `<div>world</div>`);
|
2019-01-25 15:28:21 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
fixture.remove();
|
|
|
|
|
});
|
2019-01-16 13:19:58 +01:00
|
|
|
|
2019-09-24 14:06:53 +02:00
|
|
|
class Widget extends Component<any, any> {}
|
2019-02-04 21:09:17 +01:00
|
|
|
|
|
|
|
|
function children(w: Widget): Widget[] {
|
2019-04-13 14:14:34 +02:00
|
|
|
const childrenMap = w.__owl__.children;
|
2019-01-27 13:50:53 +01:00
|
|
|
return Object.keys(childrenMap).map(id => childrenMap[id]);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
// Test components
|
2019-02-04 21:09:17 +01:00
|
|
|
class Counter extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({
|
2019-01-25 15:44:32 +01:00
|
|
|
counter: 0
|
2019-09-24 14:06:53 +02:00
|
|
|
});
|
2019-01-25 15:44:32 +01:00
|
|
|
|
|
|
|
|
inc() {
|
2019-04-17 10:07:31 +02:00
|
|
|
this.state.counter++;
|
2019-01-25 15:44:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 22:37:08 +02:00
|
|
|
class WidgetB extends Widget {}
|
|
|
|
|
|
2019-02-04 21:09:17 +01:00
|
|
|
class WidgetA extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { b: WidgetB };
|
2019-01-27 13:50:53 +01:00
|
|
|
}
|
|
|
|
|
|
2019-01-25 15:28:21 +01:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// Tests
|
|
|
|
|
//------------------------------------------------------------------------------
|
2019-01-16 13:19:58 +01:00
|
|
|
|
|
|
|
|
describe("basic widget properties", () => {
|
2019-09-24 14:06:53 +02:00
|
|
|
test("props is properly defined", async () => {
|
2019-03-22 09:28:56 +01:00
|
|
|
const widget = new Widget(env);
|
|
|
|
|
expect(widget.props).toEqual({});
|
|
|
|
|
});
|
|
|
|
|
|
2019-01-16 13:19:58 +01:00
|
|
|
test("has no el after creation", async () => {
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new Widget(env);
|
2019-01-16 13:19:58 +01:00
|
|
|
expect(widget.el).toBe(null);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can be mounted", async () => {
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new Widget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div></div>");
|
2019-01-16 13:19:58 +01:00
|
|
|
});
|
|
|
|
|
|
2019-06-14 23:33:45 +02:00
|
|
|
test("crashes if it cannot find a template", async () => {
|
|
|
|
|
expect.assertions(1);
|
2019-09-24 14:06:53 +02:00
|
|
|
class SomeWidget extends Component<any, any> {}
|
2019-06-14 23:33:45 +02:00
|
|
|
try {
|
2019-10-07 17:01:55 +02:00
|
|
|
new SomeWidget(env);
|
2019-06-14 23:33:45 +02:00
|
|
|
} catch (e) {
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(e.message).toBe('Could not find template for component "SomeWidget"');
|
2019-06-14 23:33:45 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-01-16 13:19:58 +01:00
|
|
|
test("can be clicked on and updated", async () => {
|
2019-05-06 13:45:50 +02:00
|
|
|
const counter = new Counter(env);
|
|
|
|
|
await counter.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>0<button>Inc</button></div>");
|
|
|
|
|
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
|
|
|
|
|
await button.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>1<button>Inc</button></div>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("cannot be clicked on and updated if not in DOM", async () => {
|
2019-01-25 15:28:21 +01:00
|
|
|
const counter = new Counter(env);
|
2019-01-16 13:19:58 +01:00
|
|
|
const target = document.createElement("div");
|
|
|
|
|
await counter.mount(target);
|
|
|
|
|
expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>");
|
2019-01-25 15:28:21 +01:00
|
|
|
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
|
|
|
|
|
await button.click();
|
2019-04-17 10:07:31 +02:00
|
|
|
await nextTick();
|
2019-05-06 13:45:50 +02:00
|
|
|
expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>");
|
|
|
|
|
expect(counter.state.counter).toBe(1);
|
2019-01-16 13:19:58 +01:00
|
|
|
});
|
2019-01-21 14:38:44 +01:00
|
|
|
|
|
|
|
|
test("widget style and classname", async () => {
|
2019-09-12 09:45:32 +02:00
|
|
|
class StyledWidget extends Widget {
|
|
|
|
|
static template = xml`
|
|
|
|
|
<div style="font-weight:bold;" class="some-class">world</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new StyledWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe(`<div style="font-weight:bold;" class="some-class">world</div>`);
|
2019-01-21 14:38:44 +01:00
|
|
|
});
|
2019-01-24 13:48:31 +01:00
|
|
|
|
2019-04-17 10:07:31 +02:00
|
|
|
test("changing state before first render does not trigger a render", async () => {
|
2019-01-24 13:48:31 +01:00
|
|
|
let renderCalls = 0;
|
2019-02-04 21:09:17 +01:00
|
|
|
class TestW extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ drinks: 1 });
|
2019-01-24 13:48:31 +01:00
|
|
|
async willStart() {
|
2019-04-17 10:07:31 +02:00
|
|
|
this.state.drinks++;
|
2019-01-24 13:48:31 +01:00
|
|
|
}
|
2019-09-19 11:42:00 +02:00
|
|
|
async __render(f) {
|
2019-01-24 13:48:31 +01:00
|
|
|
renderCalls++;
|
2019-09-19 11:42:00 +02:00
|
|
|
return super.__render(f);
|
2019-01-24 13:48:31 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new TestW(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-01-24 13:48:31 +01:00
|
|
|
expect(renderCalls).toBe(1);
|
|
|
|
|
});
|
2019-01-26 20:33:23 +01:00
|
|
|
|
2019-01-27 15:12:35 +01:00
|
|
|
test("keeps a reference to env", async () => {
|
2019-01-26 20:33:23 +01:00
|
|
|
const widget = new Widget(env);
|
|
|
|
|
expect(widget.env).toBe(env);
|
|
|
|
|
});
|
2019-01-27 15:12:35 +01:00
|
|
|
|
|
|
|
|
test("do not remove previously rendered dom if not necessary", async () => {
|
|
|
|
|
const widget = new Widget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe(`<div></div>`);
|
|
|
|
|
widget.el!.appendChild(document.createElement("span"));
|
|
|
|
|
expect(fixture.innerHTML).toBe(`<div><span></span></div>`);
|
|
|
|
|
widget.render();
|
|
|
|
|
expect(fixture.innerHTML).toBe(`<div><span></span></div>`);
|
|
|
|
|
});
|
2019-09-02 15:27:37 +02:00
|
|
|
|
|
|
|
|
test("reconciliation alg is not confused in some specific situation", async () => {
|
|
|
|
|
// in this test, we set t-key to 4 because it was in conflict with the
|
|
|
|
|
// template id corresponding to the first child.
|
2019-09-24 14:06:53 +02:00
|
|
|
class Child extends Component<any, any> {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<span>child</span>`;
|
|
|
|
|
}
|
2019-09-02 15:27:37 +02:00
|
|
|
|
2019-09-24 14:06:53 +02:00
|
|
|
class Parent extends Component<any, any> {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<Child />
|
|
|
|
|
<Child t-key="4"/>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-09-02 15:27:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new Parent(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>child</span><span>child</span></div>");
|
|
|
|
|
});
|
2019-01-16 13:19:58 +01:00
|
|
|
});
|
2019-01-16 23:03:51 +01:00
|
|
|
|
|
|
|
|
describe("lifecycle hooks", () => {
|
|
|
|
|
test("willStart hook is called", async () => {
|
|
|
|
|
let willstart = false;
|
2019-02-04 21:09:17 +01:00
|
|
|
class HookWidget extends Widget {
|
2019-01-16 23:03:51 +01:00
|
|
|
async willStart() {
|
|
|
|
|
willstart = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new HookWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-01-16 23:03:51 +01:00
|
|
|
expect(willstart).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("mounted hook is not called if not in DOM", async () => {
|
|
|
|
|
let mounted = false;
|
2019-02-04 21:09:17 +01:00
|
|
|
class HookWidget extends Widget {
|
2019-01-16 23:03:51 +01:00
|
|
|
async mounted() {
|
|
|
|
|
mounted = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new HookWidget(env);
|
2019-01-16 23:03:51 +01:00
|
|
|
const target = document.createElement("div");
|
|
|
|
|
await widget.mount(target);
|
|
|
|
|
expect(mounted).toBe(false);
|
|
|
|
|
});
|
2019-01-21 14:54:13 +01:00
|
|
|
|
|
|
|
|
test("mounted hook is called if mounted in DOM", async () => {
|
|
|
|
|
let mounted = false;
|
2019-02-04 21:09:17 +01:00
|
|
|
class HookWidget extends Widget {
|
2019-01-21 14:54:13 +01:00
|
|
|
async mounted() {
|
|
|
|
|
mounted = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new HookWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-01-21 14:54:13 +01:00
|
|
|
expect(mounted).toBe(true);
|
|
|
|
|
});
|
2019-01-21 16:26:51 +01:00
|
|
|
|
2019-01-25 11:12:20 +01:00
|
|
|
test("willStart hook is called on subwidget", async () => {
|
|
|
|
|
let ok = false;
|
2019-02-04 21:09:17 +01:00
|
|
|
class ChildWidget extends Widget {
|
2019-01-25 11:12:20 +01:00
|
|
|
async willStart() {
|
|
|
|
|
ok = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
|
|
|
|
|
class ParentWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<div><t t-component="child"/></div>`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: ChildWidget };
|
|
|
|
|
}
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-01-25 11:12:20 +01:00
|
|
|
expect(ok).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("mounted hook is called on subcomponents, in proper order", async () => {
|
2019-04-26 16:20:50 +02:00
|
|
|
const steps: any[] = [];
|
|
|
|
|
|
2019-02-04 21:09:17 +01:00
|
|
|
class ChildWidget extends Widget {
|
2019-01-25 11:01:43 +01:00
|
|
|
mounted() {
|
|
|
|
|
expect(document.body.contains(this.el)).toBe(true);
|
2019-04-26 16:20:50 +02:00
|
|
|
steps.push("child:mounted");
|
2019-01-21 16:26:51 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
|
|
|
|
|
class ParentWidget extends Widget {
|
2019-09-19 11:42:00 +02:00
|
|
|
static template = xml`<div><ChildWidget /></div>`;
|
2019-09-12 09:45:32 +02:00
|
|
|
static components = { ChildWidget };
|
2019-09-10 22:37:08 +02:00
|
|
|
mounted() {
|
|
|
|
|
steps.push("parent:mounted");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-04-26 16:20:50 +02:00
|
|
|
expect(steps).toEqual(["child:mounted", "parent:mounted"]);
|
2019-01-21 16:26:51 +01:00
|
|
|
});
|
2019-01-25 11:12:20 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("mounted hook is called on subsubcomponents, in proper order", async () => {
|
2019-04-26 17:32:02 +02:00
|
|
|
const steps: any[] = [];
|
|
|
|
|
|
2019-09-10 22:37:08 +02:00
|
|
|
class ChildChildWidget extends Widget {
|
2019-04-26 17:32:02 +02:00
|
|
|
mounted() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("childchild:mounted");
|
2019-04-26 17:32:02 +02:00
|
|
|
}
|
|
|
|
|
willUnmount() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("childchild:willUnmount");
|
2019-04-26 17:32:02 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
|
2019-04-26 17:32:02 +02:00
|
|
|
class ChildWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<div><t t-component="childchild"/></div>`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { childchild: ChildChildWidget };
|
2019-04-26 17:32:02 +02:00
|
|
|
mounted() {
|
|
|
|
|
steps.push("child:mounted");
|
|
|
|
|
}
|
|
|
|
|
willUnmount() {
|
|
|
|
|
steps.push("child:willUnmount");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
|
|
|
|
|
class ParentWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<div><t t-if="state.flag"><t t-component="child"/></t></div>`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: ChildWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: false });
|
2019-04-26 17:32:02 +02:00
|
|
|
mounted() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("parent:mounted");
|
2019-04-26 17:32:02 +02:00
|
|
|
}
|
|
|
|
|
willUnmount() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("parent:willUnmount");
|
2019-04-26 17:32:02 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(steps).toEqual(["parent:mounted"]);
|
|
|
|
|
widget.state.flag = true;
|
|
|
|
|
await nextTick();
|
|
|
|
|
widget.destroy();
|
|
|
|
|
expect(steps).toEqual([
|
|
|
|
|
"parent:mounted",
|
|
|
|
|
"childchild:mounted",
|
|
|
|
|
"child:mounted",
|
|
|
|
|
"parent:willUnmount",
|
|
|
|
|
"child:willUnmount",
|
|
|
|
|
"childchild:willUnmount"
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("willPatch, patched hook are called on subsubcomponents, in proper order", async () => {
|
2019-04-27 19:20:29 +02:00
|
|
|
const steps: any[] = [];
|
|
|
|
|
|
2019-09-10 22:37:08 +02:00
|
|
|
env.qweb.addTemplate("ChildWidget", `<div><t t-component="childchild" n="props.n"/></div>`);
|
|
|
|
|
env.qweb.addTemplate("ChildChildWidget", `<div><t t-esc="props.n"/></div>`);
|
|
|
|
|
|
|
|
|
|
class ChildChildWidget extends Widget {
|
2019-04-27 19:20:29 +02:00
|
|
|
willPatch() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("childchild:willPatch");
|
2019-04-27 19:20:29 +02:00
|
|
|
}
|
|
|
|
|
patched() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("childchild:patched");
|
2019-04-27 19:20:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class ChildWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { childchild: ChildChildWidget };
|
2019-04-27 19:20:29 +02:00
|
|
|
willPatch() {
|
|
|
|
|
steps.push("child:willPatch");
|
|
|
|
|
}
|
|
|
|
|
patched() {
|
|
|
|
|
steps.push("child:patched");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
|
2019-09-10 22:37:08 +02:00
|
|
|
env.qweb.addTemplate("ParentWidget", `<div><t t-component="child" n="state.n"/></div>`);
|
|
|
|
|
class ParentWidget extends Widget {
|
|
|
|
|
static components = { child: ChildWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ n: 1 });
|
2019-04-27 19:20:29 +02:00
|
|
|
willPatch() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("parent:willPatch");
|
2019-04-27 19:20:29 +02:00
|
|
|
}
|
|
|
|
|
patched() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("parent:patched");
|
2019-04-27 19:20:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
|
2019-04-27 19:20:29 +02:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(steps).toEqual([]);
|
|
|
|
|
widget.state.n = 2;
|
|
|
|
|
await nextTick();
|
|
|
|
|
widget.destroy();
|
|
|
|
|
expect(steps).toEqual([
|
|
|
|
|
"parent:willPatch",
|
|
|
|
|
"child:willPatch",
|
|
|
|
|
"childchild:willPatch",
|
|
|
|
|
"childchild:patched",
|
|
|
|
|
"child:patched",
|
|
|
|
|
"parent:patched"
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
2019-01-25 11:12:20 +01:00
|
|
|
test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => {
|
2019-04-26 16:20:50 +02:00
|
|
|
const steps: string[] = [];
|
|
|
|
|
|
2019-02-03 09:42:35 +01:00
|
|
|
// the t-else part in the template is important. This is
|
|
|
|
|
// necessary to have a situation that could confuse the vdom
|
|
|
|
|
// patching algorithm
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
|
|
|
|
`
|
2019-01-25 15:28:21 +01:00
|
|
|
<div>
|
|
|
|
|
<t t-if="state.ok">
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="child"/>
|
2019-01-25 15:28:21 +01:00
|
|
|
</t>
|
|
|
|
|
<t t-else="1">
|
|
|
|
|
<div/>
|
|
|
|
|
</t>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
2019-02-04 21:09:17 +01:00
|
|
|
class ChildWidget extends Widget {
|
2019-01-25 11:12:20 +01:00
|
|
|
async willStart() {
|
2019-04-26 16:20:50 +02:00
|
|
|
steps.push("child:willStart");
|
2019-01-25 11:12:20 +01:00
|
|
|
}
|
|
|
|
|
mounted() {
|
2019-04-26 16:20:50 +02:00
|
|
|
steps.push("child:mounted");
|
2019-01-25 11:12:20 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ ok: false });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: ChildWidget };
|
|
|
|
|
}
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-04-26 16:20:50 +02:00
|
|
|
expect(steps).toEqual([]);
|
2019-04-16 22:59:54 +02:00
|
|
|
widget.state.ok = true;
|
|
|
|
|
await nextTick();
|
2019-04-26 16:20:50 +02:00
|
|
|
expect(steps).toEqual(["child:willStart", "child:mounted"]);
|
2019-01-25 11:12:20 +01:00
|
|
|
});
|
2019-01-24 22:17:06 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("mounted hook is correctly called on subcomponents created in mounted hook", async done => {
|
2019-01-24 22:17:06 +01:00
|
|
|
// the issue here is that the parent widget creates in the
|
|
|
|
|
// mounted hook a new widget, which means that it modifies
|
|
|
|
|
// in place its list of children. But this list of children is currently
|
|
|
|
|
// being visited, so the mount action of the parent could cause a mount
|
|
|
|
|
// action of the new child widget, even though it is not ready yet.
|
|
|
|
|
expect.assertions(1);
|
|
|
|
|
const target = document.createElement("div");
|
|
|
|
|
document.body.appendChild(target);
|
2019-02-04 21:09:17 +01:00
|
|
|
class ParentWidget extends Widget {
|
2019-01-24 22:17:06 +01:00
|
|
|
mounted() {
|
|
|
|
|
const child = new ChildWidget(this);
|
|
|
|
|
child.mount(this.el!);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-04 21:09:17 +01:00
|
|
|
class ChildWidget extends Widget {
|
2019-01-24 22:17:06 +01:00
|
|
|
mounted() {
|
|
|
|
|
expect(this.el).toBeTruthy();
|
|
|
|
|
done();
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new ParentWidget(env);
|
2019-01-24 22:17:06 +01:00
|
|
|
await widget.mount(target);
|
|
|
|
|
});
|
2019-01-27 15:40:10 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("components are unmounted and destroyed if no longer in DOM", async () => {
|
2019-01-27 15:40:10 +01:00
|
|
|
let steps: string[] = [];
|
2019-09-12 09:45:32 +02:00
|
|
|
|
2019-02-04 21:09:17 +01:00
|
|
|
class ChildWidget extends Widget {
|
2019-01-27 15:40:10 +01:00
|
|
|
constructor(parent) {
|
|
|
|
|
super(parent);
|
|
|
|
|
steps.push("init");
|
|
|
|
|
}
|
|
|
|
|
async willStart() {
|
|
|
|
|
steps.push("willstart");
|
|
|
|
|
}
|
|
|
|
|
mounted() {
|
|
|
|
|
steps.push("mounted");
|
|
|
|
|
}
|
|
|
|
|
willUnmount() {
|
|
|
|
|
steps.push("willunmount");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<t t-if="state.ok"><ChildWidget /></t>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
static components = { ChildWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ ok: true });
|
2019-09-10 22:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-27 15:40:10 +01:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(steps).toEqual(["init", "willstart", "mounted"]);
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.ok = false;
|
|
|
|
|
await nextTick();
|
2019-04-10 09:35:20 +02:00
|
|
|
expect(steps).toEqual(["init", "willstart", "mounted", "willunmount"]);
|
2019-01-27 15:40:10 +01:00
|
|
|
});
|
2019-01-27 21:03:13 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("components are unmounted and destroyed if no longer in DOM, even after updateprops", async () => {
|
2019-04-09 15:24:15 +02:00
|
|
|
let childUnmounted = false;
|
2019-09-12 09:45:32 +02:00
|
|
|
|
2019-04-09 15:24:15 +02:00
|
|
|
class ChildWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<span><t t-esc="props.n"/></span>`;
|
2019-04-09 15:24:15 +02:00
|
|
|
willUnmount() {
|
|
|
|
|
childUnmounted = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<div t-if="state.flag">
|
|
|
|
|
<ChildWidget n="state.n"/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ChildWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ n: 0, flag: true });
|
2019-04-09 15:24:15 +02:00
|
|
|
increment() {
|
2019-04-17 10:07:31 +02:00
|
|
|
this.state.n += 1;
|
2019-04-09 15:24:15 +02:00
|
|
|
}
|
|
|
|
|
toggleSubWidget() {
|
2019-04-17 10:07:31 +02:00
|
|
|
this.state.flag = !this.state.flag;
|
2019-04-09 15:24:15 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div><span>0</span></div></div>");
|
|
|
|
|
widget.increment();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div><span>1</span></div></div>");
|
|
|
|
|
widget.toggleSubWidget();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div></div>");
|
|
|
|
|
expect(childUnmounted).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
2019-01-27 21:03:13 +01:00
|
|
|
test("hooks are called in proper order in widget creation/destruction", async () => {
|
|
|
|
|
let steps: string[] = [];
|
2019-09-12 09:45:32 +02:00
|
|
|
|
2019-09-10 22:37:08 +02:00
|
|
|
class ChildWidget extends Widget {
|
2019-01-27 21:03:13 +01:00
|
|
|
constructor(parent) {
|
|
|
|
|
super(parent);
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("c init");
|
2019-01-27 21:03:13 +01:00
|
|
|
}
|
|
|
|
|
async willStart() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("c willstart");
|
2019-01-27 21:03:13 +01:00
|
|
|
}
|
|
|
|
|
mounted() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("c mounted");
|
2019-01-27 21:03:13 +01:00
|
|
|
}
|
|
|
|
|
willUnmount() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("c willunmount");
|
2019-01-27 21:03:13 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<div><t t-component="child"/></div>`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: ChildWidget };
|
2019-01-27 21:03:13 +01:00
|
|
|
constructor(parent) {
|
|
|
|
|
super(parent);
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("p init");
|
2019-01-27 21:03:13 +01:00
|
|
|
}
|
|
|
|
|
async willStart() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("p willstart");
|
2019-01-27 21:03:13 +01:00
|
|
|
}
|
|
|
|
|
mounted() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("p mounted");
|
2019-01-27 21:03:13 +01:00
|
|
|
}
|
|
|
|
|
willUnmount() {
|
2019-09-10 22:37:08 +02:00
|
|
|
steps.push("p willunmount");
|
2019-01-27 21:03:13 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
|
2019-01-27 21:03:13 +01:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
widget.destroy();
|
|
|
|
|
expect(steps).toEqual([
|
|
|
|
|
"p init",
|
|
|
|
|
"p willstart",
|
|
|
|
|
"c init",
|
|
|
|
|
"c willstart",
|
|
|
|
|
"c mounted",
|
2019-04-26 16:20:50 +02:00
|
|
|
"p mounted",
|
|
|
|
|
"p willunmount",
|
|
|
|
|
"c willunmount"
|
2019-01-27 21:03:13 +01:00
|
|
|
]);
|
|
|
|
|
});
|
2019-01-28 16:16:22 +01:00
|
|
|
|
2019-04-09 13:51:04 +02:00
|
|
|
test("willUpdateProps hook is called", async () => {
|
|
|
|
|
let def = makeDeferred();
|
2019-06-22 14:44:09 +02:00
|
|
|
env.qweb.addTemplate("Parent", '<span><Child n="state.n"/></span>');
|
2019-04-09 13:51:04 +02:00
|
|
|
class HookWidget extends Widget {
|
|
|
|
|
willUpdateProps(nextProps) {
|
|
|
|
|
expect(nextProps.n).toBe(2);
|
|
|
|
|
return def;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ n: 1 });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child: HookWidget };
|
|
|
|
|
}
|
|
|
|
|
env.qweb.addTemplate("HookWidget", '<span><t t-esc="props.n"/></span>');
|
2019-04-27 19:20:29 +02:00
|
|
|
const widget = new Parent(env);
|
2019-04-09 13:51:04 +02:00
|
|
|
await widget.mount(fixture);
|
2019-04-27 19:20:29 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<span><span>1</span></span>");
|
|
|
|
|
widget.state.n = 2;
|
2019-04-09 13:51:04 +02:00
|
|
|
await nextTick();
|
2019-04-27 19:20:29 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<span><span>1</span></span>");
|
2019-04-09 13:51:04 +02:00
|
|
|
def.resolve();
|
|
|
|
|
await nextTick();
|
2019-04-27 19:20:29 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<span><span>2</span></span>");
|
2019-04-09 13:51:04 +02:00
|
|
|
});
|
|
|
|
|
|
2019-04-17 10:07:31 +02:00
|
|
|
test("patched hook is called after updating State", async () => {
|
2019-04-01 15:42:24 +02:00
|
|
|
let n = 0;
|
|
|
|
|
|
|
|
|
|
class TestWidget extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ a: 1 });
|
2019-04-01 15:42:24 +02:00
|
|
|
|
2019-04-09 10:37:53 +02:00
|
|
|
patched() {
|
2019-04-01 15:42:24 +02:00
|
|
|
n++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new TestWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(n).toBe(0);
|
|
|
|
|
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.a = 1; // empty update, should do nothing
|
|
|
|
|
await nextTick();
|
2019-04-01 15:42:24 +02:00
|
|
|
expect(n).toBe(0);
|
|
|
|
|
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.a = 3;
|
|
|
|
|
await nextTick();
|
2019-04-01 15:42:24 +02:00
|
|
|
expect(n).toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
2019-04-09 10:37:53 +02:00
|
|
|
test("patched hook is called after updateProps", async () => {
|
2019-04-01 15:42:24 +02:00
|
|
|
let n = 0;
|
|
|
|
|
|
|
|
|
|
class TestWidget extends Widget {
|
2019-04-09 10:37:53 +02:00
|
|
|
patched() {
|
2019-04-01 15:42:24 +02:00
|
|
|
n++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<div><Child a="state.a"/></div>`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ a: 1 });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child: TestWidget };
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-27 19:20:29 +02:00
|
|
|
const widget = new Parent(env);
|
2019-04-01 15:42:24 +02:00
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(n).toBe(0);
|
|
|
|
|
|
2019-04-27 19:20:29 +02:00
|
|
|
widget.state.a = 2;
|
|
|
|
|
await nextTick();
|
2019-04-01 15:42:24 +02:00
|
|
|
expect(n).toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
2019-01-28 16:16:22 +01:00
|
|
|
test("shouldUpdate hook prevent rerendering", async () => {
|
|
|
|
|
let shouldUpdate = false;
|
2019-02-04 21:09:17 +01:00
|
|
|
class TestWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<div><t t-esc="props.val"/></div>`;
|
2019-01-28 16:16:22 +01:00
|
|
|
shouldUpdate() {
|
|
|
|
|
return shouldUpdate;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<div><Child val="state.val"/></div>`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child: TestWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 42 });
|
2019-09-10 22:37:08 +02:00
|
|
|
}
|
2019-09-12 09:45:32 +02:00
|
|
|
|
2019-04-27 19:20:29 +02:00
|
|
|
const widget = new Parent(env);
|
2019-01-28 16:16:22 +01:00
|
|
|
await widget.mount(fixture);
|
2019-04-27 19:20:29 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>42</div></div>");
|
|
|
|
|
widget.state.val = 123;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>42</div></div>");
|
2019-01-28 16:16:22 +01:00
|
|
|
shouldUpdate = true;
|
2019-04-27 19:20:29 +02:00
|
|
|
widget.state.val = 666;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>666</div></div>");
|
2019-01-28 16:16:22 +01:00
|
|
|
});
|
2019-03-29 12:50:45 +01:00
|
|
|
|
|
|
|
|
test("sub widget (inside sub node): hooks are correctly called", async () => {
|
|
|
|
|
let created = false;
|
|
|
|
|
let mounted = false;
|
|
|
|
|
|
|
|
|
|
class ChildWidget extends Widget {
|
|
|
|
|
constructor(parent, props) {
|
|
|
|
|
super(parent, props);
|
|
|
|
|
created = true;
|
|
|
|
|
}
|
|
|
|
|
mounted() {
|
|
|
|
|
mounted = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<t t-if="state.flag">
|
|
|
|
|
<div><t t-component="child"/></div>
|
|
|
|
|
</t>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: ChildWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: false });
|
2019-09-10 22:37:08 +02:00
|
|
|
}
|
2019-03-29 12:50:45 +01:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(created).toBe(false);
|
|
|
|
|
expect(mounted).toBe(false);
|
2019-04-17 10:07:31 +02:00
|
|
|
|
|
|
|
|
widget.state.flag = true;
|
|
|
|
|
await nextTick();
|
2019-03-29 12:50:45 +01:00
|
|
|
expect(mounted).toBe(true);
|
|
|
|
|
expect(created).toBe(true);
|
|
|
|
|
});
|
2019-04-05 16:30:35 +02:00
|
|
|
|
2019-04-09 10:37:53 +02:00
|
|
|
test("willPatch/patched hook", async () => {
|
2019-04-05 16:30:35 +02:00
|
|
|
const steps: string[] = [];
|
2019-09-12 09:45:32 +02:00
|
|
|
|
2019-09-10 22:37:08 +02:00
|
|
|
class ChildWidget extends Widget {
|
|
|
|
|
willPatch() {
|
|
|
|
|
steps.push("child:willPatch");
|
|
|
|
|
}
|
|
|
|
|
patched() {
|
|
|
|
|
steps.push("child:patched");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<t t-component="child" v="state.n"/>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: ChildWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ n: 1 });
|
2019-04-05 16:30:35 +02:00
|
|
|
willPatch() {
|
|
|
|
|
steps.push("parent:willPatch");
|
|
|
|
|
}
|
2019-09-29 16:47:06 +02:00
|
|
|
patched() {
|
2019-04-09 10:37:53 +02:00
|
|
|
steps.push("parent:patched");
|
2019-04-05 16:30:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(steps).toEqual([]);
|
2019-04-16 22:59:54 +02:00
|
|
|
widget.state.n = 2;
|
|
|
|
|
await nextTick();
|
2019-04-05 16:30:35 +02:00
|
|
|
|
|
|
|
|
// Not sure about this order. If you disagree, feel free to open an issue...
|
|
|
|
|
expect(steps).toEqual([
|
2019-04-27 19:20:29 +02:00
|
|
|
"parent:willPatch",
|
2019-04-05 16:30:35 +02:00
|
|
|
"child:willPatch",
|
2019-04-09 10:37:53 +02:00
|
|
|
"child:patched",
|
|
|
|
|
"parent:patched"
|
2019-04-05 16:30:35 +02:00
|
|
|
]);
|
|
|
|
|
});
|
2019-04-26 15:30:59 +02:00
|
|
|
|
|
|
|
|
test("willPatch/patched hook with t-keepalive", async () => {
|
|
|
|
|
// we make sure here that willPatch/patched is only called if widget is in
|
|
|
|
|
// dom, mounted
|
|
|
|
|
const steps: string[] = [];
|
2019-08-28 10:02:38 +02:00
|
|
|
|
2019-04-26 15:30:59 +02:00
|
|
|
class ChildWidget extends Widget {
|
|
|
|
|
willPatch() {
|
|
|
|
|
steps.push("child:willPatch");
|
|
|
|
|
}
|
|
|
|
|
patched() {
|
|
|
|
|
steps.push("child:patched");
|
|
|
|
|
}
|
|
|
|
|
willUnmount() {
|
|
|
|
|
steps.push("child:willUnmount");
|
|
|
|
|
}
|
|
|
|
|
mounted() {
|
|
|
|
|
steps.push("child:mounted");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<ChildWidget t-if="state.flag" v="state.n" t-keepalive="1"/>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ChildWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ n: 1, flag: true });
|
2019-09-10 22:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-26 15:30:59 +02:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-08-28 10:02:38 +02:00
|
|
|
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(env.qweb.templates[ParentWidget.template].fn.toString()).toMatchSnapshot();
|
2019-04-26 16:20:50 +02:00
|
|
|
expect(steps).toEqual(["child:mounted"]);
|
2019-04-26 15:30:59 +02:00
|
|
|
widget.state.flag = false;
|
|
|
|
|
await nextTick();
|
2019-04-26 16:20:50 +02:00
|
|
|
expect(steps).toEqual(["child:mounted", "child:willUnmount"]);
|
2019-04-26 15:30:59 +02:00
|
|
|
widget.state.flag = true;
|
|
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(steps).toEqual(["child:mounted", "child:willUnmount", "child:mounted"]);
|
2019-04-26 15:30:59 +02:00
|
|
|
});
|
2019-01-16 23:03:51 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("destroy method", () => {
|
|
|
|
|
test("destroy remove the widget from the DOM", async () => {
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new Widget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-01-16 23:03:51 +01:00
|
|
|
expect(document.contains(widget.el)).toBe(true);
|
|
|
|
|
widget.destroy();
|
|
|
|
|
expect(document.contains(widget.el)).toBe(false);
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(widget.__owl__.isMounted).toBe(false);
|
|
|
|
|
expect(widget.__owl__.isDestroyed).toBe(true);
|
2019-01-16 23:03:51 +01:00
|
|
|
});
|
2019-01-26 18:20:49 +01:00
|
|
|
|
2019-01-27 21:03:13 +01:00
|
|
|
test("destroying a parent also destroys its children", async () => {
|
|
|
|
|
const parent = new WidgetA(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
const child = children(parent)[0];
|
|
|
|
|
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(child.__owl__.isDestroyed).toBe(false);
|
2019-01-27 21:03:13 +01:00
|
|
|
parent.destroy();
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(child.__owl__.isDestroyed).toBe(true);
|
2019-01-27 21:03:13 +01:00
|
|
|
});
|
|
|
|
|
|
2019-01-27 13:50:53 +01:00
|
|
|
test("destroy remove the parent/children link", async () => {
|
|
|
|
|
const parent = new WidgetA(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
const child = children(parent)[0];
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(child.__owl__.parent).toBe(parent);
|
2019-01-27 13:50:53 +01:00
|
|
|
expect(children(parent).length).toBe(1);
|
|
|
|
|
child.destroy();
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(child.__owl__.parent).toBe(null);
|
2019-01-27 13:50:53 +01:00
|
|
|
expect(children(parent).length).toBe(0);
|
|
|
|
|
});
|
2019-01-27 21:50:22 +01:00
|
|
|
|
|
|
|
|
test("destroying a widget before willStart is done", async () => {
|
2019-02-07 12:10:44 +01:00
|
|
|
let def = makeDeferred();
|
2019-01-27 21:50:22 +01:00
|
|
|
let isRendered = false;
|
2019-02-04 21:09:17 +01:00
|
|
|
class DelayedWidget extends Widget {
|
2019-01-27 21:50:22 +01:00
|
|
|
willStart() {
|
2019-02-07 12:10:44 +01:00
|
|
|
return def;
|
2019-01-27 21:50:22 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
expect(fixture.innerHTML).toBe("");
|
|
|
|
|
const widget = new DelayedWidget(env);
|
|
|
|
|
widget.mount(fixture);
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(widget.__owl__.isMounted).toBe(false);
|
|
|
|
|
expect(widget.__owl__.isDestroyed).toBe(false);
|
2019-01-27 21:50:22 +01:00
|
|
|
widget.destroy();
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(widget.__owl__.isMounted).toBe(false);
|
|
|
|
|
expect(widget.__owl__.isDestroyed).toBe(true);
|
2019-02-07 12:10:44 +01:00
|
|
|
def.resolve();
|
|
|
|
|
await nextTick();
|
2019-01-27 21:50:22 +01:00
|
|
|
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(widget.__owl__.isMounted).toBe(false);
|
|
|
|
|
expect(widget.__owl__.isDestroyed).toBe(true);
|
2019-04-26 16:20:50 +02:00
|
|
|
expect(widget.__owl__.vnode).toBe(undefined);
|
2019-01-27 21:50:22 +01:00
|
|
|
expect(fixture.innerHTML).toBe("");
|
|
|
|
|
expect(isRendered).toBe(false);
|
|
|
|
|
});
|
2019-09-01 21:24:54 +02:00
|
|
|
|
|
|
|
|
test("destroying a widget before being mounted", async () => {
|
2019-09-24 14:06:53 +02:00
|
|
|
class GrandChild extends Component<any, any> {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<span>
|
|
|
|
|
<t t-esc="props.val.val"/>
|
|
|
|
|
</span>
|
|
|
|
|
`;
|
|
|
|
|
}
|
2019-09-24 14:06:53 +02:00
|
|
|
class Child extends Component<any, any> {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<span>
|
|
|
|
|
<GrandChild t-if="state.flag" val="something"/>
|
|
|
|
|
<button t-on-click="doSomething">click</button>
|
|
|
|
|
</span>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { GrandChild };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 33, flag: false });
|
2019-09-01 21:24:54 +02:00
|
|
|
doSomething() {
|
|
|
|
|
this.state.val = 12;
|
|
|
|
|
this.state.flag = true;
|
|
|
|
|
this.trigger("some-event");
|
|
|
|
|
}
|
|
|
|
|
get something() {
|
|
|
|
|
return { val: this.state.val };
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-24 14:06:53 +02:00
|
|
|
class Parent extends Component<any, any> {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div t-on-some-event="doStuff">
|
|
|
|
|
<Child />
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ p: 1 });
|
2019-09-10 22:37:08 +02:00
|
|
|
doStuff() {
|
|
|
|
|
this.state.p = 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-01 21:24:54 +02:00
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span><button>click</button></span></div>");
|
|
|
|
|
fixture.querySelector("button")!.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span><span>12</span><button>click</button></span></div>");
|
|
|
|
|
});
|
2019-01-16 23:03:51 +01:00
|
|
|
});
|
2019-01-20 14:46:36 +01:00
|
|
|
|
|
|
|
|
describe("composition", () => {
|
2019-01-21 15:46:29 +01:00
|
|
|
test("a widget with a sub widget", async () => {
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new WidgetA(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>Hello<div>world</div></div>");
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(children(widget)[0].__owl__.parent).toBe(widget);
|
2019-01-20 14:46:36 +01:00
|
|
|
});
|
2019-01-21 15:46:29 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("can use components from the global registry", async () => {
|
2019-09-12 10:45:40 +02:00
|
|
|
QWeb.registerComponent("WidgetB", WidgetB);
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("ParentWidget", `<div><t t-component="WidgetB"/></div>`);
|
2019-05-23 16:14:52 +02:00
|
|
|
class ParentWidget extends Widget {}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>world</div></div>");
|
2019-06-20 09:42:33 +02:00
|
|
|
delete QWeb.components["WidgetB"];
|
2019-05-23 16:14:52 +02:00
|
|
|
});
|
|
|
|
|
|
2019-09-21 09:28:56 +02:00
|
|
|
test("can use dynamic components (the class) if given", async () => {
|
2019-09-24 14:06:53 +02:00
|
|
|
class A extends Component<any, any> {
|
2019-09-21 09:28:56 +02:00
|
|
|
static template = xml`<span>child a</span>`;
|
|
|
|
|
}
|
2019-09-24 14:06:53 +02:00
|
|
|
class B extends Component<any, any> {
|
2019-09-21 09:28:56 +02:00
|
|
|
static template = xml`<span>child b</span>`;
|
|
|
|
|
}
|
2019-09-24 14:06:53 +02:00
|
|
|
class App extends Component<any, any> {
|
2019-09-21 09:28:56 +02:00
|
|
|
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({
|
2019-09-21 09:28:56 +02:00
|
|
|
child: "a"
|
2019-09-24 14:06:53 +02:00
|
|
|
});
|
2019-09-21 09:28:56 +02:00
|
|
|
get myComponent() {
|
|
|
|
|
return this.state.child === "a" ? A : B;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new App(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>child a</span>");
|
|
|
|
|
widget.state.child = "b";
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>child b</span>");
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-23 16:14:52 +02:00
|
|
|
test("don't fallback to global registry if widget defined locally", async () => {
|
2019-09-12 10:45:40 +02:00
|
|
|
QWeb.registerComponent("WidgetB", WidgetB); // should not use this widget
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("ParentWidget", `<div><t t-component="WidgetB"/></div>`);
|
2019-05-23 16:14:52 +02:00
|
|
|
env.qweb.addTemplate("AnotherWidgetB", `<span>Belgium</span>`);
|
|
|
|
|
class AnotherWidgetB extends Widget {}
|
|
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { WidgetB: AnotherWidgetB };
|
2019-05-23 16:14:52 +02:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>Belgium</span></div>");
|
2019-06-20 09:42:33 +02:00
|
|
|
delete QWeb.components["WidgetB"];
|
2019-05-23 16:14:52 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("can define components in template without t-component", async () => {
|
2019-06-18 09:24:52 +02:00
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="P"><C a="1"/></div>
|
|
|
|
|
<span t-name="C"><t t-esc="props.a"/></span>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class C extends Widget {}
|
|
|
|
|
class P extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { C };
|
2019-06-18 09:24:52 +02:00
|
|
|
}
|
|
|
|
|
const parent = new P(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-09 14:33:56 +02:00
|
|
|
test("display a nice error if it cannot find component", async () => {
|
|
|
|
|
const consoleError = console.error;
|
|
|
|
|
console.error = jest.fn();
|
|
|
|
|
|
2019-06-22 14:44:09 +02:00
|
|
|
env.qweb.addTemplate("Parent", `<div><SomeMispelledWidget /></div>`);
|
2019-05-15 17:17:39 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { SomeWidget: Widget };
|
2019-05-15 17:17:39 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
2019-07-09 14:33:56 +02:00
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(console.error).toBeCalledTimes(1);
|
|
|
|
|
expect((<any>console.error).mock.calls[0][0].message).toMatch(
|
|
|
|
|
'Cannot find the definition of component "SomeMispelledWidget"'
|
|
|
|
|
);
|
|
|
|
|
console.error = consoleError;
|
2019-05-15 17:17:39 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("t-refs on widget are components", async () => {
|
2019-02-04 21:09:17 +01:00
|
|
|
class WidgetC extends Widget {
|
2019-09-26 21:56:01 +02:00
|
|
|
static template = xml`<div>Hello<WidgetB t-ref="mywidgetb" /></div>`;
|
|
|
|
|
static components = { WidgetB };
|
|
|
|
|
widget = useRef("mywidgetb");
|
2019-01-21 15:54:18 +01:00
|
|
|
}
|
2019-09-26 21:56:01 +02:00
|
|
|
|
2019-01-25 15:28:21 +01:00
|
|
|
const widget = new WidgetC(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(widget.widget.comp).toBeInstanceOf(WidgetB);
|
2019-01-21 15:46:29 +01:00
|
|
|
});
|
2019-01-25 15:44:32 +01:00
|
|
|
|
2019-04-23 11:28:30 +02:00
|
|
|
test("t-refs are bound at proper timing", async () => {
|
|
|
|
|
expect.assertions(2);
|
2019-09-12 09:45:32 +02:00
|
|
|
class ParentWidget extends Widget {
|
|
|
|
|
static template = xml`
|
2019-04-23 11:28:30 +02:00
|
|
|
<div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-foreach="state.list" t-as="elem" t-ref="child" t-key="elem" t-component="Widget"/>
|
2019-09-12 09:45:32 +02:00
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Widget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ list: <any>[] });
|
2019-09-26 21:56:01 +02:00
|
|
|
child = useRef("child");
|
2019-04-23 11:28:30 +02:00
|
|
|
willPatch() {
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(this.child.comp).toBeNull();
|
2019-04-23 11:28:30 +02:00
|
|
|
}
|
|
|
|
|
patched() {
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(this.child.comp).not.toBeNull();
|
2019-04-23 11:28:30 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parent = new ParentWidget(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
parent.state.list.push(1);
|
2019-04-26 16:20:50 +02:00
|
|
|
await nextTick();
|
2019-04-23 11:28:30 +02:00
|
|
|
});
|
|
|
|
|
|
2019-04-25 10:15:33 +02:00
|
|
|
test("t-refs are bound at proper timing (2)", async () => {
|
|
|
|
|
expect.assertions(10);
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
|
|
|
|
`
|
2019-04-25 10:15:33 +02:00
|
|
|
<div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-if="state.child1" t-ref="child1" t-component="Widget"/>
|
|
|
|
|
<t t-if="state.child2" t-ref="child2" t-component="Widget"/>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Widget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ child1: true, child2: false });
|
2019-09-26 21:56:01 +02:00
|
|
|
child1 = useRef("child1");
|
|
|
|
|
child2 = useRef("child2");
|
2019-04-25 10:15:33 +02:00
|
|
|
count = 0;
|
|
|
|
|
mounted() {
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(this.child1.comp).toBeDefined();
|
|
|
|
|
expect(this.child2.comp).toBeNull();
|
2019-04-25 10:15:33 +02:00
|
|
|
}
|
|
|
|
|
willPatch() {
|
|
|
|
|
if (this.count === 0) {
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(this.child1.comp).toBeDefined();
|
|
|
|
|
expect(this.child2.comp).toBeNull();
|
2019-04-25 10:15:33 +02:00
|
|
|
}
|
|
|
|
|
if (this.count === 1) {
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(this.child1.comp).toBeDefined();
|
|
|
|
|
expect(this.child2.comp).toBeDefined();
|
2019-04-25 10:15:33 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
patched() {
|
|
|
|
|
if (this.count === 0) {
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(this.child1.comp).toBeDefined();
|
|
|
|
|
expect(this.child2.comp).toBeDefined();
|
2019-04-25 10:15:33 +02:00
|
|
|
}
|
|
|
|
|
if (this.count === 1) {
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(this.child1.comp).toBeNull();
|
|
|
|
|
expect(this.child2.comp).toBeDefined();
|
2019-04-25 10:15:33 +02:00
|
|
|
}
|
|
|
|
|
this.count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parent = new ParentWidget(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
parent.state.child2 = true;
|
2019-04-26 16:20:50 +02:00
|
|
|
await nextTick();
|
2019-04-25 10:15:33 +02:00
|
|
|
parent.state.child1 = false;
|
2019-04-26 16:20:50 +02:00
|
|
|
await nextTick();
|
2019-04-25 10:15:33 +02:00
|
|
|
});
|
|
|
|
|
|
2019-01-25 15:44:32 +01:00
|
|
|
test("modifying a sub widget", async () => {
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("ParentWidget", `<div><t t-component="Counter"/></div>`);
|
2019-02-04 21:09:17 +01:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Counter };
|
2019-01-25 15:44:32 +01:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>0<button>Inc</button></div></div>");
|
2019-01-25 15:44:32 +01:00
|
|
|
const button = fixture.getElementsByTagName("button")[0];
|
|
|
|
|
await button.click();
|
2019-04-17 10:07:31 +02:00
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>1<button>Inc</button></div></div>");
|
2019-01-25 15:44:32 +01:00
|
|
|
});
|
2019-01-26 08:40:09 +01:00
|
|
|
|
2019-05-04 22:46:58 +02:00
|
|
|
test("refs in a loop", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
|
|
|
|
`<div>
|
2019-05-04 22:46:58 +02:00
|
|
|
<t t-foreach="state.items" t-as="item">
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="Child" t-ref="{{item}}" t-key="item"/>
|
2019-05-04 22:46:58 +02:00
|
|
|
</t>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child: Widget };
|
2019-09-26 21:56:01 +02:00
|
|
|
elem1 = useRef("1");
|
|
|
|
|
elem2 = useRef("2");
|
|
|
|
|
elem3 = useRef("3");
|
|
|
|
|
elem4 = useRef("4");
|
|
|
|
|
state = useState({ items: [1, 2, 3] });
|
2019-05-04 22:46:58 +02:00
|
|
|
}
|
|
|
|
|
const parent = new ParentWidget(env);
|
|
|
|
|
await parent.mount(fixture);
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(parent.elem1.comp).toBeDefined();
|
|
|
|
|
expect(parent.elem2.comp).toBeDefined();
|
|
|
|
|
expect(parent.elem3.comp).toBeDefined();
|
|
|
|
|
expect(parent.elem4.comp).toBeNull();
|
2019-05-04 22:46:58 +02:00
|
|
|
});
|
|
|
|
|
|
2019-01-26 08:40:09 +01:00
|
|
|
test("parent's elm for a children === children's elm, even after rerender", async () => {
|
|
|
|
|
const widget = new WidgetA(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
2019-04-13 14:14:34 +02:00
|
|
|
expect((<any>widget.__owl__.vnode!.children![1]).elm).toBe(
|
|
|
|
|
(<any>children(widget)[0].__owl__.vnode).elm
|
2019-01-26 08:40:09 +01:00
|
|
|
);
|
2019-01-27 13:50:53 +01:00
|
|
|
await children(widget)[0].render();
|
2019-04-13 14:14:34 +02:00
|
|
|
expect((<any>widget.__owl__.vnode!.children![1]).elm).toBe(
|
|
|
|
|
(<any>children(widget)[0].__owl__.vnode).elm
|
2019-01-26 08:40:09 +01:00
|
|
|
);
|
|
|
|
|
});
|
2019-01-26 20:33:23 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("parent env is propagated to child components", async () => {
|
2019-01-26 20:33:23 +01:00
|
|
|
const widget = new WidgetA(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
2019-01-27 13:50:53 +01:00
|
|
|
expect(children(widget)[0].env).toBe(env);
|
2019-01-26 20:33:23 +01:00
|
|
|
});
|
2019-01-27 22:16:00 +01:00
|
|
|
|
|
|
|
|
test("rerendering a widget with a sub widget", async () => {
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("ParentWidget", `<div><t t-component="Counter"/></div>`);
|
2019-02-04 21:09:17 +01:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Counter };
|
2019-01-27 22:16:00 +01:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
const button = fixture.getElementsByTagName("button")[0];
|
|
|
|
|
await button.click();
|
2019-04-17 10:07:31 +02:00
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>1<button>Inc</button></div></div>");
|
2019-01-27 22:16:00 +01:00
|
|
|
await widget.render();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>1<button>Inc</button></div></div>");
|
2019-01-27 22:16:00 +01:00
|
|
|
});
|
2019-01-28 15:51:20 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("sub components are destroyed if no longer in dom, then recreated", async () => {
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("ParentWidget", `<div><t t-if="state.ok"><Counter /></t></div>`);
|
2019-02-04 21:09:17 +01:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ ok: true });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Counter };
|
2019-01-28 15:51:20 +01:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
const button = fixture.getElementsByTagName("button")[0];
|
|
|
|
|
await button.click();
|
2019-04-17 10:07:31 +02:00
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>1<button>Inc</button></div></div>");
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.ok = false;
|
|
|
|
|
await nextTick();
|
2019-01-28 15:51:20 +01:00
|
|
|
expect(fixture.innerHTML).toBe("<div></div>");
|
2019-04-17 10:07:31 +02:00
|
|
|
|
|
|
|
|
widget.state.ok = true;
|
|
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>0<button>Inc</button></div></div>");
|
2019-01-28 15:51:20 +01:00
|
|
|
});
|
2019-01-29 15:19:02 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("sub components with t-keepalive are not destroyed if no longer in dom", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
2019-06-18 09:24:52 +02:00
|
|
|
`<div><t t-if="state.ok"><Counter t-keepalive="1"/></t></div>`
|
2019-05-15 11:15:08 +02:00
|
|
|
);
|
2019-02-04 21:09:17 +01:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ ok: true });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Counter };
|
2019-01-31 10:55:06 +01:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
const button = fixture.getElementsByTagName("button")[0];
|
|
|
|
|
await button.click();
|
2019-04-17 10:07:31 +02:00
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>1<button>Inc</button></div></div>");
|
2019-01-31 15:14:26 +01:00
|
|
|
const counter = children(widget)[0];
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(counter.__owl__.isMounted).toBe(true);
|
2019-04-17 10:07:31 +02:00
|
|
|
|
|
|
|
|
widget.state.ok = false;
|
|
|
|
|
await nextTick();
|
2019-01-31 10:55:06 +01:00
|
|
|
expect(fixture.innerHTML).toBe("<div></div>");
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(counter.__owl__.isMounted).toBe(false);
|
2019-04-17 10:07:31 +02:00
|
|
|
|
|
|
|
|
widget.state.ok = true;
|
|
|
|
|
await nextTick();
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(counter.__owl__.isMounted).toBe(true);
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>1<button>Inc</button></div></div>");
|
2019-01-31 10:55:06 +01:00
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("sub components dom state with t-keepalive is preserved", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
2019-06-18 09:24:52 +02:00
|
|
|
`<div><t t-if="state.ok"><InputWidget t-keepalive="1"/></t></div>`
|
2019-05-15 11:15:08 +02:00
|
|
|
);
|
2019-09-10 22:37:08 +02:00
|
|
|
class InputWidget extends Widget {}
|
2019-02-04 21:09:17 +01:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ ok: true });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { InputWidget };
|
2019-01-31 10:55:06 +01:00
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("InputWidget", "<input/>");
|
2019-01-31 10:55:06 +01:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
const input = fixture.getElementsByTagName("input")[0];
|
|
|
|
|
input.value = "test";
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.ok = false;
|
|
|
|
|
await nextTick();
|
2019-01-31 10:55:06 +01:00
|
|
|
expect(fixture.innerHTML).toBe("<div></div>");
|
2019-04-17 10:07:31 +02:00
|
|
|
|
|
|
|
|
widget.state.ok = true;
|
|
|
|
|
await nextTick();
|
2019-01-31 10:55:06 +01:00
|
|
|
expect(fixture.innerHTML).toBe("<div><input></div>");
|
|
|
|
|
const input2 = fixture.getElementsByTagName("input")[0];
|
|
|
|
|
expect(input).toBe(input2);
|
|
|
|
|
expect(input2.value).toBe("test");
|
2019-08-28 10:02:38 +02:00
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
2019-01-31 10:55:06 +01:00
|
|
|
});
|
|
|
|
|
|
2019-06-06 08:49:29 +02:00
|
|
|
test("sub widget with t-ref and t-keepalive", async () => {
|
2019-09-26 21:56:01 +02:00
|
|
|
class ChildWidget extends Widget {
|
|
|
|
|
static template = xml`<span>Hello</span>`;
|
|
|
|
|
}
|
2019-06-06 08:49:29 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-26 21:56:01 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<t t-if="state.ok"><ChildWidget t-ref="child" t-keepalive="1"/></t>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ChildWidget };
|
2019-09-26 21:56:01 +02:00
|
|
|
state = useState({ ok: true });
|
|
|
|
|
child = useRef("child");
|
2019-06-06 08:49:29 +02:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
let child = children(widget)[0];
|
|
|
|
|
|
2019-06-07 14:10:59 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><span>Hello</span></div>");
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(widget.child.comp).toEqual(child);
|
2019-06-06 08:49:29 +02:00
|
|
|
|
|
|
|
|
widget.state.ok = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2019-06-07 14:10:59 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div></div>");
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(widget.child.comp).toEqual(child);
|
2019-06-06 08:49:29 +02:00
|
|
|
|
|
|
|
|
widget.state.ok = true;
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2019-06-07 14:10:59 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><span>Hello</span></div>");
|
2019-09-26 21:56:01 +02:00
|
|
|
expect(widget.child.comp).toEqual(child);
|
2019-06-06 08:49:29 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("sub components rendered in a loop", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("ChildWidget", `<span><t t-esc="props.n"/></span>`);
|
|
|
|
|
class ChildWidget extends Widget {}
|
2019-02-03 09:42:35 +01:00
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"Parent",
|
|
|
|
|
`
|
2019-01-29 15:19:02 +01:00
|
|
|
<div>
|
|
|
|
|
<t t-foreach="state.numbers" t-as="number">
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="ChildWidget" t-key="number" n="number"/>
|
2019-01-29 15:19:02 +01:00
|
|
|
</t>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class Parent extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({
|
2019-01-29 15:19:02 +01:00
|
|
|
numbers: [1, 2, 3]
|
2019-09-24 14:06:53 +02:00
|
|
|
});
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ChildWidget };
|
2019-01-29 15:19:02 +01:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(normalize(fixture.innerHTML)).toBe(
|
|
|
|
|
normalize(`
|
|
|
|
|
<div>
|
|
|
|
|
<span>1</span>
|
|
|
|
|
<span>2</span>
|
|
|
|
|
<span>3</span>
|
|
|
|
|
</div>
|
|
|
|
|
`)
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-01-30 11:29:54 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("sub components with some state rendered in a loop", async () => {
|
2019-01-30 11:29:54 +01:00
|
|
|
let n = 1;
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("ChildWidget", `<span><t t-esc="state.n"/></span>`);
|
2019-02-04 21:09:17 +01:00
|
|
|
class ChildWidget extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state: any;
|
2019-01-30 11:29:54 +01:00
|
|
|
constructor(parent) {
|
|
|
|
|
super(parent);
|
2019-09-24 14:06:53 +02:00
|
|
|
this.state = useState({ n });
|
2019-01-30 11:29:54 +01:00
|
|
|
n++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-02 11:41:19 +01:00
|
|
|
|
2019-03-13 14:10:15 +01:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"parent",
|
|
|
|
|
`<div>
|
2019-01-30 11:29:54 +01:00
|
|
|
<t t-foreach="state.numbers" t-as="number">
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="ChildWidget" t-key="number"/>
|
2019-01-30 11:29:54 +01:00
|
|
|
</t>
|
2019-03-13 14:10:15 +01:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class Parent extends Widget {
|
2019-09-12 09:48:17 +02:00
|
|
|
static template = "parent";
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({
|
2019-01-30 11:29:54 +01:00
|
|
|
numbers: [1, 2, 3]
|
2019-09-24 14:06:53 +02:00
|
|
|
});
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ChildWidget };
|
2019-01-30 11:29:54 +01:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
2019-04-17 10:07:31 +02:00
|
|
|
parent.state.numbers = [1, 3];
|
|
|
|
|
await nextTick();
|
2019-01-30 11:29:54 +01:00
|
|
|
expect(normalize(fixture.innerHTML)).toBe(
|
|
|
|
|
normalize(`
|
|
|
|
|
<div>
|
|
|
|
|
<span>1</span>
|
|
|
|
|
<span>3</span>
|
|
|
|
|
</div>
|
|
|
|
|
`)
|
|
|
|
|
);
|
2019-05-02 09:47:33 +02:00
|
|
|
expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot();
|
2019-01-30 11:29:54 +01:00
|
|
|
});
|
2019-02-15 14:35:46 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("sub components between t-ifs", async () => {
|
2019-02-15 14:35:46 +01:00
|
|
|
// this confuses the patching algorithm...
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("ChildWidget", `<span>child</span>`);
|
|
|
|
|
class ChildWidget extends Widget {}
|
2019-02-15 14:35:46 +01:00
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"Parent",
|
|
|
|
|
`<div>
|
2019-02-15 14:35:46 +01:00
|
|
|
<h1 t-if="state.flag">hey</h1>
|
|
|
|
|
<h2 t-else="1">noo</h2>
|
2019-06-18 09:24:52 +02:00
|
|
|
<span><ChildWidget/></span>
|
2019-02-15 14:35:46 +01:00
|
|
|
<t t-if="state.flag"><span>test</span></t>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
class Parent extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: false });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ChildWidget };
|
2019-02-15 14:35:46 +01:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
const child = children(parent)[0];
|
2019-04-17 10:07:31 +02:00
|
|
|
|
|
|
|
|
parent.state.flag = true;
|
|
|
|
|
await nextTick();
|
2019-02-15 14:35:46 +01:00
|
|
|
expect(children(parent)[0]).toBe(child);
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(child.__owl__.isDestroyed).toBe(false);
|
2019-02-15 14:35:46 +01:00
|
|
|
expect(normalize(fixture.innerHTML)).toBe(
|
|
|
|
|
normalize(`
|
|
|
|
|
<div>
|
|
|
|
|
<h1>hey</h1>
|
|
|
|
|
<span><span>child</span></span>
|
|
|
|
|
<span>test</span>
|
|
|
|
|
</div>
|
|
|
|
|
`)
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-05-28 14:30:08 +02:00
|
|
|
|
2019-09-03 09:32:38 +02:00
|
|
|
test("list of sub components inside other nodes", async () => {
|
|
|
|
|
// this confuses the patching algorithm...
|
|
|
|
|
env.qweb.addTemplate("ChildWidget", `<span>child</span>`);
|
|
|
|
|
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<div t-foreach="state.blips" t-as="blip" t-key="blip.id">
|
|
|
|
|
<SubWidget />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span t-name="SubWidget">asdf</span>
|
|
|
|
|
</templates>`);
|
|
|
|
|
|
|
|
|
|
class SubWidget extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { SubWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ blips: [{ a: "a", id: 1 }, { b: "b", id: 2 }, { c: "c", id: 4 }] });
|
2019-09-03 09:32:38 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
"<div><div><span>asdf</span></div><div><span>asdf</span></div><div><span>asdf</span></div></div>"
|
|
|
|
|
);
|
|
|
|
|
parent.state.blips.splice(0, 1);
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
"<div><div><span>asdf</span></div><div><span>asdf</span></div></div>"
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2019-09-03 16:38:24 +02:00
|
|
|
test("list of two sub components inside other nodes", async () => {
|
|
|
|
|
// this confuses the patching algorithm...
|
|
|
|
|
env.qweb.addTemplate("ChildWidget", `<span>child</span>`);
|
|
|
|
|
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<div t-foreach="state.blips" t-as="blip" t-key="blip.id">
|
|
|
|
|
<SubWidget />
|
|
|
|
|
<SubWidget />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span t-name="SubWidget">asdf</span>
|
|
|
|
|
</templates>`);
|
|
|
|
|
|
|
|
|
|
class SubWidget extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { SubWidget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ blips: [{ a: "a", id: 1 }] });
|
2019-09-03 16:38:24 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
2019-09-04 22:48:37 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div><span>asdf</span><span>asdf</span></div></div>");
|
2019-09-03 16:38:24 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("t-component with dynamic value", async () => {
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("ParentWidget", `<div><t t-component="{{state.widget}}"/></div>`);
|
2019-05-28 14:30:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { WidgetB };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ widget: "WidgetB" });
|
2019-05-28 14:30:08 +02:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>world</div></div>");
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("t-component with dynamic value 2", async () => {
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("ParentWidget", `<div><t t-component="Widget{{state.widget}}"/></div>`);
|
2019-05-28 14:30:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { WidgetB };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ widget: "B" });
|
2019-05-28 14:30:08 +02:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>world</div></div>");
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
2019-01-20 14:46:36 +01:00
|
|
|
});
|
2019-01-28 12:03:59 +01:00
|
|
|
|
2019-06-03 10:10:41 +02:00
|
|
|
describe("props evaluation ", () => {
|
2019-01-28 12:03:59 +01:00
|
|
|
test("explicit object prop", async () => {
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("Parent", `<div><t t-component="child" value="state.val"/></div>`);
|
2019-02-04 21:09:17 +01:00
|
|
|
class Child extends Widget {
|
2019-01-28 12:03:59 +01:00
|
|
|
state: { someval: number };
|
|
|
|
|
constructor(parent: Parent, props: { value: number }) {
|
|
|
|
|
super(parent);
|
2019-09-24 14:06:53 +02:00
|
|
|
this.state = useState({ someval: props.value });
|
2019-01-28 12:03:59 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class Parent extends Widget {
|
|
|
|
|
static components = { child: Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 42 });
|
2019-09-10 22:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
env.qweb.addTemplate("Child", `<span><t t-esc="state.someval"/></span>`);
|
2019-01-28 12:03:59 +01:00
|
|
|
|
|
|
|
|
const widget = new Parent(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
|
|
|
|
|
});
|
|
|
|
|
|
2019-03-14 15:53:31 +01:00
|
|
|
test("accept ES6-like syntax for props (with getters)", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("Child", `<span><t t-esc="props.greetings"/></span>`);
|
|
|
|
|
class Child extends Widget {}
|
2019-03-14 15:53:31 +01:00
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("Parent", `<div><t t-component="child" greetings="greetings"/></div>`);
|
2019-03-14 15:53:31 +01:00
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-03-14 15:53:31 +01:00
|
|
|
get greetings() {
|
|
|
|
|
return `hello ${this.props.name}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new Parent(env, { name: "aaron" });
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>hello aaron</span></div>");
|
|
|
|
|
});
|
2019-03-15 10:11:50 +01:00
|
|
|
|
2019-06-03 10:10:41 +02:00
|
|
|
test("t-set works ", async () => {
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"Parent",
|
|
|
|
|
`
|
2019-03-15 10:11:50 +01:00
|
|
|
<div>
|
|
|
|
|
<t t-set="val" t-value="42"/>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="child" val="val"/>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-03-15 10:11:50 +01:00
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"Child",
|
|
|
|
|
`
|
2019-03-15 10:11:50 +01:00
|
|
|
<span>
|
|
|
|
|
<t t-esc="props.val"/>
|
2019-05-15 11:15:08 +02:00
|
|
|
</span>`
|
|
|
|
|
);
|
2019-03-15 10:11:50 +01:00
|
|
|
|
|
|
|
|
const widget = new Parent(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(normalize(fixture.innerHTML)).toBe("<div><span>42</span></div>");
|
|
|
|
|
});
|
2019-01-28 12:03:59 +01:00
|
|
|
});
|
2019-01-30 13:33:37 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
describe("class and style attributes with t-component", () => {
|
2019-05-04 22:26:51 +02:00
|
|
|
test("class is properly added on widget root el", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
|
|
|
|
`
|
2019-05-04 22:26:51 +02:00
|
|
|
<div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="child" class="a b"/>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-05-15 11:15:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-05-04 22:26:51 +02:00
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("Child", `<div class="c"/>`);
|
2019-05-04 22:26:51 +02:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe(`<div><div class="c a b"></div></div>`);
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-06 10:36:40 +02:00
|
|
|
test("t-att-class is properly added/removed on widget root el", async () => {
|
|
|
|
|
env.qweb.addTemplate(
|
2019-05-15 11:15:08 +02:00
|
|
|
"ParentWidget",
|
2019-05-06 10:36:40 +02:00
|
|
|
`<div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="child" t-att-class="{a:state.a, b:state.b}"/>
|
2019-05-06 10:36:40 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-05-06 10:36:40 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ a: true, b: false });
|
2019-05-06 10:36:40 +02:00
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("Child", `<div class="c"/>`);
|
2019-05-06 10:36:40 +02:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe(`<div><div class="c a"></div></div>`);
|
2019-05-15 11:15:08 +02:00
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
2019-05-06 10:36:40 +02:00
|
|
|
|
|
|
|
|
widget.state.a = false;
|
|
|
|
|
widget.state.b = true;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe(`<div><div class="c b"></div></div>`);
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-25 10:14:16 +02:00
|
|
|
test("class with extra whitespaces", async () => {
|
|
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
|
|
|
|
`<div>
|
|
|
|
|
<Child class="a b c d"/>
|
|
|
|
|
</div>`
|
|
|
|
|
);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-06-25 10:14:16 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-06-25 10:14:16 +02:00
|
|
|
}
|
|
|
|
|
env.qweb.addTemplate("Child", `<div/>`);
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe(`<div><div class="a b c d"></div></div>`);
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-25 14:24:03 +02:00
|
|
|
test("t-att-class is properly added/removed on widget root el (v2)", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ParentWidget">
|
|
|
|
|
<Child class="a" t-att-class="{ b: state.b }" t-ref="child"/>
|
|
|
|
|
</div>
|
|
|
|
|
<span t-name="Child" class="c" t-att-class="{ d: state.d }"/>
|
|
|
|
|
</templates>`);
|
|
|
|
|
|
|
|
|
|
class Child extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ d: true });
|
2019-06-25 14:24:03 +02:00
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
|
|
|
|
static components = { Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ b: true });
|
2019-09-26 21:56:01 +02:00
|
|
|
child = useRef("child");
|
2019-09-10 22:37:08 +02:00
|
|
|
}
|
2019-06-25 14:24:03 +02:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
const span = fixture.querySelector("span")!;
|
|
|
|
|
expect(span.className).toBe("c d a b");
|
|
|
|
|
|
|
|
|
|
widget.state.b = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(span.className).toBe("c d a");
|
|
|
|
|
|
2019-09-26 21:56:01 +02:00
|
|
|
(widget.child.comp as Child).state.d = false;
|
2019-06-25 14:24:03 +02:00
|
|
|
await nextTick();
|
|
|
|
|
expect(span.className).toBe("c a");
|
|
|
|
|
|
|
|
|
|
widget.state.b = true;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(span.className).toBe("c a b");
|
|
|
|
|
|
2019-09-26 21:56:01 +02:00
|
|
|
(widget.child.comp as Child).state.d = true;
|
2019-06-25 14:24:03 +02:00
|
|
|
await nextTick();
|
|
|
|
|
expect(span.className).toBe("c a b d");
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("t-att-class is properly added/removed on widget root el (v3)", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ParentWidget">
|
|
|
|
|
<Child class="a" t-att-class="state.b ? 'b' : ''" t-ref="child"/>
|
|
|
|
|
</div>
|
|
|
|
|
<span t-name="Child" class="c" t-att-class="state.d ? 'd' : ''"/>
|
|
|
|
|
</templates>`);
|
|
|
|
|
|
|
|
|
|
class Child extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ d: true });
|
2019-06-25 14:24:03 +02:00
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
|
|
|
|
static components = { Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ b: true });
|
2019-09-26 21:56:01 +02:00
|
|
|
child = useRef("child");
|
2019-09-10 22:37:08 +02:00
|
|
|
}
|
2019-06-25 14:24:03 +02:00
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
const span = fixture.querySelector("span")!;
|
|
|
|
|
expect(span.className).toBe("c d a b");
|
|
|
|
|
|
|
|
|
|
widget.state.b = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(span.className).toBe("c d a");
|
|
|
|
|
|
2019-09-26 21:56:01 +02:00
|
|
|
(widget.child.comp as Child).state.d = false;
|
2019-06-25 14:24:03 +02:00
|
|
|
await nextTick();
|
|
|
|
|
expect(span.className).toBe("c a");
|
|
|
|
|
|
|
|
|
|
widget.state.b = true;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(span.className).toBe("c a b");
|
|
|
|
|
|
2019-09-26 21:56:01 +02:00
|
|
|
(widget.child.comp as Child).state.d = true;
|
2019-06-25 14:24:03 +02:00
|
|
|
await nextTick();
|
|
|
|
|
expect(span.className).toBe("c a b d");
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("class on components do not interfere with user defined classes", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="App" t-att-class="{ c: state.c }" />
|
|
|
|
|
</templates>`);
|
|
|
|
|
|
|
|
|
|
class App extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ c: true });
|
2019-06-25 14:24:03 +02:00
|
|
|
mounted() {
|
|
|
|
|
this.el!.classList.add("user");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new App(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe('<div class="c user"></div>');
|
|
|
|
|
|
|
|
|
|
widget.state.c = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe('<div class="user"></div>');
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-04 22:26:51 +02:00
|
|
|
test("style is properly added on widget root el", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
|
|
|
|
`
|
2019-05-04 22:26:51 +02:00
|
|
|
<div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="child" style="font-weight: bold;"/>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Widget };
|
2019-05-04 22:26:51 +02:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe(`<div><div style="font-weight: bold;"></div></div>`);
|
2019-05-06 10:36:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("dynamic t-att-style is properly added and updated on widget root el", async () => {
|
|
|
|
|
env.qweb.addTemplate(
|
2019-05-15 11:15:08 +02:00
|
|
|
"ParentWidget",
|
2019-05-06 10:36:40 +02:00
|
|
|
`
|
|
|
|
|
<div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="child" t-att-style="state.style"/>
|
2019-05-06 10:36:40 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Widget };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ style: "font-size: 20px" });
|
2019-05-06 10:36:40 +02:00
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
2019-05-06 10:36:40 +02:00
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe(`<div><div style="font-size: 20px;"></div></div>`);
|
2019-05-06 10:36:40 +02:00
|
|
|
|
|
|
|
|
widget.state.style = "font-size: 30px";
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe(`<div><div style="font-size: 30px;"></div></div>`);
|
2019-05-04 22:26:51 +02:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
describe("other directives with t-component", () => {
|
2019-02-02 18:17:21 +01:00
|
|
|
test("t-on works as expected", async () => {
|
2019-05-31 14:03:34 +02:00
|
|
|
expect.assertions(4);
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
2019-06-20 09:42:33 +02:00
|
|
|
<div t-name="ParentWidget"><t t-component="child" t-on-custom-event="someMethod"/></div>
|
2019-05-31 14:03:34 +02:00
|
|
|
</templates>
|
|
|
|
|
`);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-02-04 21:09:17 +01:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-05-31 14:03:34 +02:00
|
|
|
n = 0;
|
|
|
|
|
someMethod(ev) {
|
|
|
|
|
expect(ev.detail).toBe(43);
|
|
|
|
|
this.n++;
|
2019-02-02 18:17:21 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
let child = children(widget)[0];
|
2019-05-31 14:03:34 +02:00
|
|
|
expect(widget.n).toBe(0);
|
|
|
|
|
child.trigger("custom-event", 43);
|
|
|
|
|
expect(widget.n).toBe(1);
|
2019-02-02 18:17:21 +01:00
|
|
|
child.destroy();
|
2019-05-31 14:03:34 +02:00
|
|
|
child.trigger("custom-event", 43);
|
|
|
|
|
expect(widget.n).toBe(1);
|
2019-02-02 18:17:21 +01:00
|
|
|
});
|
2019-03-04 09:29:27 +01:00
|
|
|
|
2019-06-03 15:12:21 +02:00
|
|
|
test("t-on with handler bound to argument", async () => {
|
|
|
|
|
expect.assertions(3);
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
2019-06-20 09:42:33 +02:00
|
|
|
<div t-name="ParentWidget"><t t-component="child" t-on-ev="onEv(3)"/></div>
|
2019-06-03 15:12:21 +02:00
|
|
|
</templates>
|
|
|
|
|
`);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-06-03 15:12:21 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-06-03 15:12:21 +02:00
|
|
|
onEv(n, ev) {
|
|
|
|
|
expect(n).toBe(3);
|
|
|
|
|
expect(ev.detail).toBe(43);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
let child = children(widget)[0];
|
|
|
|
|
child.trigger("ev", 43);
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("t-on with handler bound to object", async () => {
|
|
|
|
|
expect.assertions(3);
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
2019-06-20 09:42:33 +02:00
|
|
|
<div t-name="ParentWidget"><t t-component="child" t-on-ev="onEv({val: 3})"/></div>
|
2019-06-03 15:12:21 +02:00
|
|
|
</templates>
|
|
|
|
|
`);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-06-03 15:12:21 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-06-03 15:12:21 +02:00
|
|
|
onEv(o, ev) {
|
|
|
|
|
expect(o).toEqual({ val: 3 });
|
|
|
|
|
expect(ev.detail).toBe(43);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
let child = children(widget)[0];
|
|
|
|
|
child.trigger("ev", 43);
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("t-on with handler bound to empty object", async () => {
|
|
|
|
|
expect.assertions(3);
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
2019-06-20 09:42:33 +02:00
|
|
|
<div t-name="ParentWidget"><t t-component="child" t-on-ev="onEv({})"/></div>
|
2019-06-03 15:12:21 +02:00
|
|
|
</templates>
|
|
|
|
|
`);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-06-03 15:12:21 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-06-03 15:12:21 +02:00
|
|
|
onEv(o, ev) {
|
|
|
|
|
expect(o).toEqual({});
|
|
|
|
|
expect(ev.detail).toBe(43);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
let child = children(widget)[0];
|
|
|
|
|
child.trigger("ev", 43);
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("t-on with handler bound to empty object (with non empty inner string)", async () => {
|
|
|
|
|
expect.assertions(3);
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
2019-06-20 09:42:33 +02:00
|
|
|
<div t-name="ParentWidget"><t t-component="child" t-on-ev="onEv({ })"/></div>
|
2019-06-03 15:12:21 +02:00
|
|
|
</templates>
|
|
|
|
|
`);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-06-03 15:12:21 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-06-03 15:12:21 +02:00
|
|
|
onEv(o, ev) {
|
|
|
|
|
expect(o).toEqual({});
|
|
|
|
|
expect(ev.detail).toBe(43);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
let child = children(widget)[0];
|
|
|
|
|
child.trigger("ev", 43);
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-03 14:28:10 +02:00
|
|
|
test("t-on with stop and/or prevent modifiers", async () => {
|
|
|
|
|
expect.assertions(7);
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ParentWidget">
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="child"
|
2019-06-03 14:28:10 +02:00
|
|
|
t-on-ev-1.stop="onEv1"
|
|
|
|
|
t-on-ev-2.prevent="onEv2"
|
|
|
|
|
t-on-ev-3.stop.prevent="onEv3"/>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-06-03 14:28:10 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-06-03 14:28:10 +02:00
|
|
|
onEv1(ev) {
|
|
|
|
|
expect(ev.defaultPrevented).toBe(false);
|
|
|
|
|
expect(ev.cancelBubble).toBe(true);
|
|
|
|
|
}
|
|
|
|
|
onEv2(ev) {
|
|
|
|
|
expect(ev.defaultPrevented).toBe(true);
|
|
|
|
|
expect(ev.cancelBubble).toBe(false);
|
|
|
|
|
}
|
|
|
|
|
onEv3(ev) {
|
|
|
|
|
expect(ev.defaultPrevented).toBe(true);
|
|
|
|
|
expect(ev.cancelBubble).toBe(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
const child = children(widget)[0];
|
|
|
|
|
child.trigger("ev-1");
|
|
|
|
|
child.trigger("ev-2");
|
|
|
|
|
child.trigger("ev-3");
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("t-on with self modifier", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ParentWidget">
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="child" t-on-ev-1="onEv1" t-on-ev-2.self="onEv2"/>
|
2019-06-03 14:28:10 +02:00
|
|
|
</div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<div t-name="Child"><t t-component="child"/></div>
|
2019-06-03 14:28:10 +02:00
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
const steps: string[] = [];
|
2019-09-10 22:37:08 +02:00
|
|
|
class GrandChild extends Widget {}
|
|
|
|
|
class Child extends Widget {
|
|
|
|
|
static components = { child: GrandChild };
|
|
|
|
|
}
|
2019-06-03 14:28:10 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-06-03 14:28:10 +02:00
|
|
|
onEv1(ev) {
|
|
|
|
|
steps.push("onEv1");
|
|
|
|
|
}
|
|
|
|
|
onEv2(ev) {
|
|
|
|
|
steps.push("onEv2");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
const child = children(widget)[0];
|
|
|
|
|
const grandChild = children(child)[0];
|
|
|
|
|
child.trigger("ev-1");
|
|
|
|
|
child.trigger("ev-2");
|
|
|
|
|
expect(steps).toEqual(["onEv1", "onEv2"]);
|
|
|
|
|
grandChild.trigger("ev-1");
|
|
|
|
|
grandChild.trigger("ev-2");
|
|
|
|
|
expect(steps).toEqual(["onEv1", "onEv2", "onEv1"]);
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("t-on with self and prevent modifiers (order matters)", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ParentWidget">
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-component="child" t-on-ev.self.prevent="onEv"/>
|
2019-06-03 14:28:10 +02:00
|
|
|
</div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<div t-name="Child"><t t-component="child"/></div>
|
2019-06-03 14:28:10 +02:00
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
const steps: boolean[] = [];
|
2019-09-10 22:37:08 +02:00
|
|
|
class GrandChild extends Widget {}
|
|
|
|
|
class Child extends Widget {
|
|
|
|
|
static components = { child: GrandChild };
|
|
|
|
|
}
|
2019-06-03 14:28:10 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-06-03 14:28:10 +02:00
|
|
|
onEv() {}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
(<HTMLElement>fixture).addEventListener("ev", function(e) {
|
|
|
|
|
steps.push(e.defaultPrevented);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const child = children(widget)[0];
|
|
|
|
|
const grandChild = children(child)[0];
|
|
|
|
|
child.trigger("ev");
|
|
|
|
|
grandChild.trigger("ev");
|
|
|
|
|
expect(steps).toEqual([true, false]);
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("t-on with prevent and self modifiers (order matters)", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ParentWidget">
|
2019-06-18 09:24:52 +02:00
|
|
|
<Child t-on-ev.prevent.self="onEv"/>
|
2019-06-03 14:28:10 +02:00
|
|
|
</div>
|
2019-06-18 09:24:52 +02:00
|
|
|
<div t-name="Child"><GrandChild/></div>
|
2019-06-03 14:28:10 +02:00
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
const steps: boolean[] = [];
|
2019-09-10 22:37:08 +02:00
|
|
|
class GrandChild extends Widget {}
|
|
|
|
|
class Child extends Widget {
|
|
|
|
|
static components = { GrandChild };
|
|
|
|
|
}
|
2019-06-03 14:28:10 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-06-03 14:28:10 +02:00
|
|
|
onEv() {}
|
|
|
|
|
}
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
(<HTMLElement>fixture).addEventListener("ev", function(e) {
|
|
|
|
|
steps.push(e.defaultPrevented);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const child = children(widget)[0];
|
|
|
|
|
const grandChild = children(child)[0];
|
|
|
|
|
child.trigger("ev");
|
|
|
|
|
grandChild.trigger("ev");
|
|
|
|
|
expect(steps).toEqual([true, true]);
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("t-if works with t-component", async () => {
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("ParentWidget", `<div><t t-component="child" t-if="state.flag"/></div>`);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-03-04 09:29:27 +01:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: true });
|
2019-03-04 09:29:27 +01:00
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("Child", "<span>hey</span>");
|
2019-03-04 09:29:27 +01:00
|
|
|
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>hey</span></div>");
|
|
|
|
|
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.flag = false;
|
|
|
|
|
await nextTick();
|
2019-03-04 09:29:27 +01:00
|
|
|
expect(fixture.innerHTML).toBe("<div></div>");
|
|
|
|
|
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.flag = true;
|
|
|
|
|
await nextTick();
|
2019-03-04 09:29:27 +01:00
|
|
|
expect(fixture.innerHTML).toBe("<div><span>hey</span></div>");
|
|
|
|
|
});
|
2019-03-13 14:29:57 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("t-else works with t-component", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
|
|
|
|
`
|
2019-03-13 14:29:57 +01:00
|
|
|
<div>
|
|
|
|
|
<div t-if="state.flag">somediv</div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-else="1" t-component="child"/>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-05-15 11:15:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: true });
|
2019-03-13 14:29:57 +01:00
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("Child", "<span>hey</span>");
|
2019-03-13 14:29:57 +01:00
|
|
|
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(normalize(fixture.innerHTML)).toBe("<div><div>somediv</div></div>");
|
|
|
|
|
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.flag = false;
|
|
|
|
|
await nextTick();
|
2019-03-13 14:29:57 +01:00
|
|
|
expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>");
|
|
|
|
|
});
|
2019-04-16 14:17:02 +02:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("t-elif works with t-component", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
|
|
|
|
`
|
2019-05-10 19:35:58 +02:00
|
|
|
<div>
|
|
|
|
|
<div t-if="state.flag">somediv</div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-elif="!state.flag" t-component="child"/>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-05-15 11:15:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: true });
|
2019-05-10 19:35:58 +02:00
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("Child", "<span>hey</span>");
|
2019-05-10 19:35:58 +02:00
|
|
|
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(normalize(fixture.innerHTML)).toBe("<div><div>somediv</div></div>");
|
|
|
|
|
|
|
|
|
|
widget.state.flag = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>");
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("t-else with empty string works with t-component", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"ParentWidget",
|
|
|
|
|
`
|
2019-04-16 14:17:02 +02:00
|
|
|
<div>
|
|
|
|
|
<div t-if="state.flag">somediv</div>
|
2019-06-20 09:42:33 +02:00
|
|
|
<t t-else="" t-component="child"/>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-05-15 11:15:08 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: true });
|
2019-04-16 14:17:02 +02:00
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("Child", "<span>hey</span>");
|
2019-04-16 14:17:02 +02:00
|
|
|
|
|
|
|
|
const widget = new ParentWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(normalize(fixture.innerHTML)).toBe("<div><div>somediv</div></div>");
|
|
|
|
|
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.flag = false;
|
|
|
|
|
await nextTick();
|
2019-04-16 14:17:02 +02:00
|
|
|
expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>");
|
|
|
|
|
});
|
2019-02-02 18:17:21 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("random stuff/miscellaneous", () => {
|
2019-01-30 13:33:37 +01:00
|
|
|
test("widget after a t-foreach", async () => {
|
|
|
|
|
// this test makes sure that the foreach directive does not pollute sub
|
2019-06-20 09:42:33 +02:00
|
|
|
// context with the inLoop variable, which is then used in the t-component
|
2019-01-30 13:33:37 +01:00
|
|
|
// directive as a key
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"Test",
|
2019-06-20 09:42:33 +02:00
|
|
|
`<div><t t-foreach="Array(2)">txt</t><t t-component="widget"/></div>`
|
2019-05-15 11:15:08 +02:00
|
|
|
);
|
2019-02-04 21:09:17 +01:00
|
|
|
class Test extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { widget: Widget };
|
2019-01-30 13:33:37 +01:00
|
|
|
}
|
|
|
|
|
const widget = new Test(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>txttxt<div></div></div>");
|
|
|
|
|
});
|
2019-01-30 14:56:47 +01:00
|
|
|
|
2019-06-21 16:23:42 +02:00
|
|
|
test("t-on with handler bound to dynamic argument on a t-foreach", async () => {
|
|
|
|
|
expect.assertions(3);
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ParentWidget">
|
|
|
|
|
<t t-foreach="props.items" t-as="item">
|
|
|
|
|
<Child t-key="item" t-on-ev="onEv(item)"/>
|
|
|
|
|
</t>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
const items = [1, 2, 3, 4];
|
|
|
|
|
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-06-21 16:23:42 +02:00
|
|
|
class ParentWidget extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-06-21 16:23:42 +02:00
|
|
|
onEv(n, ev) {
|
|
|
|
|
expect(n).toBe(1);
|
|
|
|
|
expect(ev.detail).toBe(43);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new ParentWidget(env, { items });
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
children(widget)[0].trigger("ev", 43);
|
|
|
|
|
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2019-01-30 14:56:47 +01:00
|
|
|
test("updating widget immediately", async () => {
|
|
|
|
|
// in this situation, we protect against a bug that occurred: because of the
|
2019-06-20 09:42:33 +02:00
|
|
|
// interplay between components and vnodes, a sub widget vnode was patched
|
2019-01-30 14:56:47 +01:00
|
|
|
// twice.
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("Parent", `<div><t t-component="child" flag="state.flag"/></div>`);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-02-04 21:09:17 +01:00
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: false });
|
2019-01-30 14:56:47 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("Child", `<span>abc<t t-if="props.flag">def</t></span>`);
|
2019-01-30 14:56:47 +01:00
|
|
|
|
|
|
|
|
const widget = new Parent(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>abc</span></div>");
|
2019-04-17 10:07:31 +02:00
|
|
|
widget.state.flag = true;
|
|
|
|
|
await nextTick();
|
2019-01-30 14:56:47 +01:00
|
|
|
expect(fixture.innerHTML).toBe("<div><span>abcdef</span></div>");
|
|
|
|
|
});
|
2019-02-14 12:37:07 +01:00
|
|
|
|
|
|
|
|
test("snapshotting compiled code", async () => {
|
|
|
|
|
env.qweb.addTemplate(
|
2019-05-15 11:15:08 +02:00
|
|
|
"Parent",
|
2019-06-20 09:42:33 +02:00
|
|
|
`<div><t t-component="child" t-key="'somestring'" flag="state.flag"/></div>`
|
2019-02-14 12:37:07 +01:00
|
|
|
);
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
2019-02-14 12:37:07 +01:00
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { child: Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: false });
|
2019-02-14 12:37:07 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("Child", `<span>abc<t t-if="props.flag">def</t></span>`);
|
2019-02-14 12:37:07 +01:00
|
|
|
|
2019-03-28 17:35:08 +01:00
|
|
|
const widget = new Parent(env);
|
|
|
|
|
await widget.mount(fixture);
|
2019-05-15 11:15:08 +02:00
|
|
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
2019-03-28 17:35:08 +01:00
|
|
|
});
|
|
|
|
|
|
2019-05-08 15:21:33 +02:00
|
|
|
test("component semantics", async () => {
|
|
|
|
|
let steps: string[] = [];
|
|
|
|
|
let c: C;
|
|
|
|
|
|
|
|
|
|
class TestWidget extends Widget {
|
|
|
|
|
name: string = "test";
|
|
|
|
|
async willStart() {
|
|
|
|
|
steps.push(`${this.name}:willStart`);
|
|
|
|
|
}
|
2019-09-19 11:42:00 +02:00
|
|
|
__render(f) {
|
2019-05-08 15:21:33 +02:00
|
|
|
steps.push(`${this.name}:render`);
|
2019-09-19 11:42:00 +02:00
|
|
|
return super.__render(f);
|
2019-05-08 15:21:33 +02:00
|
|
|
}
|
2019-06-24 13:02:12 +02:00
|
|
|
__patch(vnode) {
|
|
|
|
|
steps.push(`${this.name}:__patch`);
|
|
|
|
|
super.__patch(vnode);
|
2019-05-08 15:21:33 +02:00
|
|
|
}
|
2019-06-24 13:02:12 +02:00
|
|
|
__mount(vnode, elm) {
|
|
|
|
|
steps.push(`${this.name}:__patch(from __mount)`);
|
|
|
|
|
return super.__mount(vnode, elm);
|
2019-05-08 15:21:33 +02:00
|
|
|
}
|
|
|
|
|
mounted() {
|
|
|
|
|
steps.push(`${this.name}:mounted`);
|
|
|
|
|
}
|
|
|
|
|
async willUpdateProps() {
|
|
|
|
|
steps.push(`${this.name}:willUpdateProps`);
|
|
|
|
|
}
|
|
|
|
|
willPatch() {
|
|
|
|
|
steps.push(`${this.name}:willPatch`);
|
|
|
|
|
}
|
|
|
|
|
patched() {
|
|
|
|
|
steps.push(`${this.name}:patched`);
|
|
|
|
|
}
|
|
|
|
|
willUnmount() {
|
|
|
|
|
steps.push(`${this.name}:willUnmount`);
|
|
|
|
|
}
|
|
|
|
|
destroy() {
|
|
|
|
|
super.destroy();
|
|
|
|
|
steps.push(`${this.name}:destroy`);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("B", `<div>B</div>`);
|
2019-05-08 15:21:33 +02:00
|
|
|
class B extends TestWidget {
|
|
|
|
|
name = "B";
|
|
|
|
|
constructor(parent, props) {
|
|
|
|
|
super(parent, props);
|
|
|
|
|
steps.push("B:constructor");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("D", `<div>D</div>`);
|
2019-05-08 15:21:33 +02:00
|
|
|
class D extends TestWidget {
|
|
|
|
|
name = "D";
|
|
|
|
|
constructor(parent, props) {
|
|
|
|
|
super(parent, props);
|
|
|
|
|
steps.push("D:constructor");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("E", `<div>E</div>`);
|
2019-05-08 15:21:33 +02:00
|
|
|
class E extends TestWidget {
|
|
|
|
|
name = "E";
|
|
|
|
|
constructor(parent, props) {
|
|
|
|
|
super(parent, props);
|
|
|
|
|
steps.push("E:constructor");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("F", `<div>F</div>`);
|
2019-05-08 15:21:33 +02:00
|
|
|
class F extends TestWidget {
|
|
|
|
|
name = "F";
|
|
|
|
|
constructor(parent, props) {
|
|
|
|
|
super(parent, props);
|
|
|
|
|
steps.push("F:constructor");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"C",
|
|
|
|
|
`
|
|
|
|
|
<div>C<D />
|
|
|
|
|
<E t-if="state.flag" />
|
|
|
|
|
<F t-else="!state.flag" />
|
|
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class C extends TestWidget {
|
|
|
|
|
static components = { D, E, F };
|
|
|
|
|
name = "C";
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: true });
|
2019-09-10 22:37:08 +02:00
|
|
|
|
|
|
|
|
constructor(parent, props) {
|
|
|
|
|
super(parent, props);
|
|
|
|
|
c = this;
|
|
|
|
|
steps.push("C:constructor");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
env.qweb.addTemplate("A", `<div>A<B /><C /></div>`);
|
|
|
|
|
class A extends TestWidget {
|
|
|
|
|
static components = { B, C };
|
|
|
|
|
name = "A";
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-08 15:21:33 +02:00
|
|
|
const a = new A(env);
|
|
|
|
|
await a.mount(fixture);
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe(`<div>A<div>B</div><div>C<div>D</div><div>E</div></div></div>`);
|
2019-05-08 15:21:33 +02:00
|
|
|
expect(steps).toEqual([
|
|
|
|
|
"A:willStart",
|
|
|
|
|
"A:render",
|
|
|
|
|
"B:constructor",
|
|
|
|
|
"B:willStart",
|
|
|
|
|
"C:constructor",
|
|
|
|
|
"C:willStart",
|
|
|
|
|
"B:render",
|
|
|
|
|
"C:render",
|
|
|
|
|
"D:constructor",
|
|
|
|
|
"D:willStart",
|
|
|
|
|
"E:constructor",
|
|
|
|
|
"E:willStart",
|
|
|
|
|
"D:render",
|
|
|
|
|
"E:render",
|
2019-06-24 13:02:12 +02:00
|
|
|
"A:__patch",
|
|
|
|
|
"B:__patch(from __mount)",
|
|
|
|
|
"C:__patch(from __mount)",
|
|
|
|
|
"D:__patch(from __mount)",
|
|
|
|
|
"E:__patch(from __mount)",
|
2019-05-08 15:21:33 +02:00
|
|
|
"B:mounted",
|
|
|
|
|
"D:mounted",
|
|
|
|
|
"E:mounted",
|
|
|
|
|
"C:mounted",
|
2019-05-10 15:03:41 +02:00
|
|
|
"A:mounted"
|
2019-05-08 15:21:33 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// update
|
|
|
|
|
steps = [];
|
|
|
|
|
c!.state.flag = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(steps).toEqual([
|
|
|
|
|
"C:render",
|
|
|
|
|
"D:willUpdateProps",
|
|
|
|
|
"F:constructor",
|
|
|
|
|
"F:willStart",
|
|
|
|
|
"D:render",
|
|
|
|
|
"F:render",
|
|
|
|
|
"C:willPatch",
|
|
|
|
|
"D:willPatch",
|
2019-06-24 13:02:12 +02:00
|
|
|
"C:__patch",
|
2019-05-08 15:21:33 +02:00
|
|
|
"E:willUnmount",
|
|
|
|
|
"E:destroy",
|
2019-06-24 13:02:12 +02:00
|
|
|
"F:__patch(from __mount)",
|
2019-05-08 15:21:33 +02:00
|
|
|
"F:mounted",
|
2019-06-24 13:02:12 +02:00
|
|
|
"D:__patch",
|
2019-05-08 15:21:33 +02:00
|
|
|
"D:patched",
|
|
|
|
|
"C:patched"
|
|
|
|
|
]);
|
|
|
|
|
});
|
2019-09-21 23:05:44 +02:00
|
|
|
|
|
|
|
|
test("can inject values in tagged templates", async () => {
|
2019-09-22 22:24:24 +02:00
|
|
|
const SUBTEMPLATE = xml`<span><t t-esc="state.n"/></span>`;
|
|
|
|
|
class Parent extends Widget {
|
|
|
|
|
static template = xml`<div><t t-call="${SUBTEMPLATE}"/></div>`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ n: 42 });
|
2019-09-22 22:24:24 +02:00
|
|
|
}
|
2019-09-21 23:05:44 +02:00
|
|
|
|
|
|
|
|
const widget = new Parent(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
2019-09-22 22:24:24 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
|
2019-09-21 23:05:44 +02:00
|
|
|
});
|
2019-01-30 14:56:47 +01:00
|
|
|
});
|
2019-02-07 12:46:30 +01:00
|
|
|
|
|
|
|
|
describe("async rendering", () => {
|
2019-02-08 09:17:04 +01:00
|
|
|
test("destroying a widget before start is over", async () => {
|
|
|
|
|
let def = makeDeferred();
|
|
|
|
|
class W extends Widget {
|
|
|
|
|
willStart(): Promise<void> {
|
|
|
|
|
return def;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const w = new W(env);
|
|
|
|
|
w.mount(fixture);
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(w.__owl__.isDestroyed).toBe(false);
|
|
|
|
|
expect(w.__owl__.isMounted).toBe(false);
|
2019-02-08 09:17:04 +01:00
|
|
|
w.destroy();
|
|
|
|
|
def.resolve();
|
|
|
|
|
await nextTick();
|
2019-04-13 14:14:34 +02:00
|
|
|
expect(w.__owl__.isDestroyed).toBe(true);
|
|
|
|
|
expect(w.__owl__.isMounted).toBe(false);
|
2019-02-08 09:17:04 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("destroying/recreating a subwidget with different props (if start is not over)", async () => {
|
|
|
|
|
let def = makeDeferred();
|
|
|
|
|
let n = 0;
|
2019-06-28 10:32:03 +02:00
|
|
|
env.qweb.addTemplate("W", `<div><t t-if="state.val > 1"><Child val="state.val"/></t></div>`);
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("Child", `<span>child:<t t-esc="props.val"/></span>`);
|
2019-02-08 09:17:04 +01:00
|
|
|
class Child extends Widget {
|
|
|
|
|
constructor(parent, props) {
|
|
|
|
|
super(parent, props);
|
|
|
|
|
n++;
|
|
|
|
|
}
|
|
|
|
|
willStart(): Promise<void> {
|
|
|
|
|
return def;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class W extends Widget {
|
|
|
|
|
static components = { Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 1 });
|
2019-09-10 22:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-08 09:17:04 +01:00
|
|
|
const w = new W(env);
|
|
|
|
|
await w.mount(fixture);
|
|
|
|
|
expect(n).toBe(0);
|
2019-04-16 22:59:54 +02:00
|
|
|
w.state.val = 2;
|
|
|
|
|
|
|
|
|
|
await nextMicroTick();
|
2019-02-08 09:17:04 +01:00
|
|
|
expect(n).toBe(1);
|
2019-04-16 22:59:54 +02:00
|
|
|
w.state.val = 3;
|
|
|
|
|
await nextMicroTick();
|
2019-02-08 09:17:04 +01:00
|
|
|
expect(n).toBe(2);
|
|
|
|
|
def.resolve();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(children(w).length).toBe(1);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>child:3</span></div>");
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("creating two async components, scenario 1", async () => {
|
2019-02-07 20:24:01 +01:00
|
|
|
let defA = makeDeferred();
|
|
|
|
|
let defB = makeDeferred();
|
2019-02-07 12:46:30 +01:00
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("ChildA", "<span>a</span>");
|
2019-02-07 20:24:01 +01:00
|
|
|
class ChildA extends Widget {
|
|
|
|
|
willStart(): Promise<void> {
|
|
|
|
|
return defA;
|
|
|
|
|
}
|
2019-02-07 12:46:30 +01:00
|
|
|
}
|
|
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("ChildB", "<span>b</span>");
|
2019-02-07 20:24:01 +01:00
|
|
|
class ChildB extends Widget {
|
|
|
|
|
willStart(): Promise<void> {
|
|
|
|
|
return defB;
|
|
|
|
|
}
|
2019-02-07 12:46:30 +01:00
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"Parent",
|
|
|
|
|
`
|
2019-02-07 12:46:30 +01:00
|
|
|
<div>
|
2019-06-18 09:24:52 +02:00
|
|
|
<t t-if="state.flagA"><ChildA /></t>
|
|
|
|
|
<t t-if="state.flagB"><ChildB /></t>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ChildA, ChildB };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flagA: false, flagB: false });
|
2019-02-07 12:46:30 +01:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>");
|
2019-04-17 10:07:31 +02:00
|
|
|
parent.state.flagA = true;
|
2019-02-07 12:46:30 +01:00
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>");
|
2019-04-17 10:07:31 +02:00
|
|
|
parent.state.flagB = true;
|
2019-02-07 12:46:30 +01:00
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>");
|
2019-02-07 20:24:01 +01:00
|
|
|
defB.resolve();
|
2019-02-07 12:46:30 +01:00
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>");
|
2019-02-07 20:24:01 +01:00
|
|
|
defA.resolve();
|
2019-02-07 12:46:30 +01:00
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe(
|
|
|
|
|
"<div><span>a</span><span>b</span></div>"
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-02-07 20:24:01 +01:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("creating two async components, scenario 2", async () => {
|
2019-02-07 20:24:01 +01:00
|
|
|
let defA = makeDeferred();
|
|
|
|
|
let defB = makeDeferred();
|
|
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("ChildA", `<span>a<t t-esc="props.val"/></span>`);
|
2019-02-07 20:24:01 +01:00
|
|
|
class ChildA extends Widget {
|
2019-06-24 13:02:12 +02:00
|
|
|
__updateProps(props, forceUpdate, fiber): Promise<void> {
|
|
|
|
|
return defA.then(() => super.__updateProps(props, forceUpdate, fiber));
|
2019-02-07 20:24:01 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("ChildB", `<span>b<t t-esc="props.val"/></span>`);
|
2019-02-07 20:24:01 +01:00
|
|
|
class ChildB extends Widget {
|
|
|
|
|
willStart(): Promise<void> {
|
|
|
|
|
return defB;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"Parent",
|
|
|
|
|
`
|
2019-02-07 20:24:01 +01:00
|
|
|
<div>
|
2019-06-18 09:24:52 +02:00
|
|
|
<ChildA val="state.valA"/>
|
|
|
|
|
<t t-if="state.flagB"><ChildB val="state.valB"/></t>
|
2019-05-15 11:15:08 +02:00
|
|
|
</div>`
|
|
|
|
|
);
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ChildA, ChildB };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ valA: 1, valB: 2, flagB: false });
|
2019-02-07 20:24:01 +01:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div><span>a1</span></div>");
|
2019-04-17 10:07:31 +02:00
|
|
|
parent.state.valA = 2;
|
2019-02-07 20:24:01 +01:00
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div><span>a1</span></div>");
|
2019-04-17 10:07:31 +02:00
|
|
|
parent.state.flagB = true;
|
2019-02-07 20:24:01 +01:00
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div><span>a1</span></div>");
|
2019-02-07 20:24:01 +01:00
|
|
|
defB.resolve();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div><span>a1</span></div>");
|
2019-02-07 20:24:01 +01:00
|
|
|
defA.resolve();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe(
|
|
|
|
|
"<div><span>a2</span><span>b2</span></div>"
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-05-06 14:23:29 +02:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("components in a node in a t-foreach ", async () => {
|
2019-05-15 15:18:27 +02:00
|
|
|
class Child extends Widget {}
|
|
|
|
|
|
|
|
|
|
class App extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-05-15 15:18:27 +02:00
|
|
|
|
|
|
|
|
get items() {
|
|
|
|
|
return [1, 2];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Child"><t t-esc="props.item"/></div>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<ul>
|
|
|
|
|
<t t-foreach="items" t-as="item">
|
|
|
|
|
<li t-key="'li_'+item">
|
2019-06-18 09:24:52 +02:00
|
|
|
<Child item="item"/>
|
2019-05-15 15:18:27 +02:00
|
|
|
</li>
|
|
|
|
|
</t>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
2019-05-15 17:17:39 +02:00
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
"<div><ul><li><div>1</div></li><li><div>2</div></li></ul></div>"
|
|
|
|
|
);
|
2019-05-15 15:18:27 +02:00
|
|
|
});
|
|
|
|
|
|
2019-05-07 11:36:03 +02:00
|
|
|
test("properly behave when destroyed/unmounted while rendering ", async () => {
|
2019-05-06 14:23:29 +02:00
|
|
|
let def = Promise.resolve();
|
|
|
|
|
|
2019-06-18 09:24:52 +02:00
|
|
|
env.qweb.addTemplate("Child", `<div><SubChild /></div>`);
|
2019-09-10 22:37:08 +02:00
|
|
|
class SubChild extends Widget {
|
|
|
|
|
willPatch() {
|
|
|
|
|
throw new Error("Should not happen!");
|
|
|
|
|
}
|
|
|
|
|
patched() {
|
|
|
|
|
throw new Error("Should not happen!");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-06 14:23:29 +02:00
|
|
|
class Child extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { SubChild };
|
2019-05-06 14:23:29 +02:00
|
|
|
mounted() {
|
|
|
|
|
// from now on, each rendering in child widget will be delayed (see
|
2019-06-24 13:02:12 +02:00
|
|
|
// __render)
|
2019-05-06 14:23:29 +02:00
|
|
|
def = makeDeferred();
|
|
|
|
|
}
|
2019-09-19 11:42:00 +02:00
|
|
|
async __render(f) {
|
|
|
|
|
const result = await super.__render(f);
|
2019-05-06 14:23:29 +02:00
|
|
|
await def;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate(
|
|
|
|
|
"Parent",
|
|
|
|
|
`
|
2019-06-18 09:24:52 +02:00
|
|
|
<div><t t-if="state.flag"><Child val="state.val"/></t></div>`
|
2019-05-15 11:15:08 +02:00
|
|
|
);
|
2019-05-06 14:23:29 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: true, val: "Framboise Lindemans" });
|
2019-05-06 14:23:29 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div><div></div></div></div>");
|
|
|
|
|
|
|
|
|
|
// this change triggers a rendering of the parent. This rendering is delayed,
|
|
|
|
|
// because child is now waiting for def to be resolved
|
|
|
|
|
parent.state.val = "Framboise Girardin";
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
|
|
// with this, we remove child, and childchild, even though it is not finished
|
|
|
|
|
// rendering from previous changes
|
|
|
|
|
parent.state.flag = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
|
|
// we now resolve def, so the child rendering is now complete.
|
|
|
|
|
(<any>def).resolve();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div></div>");
|
|
|
|
|
});
|
2019-06-01 21:44:43 +02:00
|
|
|
|
|
|
|
|
test("reuse widget if possible, in some async situation", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<span t-name="ChildA">a<t t-esc="props.val"/></span>
|
|
|
|
|
<span t-name="ChildB">b<t t-esc="props.val"/></span>
|
|
|
|
|
<span t-name="Parent">
|
|
|
|
|
<t t-if="state.flag">
|
2019-06-18 09:24:52 +02:00
|
|
|
<ChildA val="state.valA"/>
|
|
|
|
|
<ChildB val="state.valB"/>
|
2019-06-01 21:44:43 +02:00
|
|
|
</t>
|
|
|
|
|
</span>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
let destroyCount = 0;
|
|
|
|
|
class ChildA extends Widget {
|
2019-06-03 14:28:10 +02:00
|
|
|
destroy() {
|
|
|
|
|
destroyCount++;
|
|
|
|
|
super.destroy();
|
|
|
|
|
}
|
2019-06-01 21:44:43 +02:00
|
|
|
}
|
|
|
|
|
class ChildB extends Widget {
|
2019-06-03 14:28:10 +02:00
|
|
|
willStart(): any {
|
|
|
|
|
return new Promise(function() {});
|
|
|
|
|
}
|
2019-06-01 21:44:43 +02:00
|
|
|
}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ChildA, ChildB };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ valA: 1, valB: 2, flag: false });
|
2019-06-01 21:44:43 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(destroyCount).toBe(0);
|
|
|
|
|
|
|
|
|
|
parent.state.flag = true;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(destroyCount).toBe(0);
|
|
|
|
|
|
|
|
|
|
parent.state.valB = 3;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(destroyCount).toBe(0);
|
|
|
|
|
});
|
2019-06-14 10:17:13 +02:00
|
|
|
|
2019-06-18 09:24:52 +02:00
|
|
|
test("delayed component with t-asyncroot directive", async () => {
|
2019-06-14 10:17:13 +02:00
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<button t-on-click="updateApp">Update App State</button>
|
|
|
|
|
<div class="children">
|
2019-06-18 09:24:52 +02:00
|
|
|
<Child val="state.val"/>
|
|
|
|
|
<AsyncChild t-asyncroot="1" val="state.val"/>
|
2019-06-14 10:17:13 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span t-name="Child"><t t-esc="props.val"/></span>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
let def;
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
|
|
|
|
class AsyncChild extends Child {
|
|
|
|
|
willUpdateProps() {
|
|
|
|
|
return def;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-14 10:17:13 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child, AsyncChild };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 0 });
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
updateApp() {
|
|
|
|
|
this.state.val++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0</span><span>0</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
// click on button to increment Parent counter
|
|
|
|
|
def = makeDeferred();
|
|
|
|
|
fixture.querySelector("button")!.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>0</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
def.resolve();
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>1</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-18 09:24:52 +02:00
|
|
|
test("fast component with t-asyncroot directive", async () => {
|
2019-06-14 10:17:13 +02:00
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<button t-on-click="updateApp">Update App State</button>
|
|
|
|
|
<div class="children">
|
2019-06-18 09:24:52 +02:00
|
|
|
<Child t-asyncroot="1" val="state.val"/>
|
|
|
|
|
<AsyncChild val="state.val"/>
|
2019-06-14 10:17:13 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span t-name="Child"><t t-esc="props.val"/></span>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
let def;
|
2019-09-10 22:37:08 +02:00
|
|
|
class Child extends Widget {}
|
|
|
|
|
class AsyncChild extends Child {
|
|
|
|
|
willUpdateProps() {
|
|
|
|
|
return def;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-14 10:17:13 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child, AsyncChild };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 0 });
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
updateApp() {
|
|
|
|
|
this.state.val++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0</span><span>0</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
// click on button to increment Parent counter
|
|
|
|
|
def = makeDeferred();
|
|
|
|
|
fixture.querySelector("button")!.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>0</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
def.resolve();
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>1</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("t-component with t-asyncroot directive: mixed re-renderings", async () => {
|
2019-06-14 10:17:13 +02:00
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<button t-on-click="updateApp">Update App State</button>
|
|
|
|
|
<div class="children">
|
2019-06-18 09:24:52 +02:00
|
|
|
<Child val="state.val"/>
|
|
|
|
|
<AsyncChild t-asyncroot="1" val="state.val"/>
|
2019-06-14 10:17:13 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span t-name="Child" t-on-click="increment">
|
|
|
|
|
<t t-esc="state.val"/>/<t t-esc="props.val"/>
|
|
|
|
|
</span>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
let def;
|
|
|
|
|
class Child extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 0 });
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
increment() {
|
|
|
|
|
this.state.val++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class AsyncChild extends Child {
|
|
|
|
|
willUpdateProps() {
|
|
|
|
|
return def;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class Parent extends Widget {
|
|
|
|
|
static components = { Child, AsyncChild };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 0 });
|
2019-09-10 22:37:08 +02:00
|
|
|
|
|
|
|
|
updateApp() {
|
|
|
|
|
this.state.val++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0/0</span><span>0/0</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
// click on button to increment Parent counter
|
|
|
|
|
def = makeDeferred();
|
|
|
|
|
fixture.querySelector("button")!.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0/1</span><span>0/0</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
// click on each Child to increment their local counter
|
|
|
|
|
const children = parent.el!.querySelectorAll("span");
|
|
|
|
|
children[0]!.click();
|
|
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1/1</span><span>0/0</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
children[1]!.click();
|
|
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1/1</span><span>1/0</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
|
|
|
|
|
// finalize first re-rendering (coming from the props update)
|
|
|
|
|
def.resolve();
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1/1</span><span>1/1</span>");
|
2019-06-14 10:17:13 +02:00
|
|
|
});
|
2019-07-30 08:47:31 +02:00
|
|
|
|
|
|
|
|
test("rendering component again in next microtick", async () => {
|
|
|
|
|
class Child extends Widget {}
|
|
|
|
|
|
|
|
|
|
class App extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-07-30 08:47:31 +02:00
|
|
|
|
|
|
|
|
async onClick() {
|
|
|
|
|
(env as any).flag = true;
|
|
|
|
|
this.render();
|
|
|
|
|
await Promise.resolve();
|
|
|
|
|
this.render();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Child">Child</div>
|
|
|
|
|
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<button t-on-click="onClick">Click</button>
|
|
|
|
|
<t t-if="env.flag"><Child/></t>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><button>Click</button></div>");
|
|
|
|
|
fixture.querySelector("button")!.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><button>Click</button><div>Child</div></div>");
|
|
|
|
|
});
|
2019-02-07 12:46:30 +01:00
|
|
|
});
|
2019-03-19 11:48:30 +01:00
|
|
|
|
2019-04-16 22:59:54 +02:00
|
|
|
describe("widget and observable state", () => {
|
|
|
|
|
test("widget is rerendered when its state is changed", async () => {
|
2019-05-15 11:15:08 +02:00
|
|
|
env.qweb.addTemplate("TestWidget", `<div><t t-esc="state.drink"/></div>`);
|
2019-04-16 22:59:54 +02:00
|
|
|
class TestWidget extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ drink: "water" });
|
2019-04-16 22:59:54 +02:00
|
|
|
}
|
|
|
|
|
const widget = new TestWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>water</div>");
|
|
|
|
|
widget.state.drink = "beer";
|
|
|
|
|
|
|
|
|
|
// 2 microtask ticks: one for observer, one for rendering
|
|
|
|
|
await nextMicroTick();
|
|
|
|
|
await nextMicroTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>beer</div>");
|
|
|
|
|
});
|
2019-04-17 16:52:39 +02:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
test("subcomponents cannot change observable state received from parent", async () => {
|
2019-07-09 14:33:56 +02:00
|
|
|
const consoleError = console.error;
|
|
|
|
|
console.error = jest.fn();
|
2019-06-22 14:44:09 +02:00
|
|
|
env.qweb.addTemplate("Parent", `<div><Child obj="state.obj"/></div>`);
|
2019-04-17 16:52:39 +02:00
|
|
|
class Child extends Widget {
|
|
|
|
|
constructor(parent, props) {
|
|
|
|
|
super(parent, props);
|
2019-06-03 10:10:41 +02:00
|
|
|
props.obj.coffee = 2;
|
2019-04-17 16:52:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 22:37:08 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ obj: { coffee: 1 } });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
|
|
|
|
}
|
2019-04-17 16:52:39 +02:00
|
|
|
const parent = new Parent(env);
|
2019-07-09 14:33:56 +02:00
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(console.error).toBeCalledTimes(1);
|
|
|
|
|
expect((<any>console.error).mock.calls[0][0].message).toMatch(
|
|
|
|
|
'Observed state cannot be changed here! (key: "coffee", val: "2")'
|
|
|
|
|
);
|
|
|
|
|
console.error = consoleError;
|
2019-04-17 16:52:39 +02:00
|
|
|
});
|
2019-04-16 22:59:54 +02:00
|
|
|
});
|
2019-05-10 15:03:54 +02:00
|
|
|
|
2019-05-14 23:34:28 +02:00
|
|
|
describe("can deduce template from name", () => {
|
|
|
|
|
test("can find template if name of component", async () => {
|
|
|
|
|
class ABC extends Widget {}
|
|
|
|
|
env.qweb.addTemplate("ABC", "<span>Orval</span>");
|
|
|
|
|
const abc = new ABC(env);
|
|
|
|
|
await abc.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can find template of parent component", async () => {
|
|
|
|
|
class ABC extends Widget {}
|
|
|
|
|
class DEF extends ABC {}
|
|
|
|
|
env.qweb.addTemplate("ABC", "<span>Orval</span>");
|
|
|
|
|
const def = new DEF(env);
|
|
|
|
|
await def.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can find template of parent component, defined by template key", async () => {
|
|
|
|
|
class ABC extends Widget {
|
2019-09-12 09:48:17 +02:00
|
|
|
static template = "Achel";
|
2019-05-14 23:34:28 +02:00
|
|
|
}
|
|
|
|
|
class DEF extends ABC {}
|
|
|
|
|
env.qweb.addTemplate("Achel", "<span>Orval</span>");
|
|
|
|
|
const def = new DEF(env);
|
|
|
|
|
await def.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("templates are found in proper qweb instance", async () => {
|
2019-05-17 15:52:16 +02:00
|
|
|
const env2 = makeTestEnv();
|
2019-05-14 23:34:28 +02:00
|
|
|
env.qweb.addTemplate("ABC", "<span>Rochefort 8</span>");
|
|
|
|
|
env2.qweb.addTemplate("ABC", "<span>Rochefort 10</span>");
|
|
|
|
|
class ABC extends Widget {}
|
|
|
|
|
const abc = new ABC(env);
|
|
|
|
|
await abc.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>Rochefort 8</span>");
|
|
|
|
|
abc.destroy();
|
|
|
|
|
const abc2 = new ABC(env2);
|
|
|
|
|
await abc2.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>Rochefort 10</span>");
|
|
|
|
|
});
|
|
|
|
|
});
|
2019-06-11 15:02:10 +02:00
|
|
|
|
|
|
|
|
describe("t-slot directive", () => {
|
|
|
|
|
test("can define and call slots", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
2019-06-18 09:24:52 +02:00
|
|
|
<Dialog>
|
2019-06-11 15:02:10 +02:00
|
|
|
<t t-set="header"><span>header</span></t>
|
|
|
|
|
<t t-set="footer"><span>footer</span></t>
|
2019-06-18 09:24:52 +02:00
|
|
|
</Dialog>
|
2019-06-11 15:02:10 +02:00
|
|
|
</div>
|
|
|
|
|
<div t-name="Dialog">
|
|
|
|
|
<div><t t-slot="header"/></div>
|
|
|
|
|
<div><t t-slot="footer"/></div>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Dialog extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Dialog };
|
2019-06-11 15:02:10 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
"<div><div><div><span>header</span></div><div><span>footer</span></div></div></div>"
|
|
|
|
|
);
|
|
|
|
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
|
|
|
|
expect(env.qweb.templates.Dialog.fn.toString()).toMatchSnapshot();
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_header"].toString()).toMatchSnapshot();
|
|
|
|
|
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
2019-06-11 15:02:10 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("slots are rendered with proper context", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<span class="counter"><t t-esc="state.val"/></span>
|
2019-06-18 09:24:52 +02:00
|
|
|
<Dialog>
|
2019-06-11 15:02:10 +02:00
|
|
|
<t t-set="footer"><button t-on-click="doSomething">do something</button></t>
|
2019-06-18 09:24:52 +02:00
|
|
|
</Dialog>
|
2019-06-11 15:02:10 +02:00
|
|
|
</div>
|
|
|
|
|
<span t-name="Dialog"><t t-slot="footer"/></span>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Dialog extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Dialog };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 0 });
|
2019-06-11 15:02:10 +02:00
|
|
|
doSomething() {
|
|
|
|
|
this.state.val++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
fixture.querySelector("button")!.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
|
|
|
|
);
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
2019-06-11 15:02:10 +02:00
|
|
|
});
|
|
|
|
|
|
2019-07-05 09:45:55 +02:00
|
|
|
test("slots are rendered with proper context, part 2", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<a t-name="Link" t-att-href="props.to">
|
|
|
|
|
<t t-slot="default"/>
|
|
|
|
|
</a>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<u><li t-foreach="state.users" t-as="user" t-key="user.id">
|
|
|
|
|
<Link to="'/user/' + user.id">User <t t-esc="user.name"/></Link>
|
|
|
|
|
</li></u>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Link extends Widget {}
|
|
|
|
|
|
|
|
|
|
class App extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ users: [{ id: 1, name: "Aaron" }, { id: 2, name: "David" }] });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Link };
|
2019-07-05 09:45:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
2019-07-08 16:41:29 +02:00
|
|
|
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User David</a></li></u></div>'
|
2019-07-05 09:45:55 +02:00
|
|
|
);
|
|
|
|
|
expect(env.qweb.templates.Link.fn.toString()).toMatchSnapshot();
|
|
|
|
|
expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot();
|
|
|
|
|
|
|
|
|
|
// test updateprops here
|
|
|
|
|
app.state.users[1].name = "Mathieu";
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
2019-07-08 16:41:29 +02:00
|
|
|
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
|
2019-07-05 09:45:55 +02:00
|
|
|
);
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
2019-07-05 09:45:55 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("slots are rendered with proper context, part 3", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<a t-name="Link" t-att-href="props.to">
|
|
|
|
|
<t t-slot="default"/>
|
|
|
|
|
</a>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<u><li t-foreach="state.users" t-as="user" t-key="user.id" >
|
|
|
|
|
<t t-set="userdescr" t-value="'User ' + user.name"/>
|
|
|
|
|
<Link to="'/user/' + user.id"><t t-esc="userdescr"/></Link>
|
|
|
|
|
</li></u>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Link extends Widget {}
|
|
|
|
|
|
|
|
|
|
class App extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ users: [{ id: 1, name: "Aaron" }, { id: 2, name: "David" }] });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Link };
|
2019-07-05 09:45:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
2019-07-08 16:41:29 +02:00
|
|
|
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User David</a></li></u></div>'
|
2019-07-05 09:45:55 +02:00
|
|
|
);
|
|
|
|
|
expect(env.qweb.templates.Link.fn.toString()).toMatchSnapshot();
|
|
|
|
|
expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot();
|
|
|
|
|
|
|
|
|
|
// test updateprops here
|
|
|
|
|
app.state.users[1].name = "Mathieu";
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
2019-07-08 16:41:29 +02:00
|
|
|
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
|
2019-07-05 09:45:55 +02:00
|
|
|
);
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
2019-07-08 16:41:29 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("slots are rendered with proper context, part 4", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<a t-name="Link" t-att-href="props.to">
|
|
|
|
|
<t t-slot="default"/>
|
|
|
|
|
</a>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<t t-set="userdescr" t-value="'User ' + state.user.name"/>
|
|
|
|
|
<Link to="'/user/' + state.user.id"><t t-esc="userdescr"/></Link>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Link extends Widget {}
|
2019-07-05 09:45:55 +02:00
|
|
|
|
2019-07-08 16:41:29 +02:00
|
|
|
class App extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ user: { id: 1, name: "Aaron" } });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Link };
|
2019-07-08 16:41:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe('<div><a href="/user/1">User Aaron</a></div>');
|
|
|
|
|
|
|
|
|
|
expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot();
|
|
|
|
|
|
|
|
|
|
// test updateprops here
|
|
|
|
|
app.state.user.name = "David";
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe('<div><a href="/user/1">User David</a></div>');
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
2019-07-05 09:45:55 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-11 15:02:10 +02:00
|
|
|
test("refs are properly bound in slots", async () => {
|
2019-09-26 21:56:01 +02:00
|
|
|
class Dialog extends Widget {
|
|
|
|
|
static template = xml`<span><t t-slot="footer"/></span>`;
|
|
|
|
|
}
|
|
|
|
|
class Parent extends Widget {
|
|
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
2019-06-11 15:02:10 +02:00
|
|
|
<span class="counter"><t t-esc="state.val"/></span>
|
2019-06-18 09:24:52 +02:00
|
|
|
<Dialog>
|
2019-06-11 15:02:10 +02:00
|
|
|
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
|
2019-06-18 09:24:52 +02:00
|
|
|
</Dialog>
|
2019-06-11 15:02:10 +02:00
|
|
|
</div>
|
2019-09-26 21:56:01 +02:00
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Dialog };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 0 });
|
2019-09-26 21:56:01 +02:00
|
|
|
button = useRef("myButton");
|
2019-06-11 15:02:10 +02:00
|
|
|
doSomething() {
|
|
|
|
|
this.state.val++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
|
|
|
|
|
);
|
|
|
|
|
|
2019-09-26 21:56:01 +02:00
|
|
|
parent.button.el!.click();
|
2019-06-11 15:02:10 +02:00
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
|
|
|
|
);
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
2019-06-11 15:02:10 +02:00
|
|
|
});
|
2019-06-23 09:03:18 +02:00
|
|
|
|
|
|
|
|
test("content is the default slot", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<Dialog>
|
|
|
|
|
<span>sts rocks</span>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="Dialog"><t t-slot="default"/></div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Dialog extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Dialog };
|
2019-06-23 09:03:18 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div><span>sts rocks</span></div></div>");
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
2019-06-23 09:03:18 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-24 22:59:13 +02:00
|
|
|
test("default slot work with text nodes", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<Dialog>sts rocks</Dialog>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="Dialog"><t t-slot="default"/></div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Dialog extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Dialog };
|
2019-06-24 22:59:13 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
2019-06-25 14:24:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div>sts rocks</div></div>");
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
2019-06-24 22:59:13 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-23 09:03:18 +02:00
|
|
|
test("multiple roots are allowed in a named slot", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<Dialog>
|
|
|
|
|
<t t-set="content">
|
|
|
|
|
<span>sts</span>
|
|
|
|
|
<span>rocks</span>
|
|
|
|
|
</t>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="Dialog"><t t-slot="content"/></div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Dialog extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Dialog };
|
2019-06-23 09:03:18 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_content"].toString()).toMatchSnapshot();
|
2019-06-23 09:03:18 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("multiple roots are allowed in a default slot", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<Dialog>
|
|
|
|
|
<span>sts</span>
|
|
|
|
|
<span>rocks</span>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="Dialog"><t t-slot="default"/></div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Dialog extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Dialog };
|
2019-06-23 09:03:18 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
2019-06-23 09:03:18 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-24 12:57:15 +02:00
|
|
|
test("missing slots are ignored", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<Dialog/>
|
|
|
|
|
</div>
|
|
|
|
|
<span t-name="Dialog">
|
|
|
|
|
<t t-slot="default"/>
|
|
|
|
|
<span>some content</span>
|
|
|
|
|
<t t-slot="footer"/>
|
|
|
|
|
</span>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Dialog extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Dialog };
|
2019-06-24 12:57:15 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><span><span>some content</span></span></div>");
|
2019-06-24 12:57:15 +02:00
|
|
|
});
|
2019-06-24 15:28:01 +02:00
|
|
|
|
|
|
|
|
test("t-debug on a t-set (defining a slot)", async () => {
|
|
|
|
|
const consoleLog = console.log;
|
|
|
|
|
console.log = jest.fn();
|
|
|
|
|
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<Dialog><t t-set="content" t-debug="1">abc</t></Dialog>
|
|
|
|
|
</div>
|
|
|
|
|
<span t-name="Dialog">
|
|
|
|
|
<t t-slot="content"/>
|
|
|
|
|
</span>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Dialog extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Dialog };
|
2019-06-24 15:28:01 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(console.log).toHaveBeenCalledTimes(0);
|
|
|
|
|
console.log = consoleLog;
|
|
|
|
|
});
|
2019-07-09 16:38:30 +02:00
|
|
|
|
|
|
|
|
test("slot preserves properly parented relationship", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="Parent">
|
|
|
|
|
<Child>
|
|
|
|
|
<GrandChild/>
|
|
|
|
|
</Child>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="Child"><t t-slot="default"/></div>
|
|
|
|
|
<div t-name="GrandChild">Grand Child</div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class Child extends Widget {}
|
|
|
|
|
class GrandChild extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child, GrandChild };
|
2019-07-09 16:38:30 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div><div>Grand Child</div></div></div>");
|
|
|
|
|
|
|
|
|
|
const parentChildren = children(parent);
|
|
|
|
|
expect(parentChildren.length).toBe(1);
|
|
|
|
|
expect(parentChildren[0]).toBeInstanceOf(Child);
|
|
|
|
|
|
|
|
|
|
const childrenChildren = children(parentChildren[0]);
|
|
|
|
|
expect(childrenChildren.length).toBe(1);
|
|
|
|
|
expect(childrenChildren[0]).toBeInstanceOf(GrandChild);
|
|
|
|
|
});
|
2019-07-11 14:00:15 +02:00
|
|
|
|
|
|
|
|
test("slot are properly rendered if inner props are changed", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="SomeComponent">
|
|
|
|
|
SC:<t t-esc="props.val"/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div t-name="GenericComponent">
|
|
|
|
|
<t t-slot="default" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<button t-on-click="inc">Inc[<t t-esc="state.val"/>]</button>
|
|
|
|
|
<GenericComponent>
|
|
|
|
|
<SomeComponent val="state.val"/>
|
|
|
|
|
</GenericComponent>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>
|
|
|
|
|
`);
|
|
|
|
|
class SomeComponent extends Widget {}
|
|
|
|
|
class GenericComponent extends Widget {}
|
|
|
|
|
class App extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { GenericComponent, SomeComponent };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 4 });
|
2019-07-11 14:00:15 +02:00
|
|
|
|
|
|
|
|
inc() {
|
|
|
|
|
this.state.val++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><button>Inc[4]</button><div><div> SC:4</div></div></div>");
|
2019-07-09 14:33:56 +02:00
|
|
|
(<any>fixture.querySelector("button")).click();
|
2019-07-11 14:00:15 +02:00
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><button>Inc[5]</button><div><div> SC:5</div></div></div>");
|
|
|
|
|
});
|
2019-09-22 22:24:24 +02:00
|
|
|
|
|
|
|
|
test("slots and wrapper components", async () => {
|
2019-09-24 14:06:53 +02:00
|
|
|
class Link extends Component<any, any> {
|
2019-09-22 22:24:24 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<a href="abc">
|
|
|
|
|
<t t-slot="default"/>
|
|
|
|
|
</a>`;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-24 14:06:53 +02:00
|
|
|
class A extends Component<any, any> {
|
2019-09-22 22:24:24 +02:00
|
|
|
static template = xml`<Link>hey</Link>`;
|
|
|
|
|
static components = { Link: Link };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const a = new A(env);
|
|
|
|
|
await a.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(`<a href="abc">hey</a>`);
|
|
|
|
|
});
|
2019-06-11 15:02:10 +02:00
|
|
|
});
|
2019-06-13 16:27:12 +02:00
|
|
|
|
|
|
|
|
describe("t-model directive", () => {
|
|
|
|
|
test("basic use, on an input", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="SomeComponent">
|
2019-09-24 14:06:53 +02:00
|
|
|
<input t-model="state.text"/>
|
2019-06-13 16:27:12 +02:00
|
|
|
<span><t t-esc="state.text"/></span>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class SomeComponent extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ text: "" });
|
2019-06-13 16:27:12 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
|
|
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
|
|
|
await editInput(input, "test");
|
|
|
|
|
expect(comp.state.text).toBe("test");
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
|
|
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2019-09-24 14:06:53 +02:00
|
|
|
test("basic use, on another key in component", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="SomeComponent">
|
|
|
|
|
<input t-model="some.text"/>
|
|
|
|
|
<span><t t-esc="some.text"/></span>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class SomeComponent extends Widget {
|
|
|
|
|
some = useState({ text: "" });
|
|
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
|
|
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
|
|
|
await editInput(input, "test");
|
|
|
|
|
expect(comp.some.text).toBe("test");
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
|
|
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-13 16:27:12 +02:00
|
|
|
test("on an input, type=checkbox", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="SomeComponent">
|
2019-09-24 14:06:53 +02:00
|
|
|
<input type="checkbox" t-model="state.flag"/>
|
2019-06-13 16:27:12 +02:00
|
|
|
<span>
|
|
|
|
|
<t t-if="state.flag">yes</t>
|
|
|
|
|
<t t-else="1">no</t>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class SomeComponent extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: false });
|
2019-06-13 16:27:12 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe('<div><input type="checkbox"><span>no</span></div>');
|
2019-06-13 16:27:12 +02:00
|
|
|
|
|
|
|
|
let input = fixture.querySelector("input")!;
|
|
|
|
|
input.click();
|
|
|
|
|
await nextTick();
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe('<div><input type="checkbox"><span>yes</span></div>');
|
2019-06-13 16:27:12 +02:00
|
|
|
expect(comp.state.flag).toBe(true);
|
|
|
|
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
|
|
|
|
|
|
|
|
|
input.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(comp.state.flag).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("on an textarea", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="SomeComponent">
|
2019-09-24 14:06:53 +02:00
|
|
|
<textarea t-model="state.text"/>
|
2019-06-13 16:27:12 +02:00
|
|
|
<span><t t-esc="state.text"/></span>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class SomeComponent extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ text: "" });
|
2019-06-13 16:27:12 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><textarea></textarea><span></span></div>");
|
2019-06-13 16:27:12 +02:00
|
|
|
|
|
|
|
|
const textarea = fixture.querySelector("textarea")!;
|
|
|
|
|
await editInput(textarea, "test");
|
|
|
|
|
expect(comp.state.text).toBe("test");
|
2019-06-28 10:32:03 +02:00
|
|
|
expect(fixture.innerHTML).toBe("<div><textarea></textarea><span>test</span></div>");
|
2019-06-13 16:27:12 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("on an input type=radio", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="SomeComponent">
|
2019-09-24 14:06:53 +02:00
|
|
|
<input type="radio" id="one" value="One" t-model="state.choice"/>
|
|
|
|
|
<input type="radio" id="two" value="Two" t-model="state.choice"/>
|
2019-06-13 16:27:12 +02:00
|
|
|
<span>Choice: <t t-esc="state.choice"/></span>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class SomeComponent extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ choice: "" });
|
2019-06-13 16:27:12 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: </span></div>'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const firstInput = fixture.querySelector("input")!;
|
|
|
|
|
firstInput.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(comp.state.choice).toBe("One");
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: One</span></div>'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const secondInput = fixture.querySelectorAll("input")[1];
|
|
|
|
|
secondInput.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(comp.state.choice).toBe("Two");
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: Two</span></div>'
|
|
|
|
|
);
|
|
|
|
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("on a select", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="SomeComponent">
|
2019-09-24 14:06:53 +02:00
|
|
|
<select t-model="state.color">
|
2019-06-13 16:27:12 +02:00
|
|
|
<option value="">Please select one</option>
|
|
|
|
|
<option value="red">Red</option>
|
|
|
|
|
<option value="blue">Blue</option>
|
|
|
|
|
</select>
|
|
|
|
|
<span>Choice: <t t-esc="state.color"/></span>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class SomeComponent extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ color: "" });
|
2019-06-13 16:27:12 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<div><select><option value="">Please select one</option><option value="red">Red</option><option value="blue">Blue</option></select><span>Choice: </span></div>'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const select = fixture.querySelector("select")!;
|
|
|
|
|
select.value = "red";
|
|
|
|
|
select.dispatchEvent(new Event("change"));
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
|
|
expect(comp.state.color).toBe("red");
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<div><select><option value="">Please select one</option><option value="red">Red</option><option value="blue">Blue</option></select><span>Choice: red</span></div>'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2019-09-04 22:43:17 +02:00
|
|
|
test("on a select, initial state", async () => {
|
|
|
|
|
class SomeComponent extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
2019-09-24 14:06:53 +02:00
|
|
|
<select t-model="state.color">
|
2019-09-12 09:45:32 +02:00
|
|
|
<option value="">Please select one</option>
|
|
|
|
|
<option value="red">Red</option>
|
|
|
|
|
<option value="blue">Blue</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ color: "red" });
|
2019-09-04 22:43:17 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
const select = fixture.querySelector("select")!;
|
|
|
|
|
expect(select.value).toBe("red");
|
|
|
|
|
});
|
|
|
|
|
|
2019-09-05 08:45:55 +02:00
|
|
|
test("on a sub state key", async () => {
|
|
|
|
|
class SomeComponent extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
2019-09-24 14:06:53 +02:00
|
|
|
<input t-model="state.something.text"/>
|
2019-09-12 09:45:32 +02:00
|
|
|
<span><t t-esc="state.something.text"/></span>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ something: { text: "" } });
|
2019-09-05 08:45:55 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
|
|
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
|
|
|
await editInput(input, "test");
|
|
|
|
|
expect(comp.state.something.text).toBe("test");
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
2019-09-05 08:45:55 +02:00
|
|
|
});
|
|
|
|
|
|
2019-06-13 16:27:12 +02:00
|
|
|
test(".lazy modifier", async () => {
|
2019-09-12 09:45:32 +02:00
|
|
|
class SomeComponent extends Widget {
|
|
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
2019-09-24 14:06:53 +02:00
|
|
|
<input t-model.lazy="state.text"/>
|
2019-06-13 16:27:12 +02:00
|
|
|
<span><t t-esc="state.text"/></span>
|
|
|
|
|
</div>
|
2019-09-12 09:45:32 +02:00
|
|
|
`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ text: "" });
|
2019-06-13 16:27:12 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
|
|
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
|
|
|
input.value = "test";
|
|
|
|
|
input.dispatchEvent(new Event("input"));
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(comp.state.text).toBe("");
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
|
|
|
|
input.dispatchEvent(new Event("change"));
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(comp.state.text).toBe("test");
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
2019-06-13 16:27:12 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test(".trim modifier", async () => {
|
2019-09-12 09:45:32 +02:00
|
|
|
class SomeComponent extends Widget {
|
|
|
|
|
static template = xml`
|
2019-06-13 16:27:12 +02:00
|
|
|
<div t-name="SomeComponent">
|
2019-09-24 14:06:53 +02:00
|
|
|
<input t-model.trim="state.text"/>
|
2019-09-12 09:45:32 +02:00
|
|
|
<span><t t-esc="state.text"/></span>
|
2019-06-13 16:27:12 +02:00
|
|
|
</div>
|
2019-09-12 09:45:32 +02:00
|
|
|
`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ text: "" });
|
2019-06-13 16:27:12 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
|
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
|
|
|
await editInput(input, " test ");
|
|
|
|
|
expect(comp.state.text).toBe("test");
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test(".number modifier", async () => {
|
|
|
|
|
class SomeComponent extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
2019-09-24 14:06:53 +02:00
|
|
|
<input t-model.number="state.number"/>
|
2019-09-12 09:45:32 +02:00
|
|
|
<span><t t-esc="state.number"/></span>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ number: 0 });
|
2019-06-13 16:27:12 +02:00
|
|
|
}
|
|
|
|
|
const comp = new SomeComponent(env);
|
|
|
|
|
await comp.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span>0</span></div>");
|
|
|
|
|
|
|
|
|
|
const input = fixture.querySelector("input")!;
|
|
|
|
|
await editInput(input, "13");
|
|
|
|
|
expect(comp.state.number).toBe(13);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span>13</span></div>");
|
|
|
|
|
|
|
|
|
|
await editInput(input, "invalid");
|
|
|
|
|
expect(comp.state.number).toBe("invalid");
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><input><span>invalid</span></div>");
|
|
|
|
|
});
|
|
|
|
|
});
|
2019-06-22 14:44:09 +02:00
|
|
|
|
|
|
|
|
describe("environment and plugins", () => {
|
|
|
|
|
// some source of external events
|
|
|
|
|
let bus = new EventBus();
|
|
|
|
|
|
|
|
|
|
// definition of a plugin
|
|
|
|
|
const somePlugin = env => {
|
|
|
|
|
env.someFlag = true;
|
|
|
|
|
bus.on("some-event", null, () => {
|
|
|
|
|
env.someFlag = !env.someFlag;
|
2019-07-30 14:36:51 +02:00
|
|
|
env.qweb.forceUpdate();
|
2019-06-22 14:44:09 +02:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
test("plugin works as expected", async () => {
|
|
|
|
|
somePlugin(env);
|
2019-09-12 09:45:32 +02:00
|
|
|
class App extends Widget {
|
2019-09-19 11:42:00 +02:00
|
|
|
static template = xml`
|
2019-09-12 09:45:32 +02:00
|
|
|
<div>
|
2019-06-22 14:44:09 +02:00
|
|
|
<t t-if="env.someFlag">Red</t>
|
|
|
|
|
<t t-else="1">Blue</t>
|
|
|
|
|
</div>
|
2019-09-12 09:45:32 +02:00
|
|
|
`;
|
|
|
|
|
}
|
2019-06-22 14:44:09 +02:00
|
|
|
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>Red</div>");
|
|
|
|
|
bus.trigger("some-event");
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>Blue</div>");
|
|
|
|
|
});
|
|
|
|
|
});
|
2019-07-09 14:33:56 +02:00
|
|
|
|
|
|
|
|
describe("component error handling (catchError)", () => {
|
|
|
|
|
/**
|
|
|
|
|
* This test suite requires often to wait for 3 ticks. Here is why:
|
|
|
|
|
* - First tick is to let the app render and crash.
|
|
|
|
|
* - When we crash, we call the catchError handler in a setTimeout (because we
|
|
|
|
|
* need to wait for the previous rendering to be completely stopped). So, we
|
|
|
|
|
* need to wait for the second tick.
|
|
|
|
|
* - Then, when the handler changes the state, we need to wait for the interface
|
|
|
|
|
* to be rerendered.
|
|
|
|
|
* */
|
|
|
|
|
|
|
|
|
|
test("can catch an error in a component render function", async () => {
|
|
|
|
|
const consoleError = console.error;
|
|
|
|
|
console.error = jest.fn();
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ErrorBoundary">
|
|
|
|
|
<t t-if="state.error">Error handled</t>
|
|
|
|
|
<t t-else="1"><t t-slot="default" /></t>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="ErrorComponent">hey<t t-esc="props.flag and state.this.will.crash"/>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<ErrorBoundary><ErrorComponent flag="state.flag"/></ErrorBoundary>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
const handler = jest.fn();
|
|
|
|
|
env.qweb.on("error", null, handler);
|
|
|
|
|
class ErrorComponent extends Widget {}
|
|
|
|
|
class ErrorBoundary extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ error: false });
|
2019-07-09 14:33:56 +02:00
|
|
|
|
|
|
|
|
catchError() {
|
|
|
|
|
this.state.error = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class App extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: false });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ErrorBoundary, ErrorComponent };
|
2019-07-09 14:33:56 +02:00
|
|
|
}
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div><div>hey</div></div></div>");
|
|
|
|
|
app.state.flag = true;
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
|
|
|
|
|
|
|
|
|
|
expect(console.error).toBeCalledTimes(1);
|
|
|
|
|
console.error = consoleError;
|
|
|
|
|
expect(handler).toBeCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("no component catching error lead to full app destruction", async () => {
|
|
|
|
|
const handler = jest.fn();
|
|
|
|
|
env.qweb.on("error", null, handler);
|
|
|
|
|
const consoleError = console.error;
|
|
|
|
|
console.error = jest.fn();
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ErrorComponent">hey<t t-esc="props.flag and state.this.will.crash"/>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<ErrorComponent flag="state.flag"/>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class ErrorComponent extends Widget {}
|
|
|
|
|
class App extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: false });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ErrorComponent };
|
2019-07-09 14:33:56 +02:00
|
|
|
}
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>hey</div></div>");
|
|
|
|
|
app.state.flag = true;
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("");
|
|
|
|
|
|
|
|
|
|
expect(console.error).toBeCalledTimes(1);
|
|
|
|
|
console.error = consoleError;
|
|
|
|
|
expect(app.__owl__.isDestroyed).toBe(true);
|
|
|
|
|
expect(handler).toBeCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can catch an error in the initial call of a component render function", async () => {
|
|
|
|
|
const handler = jest.fn();
|
|
|
|
|
env.qweb.on("error", null, handler);
|
|
|
|
|
const consoleError = console.error;
|
|
|
|
|
console.error = jest.fn();
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ErrorBoundary">
|
|
|
|
|
<t t-if="state.error">Error handled</t>
|
|
|
|
|
<t t-else="1"><t t-slot="default" /></t>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="ErrorComponent">hey<t t-esc="state.this.will.crash"/>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<ErrorBoundary><ErrorComponent /></ErrorBoundary>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class ErrorComponent extends Widget {}
|
|
|
|
|
class ErrorBoundary extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ error: false });
|
2019-07-09 14:33:56 +02:00
|
|
|
|
|
|
|
|
catchError() {
|
|
|
|
|
this.state.error = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class App extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ErrorBoundary, ErrorComponent };
|
2019-07-09 14:33:56 +02:00
|
|
|
}
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
|
|
|
|
|
|
|
|
|
|
expect(console.error).toBeCalledTimes(1);
|
|
|
|
|
console.error = consoleError;
|
|
|
|
|
expect(handler).toBeCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can catch an error in the constructor call of a component render function", async () => {
|
|
|
|
|
const handler = jest.fn();
|
|
|
|
|
env.qweb.on("error", null, handler);
|
|
|
|
|
const consoleError = console.error;
|
|
|
|
|
console.error = jest.fn();
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ErrorBoundary">
|
|
|
|
|
<t t-if="state.error">Error handled</t>
|
|
|
|
|
<t t-else="1"><t t-slot="default" /></t>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="ErrorComponent">Some text</div>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<ErrorBoundary><ErrorComponent /></ErrorBoundary>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class ErrorComponent extends Widget {
|
|
|
|
|
constructor(parent) {
|
|
|
|
|
super(parent);
|
|
|
|
|
throw new Error("NOOOOO");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class ErrorBoundary extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ error: false });
|
2019-07-09 14:33:56 +02:00
|
|
|
|
|
|
|
|
catchError() {
|
|
|
|
|
this.state.error = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class App extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ErrorBoundary, ErrorComponent };
|
2019-07-09 14:33:56 +02:00
|
|
|
}
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
|
|
|
|
|
|
|
|
|
|
expect(console.error).toBeCalledTimes(1);
|
|
|
|
|
console.error = consoleError;
|
|
|
|
|
expect(handler).toBeCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can catch an error in the willStart call", async () => {
|
|
|
|
|
const consoleError = console.error;
|
|
|
|
|
console.error = jest.fn();
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ErrorBoundary">
|
|
|
|
|
<t t-if="state.error">Error handled</t>
|
|
|
|
|
<t t-else="1"><t t-slot="default" /></t>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="ErrorComponent">Some text</div>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<ErrorBoundary><ErrorComponent /></ErrorBoundary>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class ErrorComponent extends Widget {
|
|
|
|
|
async willStart() {
|
|
|
|
|
// we wait a little bit to be in a different stack frame
|
|
|
|
|
await nextTick();
|
|
|
|
|
throw new Error("NOOOOO");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class ErrorBoundary extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ error: false });
|
2019-07-09 14:33:56 +02:00
|
|
|
|
|
|
|
|
catchError() {
|
|
|
|
|
this.state.error = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class App extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ErrorBoundary, ErrorComponent };
|
2019-07-09 14:33:56 +02:00
|
|
|
}
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
|
|
|
|
|
|
|
|
|
|
expect(console.error).toBeCalledTimes(1);
|
|
|
|
|
console.error = consoleError;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can catch an error in the mounted call", async () => {
|
|
|
|
|
console.error = jest.fn();
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ErrorBoundary">
|
|
|
|
|
<t t-if="state.error">Error handled</t>
|
|
|
|
|
<t t-else="1"><t t-slot="default" /></t>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="ErrorComponent">Some text</div>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<ErrorBoundary><ErrorComponent /></ErrorBoundary>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class ErrorComponent extends Widget {
|
|
|
|
|
mounted() {
|
|
|
|
|
throw new Error("NOOOOO");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class ErrorBoundary extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ error: false });
|
2019-07-09 14:33:56 +02:00
|
|
|
|
|
|
|
|
catchError() {
|
|
|
|
|
this.state.error = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class App extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ErrorBoundary, ErrorComponent };
|
2019-07-09 14:33:56 +02:00
|
|
|
}
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can catch an error in the willPatch call", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<div t-name="ErrorBoundary">
|
|
|
|
|
<t t-if="state.error">Error handled</t>
|
|
|
|
|
<t t-else="1"><t t-slot="default" /></t>
|
|
|
|
|
</div>
|
|
|
|
|
<div t-name="ErrorComponent"><t t-esc="props.message"/></div>
|
|
|
|
|
<div t-name="App">
|
|
|
|
|
<ErrorBoundary><ErrorComponent message="state.message" /></ErrorBoundary>
|
|
|
|
|
</div>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class ErrorComponent extends Widget {
|
|
|
|
|
willPatch() {
|
|
|
|
|
throw new Error("NOOOOO");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class ErrorBoundary extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ error: false });
|
2019-07-09 14:33:56 +02:00
|
|
|
|
|
|
|
|
catchError() {
|
|
|
|
|
this.state.error = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class App extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ message: "abc" });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { ErrorBoundary, ErrorComponent };
|
2019-07-09 14:33:56 +02:00
|
|
|
}
|
|
|
|
|
const app = new App(env);
|
|
|
|
|
await app.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div><div>abc</div></div></div>");
|
|
|
|
|
app.state.message = "def";
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
|
|
|
|
|
});
|
|
|
|
|
});
|
2019-07-11 16:19:59 +02:00
|
|
|
|
|
|
|
|
describe("top level sub widgets", () => {
|
|
|
|
|
test("basic use", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<t t-name="Parent">
|
|
|
|
|
<Child p="1"/>
|
|
|
|
|
</t>
|
|
|
|
|
<span t-name="Child">child<t t-esc="props.p"/></span>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class Child extends Widget {}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-07-11 16:19:59 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>child1</span>");
|
|
|
|
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("sub widget is interactive", async () => {
|
|
|
|
|
env.qweb.addTemplates(`
|
|
|
|
|
<templates>
|
|
|
|
|
<t t-name="Parent">
|
|
|
|
|
<Child p="1"/>
|
|
|
|
|
</t>
|
|
|
|
|
<span t-name="Child"><button t-on-click="inc">click</button>child<t t-esc="state.val"/></span>
|
|
|
|
|
</templates>`);
|
|
|
|
|
class Child extends Widget {
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 1 });
|
2019-07-30 08:47:31 +02:00
|
|
|
inc() {
|
|
|
|
|
this.state.val++;
|
|
|
|
|
}
|
2019-07-11 16:19:59 +02:00
|
|
|
}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-07-11 16:19:59 +02:00
|
|
|
}
|
|
|
|
|
const parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span><button>click</button>child1</span>");
|
2019-07-30 08:47:31 +02:00
|
|
|
const button = fixture.querySelector("button")!;
|
2019-07-11 16:19:59 +02:00
|
|
|
button.click();
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span><button>click</button>child2</span>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can select a sub widget ", async () => {
|
2019-09-12 09:45:32 +02:00
|
|
|
class Child extends Widget {
|
|
|
|
|
static template = xml`<span>CHILD 1</span>`;
|
|
|
|
|
}
|
|
|
|
|
class OtherChild extends Widget {
|
|
|
|
|
static template = xml`<div>CHILD 2</div>`;
|
|
|
|
|
}
|
2019-07-11 16:19:59 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<t>
|
|
|
|
|
<t t-if="env.flag"><Child /></t>
|
|
|
|
|
<t t-if="!env.flag"><OtherChild /></t>
|
|
|
|
|
</t>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child, OtherChild };
|
2019-07-11 16:19:59 +02:00
|
|
|
}
|
|
|
|
|
(<any>env).flag = true;
|
|
|
|
|
let parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>CHILD 1</span>");
|
|
|
|
|
parent.destroy();
|
|
|
|
|
(<any>env).flag = false;
|
|
|
|
|
parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>CHILD 2</div>");
|
|
|
|
|
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
2019-07-11 16:19:59 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("can select a sub widget, part 2", async () => {
|
2019-09-12 09:45:32 +02:00
|
|
|
class Child extends Widget {
|
|
|
|
|
static template = xml`<span>CHILD 1</span>`;
|
|
|
|
|
}
|
|
|
|
|
class OtherChild extends Widget {
|
|
|
|
|
static template = xml`<div>CHILD 2</div>`;
|
|
|
|
|
}
|
2019-07-11 16:19:59 +02:00
|
|
|
class Parent extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<t>
|
|
|
|
|
<t t-if="state.flag"><Child /></t>
|
|
|
|
|
<t t-if="!state.flag"><OtherChild /></t>
|
|
|
|
|
</t>
|
|
|
|
|
`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: true });
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child, OtherChild };
|
2019-07-11 16:19:59 +02:00
|
|
|
}
|
|
|
|
|
let parent = new Parent(env);
|
|
|
|
|
await parent.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>CHILD 1</span>");
|
|
|
|
|
parent.state.flag = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>CHILD 2</div>");
|
|
|
|
|
});
|
|
|
|
|
});
|
2019-08-22 13:32:53 +02:00
|
|
|
|
|
|
|
|
describe("unmounting and remounting", () => {
|
|
|
|
|
test("widget can be unmounted and remounted", async () => {
|
|
|
|
|
const steps: string[] = [];
|
|
|
|
|
class MyWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<div>Hey</div>`;
|
2019-08-22 13:32:53 +02:00
|
|
|
async willStart() {
|
|
|
|
|
steps.push("willstart");
|
|
|
|
|
}
|
|
|
|
|
mounted() {
|
|
|
|
|
steps.push("mounted");
|
|
|
|
|
}
|
|
|
|
|
willUnmount() {
|
|
|
|
|
steps.push("willunmount");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const w = new MyWidget(env);
|
|
|
|
|
await w.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
|
|
|
|
expect(steps).toEqual(["willstart", "mounted"]);
|
|
|
|
|
|
|
|
|
|
w.unmount();
|
|
|
|
|
expect(fixture.innerHTML).toBe("");
|
|
|
|
|
expect(steps).toEqual(["willstart", "mounted", "willunmount"]);
|
|
|
|
|
|
|
|
|
|
await w.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
|
|
|
|
expect(steps).toEqual(["willstart", "mounted", "willunmount", "mounted"]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("widget can be mounted twice without ill effect", async () => {
|
|
|
|
|
const steps: string[] = [];
|
|
|
|
|
class MyWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`<div>Hey</div>`;
|
2019-08-22 13:32:53 +02:00
|
|
|
async willStart() {
|
|
|
|
|
steps.push("willstart");
|
|
|
|
|
}
|
|
|
|
|
mounted() {
|
|
|
|
|
steps.push("mounted");
|
|
|
|
|
}
|
|
|
|
|
willUnmount() {
|
|
|
|
|
steps.push("willunmount");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const w = new MyWidget(env);
|
|
|
|
|
await w.mount(fixture);
|
|
|
|
|
await w.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
|
|
|
|
expect(steps).toEqual(["willstart", "mounted"]);
|
|
|
|
|
});
|
2019-08-28 10:02:38 +02:00
|
|
|
|
|
|
|
|
test("state changes in willUnmount do not trigger rerender", async () => {
|
|
|
|
|
const steps: string[] = [];
|
|
|
|
|
|
|
|
|
|
class Child extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<span><t t-esc="props.val"/><t t-esc="state.n"/></span>
|
|
|
|
|
`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ n: 2 });
|
2019-09-19 11:42:00 +02:00
|
|
|
__render(f) {
|
2019-08-28 10:02:38 +02:00
|
|
|
steps.push("render");
|
2019-09-19 11:42:00 +02:00
|
|
|
return super.__render(f);
|
2019-08-28 10:02:38 +02:00
|
|
|
}
|
|
|
|
|
willPatch() {
|
|
|
|
|
steps.push("willPatch");
|
|
|
|
|
}
|
|
|
|
|
patched() {
|
|
|
|
|
steps.push("patched");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
willUnmount() {
|
|
|
|
|
steps.push("willUnmount");
|
|
|
|
|
this.state.n = 3;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<Child t-if="state.flag" val="state.val"/>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-10 22:37:08 +02:00
|
|
|
static components = { Child };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 1, flag: true });
|
2019-08-28 10:02:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new Parent(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(steps).toEqual(["render"]);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>12</span></div>");
|
|
|
|
|
widget.state.flag = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
// we make sure here that no call to __render is done
|
|
|
|
|
expect(steps).toEqual(["render", "willUnmount"]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("state changes in willUnmount will be applied on remount", async () => {
|
|
|
|
|
class TestWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div><t t-esc="state.val"/></div>
|
|
|
|
|
`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ val: 1 });
|
2019-08-28 10:02:38 +02:00
|
|
|
willUnmount() {
|
|
|
|
|
this.state.val = 3;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new TestWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>1</div>");
|
|
|
|
|
widget.unmount();
|
|
|
|
|
expect(fixture.innerHTML).toBe("");
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>1</div>");
|
|
|
|
|
widget.unmount();
|
|
|
|
|
await widget.mount(fixture, true);
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>3</div>");
|
|
|
|
|
});
|
2019-08-22 13:32:53 +02:00
|
|
|
});
|
2019-09-11 11:28:41 +02:00
|
|
|
|
|
|
|
|
describe("dynamic root nodes", () => {
|
|
|
|
|
test("template with t-if, part 1", async () => {
|
2019-09-12 09:45:32 +02:00
|
|
|
class TestWidget extends Widget {
|
|
|
|
|
static template = xml`
|
|
|
|
|
<t>
|
|
|
|
|
<t t-if="true"><span>hey</span></t>
|
|
|
|
|
<t t-if="false"><div>abc</div></t>
|
|
|
|
|
</t>
|
|
|
|
|
`;
|
|
|
|
|
}
|
2019-09-11 11:28:41 +02:00
|
|
|
|
|
|
|
|
const widget = new TestWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>hey</span>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("template with t-if, part 2", async () => {
|
2019-09-12 09:45:32 +02:00
|
|
|
class TestWidget extends Widget {
|
|
|
|
|
static template = xml`
|
|
|
|
|
<t>
|
|
|
|
|
<t t-if="false"><span>hey</span></t>
|
|
|
|
|
<t t-if="true"><div>abc</div></t>
|
|
|
|
|
</t>
|
|
|
|
|
`;
|
|
|
|
|
}
|
2019-09-11 11:28:41 +02:00
|
|
|
|
|
|
|
|
const widget = new TestWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>abc</div>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("switching between sub branches dynamically", async () => {
|
|
|
|
|
class TestWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<t>
|
|
|
|
|
<t t-if="state.flag"><span>hey</span></t>
|
|
|
|
|
<t t-if="!state.flag"><div>abc</div></t>
|
|
|
|
|
</t>
|
|
|
|
|
`;
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: true });
|
2019-09-11 11:28:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new TestWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>hey</span>");
|
|
|
|
|
widget.state.flag = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>abc</div>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("switching between sub components dynamically", async () => {
|
2019-09-12 09:45:32 +02:00
|
|
|
class ChildA extends Widget {
|
|
|
|
|
static template = xml`<span>hey</span>`;
|
|
|
|
|
}
|
|
|
|
|
class ChildB extends Widget {
|
|
|
|
|
static template = xml`<div>abc</div>`;
|
|
|
|
|
}
|
2019-09-11 11:28:41 +02:00
|
|
|
class TestWidget extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<t>
|
|
|
|
|
<t t-if="state.flag"><ChildA/></t>
|
|
|
|
|
<t t-if="!state.flag"><ChildB/></t>
|
|
|
|
|
</t>
|
|
|
|
|
`;
|
2019-09-11 14:01:49 +02:00
|
|
|
static components = { ChildA, ChildB };
|
2019-09-24 14:06:53 +02:00
|
|
|
state = useState({ flag: true });
|
2019-09-11 11:28:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new TestWidget(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<span>hey</span>");
|
|
|
|
|
widget.state.flag = false;
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div>abc</div>");
|
|
|
|
|
});
|
2019-09-11 14:01:49 +02:00
|
|
|
});
|
2019-09-11 11:28:41 +02:00
|
|
|
|
2019-09-11 14:01:49 +02:00
|
|
|
describe("dynamic t-props", () => {
|
|
|
|
|
test("basic use", async () => {
|
|
|
|
|
expect.assertions(4);
|
2019-09-12 09:45:32 +02:00
|
|
|
|
2019-09-11 14:01:49 +02:00
|
|
|
class Child extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<span>
|
|
|
|
|
<t t-esc="props.a + props.b"/>
|
|
|
|
|
</span>
|
|
|
|
|
`;
|
2019-09-11 14:01:49 +02:00
|
|
|
constructor(parent, props) {
|
|
|
|
|
super(parent, props);
|
|
|
|
|
expect(props).toEqual({ a: 1, b: 2 });
|
|
|
|
|
expect(props).not.toBe(widget.some.obj);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
class Parent extends Widget {
|
2019-09-12 09:45:32 +02:00
|
|
|
static template = xml`
|
|
|
|
|
<div>
|
|
|
|
|
<Child t-props="some.obj"/>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2019-09-11 14:01:49 +02:00
|
|
|
static components = { Child };
|
|
|
|
|
|
|
|
|
|
some = { obj: { a: 1, b: 2 } };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const widget = new Parent(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe("<div><span>3</span></div>");
|
2019-09-12 09:45:32 +02:00
|
|
|
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
2019-09-11 14:01:49 +02:00
|
|
|
});
|
|
|
|
|
});
|
2019-09-26 15:19:45 +02:00
|
|
|
|
|
|
|
|
describe("support svg components", () => {
|
|
|
|
|
test("add proper namespace to svg", async () => {
|
|
|
|
|
class GComp extends Widget {
|
|
|
|
|
static template = xml`
|
|
|
|
|
<g>
|
|
|
|
|
<circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/>
|
|
|
|
|
</g>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Svg extends Widget {
|
|
|
|
|
static template = xml`
|
|
|
|
|
<svg>
|
|
|
|
|
<GComp/>
|
|
|
|
|
</svg>`;
|
|
|
|
|
static components = { GComp };
|
|
|
|
|
}
|
|
|
|
|
const widget = new Svg(env);
|
|
|
|
|
await widget.mount(fixture);
|
|
|
|
|
|
|
|
|
|
expect(fixture.innerHTML).toBe(
|
|
|
|
|
'<svg><g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"></circle></g></svg>'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|