Files
owl/tests/component.test.ts
T

1539 lines
44 KiB
TypeScript
Raw Normal View History

2019-03-14 15:53:31 +01:00
import { Component, Env } from "../src/component";
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,
makeTestWEnv,
nextMicroTick,
2019-02-07 12:10:44 +01:00
nextTick,
2019-02-06 13:26:21 +01:00
normalize
2019-03-14 11:50:39 +01: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.
// - env: a WEnv, necessary to create new widgets
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();
env = makeTestWEnv();
env.qweb.addTemplate(
"counter",
`<div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div>`
);
env.qweb.addTemplate("widgetA", `<div>Hello<t t-widget="b"/></div>`);
env.qweb.addTemplate("widgetB", `<div>world</div>`);
env.qweb.addTemplate("default", "<div/>");
2019-01-25 15:28:21 +01:00
});
afterEach(() => {
fixture.remove();
});
2019-01-16 13:19:58 +01:00
2019-03-19 11:48:30 +01:00
class Widget extends Component<any, any, any> {}
2019-02-04 21:09:17 +01:00
function children(w: Widget): Widget[] {
const childrenMap = w.__owl__.children;
return Object.keys(childrenMap).map(id => childrenMap[id]);
}
// Test widgets
2019-02-04 21:09:17 +01:00
class Counter extends Widget {
template = "counter";
state = {
counter: 0
};
inc() {
2019-04-17 10:07:31 +02:00
this.state.counter++;
}
}
2019-02-04 21:09:17 +01:00
class WidgetA extends Widget {
template = "widgetA";
widgets = { b: WidgetB };
}
2019-02-04 21:09:17 +01:00
class WidgetB extends Widget {
template = "widgetB";
}
2019-01-25 15:28:21 +01:00
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
2019-01-16 13:19:58 +01:00
describe("basic widget properties", () => {
2019-03-22 09:28:56 +01:00
test("props and state are properly defined", async () => {
const widget = new Widget(env);
expect(widget.props).toEqual({});
expect(widget.state).toEqual(undefined);
2019-03-22 09:28:56 +01:00
});
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
});
test("can be clicked on and updated", 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-01-16 13:19:58 +01:00
expect(target.innerHTML).toBe("<div>1<button>Inc</button></div>");
});
2019-01-21 14:38:44 +01:00
test("widget style and classname", async () => {
2019-02-04 21:09:17 +01:00
class StyledWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div style="font-weight:bold;" class="some-class">world</div>`;
2019-01-21 14:38:44 +01:00
}
2019-01-25 15:28:21 +01:00
const widget = new StyledWidget(env);
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
2019-01-21 15:54:18 +01:00
`<div style="font-weight:bold;" class="some-class">world</div>`
);
2019-01-21 14:38:44 +01:00
});
2019-04-17 10:07:31 +02:00
test("changing state before first render does not trigger a render", async () => {
let renderCalls = 0;
2019-02-04 21:09:17 +01:00
class TestW extends Widget {
2019-04-17 10:07:31 +02:00
state = { drinks: 1 };
async willStart() {
2019-04-17 10:07:31 +02:00
this.state.drinks++;
}
2019-02-07 12:10:44 +01:00
async _render() {
renderCalls++;
2019-02-07 12:10:44 +01:00
return super._render();
}
}
2019-01-25 15:28:21 +01:00
const widget = new TestW(env);
await widget.mount(fixture);
expect(renderCalls).toBe(1);
});
2019-01-27 15:12:35 +01:00
test("keeps a reference to env", async () => {
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-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
test("willStart hook is called on subwidget", async () => {
let ok = false;
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-widget="child"/></div>`;
widgets = { child: ChildWidget };
}
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
async willStart() {
ok = true;
}
}
2019-01-25 15:28:21 +01:00
const widget = new ParentWidget(env);
await widget.mount(fixture);
expect(ok).toBe(true);
});
2019-01-21 16:26:51 +01:00
test("mounted hook is called on subwidgets, in proper order", async () => {
2019-01-25 11:01:43 +01:00
expect.assertions(4);
2019-01-21 16:26:51 +01:00
let parentMounted = false;
let childMounted = false;
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-widget="child"/></div>`;
2019-01-21 16:26:51 +01:00
widgets = { child: ChildWidget };
2019-01-25 11:01:43 +01:00
mounted() {
2019-01-21 16:26:51 +01:00
expect(childMounted).toBe(false);
parentMounted = true;
}
}
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-01-21 16:26:51 +01:00
expect(parentMounted).toBe(true);
childMounted = true;
}
}
2019-01-25 15:28:21 +01:00
const widget = new ParentWidget(env);
await widget.mount(fixture);
2019-01-25 11:01:43 +01:00
expect(childMounted).toBe(true);
2019-01-21 16:26:51 +01:00
});
test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => {
expect.assertions(3);
let hookCounter = 0;
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-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `
2019-01-25 15:28:21 +01:00
<div>
<t t-if="state.ok">
<t t-widget="child"/>
</t>
<t t-else="1">
<div/>
</t>
2019-02-03 09:42:35 +01:00
</div>`;
state = { ok: false };
widgets = { child: ChildWidget };
}
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
async willStart() {
hookCounter++;
}
mounted() {
expect(hookCounter).toBe(1);
hookCounter++;
}
}
2019-01-25 15:28:21 +01:00
const widget = new ParentWidget(env);
await widget.mount(fixture);
expect(hookCounter).toBe(0); // sub widget not created yet
widget.state.ok = true;
await nextTick();
expect(hookCounter).toBe(2);
});
test("mounted hook is correctly called on subwidgets created in mounted hook", async done => {
// 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 {
mounted() {
const child = new ChildWidget(this);
child.mount(this.el!);
}
}
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
mounted() {
expect(this.el).toBeTruthy();
done();
}
}
2019-01-25 15:28:21 +01:00
const widget = new ParentWidget(env);
await widget.mount(target);
});
2019-01-27 15:40:10 +01:00
test("widgets are unmounted and destroyed if no longer in DOM", async () => {
let steps: string[] = [];
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-01-27 15:40:10 +01:00
state = { ok: true };
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div>
<t t-if="state.ok"><t t-widget="child"/></t>
</div>`;
2019-01-27 15:40:10 +01:00
widgets = { child: ChildWidget };
}
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");
}
}
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-04-09 15:24:15 +02:00
test("widgets are unmounted and destroyed if no longer in DOM, even after updateprops", async () => {
let childUnmounted = false;
class ChildWidget extends Widget {
inlineTemplate = `<span><t t-esc="props.n"/></span>`;
willUnmount() {
childUnmounted = true;
}
increment() {
2019-04-17 10:07:31 +02:00
this.state += 1;
2019-04-09 15:24:15 +02:00
}
}
class ParentWidget extends Widget {
widgets = { ChildWidget };
inlineTemplate = `
<div>
<div t-if="state.flag">
<t t-widget="ChildWidget" t-props="{n: state.n}"/>
</div>
</div>`;
state = { n: 0, flag: true };
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);
});
test("hooks are called in proper order in widget creation/destruction", async () => {
let steps: string[] = [];
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-widget="child"/></div>`;
widgets = { child: ChildWidget };
constructor(parent) {
super(parent);
steps.push("p init");
}
async willStart() {
steps.push("p willstart");
}
mounted() {
steps.push("p mounted");
}
willUnmount() {
steps.push("p willunmount");
}
}
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
constructor(parent) {
super(parent);
steps.push("c init");
}
async willStart() {
steps.push("c willstart");
}
mounted() {
steps.push("c mounted");
}
willUnmount() {
steps.push("c willunmount");
}
}
const widget = new ParentWidget(env);
await widget.mount(fixture);
widget.destroy();
expect(steps).toEqual([
"p init",
"p willstart",
"c init",
"c willstart",
"p mounted",
"c mounted",
"c willunmount",
2019-04-10 09:35:20 +02:00
"p willunmount"
]);
});
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();
class HookWidget extends Widget {
inlineTemplate = '<span><t t-esc="props.n"/></span>';
willUpdateProps(nextProps) {
expect(nextProps.n).toBe(2);
return def;
}
}
const widget = new HookWidget(env, { n: 1 });
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<span>1</span>");
widget.updateProps({ n: 2 });
await nextTick();
expect(fixture.innerHTML).toBe("<span>1</span>");
def.resolve();
await nextTick();
expect(fixture.innerHTML).toBe("<span>2</span>");
});
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 {
state = { a: 1 };
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++;
}
}
const widget = new TestWidget(env, { a: 1 });
await widget.mount(fixture);
expect(n).toBe(0);
await widget.updateProps({ a: 2 });
expect(n).toBe(1);
});
2019-04-09 10:37:53 +02:00
test("patched hook is called after updateEnv", async () => {
2019-04-01 15:42:24 +02:00
let n = 0;
class TestWidget extends Widget {
state = { a: 1 };
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);
await widget.updateEnv({ isMobile: true });
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-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-esc="props.val"/></div>`;
2019-01-28 16:16:22 +01:00
shouldUpdate() {
return shouldUpdate;
}
}
const widget = new TestWidget(env, { val: 42 });
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div>42</div>");
await widget.updateProps({ val: 123 });
expect(fixture.innerHTML).toBe("<div>42</div>");
shouldUpdate = true;
await widget.updateProps({ val: 666 });
expect(fixture.innerHTML).toBe("<div>666</div>");
});
test("sub widget (inside sub node): hooks are correctly called", async () => {
let created = false;
let mounted = false;
class ParentWidget extends Widget {
inlineTemplate = `
<div>
<t t-if="state.flag">
<div><t t-widget="child"/></div>
</t>
</div>`;
widgets = { child: ChildWidget };
state = { flag: false };
}
class ChildWidget extends Widget {
constructor(parent, props) {
super(parent, props);
created = true;
}
mounted() {
mounted = true;
}
}
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();
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[] = [];
class ParentWidget extends Widget {
inlineTemplate = `
<div>
<t t-widget="child" t-props="{v: state.n}"/>
</div>`;
widgets = { child: ChildWidget };
state = { n: 1 };
willPatch() {
steps.push("parent:willPatch");
return 'leffe';
2019-04-05 16:30:35 +02:00
}
patched(snapshot) {
expect(snapshot).toBe('leffe');
2019-04-09 10:37:53 +02:00
steps.push("parent:patched");
2019-04-05 16:30:35 +02:00
}
}
class ChildWidget extends Widget {
willPatch() {
steps.push("child:willPatch");
}
2019-04-09 10:37:53 +02:00
patched() {
steps.push("child:patched");
2019-04-05 16:30:35 +02:00
}
}
const widget = new ParentWidget(env);
await widget.mount(fixture);
expect(steps).toEqual([]);
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([
"child:willPatch",
2019-04-09 10:37:53 +02:00
"child:patched",
2019-04-05 16:30:35 +02:00
"parent:willPatch",
2019-04-09 10:37:53 +02:00
"parent:patched"
2019-04-05 16:30:35 +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);
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
test("destroying a parent also destroys its children", async () => {
const parent = new WidgetA(env);
await parent.mount(fixture);
const child = children(parent)[0];
expect(child.__owl__.isDestroyed).toBe(false);
parent.destroy();
expect(child.__owl__.isDestroyed).toBe(true);
});
test("destroy remove the parent/children link", async () => {
const parent = new WidgetA(env);
await parent.mount(fixture);
const child = children(parent)[0];
expect(child.__owl__.parent).toBe(parent);
expect(children(parent).length).toBe(1);
child.destroy();
expect(child.__owl__.parent).toBe(null);
expect(children(parent).length).toBe(0);
});
test("destroying a widget before willStart is done", async () => {
2019-02-07 12:10:44 +01:00
let def = makeDeferred();
let isRendered = false;
2019-02-04 21:09:17 +01:00
class DelayedWidget extends Widget {
willStart() {
2019-02-07 12:10:44 +01:00
return def;
}
}
expect(fixture.innerHTML).toBe("");
const widget = new DelayedWidget(env);
widget.mount(fixture);
expect(widget.__owl__.isStarted).toBe(false);
expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(false);
widget.destroy();
expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isStarted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(true);
2019-02-07 12:10:44 +01:00
def.resolve();
await nextTick();
expect(widget.__owl__.isStarted).toBe(false);
expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(true);
expect(widget.__owl__.vnode).toBe(null);
expect(fixture.innerHTML).toBe("");
expect(isRendered).toBe(false);
});
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>");
expect(children(widget)[0].__owl__.parent).toBe(widget);
2019-01-20 14:46:36 +01:00
});
2019-01-21 15:46:29 +01:00
test("t-refs on widget are widgets", async () => {
2019-02-04 21:09:17 +01:00
class WidgetC extends Widget {
2019-04-09 10:31:32 +02:00
inlineTemplate = `<div>Hello<t t-ref="'mywidgetb'" t-widget="b"/></div>`;
2019-01-21 15:54:18 +01:00
widgets = { b: WidgetB };
}
2019-01-25 15:28:21 +01:00
const widget = new WidgetC(env);
await widget.mount(fixture);
2019-01-21 15:46:29 +01:00
expect(widget.refs.mywidgetb instanceof WidgetB).toBe(true);
});
test("t-refs are bound at proper timing", async () => {
expect.assertions(2);
class ParentWidget extends Widget {
inlineTemplate = `
<div>
<t t-foreach="state.list" t-ref="'child'" t-widget="Widget"/>
</div>`;
widgets = { Widget };
state = {list: <any>[]};
willPatch() {
expect(this.refs.child).toBeUndefined();
}
patched() {
expect(this.refs.child).not.toBeUndefined();
}
}
const parent = new ParentWidget(env);
await parent.mount(fixture);
parent.state.list.push(1);
await nextTick()
});
test("t-refs are bound at proper timing (2)", async () => {
expect.assertions(10);
class ParentWidget extends Widget {
inlineTemplate = `
<div>
<t t-if="state.child1" t-ref="'child1'" t-widget="Widget"/>
<t t-if="state.child2" t-ref="'child2'" t-widget="Widget"/>
</div>`;
widgets = { Widget };
state = { child1: true, child2: false };
count = 0;
mounted() {
expect(this.refs.child1).toBeDefined();
expect(this.refs.child2).toBeUndefined();
}
willPatch() {
if (this.count === 0) {
expect(this.refs.child1).toBeDefined();
expect(this.refs.child2).toBeUndefined();
}
if (this.count === 1) {
expect(this.refs.child1).toBeDefined();
expect(this.refs.child2).toBeDefined();
}
}
patched() {
if (this.count === 0) {
expect(this.refs.child1).toBeDefined();
expect(this.refs.child2).toBeDefined();
}
if (this.count === 1) {
expect(this.refs.child1).toBeUndefined();
expect(this.refs.child2).toBeDefined();
}
this.count++;
}
}
const parent = new ParentWidget(env);
await parent.mount(fixture);
parent.state.child2 = true;
await nextTick()
parent.state.child1 = false;
await nextTick()
});
test("modifying a sub widget", async () => {
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-widget="Counter"/></div>`;
widgets = { Counter };
}
const widget = new ParentWidget(env);
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div><div>0<button>Inc</button></div></div>"
);
const button = fixture.getElementsByTagName("button")[0];
await button.click();
2019-04-17 10:07:31 +02:00
await nextTick();
expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>"
);
});
test("parent's elm for a children === children's elm, even after rerender", async () => {
const widget = new WidgetA(env);
await widget.mount(fixture);
expect((<any>widget.__owl__.vnode!.children![1]).elm).toBe(
(<any>children(widget)[0].__owl__.vnode).elm
);
await children(widget)[0].render();
expect((<any>widget.__owl__.vnode!.children![1]).elm).toBe(
(<any>children(widget)[0].__owl__.vnode).elm
);
});
test("parent env is propagated to child widgets", async () => {
const widget = new WidgetA(env);
await widget.mount(fixture);
expect(children(widget)[0].env).toBe(env);
});
test("rerendering a widget with a sub widget", async () => {
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-widget="Counter"/></div>`;
widgets = { Counter };
}
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();
expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>"
);
await widget.render();
expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>"
);
});
2019-01-28 15:51:20 +01:00
test("sub widgets are destroyed if no longer in dom, then recreated", async () => {
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-01-28 15:51:20 +01:00
state = { ok: true };
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-if="state.ok"><t t-widget="counter"/></t></div>`;
2019-01-28 15:51:20 +01:00
widgets = { counter: Counter };
}
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-01-28 15:51:20 +01: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-01-28 15:51:20 +01:00
expect(fixture.innerHTML).toBe(
"<div><div>0<button>Inc</button></div></div>"
);
});
2019-01-29 15:19:02 +01:00
test("sub widgets with t-keepalive are not destroyed if no longer in dom", async () => {
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-01-31 10:55:06 +01:00
state = { ok: true };
inlineTemplate = `<div><t t-if="state.ok"><t t-widget="counter" t-keepalive="1"/></t></div>`;
2019-01-31 10:55:06 +01:00
widgets = { counter: Counter };
}
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-01-31 10:55:06 +01:00
expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>"
);
const counter = children(widget)[0];
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>");
expect(counter.__owl__.isMounted).toBe(false);
2019-04-17 10:07:31 +02:00
widget.state.ok = true;
await nextTick();
expect(counter.__owl__.isMounted).toBe(true);
2019-01-31 10:55:06 +01:00
expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>"
);
});
test("sub widgets dom state with t-keepalive is preserved", async () => {
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-01-31 10:55:06 +01:00
state = { ok: true };
inlineTemplate = `<div><t t-if="state.ok"><t t-widget="InputWidget" t-keepalive="1"/></t></div>`;
2019-01-31 10:55:06 +01:00
widgets = { InputWidget };
}
2019-02-04 21:09:17 +01:00
class InputWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = "<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-01-29 15:19:02 +01:00
test("sub widgets rendered in a loop", async () => {
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<span><t t-esc="props.n"/></span>`;
2019-01-29 15:19:02 +01:00
}
2019-02-03 09:42:35 +01:00
2019-02-04 21:09:17 +01:00
class Parent extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `
2019-01-29 15:19:02 +01:00
<div>
<t t-foreach="state.numbers" t-as="number">
<t t-widget="ChildWidget" t-props="{n: number}"/>
</t>
2019-02-03 09:42:35 +01:00
</div>`;
2019-01-29 15:19:02 +01:00
state = {
numbers: [1, 2, 3]
};
widgets = { ChildWidget };
}
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>
`)
);
});
test("sub widgets with some state rendered in a loop", async () => {
let n = 1;
2019-02-04 21:09:17 +01:00
class ChildWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<span><t t-esc="state.n"/></span>`;
constructor(parent) {
super(parent);
this.state = { n };
n++;
}
}
2019-03-13 14:10:15 +01:00
env.qweb.addTemplate(
"parent",
`<div>
<t t-foreach="state.numbers" t-as="number">
2019-04-16 14:35:27 +02:00
<t t-widget="ChildWidget" t-key="number"/>
</t>
2019-03-13 14:10:15 +01:00
</div>`
);
class Parent extends Widget {
template = "parent";
state = {
numbers: [1, 2, 3]
};
widgets = { ChildWidget };
}
const parent = new Parent(env);
await parent.mount(fixture);
2019-04-17 10:07:31 +02:00
parent.state.numbers = [1, 3];
await nextTick();
expect(normalize(fixture.innerHTML)).toBe(
normalize(`
<div>
<span>1</span>
<span>3</span>
</div>
`)
);
2019-03-13 14:10:15 +01:00
expect(env.qweb.templates.parent.toString()).toMatchSnapshot();
});
test("sub widgets between t-ifs", async () => {
// this confuses the patching algorithm...
class ChildWidget extends Widget {
inlineTemplate = `<span>child</span>`;
}
class Parent extends Widget {
inlineTemplate = `<div>
<h1 t-if="state.flag">hey</h1>
<h2 t-else="1">noo</h2>
<span><t t-widget="ChildWidget"/></span>
<t t-if="state.flag"><span>test</span></t>
</div>`;
state = { flag: false };
widgets = { ChildWidget };
}
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();
expect(children(parent)[0]).toBe(child);
expect(child.__owl__.isDestroyed).toBe(false);
expect(normalize(fixture.innerHTML)).toBe(
normalize(`
<div>
<h1>hey</h1>
<span><span>child</span></span>
<span>test</span>
</div>
`)
);
});
2019-01-20 14:46:36 +01:00
});
describe("props evaluation (with t-props directive)", () => {
test("explicit object prop", async () => {
2019-02-04 21:09:17 +01:00
class Parent extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-widget="child" t-props="{value: state.val}"/></div>`;
widgets = { child: Child };
state = { val: 42 };
}
2019-02-04 21:09:17 +01:00
class Child extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<span><t t-esc="state.someval"/></span>`;
state: { someval: number };
constructor(parent: Parent, props: { value: number }) {
super(parent);
this.state = { someval: props.value };
}
}
const widget = new Parent(env);
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
});
test("object prop value", async () => {
2019-02-04 21:09:17 +01:00
class Parent extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-widget="child" t-props="state"/></div>`;
widgets = { child: Child };
state = { val: 42 };
}
2019-02-04 21:09:17 +01:00
class Child extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<span><t t-esc="state.someval"/></span>`;
state: { someval: number };
constructor(parent: Parent, props: { val: number }) {
super(parent);
this.state = { someval: props.val };
}
}
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 () => {
class Child extends Widget {
inlineTemplate = `<span><t t-esc="props.greetings"/></span>`;
}
class Parent extends Widget {
inlineTemplate = `<div><t t-widget="child" t-props="{greetings}"/></div>`;
widgets = { child: Child };
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
test("t-set works with t-props", async () => {
class Parent extends Widget {
inlineTemplate = `
<div>
<t t-set="val" t-value="42"/>
<t t-widget="child" t-props="{val:val}"/>
</div>`;
2019-03-15 11:54:59 +01:00
widgets = { child: Child };
2019-03-15 10:11:50 +01:00
}
class Child extends Widget {
inlineTemplate = `
<span>
<t t-esc="props.val"/>
</span>`;
}
const widget = new Parent(env);
await widget.mount(fixture);
expect(normalize(fixture.innerHTML)).toBe("<div><span>42</span></div>");
});
});
2019-01-30 13:33:37 +01:00
2019-03-04 09:29:27 +01:00
describe("other directives with t-widget", () => {
test("t-on works as expected", async () => {
let n = 0;
2019-02-04 21:09:17 +01:00
class ParentWidget extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-widget="child" t-on-customevent="someMethod"/></div>`;
widgets = { child: Child };
someMethod(arg) {
expect(arg).toBe(43);
n++;
}
}
2019-02-04 21:09:17 +01:00
class Child extends Widget {}
const widget = new ParentWidget(env);
await widget.mount(fixture);
let child = children(widget)[0];
expect(n).toBe(0);
child.trigger("customevent", 43);
expect(n).toBe(1);
child.destroy();
child.trigger("customevent", 43);
expect(n).toBe(1);
});
2019-03-04 09:29:27 +01:00
test("t-if works with t-widget", async () => {
class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="child" t-if="state.flag"/></div>`;
widgets = { child: Child };
state = { flag: true };
}
class Child extends Widget {
inlineTemplate = "<span>hey</span>";
}
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>");
});
test("t-else works with t-widget", async () => {
class ParentWidget extends Widget {
inlineTemplate = `
<div>
<div t-if="state.flag">somediv</div>
<t t-else="1" t-widget="child"/>
</div>`;
widgets = { child: Child };
state = { flag: true };
}
class Child extends Widget {
inlineTemplate = "<span>hey</span>";
}
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();
expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>");
});
test("t-else with empty string works with t-widget", async () => {
class ParentWidget extends Widget {
inlineTemplate = `
<div>
<div t-if="state.flag">somediv</div>
<t t-else="" t-widget="child"/>
</div>`;
widgets = { child: Child };
state = { flag: true };
}
class Child extends Widget {
inlineTemplate = "<span>hey</span>";
}
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();
expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>");
});
});
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
// context with the inLoop variable, which is then used in the t-widget
// directive as a key
2019-02-04 21:09:17 +01:00
class Test extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<div><t t-foreach="2">txt</t><t t-widget="widget"/></div>`;
2019-01-30 13:33:37 +01:00
widgets = { widget: Widget };
}
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
test("updating widget immediately", async () => {
// in this situation, we protect against a bug that occurred: because of the
// interplay between widgets and vnodes, a sub widget vnode was patched
// twice.
2019-02-04 21:09:17 +01:00
class Parent extends Widget {
2019-02-07 12:46:30 +01:00
inlineTemplate = `<div><t t-widget="child" t-props="{flag:state.flag}"/></div>`;
2019-01-30 14:56:47 +01:00
widgets = { child: Child };
state = { flag: false };
}
2019-02-04 21:09:17 +01:00
class Child extends Widget {
2019-02-03 09:42:35 +01:00
inlineTemplate = `<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>");
});
test("snapshotting compiled code", async () => {
env.qweb.addTemplate(
"parent",
2019-04-16 14:35:27 +02:00
`<div><t t-widget="child" t-key="'somestring'" t-props="{flag:state.flag}"/></div>`
);
class Parent extends Widget {
inlineTemplate = "parent";
widgets = { child: Child };
state = { flag: false };
}
class Child extends Widget {
inlineTemplate = `<span>abc<t t-if="props.flag">def</t></span>`;
}
const widget = new Parent(env);
await widget.mount(fixture);
expect(env.qweb.templates.parent.toString()).toMatchSnapshot();
});
test("t-props should not be undefined (snapshotting)", async () => {
env.qweb.addTemplate("parent", `<div><t t-widget="child"/></div>`);
class Parent extends Widget {
inlineTemplate = "parent";
widgets = { child: Child };
}
class Child extends Widget {
inlineTemplate = `<span>abc</span>`;
}
const widget = new Parent(env);
await widget.mount(fixture);
expect(env.qweb.templates.parent.toString()).toMatchSnapshot();
});
2019-01-30 14:56:47 +01:00
});
2019-02-07 12:46:30 +01:00
describe("async rendering", () => {
test("destroying a widget before start is over", async () => {
let def = makeDeferred();
class W extends Widget {
inlineTemplate = "invalid><";
willStart(): Promise<void> {
return def;
}
}
const w = new W(env);
w.mount(fixture);
expect(w.__owl__.isDestroyed).toBe(false);
expect(w.__owl__.isMounted).toBe(false);
expect(w.__owl__.isStarted).toBe(false);
w.destroy();
def.resolve();
await nextTick();
expect(w.__owl__.isDestroyed).toBe(true);
expect(w.__owl__.isMounted).toBe(false);
expect(w.__owl__.isStarted).toBe(false);
});
test("destroying/recreating a subwidget with different props (if start is not over)", async () => {
let def = makeDeferred();
let n = 0;
class W extends Widget {
inlineTemplate = `<div><t t-if="state.val > 1"><t t-widget="Child" t-props="{val: state.val}"/></t></div>`;
widgets = { Child };
state = { val: 1 };
}
class Child extends Widget {
inlineTemplate = `<span>child:<t t-esc="props.val"/></span>`;
constructor(parent, props) {
super(parent, props);
n++;
}
willStart(): Promise<void> {
return def;
}
}
const w = new W(env);
await w.mount(fixture);
expect(n).toBe(0);
w.state.val = 2;
await nextMicroTick();
expect(n).toBe(1);
w.state.val = 3;
await nextMicroTick();
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-02-07 20:24:01 +01:00
test("creating two async widgets, scenario 1", async () => {
let defA = makeDeferred();
let defB = makeDeferred();
2019-02-07 12:46:30 +01:00
2019-02-07 20:24:01 +01:00
class ChildA extends Widget {
inlineTemplate = "<span>a</span>";
willStart(): Promise<void> {
return defA;
}
2019-02-07 12:46:30 +01:00
}
2019-02-07 20:24:01 +01:00
class ChildB extends Widget {
inlineTemplate = "<span>b</span>";
willStart(): Promise<void> {
return defB;
}
2019-02-07 12:46:30 +01:00
}
class Parent extends Widget {
inlineTemplate = `
<div>
<t t-if="state.flagA"><t t-widget="ChildA"/></t>
<t t-if="state.flagB"><t t-widget="ChildB"/></t>
</div>`;
widgets = { ChildA, ChildB };
state = { flagA: false, flagB: false };
}
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
test("creating two async widgets, scenario 2", async () => {
let defA = makeDeferred();
let defB = makeDeferred();
class ChildA extends Widget {
inlineTemplate = `<span>a<t t-esc="props.val"/></span>`;
updateProps(props): Promise<void> {
return defA.then(() => super.updateProps(props));
}
}
class ChildB extends Widget {
inlineTemplate = `<span>b<t t-esc="props.val"/></span>`;
willStart(): Promise<void> {
return defB;
}
}
class Parent extends Widget {
inlineTemplate = `
<div>
<t t-widget="ChildA" t-props="{val:state.valA}"/>
<t t-if="state.flagB"><t t-widget="ChildB" t-props="{val:state.valB}"/></t>
</div>`;
widgets = { ChildA, ChildB };
state = { valA: 1, valB: 2, flagB: false };
}
const parent = new Parent(env);
await parent.mount(fixture);
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();
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();
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe(
"<div><span>a1</span></div>"
);
defB.resolve();
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe(
"<div><span>a1</span></div>"
);
defA.resolve();
await nextTick();
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe(
"<div><span>a2</span><span>b2</span></div>"
);
});
2019-02-07 12:46:30 +01:00
});
2019-03-19 11:48:30 +01:00
describe("updating environment", () => {
test("can update widget env", async () => {
const widget = new Widget(env);
expect(widget.env).toBe(env);
await widget.updateEnv(<any>{ somekey: 4 });
expect(widget.env).toBe(env);
expect((<any>widget).env.somekey).toBe(4);
});
test("updating widget env does not render widget (if not mounted)", async () => {
let n = 0;
class TestWidget extends Widget {
_render() {
n++;
return super._render();
}
}
const widget = new TestWidget(env);
expect(n).toBe(0);
await widget.updateEnv(<any>{ somekey: 4 });
expect(n).toBe(0);
await widget.mount(fixture);
expect(n).toBe(1);
await widget.updateEnv(<any>{ somekey: 5 });
expect(n).toBe(2);
2019-04-09 12:57:34 +02:00
widget.unmount();
expect(n).toBe(2);
await widget.updateEnv(<any>{ somekey: 5 });
expect(n).toBe(2);
});
2019-03-19 11:48:30 +01:00
test("updating child env does not modify parent env", async () => {
class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="child"/></div>`;
widgets = { child: Widget };
}
const parent = new ParentWidget(env);
await parent.mount(fixture);
const child = children(parent)[0];
expect(child.env).toBe(parent.env);
await child.updateEnv(<any>{ somekey: 4 });
expect(child.env).not.toBe(parent.env);
expect((<any>parent).env.somekey).toBeUndefined();
});
test("updating parent env does modify child env", async () => {
class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="child"/></div>`;
widgets = { child: Widget };
}
const parent = new ParentWidget(env);
await parent.mount(fixture);
const child = children(parent)[0];
expect(child.env.somekey).toBeUndefined();
await parent.updateEnv({ somekey: 4 });
expect(child.env.somekey).toBe(4);
});
test("updating parent env does modify child env, part 2", async () => {
class ParentWidget extends Widget {
inlineTemplate = `<div><t t-widget="child"/></div>`;
widgets = { child: Widget };
}
const parent = new ParentWidget(env);
await parent.mount(fixture);
const child = children(parent)[0];
expect(child.env.somekey).toBeUndefined();
await child.updateEnv({ somekey: 4 });
await parent.updateEnv({ someotherkey: 4 });
expect(child.env.someotherkey).toBe(4);
});
test("updating env force a rerender", async () => {
class TestWidget extends Widget {
inlineTemplate = `<div><t t-esc="env.someKey"/></div>`;
}
(<any>env).someKey = "hey";
const widget = new TestWidget(env);
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div>hey</div>");
await widget.updateEnv(<any>{ someKey: "rerendered" });
expect(fixture.innerHTML).toBe("<div>rerendered</div>");
});
test("updating env force rerendering children", async () => {
class Parent extends Widget {
inlineTemplate = `<div><t t-widget="Child"/></div>`;
widgets = { Child };
}
class Child extends Widget {
inlineTemplate = `<div><t t-esc="env.someKey"/></div>`;
}
(<any>env).someKey = "hey";
const widget = new Parent(env);
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div>hey</div></div>");
await widget.updateEnv(<any>{ someKey: "rerendered" });
await nextTick();
expect(fixture.innerHTML).toBe("<div><div>rerendered</div></div>");
});
2019-03-19 11:48:30 +01:00
});
describe("widget and observable state", () => {
test("widget is rerendered when its state is changed", async () => {
class TestWidget extends Widget {
state = { drink: "water" };
inlineTemplate = `<div><t t-esc="state.drink"/></div>`;
}
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>");
});
test("subwidgets cannot change observable state received from parent", async () => {
expect.assertions(1);
class Parent extends Widget {
state = { obj: { coffee: 1 } };
widgets = { Child };
inlineTemplate = `<div><t t-widget="Child" t-props="state.obj"/></div>`;
}
class Child extends Widget {
constructor(parent, props) {
super(parent, props);
props.coffee = 2;
}
}
const parent = new Parent(env);
try {
await parent.mount(fixture);
} catch (e) {
expect(e.message).toBe(
'Observed state cannot be changed here! (key: "coffee", val: "2")'
);
}
});
test("widget can add observed keys to its state", async () => {
class TestWidget extends Widget {
state: any = { a: 1 };
inlineTemplate = `<div><t t-esc="state.a"/><t t-esc="state.b"/></div>`;
}
const widget = new TestWidget(env);
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div>1</div>");
widget.set(widget.state, "b", 2);
await nextTick();
expect(fixture.innerHTML).toBe("<div>12</div>");
});
});