summaryrefslogtreecommitdiff
path: root/addons/web/static/tests/owl_compatibility_tests.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/tests/owl_compatibility_tests.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/tests/owl_compatibility_tests.js')
-rw-r--r--addons/web/static/tests/owl_compatibility_tests.js1305
1 files changed, 1305 insertions, 0 deletions
diff --git a/addons/web/static/tests/owl_compatibility_tests.js b/addons/web/static/tests/owl_compatibility_tests.js
new file mode 100644
index 00000000..7be3b17b
--- /dev/null
+++ b/addons/web/static/tests/owl_compatibility_tests.js
@@ -0,0 +1,1305 @@
+odoo.define('web.OwlCompatibilityTests', function (require) {
+ "use strict";
+
+ const { ComponentAdapter, ComponentWrapper, WidgetAdapterMixin } = require('web.OwlCompatibility');
+ const testUtils = require('web.test_utils');
+ const Widget = require('web.Widget');
+
+ const makeTestPromise = testUtils.makeTestPromise;
+ const nextTick = testUtils.nextTick;
+ const addMockEnvironmentOwl = testUtils.mock.addMockEnvironmentOwl;
+
+ const { Component, tags, useState } = owl;
+ const { xml } = tags;
+
+ // from Owl internal status enum
+ const ISMOUNTED = 3;
+ const ISDESTROYED = 5;
+
+ const WidgetAdapter = Widget.extend(WidgetAdapterMixin, {
+ destroy() {
+ this._super(...arguments);
+ WidgetAdapterMixin.destroy.call(this, ...arguments);
+ },
+ });
+
+ QUnit.module("Owl Compatibility", function () {
+ QUnit.module("ComponentAdapter");
+
+ QUnit.test("sub widget with no argument", async function (assert) {
+ assert.expect(1);
+
+ const MyWidget = Widget.extend({
+ start: function () {
+ this.$el.text('Hello World!');
+ }
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget with one argument", async function (assert) {
+ assert.expect(1);
+
+ const MyWidget = Widget.extend({
+ init: function (parent, name) {
+ this._super.apply(this, arguments);
+ this.name = name;
+ },
+ start: function () {
+ this.$el.text(`Hello ${this.name}!`);
+ }
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget" name="'World'"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget with several arguments (common Adapter)", async function (assert) {
+ assert.expect(1);
+
+ const MyWidget = Widget.extend({
+ init: function (parent, a1, a2) {
+ this._super.apply(this, arguments);
+ this.a1 = a1;
+ this.a2 = a2;
+ },
+ start: function () {
+ this.$el.text(`${this.a1} ${this.a2}!`);
+ }
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget" a1="'Hello'" a2="'World'"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ try {
+ await parent.mount(target);
+ } catch (e) {
+ assert.strictEqual(e.toString(),
+ `Error: ComponentAdapter has more than 1 argument, 'widgetArgs' must be overriden.`);
+ }
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget with several arguments (specific Adapter)", async function (assert) {
+ assert.expect(1);
+
+ const MyWidget = Widget.extend({
+ init: function (parent, a1, a2) {
+ this._super.apply(this, arguments);
+ this.a1 = a1;
+ this.a2 = a2;
+ },
+ start: function () {
+ this.$el.text(`${this.a1} ${this.a2}!`);
+ }
+ });
+ class MyWidgetAdapter extends ComponentAdapter {
+ get widgetArgs() {
+ return [this.props.a1, this.props.a2];
+ }
+ }
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <MyWidgetAdapter Component="MyWidget" a1="'Hello'" a2="'World'"/>
+ </div>`;
+ Parent.components = { MyWidgetAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget and widgetArgs props", async function (assert) {
+ assert.expect(1);
+
+ const MyWidget = Widget.extend({
+ init: function (parent, a1, a2) {
+ this._super.apply(this, arguments);
+ this.a1 = a1;
+ this.a2 = a2;
+ },
+ start: function () {
+ this.$el.text(`${this.a1} ${this.a2}!`);
+ }
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget" a1="'Hello'" a2="'World'" widgetArgs="['Hello', 'World']"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget is updated when props change", async function (assert) {
+ assert.expect(2);
+
+ const MyWidget = Widget.extend({
+ init: function (parent, name) {
+ this._super.apply(this, arguments);
+ this.name = name;
+ },
+ start: function () {
+ this.render();
+ },
+ render: function () {
+ this.$el.text(`Hello ${this.name}!`);
+ },
+ update: function (name) {
+ this.name = name;
+ },
+ });
+ class MyWidgetAdapter extends ComponentAdapter {
+ updateWidget(nextProps) {
+ return this.widget.update(nextProps.name);
+ }
+ renderWidget() {
+ this.widget.render();
+ }
+ }
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ this.state = useState({
+ name: "World",
+ });
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <MyWidgetAdapter Component="MyWidget" name="state.name"/>
+ </div>`;
+ Parent.components = { MyWidgetAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
+
+ parent.state.name = "GED";
+ await nextTick();
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hello GED!</div>');
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget is updated when props change (async)", async function (assert) {
+ assert.expect(7);
+
+ const prom = makeTestPromise();
+ const MyWidget = Widget.extend({
+ init: function (parent, name) {
+ this._super.apply(this, arguments);
+ this.name = name;
+ },
+ start: function () {
+ this.render();
+ },
+ render: function () {
+ this.$el.text(`Hello ${this.name}!`);
+ assert.step('render');
+ },
+ update: function (name) {
+ assert.step('update');
+ this.name = name;
+ },
+ });
+ class MyWidgetAdapter extends ComponentAdapter {
+ updateWidget(nextProps) {
+ return this.widget.update(nextProps.name);
+ }
+ renderWidget() {
+ this.widget.render();
+ }
+ }
+ class AsyncComponent extends Component {
+ willUpdateProps() {
+ return prom;
+ }
+ }
+ AsyncComponent.template = xml`<div>Hi <t t-esc="props.name"/>!</div>`;
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ this.state = useState({
+ name: "World",
+ });
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <AsyncComponent name="state.name"/>
+ <MyWidgetAdapter Component="MyWidget" name="state.name"/>
+ </div>`;
+ Parent.components = { AsyncComponent, MyWidgetAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hi World!</div><div>Hello World!</div>');
+
+ parent.state.name = "GED";
+ await nextTick();
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hi World!</div><div>Hello World!</div>');
+
+ prom.resolve();
+ await nextTick();
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hi GED!</div><div>Hello GED!</div>');
+
+ assert.verifySteps(['render', 'update', 'render']);
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget methods are correctly called", async function (assert) {
+ assert.expect(8);
+
+ const MyWidget = Widget.extend({
+ on_attach_callback: function () {
+ assert.step('on_attach_callback');
+ },
+ on_detach_callback: function () {
+ assert.step('on_detach_callback');
+ },
+ destroy: function () {
+ assert.step('destroy');
+ this._super.apply(this, arguments);
+ },
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.verifySteps(['on_attach_callback']);
+
+ parent.unmount();
+ await parent.mount(target);
+
+ assert.verifySteps(['on_detach_callback', 'on_attach_callback']);
+
+ parent.destroy();
+
+ assert.verifySteps(['on_detach_callback', 'destroy']);
+ });
+
+ QUnit.test("dynamic sub widget/component", async function (assert) {
+ assert.expect(1);
+
+ const MyWidget = Widget.extend({
+ start: function () {
+ this.$el.text('widget');
+ },
+ });
+ class MyComponent extends Component {}
+ MyComponent.template = xml`<div>component</div>`;
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.Children = [MyWidget, MyComponent];
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter t-foreach="Children" t-as="Child" Component="Child"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div>widget</div><div>component</div>');
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget that triggers events", async function (assert) {
+ assert.expect(5);
+
+ let widget;
+ const MyWidget = Widget.extend({
+ init: function () {
+ this._super.apply(this, arguments);
+ widget = this;
+ },
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ onSomeEvent(ev) {
+ assert.step(ev.detail.value);
+ assert.ok(ev.detail.__targetWidget instanceof MyWidget);
+ }
+ }
+ Parent.template = xml`
+ <div t-on-some-event="onSomeEvent">
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ widget.trigger_up('some-event', { value: 'a' });
+ widget.trigger_up('some_event', { value: 'b' }); // _ are converted to -
+
+ assert.verifySteps(['a', 'b']);
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget that calls _rpc", async function (assert) {
+ assert.expect(3);
+
+ const MyWidget = Widget.extend({
+ willStart: function () {
+ return this._rpc({ route: 'some/route', params: { val: 2 } });
+ },
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+ const cleanUp = await addMockEnvironmentOwl(Parent, {
+ mockRPC: function (route, args) {
+ assert.step(`${route} ${args.val}`);
+ return Promise.resolve();
+ },
+ });
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div></div>');
+ assert.verifySteps(['some/route 2']);
+
+ parent.destroy();
+ cleanUp();
+ });
+
+ QUnit.test("sub widget that calls a service", async function (assert) {
+ assert.expect(1);
+
+ const MyWidget = Widget.extend({
+ start: function () {
+ let result;
+ this.trigger_up('call_service', {
+ service: 'math',
+ method: 'sqrt',
+ args: [9],
+ callback: r => {
+ result = r;
+ },
+ });
+ assert.strictEqual(result, 3);
+ },
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+ Parent.env.services.math = {
+ sqrt: v => Math.sqrt(v),
+ };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget that requests the session", async function (assert) {
+ assert.expect(1);
+
+ const MyWidget = Widget.extend({
+ start: function () {
+ assert.strictEqual(this.getSession().key, 'value');
+ },
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+ const cleanUp = await addMockEnvironmentOwl(Parent, {
+ session: { key: 'value' },
+ });
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ parent.destroy();
+ cleanUp();
+ });
+
+ QUnit.test("sub widget that calls load_views", async function (assert) {
+ assert.expect(4);
+
+ const MyWidget = Widget.extend({
+ willStart: function () {
+ return this.loadViews('some_model', { x: 2 }, [[false, 'list']]);
+ },
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+ const cleanUp = await addMockEnvironmentOwl(Parent, {
+ mockRPC: function (route, args) {
+ assert.strictEqual(route, '/web/dataset/call_kw/some_model');
+ assert.deepEqual(args.kwargs.context, { x: 2 });
+ assert.deepEqual(args.kwargs.views, [[false, 'list']]);
+ return Promise.resolve();
+ },
+ });
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div></div>');
+
+ parent.destroy();
+ cleanUp();
+ });
+
+ QUnit.test("sub widgets in a t-if/t-else", async function (assert) {
+ assert.expect(3);
+
+ const MyWidget1 = Widget.extend({
+ start: function () {
+ this.$el.text('Hi');
+ },
+ });
+ const MyWidget2 = Widget.extend({
+ start: function () {
+ this.$el.text('Hello');
+ },
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget1 = MyWidget1;
+ this.MyWidget2 = MyWidget2;
+ this.state = useState({
+ flag: true,
+ });
+ }
+ }
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter t-if="state.flag" Component="MyWidget1"/>
+ <ComponentAdapter t-else="" Component="MyWidget2"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hi</div>');
+
+ parent.state.flag = false;
+ await nextTick();
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hello</div>');
+
+ parent.state.flag = true;
+ await nextTick();
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hi</div>');
+
+ parent.destroy();
+ });
+
+ QUnit.test("sub widget in a t-if, and events", async function (assert) {
+ assert.expect(6);
+
+ let myWidget;
+ const MyWidget = Widget.extend({
+ start: function () {
+ myWidget = this;
+ this.$el.text('Hi');
+ },
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ this.state = useState({
+ flag: true,
+ });
+ }
+ onSomeEvent(ev) {
+ assert.step(ev.detail.value);
+ }
+ }
+ Parent.template = xml`
+ <div t-on-some-event="onSomeEvent">
+ <ComponentAdapter t-if="state.flag" Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hi</div>');
+ myWidget.trigger_up('some-event', { value: 'a' });
+
+ parent.state.flag = false;
+ await nextTick();
+
+ assert.strictEqual(parent.el.innerHTML, '');
+ myWidget.trigger_up('some-event', { value: 'b' });
+
+ parent.state.flag = true;
+ await nextTick();
+
+ assert.strictEqual(parent.el.innerHTML, '<div>Hi</div>');
+ myWidget.trigger_up('some-event', { value: 'c' });
+
+ assert.verifySteps(['a', 'c']);
+
+ parent.destroy();
+ });
+
+ QUnit.test("adapter keeps same el as sub widget (modify)", async function (assert) {
+ assert.expect(7);
+
+ let myWidget;
+ const MyWidget = Widget.extend({
+ events: {
+ click: "_onClick",
+ },
+ init: function (parent, name) {
+ myWidget = this;
+ this._super.apply(this, arguments);
+ this.name = name;
+ },
+ start: function () {
+ this.render();
+ },
+ render: function () {
+ this.$el.text("Click me!");
+ },
+ update: function (name) {
+ this.name = name;
+ },
+ _onClick: function () {
+ assert.step(this.name);
+ },
+ });
+ class MyWidgetAdapter extends ComponentAdapter {
+ updateWidget(nextProps) {
+ return this.widget.update(nextProps.name);
+ }
+ renderWidget() {
+ this.widget.render();
+ }
+ }
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ this.state = useState({
+ name: "GED",
+ });
+ }
+ }
+ Parent.template = xml`
+ <MyWidgetAdapter Component="MyWidget" name="state.name"/>
+ `;
+ Parent.components = { MyWidgetAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el, myWidget.el);
+ await testUtils.dom.click(parent.el);
+
+ parent.state.name = "AAB";
+ await nextTick();
+
+ assert.strictEqual(parent.el, myWidget.el);
+ await testUtils.dom.click(parent.el);
+
+ parent.state.name = "MCM";
+ await nextTick();
+
+ assert.strictEqual(parent.el, myWidget.el);
+ await testUtils.dom.click(parent.el);
+
+ assert.verifySteps(["GED", "AAB", "MCM"]);
+
+ parent.destroy();
+ });
+
+ QUnit.test("adapter keeps same el as sub widget (replace)", async function (assert) {
+ assert.expect(7);
+
+ let myWidget;
+ const MyWidget = Widget.extend({
+ events: {
+ click: "_onClick",
+ },
+ init: function (parent, name) {
+ myWidget = this;
+ this._super.apply(this, arguments);
+ this.name = name;
+ },
+ start: function () {
+ this.render();
+ },
+ render: function () {
+ this._replaceElement("<div>Click me!</div>");
+ },
+ update: function (name) {
+ this.name = name;
+ },
+ _onClick: function () {
+ assert.step(this.name);
+ },
+ });
+ class MyWidgetAdapter extends ComponentAdapter {
+ updateWidget(nextProps) {
+ return this.widget.update(nextProps.name);
+ }
+ renderWidget() {
+ this.widget.render();
+ }
+ }
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ this.state = useState({
+ name: "GED",
+ });
+ }
+ }
+ Parent.template = xml`
+ <MyWidgetAdapter Component="MyWidget" name="state.name"/>
+ `;
+ Parent.components = { MyWidgetAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el, myWidget.el);
+ await testUtils.dom.click(parent.el);
+
+ parent.state.name = "AAB";
+ await nextTick();
+
+ assert.strictEqual(parent.el, myWidget.el);
+ await testUtils.dom.click(parent.el);
+
+ parent.state.name = "MCM";
+ await nextTick();
+
+ assert.strictEqual(parent.el, myWidget.el);
+ await testUtils.dom.click(parent.el);
+
+ assert.verifySteps(["GED", "AAB", "MCM"]);
+
+ parent.destroy();
+ });
+
+ QUnit.module('WidgetAdapterMixin and ComponentWrapper');
+
+ QUnit.test("widget with sub component", async function (assert) {
+ assert.expect(1);
+
+ class MyComponent extends Component {}
+ MyComponent.template = xml`<div>Component</div>`;
+ const MyWidget = WidgetAdapter.extend({
+ start() {
+ const component = new ComponentWrapper(this, MyComponent, {});
+ return component.mount(this.el);
+ }
+ });
+
+ const target = testUtils.prepareTarget();
+ const widget = new MyWidget();
+ await widget.appendTo(target);
+
+ assert.strictEqual(widget.el.innerHTML, '<div>Component</div>');
+
+ widget.destroy();
+ });
+
+ QUnit.test("sub component hooks are correctly called", async function (assert) {
+ assert.expect(14);
+
+ let component;
+ class MyComponent extends Component {
+ constructor(parent) {
+ super(parent);
+ assert.step("init");
+ }
+ async willStart() {
+ assert.step("willStart");
+ }
+ mounted() {
+ assert.step("mounted");
+ }
+ willUnmount() {
+ assert.step("willUnmount");
+ }
+ __destroy() {
+ super.__destroy();
+ assert.step("__destroy");
+ }
+ }
+ MyComponent.template = xml`<div>Component</div>`;
+ const MyWidget = WidgetAdapter.extend({
+ start() {
+ component = new ComponentWrapper(this, MyComponent, {});
+ return component.mount(this.el);
+ }
+ });
+
+ const target = testUtils.prepareTarget();
+ const widget = new MyWidget();
+ await widget.appendTo(target);
+
+ assert.verifySteps(['init', 'willStart', 'mounted']);
+ assert.ok(component.__owl__.status === ISMOUNTED);
+
+ widget.$el.detach();
+ widget.on_detach_callback();
+
+ assert.verifySteps(['willUnmount']);
+ assert.ok(component.__owl__.status !== ISMOUNTED);
+
+ widget.$el.appendTo(target);
+ widget.on_attach_callback();
+
+ assert.verifySteps(['mounted']);
+ assert.ok(component.__owl__.status === ISMOUNTED);
+
+ widget.destroy();
+
+ assert.verifySteps(['willUnmount', '__destroy']);
+ });
+
+ QUnit.test("isMounted with several sub components", async function (assert) {
+ assert.expect(9);
+
+ let c1;
+ let c2;
+ class MyComponent extends Component {}
+ MyComponent.template = xml`<div>Component <t t-esc="props.id"/></div>`;
+ const MyWidget = WidgetAdapter.extend({
+ start() {
+ c1 = new ComponentWrapper(this, MyComponent, {id: 1});
+ c2 = new ComponentWrapper(this, MyComponent, {id: 2});
+ return Promise.all([c1.mount(this.el), c2.mount(this.el)]);
+ }
+ });
+
+ const target = testUtils.prepareTarget();
+ const widget = new MyWidget();
+ await widget.appendTo(target);
+
+ assert.strictEqual(widget.el.innerHTML, '<div>Component 1</div><div>Component 2</div>');
+ assert.ok(c1.__owl__.status === ISMOUNTED);
+ assert.ok(c2.__owl__.status === ISMOUNTED);
+
+ widget.$el.detach();
+ widget.on_detach_callback();
+
+ assert.ok(c1.__owl__.status !== ISMOUNTED);
+ assert.ok(c2.__owl__.status !== ISMOUNTED);
+
+ widget.$el.appendTo(target);
+ widget.on_attach_callback();
+
+ assert.ok(c1.__owl__.status === ISMOUNTED);
+ assert.ok(c2.__owl__.status === ISMOUNTED);
+
+ widget.destroy();
+
+ assert.ok(c1.__owl__.status === ISDESTROYED);
+ assert.ok(c2.__owl__.status === ISDESTROYED);
+ });
+
+ QUnit.test("isMounted with several levels of sub components", async function (assert) {
+ assert.expect(5);
+
+ let child;
+ class MyChildComponent extends Component {
+ constructor() {
+ super(...arguments);
+ child = this;
+ }
+ }
+ MyChildComponent.template = xml`<div>child</div>`;
+ class MyComponent extends Component {}
+ MyComponent.template = xml`<div><MyChildComponent/></div>`;
+ MyComponent.components = { MyChildComponent };
+ const MyWidget = WidgetAdapter.extend({
+ start() {
+ let component = new ComponentWrapper(this, MyComponent, {});
+ return component.mount(this.el);
+ }
+ });
+
+ const target = testUtils.prepareTarget();
+ const widget = new MyWidget();
+ await widget.appendTo(target);
+
+ assert.strictEqual(widget.el.innerHTML, '<div><div>child</div></div>');
+ assert.ok(child.__owl__.status === ISMOUNTED);
+
+ widget.$el.detach();
+ widget.on_detach_callback();
+
+ assert.ok(child.__owl__.status !== ISMOUNTED);
+
+ widget.$el.appendTo(target);
+ widget.on_attach_callback();
+
+ assert.ok(child.__owl__.status === ISMOUNTED);
+
+ widget.destroy();
+
+ assert.ok(child.__owl__.status === ISDESTROYED);
+ });
+
+ QUnit.test("sub component can be updated (in DOM)", async function (assert) {
+ assert.expect(2);
+
+ class MyComponent extends Component {}
+ MyComponent.template = xml`<div>Component <t t-esc="props.val"/></div>`;
+ const MyWidget = WidgetAdapter.extend({
+ start() {
+ this.component = new ComponentWrapper(this, MyComponent, {val: 1});
+ return this.component.mount(this.el);
+ },
+ update() {
+ return this.component.update({val: 2});
+ },
+ });
+
+ const target = testUtils.prepareTarget();
+ const widget = new MyWidget();
+ await widget.appendTo(target);
+
+ assert.strictEqual(widget.el.innerHTML, '<div>Component 1</div>');
+
+ await widget.update();
+
+ assert.strictEqual(widget.el.innerHTML, '<div>Component 2</div>');
+
+ widget.destroy();
+ });
+
+ QUnit.test("sub component can be updated (not in DOM)", async function (assert) {
+ assert.expect(4);
+
+ class MyComponent extends Component {}
+ MyComponent.template = xml`<div>Component <t t-esc="props.val"/></div>`;
+ const MyWidget = WidgetAdapter.extend({
+ start() {
+ this.component = new ComponentWrapper(this, MyComponent, {val: 1});
+ return this.component.mount(this.el);
+ },
+ update() {
+ return this.component.update({val: 2});
+ },
+ });
+
+ const target = testUtils.prepareTarget();
+ const widget = new MyWidget();
+ await widget.appendTo(target);
+
+ assert.strictEqual(widget.el.innerHTML, '<div>Component 1</div>');
+
+ widget.$el.detach();
+ widget.on_detach_callback();
+
+ assert.ok(widget.component.__owl__.status !== ISMOUNTED);
+
+ await widget.update();
+
+ widget.$el.appendTo(target);
+ widget.on_attach_callback();
+
+ assert.ok(widget.component.__owl__.status === ISMOUNTED);
+ assert.strictEqual(widget.el.innerHTML, '<div>Component 2</div>');
+
+ widget.destroy();
+ });
+
+ QUnit.test("update a destroyed sub component", async function (assert) {
+ assert.expect(1);
+
+ class MyComponent extends Component {}
+ MyComponent.template = xml`<div>Component <t t-esc="props.val"/></div>`;
+ const MyWidget = WidgetAdapter.extend({
+ start() {
+ this.component = new ComponentWrapper(this, MyComponent, {val: 1});
+ return this.component.mount(this.el);
+ },
+ update() {
+ this.component.update({val: 2});
+ },
+ });
+
+ const target = testUtils.prepareTarget();
+ const widget = new MyWidget();
+ await widget.appendTo(target);
+
+ assert.strictEqual(widget.el.innerHTML, '<div>Component 1</div>');
+
+ widget.destroy();
+
+ widget.update(); // should not crash
+ });
+
+ QUnit.test("sub component that triggers events", async function (assert) {
+ assert.expect(3);
+
+ class WidgetComponent extends Component {}
+ WidgetComponent.template = xml`<div>Component</div>`;
+
+ const MyWidget = WidgetAdapter.extend({
+ custom_events: _.extend({}, Widget.custom_events, {
+ some_event: function (ev) {
+ assert.step(ev.data.value);
+ }
+ }),
+ start() {
+ this.component = new ComponentWrapper(this, WidgetComponent, {});
+ return this.component.mount(this.el);
+ },
+ });
+
+ const target = testUtils.prepareTarget();
+ const widget = new MyWidget();
+ await widget.appendTo(target);
+
+ widget.component.trigger('some_event', { value: 'a' });
+ widget.component.trigger('some-event', { value: 'b' }); // - are converted to _
+
+ assert.verifySteps(['a', 'b']);
+
+ widget.destroy();
+ });
+
+ QUnit.test("change parent of ComponentWrapper", async function (assert) {
+ assert.expect(7);
+
+ let myComponent;
+ let widget1;
+ let widget2;
+ class WidgetComponent extends Component {}
+ WidgetComponent.template = xml`<div>Component</div>`;
+ const MyWidget = WidgetAdapter.extend({
+ custom_events: _.extend({}, Widget.custom_events, {
+ some_event: function (ev) {
+ assert.strictEqual(this, ev.data.widget);
+ assert.step(ev.data.value);
+ }
+ }),
+ });
+ const Parent = Widget.extend({
+ start() {
+ const proms = [];
+ myComponent = new ComponentWrapper(null, WidgetComponent, {});
+ widget1 = new MyWidget();
+ widget2 = new MyWidget();
+ proms.push(myComponent.mount(this.el));
+ proms.push(widget1.appendTo(this.$el));
+ proms.push(widget2.appendTo(this.$el));
+ return Promise.all(proms);
+ }
+ });
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.appendTo(target);
+
+ // 1. No parent
+ myComponent.trigger('some-event', { value: 'a', widget: null });
+
+ assert.verifySteps([]);
+
+ // 2. No parent --> parent (widget1)
+ myComponent.unmount();
+ await myComponent.mount(widget1.el);
+ myComponent.setParent(widget1);
+
+ myComponent.trigger('some-event', { value: 'b', widget: widget1 });
+
+ assert.verifySteps(['b']);
+
+ // 3. Parent (widget1) --> new parent (widget2)
+ myComponent.unmount();
+ await myComponent.mount(widget2.el);
+ myComponent.setParent(widget2);
+
+ myComponent.trigger('some-event', { value: 'c', widget: widget2 });
+
+ assert.verifySteps(['c']);
+
+ parent.destroy();
+ });
+
+ QUnit.module('Several layers of legacy widgets and Owl components');
+
+ QUnit.test("Owl over legacy over Owl", async function (assert) {
+ assert.expect(7);
+
+ let leafComponent;
+ class MyComponent extends Component {}
+ MyComponent.template = xml`<span>Component</span>`;
+ const MyWidget = WidgetAdapter.extend({
+ custom_events: {
+ widget_event: function (ev) {
+ assert.step(`[widget] widget-event ${ev.data.value}`);
+ },
+ both_event: function (ev) {
+ assert.step(`[widget] both-event ${ev.data.value}`);
+ if (ev.data.value === 4) {
+ ev.stopPropagation();
+ }
+ }
+ },
+ start() {
+ leafComponent = new ComponentWrapper(this, MyComponent, {});
+ return leafComponent.mount(this.el);
+ },
+ });
+ class Parent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ onRootEvent(ev) {
+ assert.step(`[root] root-event ${ev.detail.value}`);
+ }
+ onBothEvent(ev) {
+ assert.step(`[root] both-event ${ev.detail.value}`);
+ }
+ }
+ Parent.template = xml`
+ <div t-on-root-event="onRootEvent" t-on-both-event="onBothEvent">
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<div><span>Component</span></div>');
+
+ leafComponent.trigger('root-event', { value: 1 });
+ leafComponent.trigger('widget-event', { value: 2 });
+ leafComponent.trigger('both-event', { value: 3 });
+ leafComponent.trigger('both-event', { value: 4 }); // will be stopped by widget
+
+ assert.verifySteps([
+ '[root] root-event 1',
+ '[widget] widget-event 2',
+ '[widget] both-event 3',
+ '[root] both-event 3',
+ '[widget] both-event 4',
+ ]);
+
+ parent.destroy();
+ });
+
+ QUnit.test("Legacy over Owl over legacy", async function (assert) {
+ assert.expect(7);
+
+ let leafWidget;
+ const MyWidget = Widget.extend({
+ start: function () {
+ leafWidget = this;
+ this.$el.text('Widget');
+ }
+ });
+ class MyComponent extends Component {
+ constructor() {
+ super(...arguments);
+ this.MyWidget = MyWidget;
+ }
+ onComponentEvent(ev) {
+ assert.step(`[component] component-event ${ev.detail.value}`);
+ }
+ onBothEvent(ev) {
+ assert.step(`[component] both-event ${ev.detail.value}`);
+ if (ev.detail.value === 4) {
+ ev.stopPropagation();
+ }
+ }
+ }
+ MyComponent.template = xml`
+ <span t-on-component-event="onComponentEvent" t-on-both-event="onBothEvent">
+ <ComponentAdapter Component="MyWidget"/>
+ </span>`;
+ MyComponent.components = { ComponentAdapter };
+ const Parent = WidgetAdapter.extend({
+ custom_events: {
+ root_event: function (ev) {
+ assert.step(`[root] root-event ${ev.data.value}`);
+ },
+ both_event: function (ev) {
+ assert.step(`[root] both-event ${ev.data.value}`);
+ },
+ },
+ start() {
+ const component = new ComponentWrapper(this, MyComponent, {});
+ return component.mount(this.el);
+ }
+ });
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.appendTo(target);
+
+ assert.strictEqual(parent.el.innerHTML, '<span><div>Widget</div></span>');
+
+ leafWidget.trigger_up('root-event', { value: 1 });
+ leafWidget.trigger_up('component-event', { value: 2 });
+ leafWidget.trigger_up('both-event', { value: 3 });
+ leafWidget.trigger_up('both-event', { value: 4 }); // will be stopped by component
+
+ assert.verifySteps([
+ '[root] root-event 1',
+ '[component] component-event 2',
+ '[component] both-event 3',
+ '[root] both-event 3',
+ '[component] both-event 4',
+ ]);
+
+ parent.destroy();
+ });
+ });
+});