odoo.define('web.OwlCompatibilityTests', function (require) { "use strict"; const fieldRegistry = require('web.field_registry'); const widgetRegistry = require('web.widgetRegistry'); const FormView = require('web.FormView'); const { ComponentAdapter, ComponentWrapper, WidgetAdapterMixin, standaloneAdapter, } = require('web.OwlCompatibility'); const testUtils = require('web.test_utils'); const Widget = require('web.Widget'); const Dialog = require("web.Dialog"); const { registry } = require("@web/core/registry"); const { LegacyComponent } = require("@web/legacy/legacy_component"); const { mapLegacyEnvToWowlEnv, useWowlService } = require("@web/legacy/utils"); const { legacyServiceProvider } = require("@web/legacy/legacy_service_provider"); const { click } = require("@web/../tests/helpers/utils"); const makeTestEnvironment = require("web.test_env"); const { makeTestEnv } = require("@web/../tests/helpers/mock_env"); const { getFixture, mount, useLogLifeCycle, destroy } = require("@web/../tests/helpers/utils"); const makeTestPromise = testUtils.makeTestPromise; const nextTick = testUtils.nextTick; const addMockEnvironmentOwl = testUtils.mock.addMockEnvironmentOwl; const { Component, EventBus, onError, onMounted, onWillDestroy, onWillStart, onWillUnmount, onWillUpdateProps, useState, xml, } = owl; 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.strictEqual(parent.el.innerHTML, '
Hello World!
'); }); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.strictEqual(parent.el.innerHTML, '
Hello World!
'); }); 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 LegacyComponent { setup() { this.MyWidget = MyWidget; this.error = false; onError((e) => { assert.strictEqual( e.toString(), // eslint-disable-next-line no-useless-escape `Error: The following error occurred in onWillStart: \"ComponentAdapter has more than 1 argument, 'widgetArgs' must be overriden.\"` ); this.error = true; this.render(); }); } } Parent.template = xml`
Error
`; Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); await mount(Parent, target); }); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } } Parent.template = xml`
`; Parent.components = { MyWidgetAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.strictEqual(parent.el.innerHTML, '
Hello World!
'); }); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.strictEqual(parent.el.innerHTML, '
Hello World!
'); }); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; this.state = useState({ name: "World", }); } } Parent.template = xml`
`; Parent.components = { MyWidgetAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.strictEqual(parent.el.innerHTML, '
Hello World!
'); parent.state.name = "GED"; await nextTick(); assert.strictEqual(parent.el.innerHTML, '
Hello GED!
'); }); 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 LegacyComponent { setup() { onWillUpdateProps(() => prom); } } AsyncComponent.template = xml`
Hi !
`; class Parent extends LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; this.state = useState({ name: "World", }); } } Parent.template = xml`
`; Parent.components = { AsyncComponent, MyWidgetAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.strictEqual(parent.el.innerHTML, '
Hi World!
Hello World!
'); parent.state.name = "GED"; await nextTick(); assert.strictEqual(parent.el.innerHTML, '
Hi World!
Hello World!
'); prom.resolve(); await nextTick(); assert.strictEqual(parent.el.innerHTML, '
Hi GED!
Hello GED!
'); assert.verifySteps(['render', 'update', 'render']); }); QUnit.test("sub widget methods are correctly called", async function (assert) { assert.expect(6); const MyWidget = Widget.extend({ on_attach_callback: function () { assert.step('on_attach_callback'); }, on_detach_callback: function () { assert.ok(document.body.contains(this.el)); assert.step('on_detach_callback'); }, destroy: function () { assert.step('destroy'); this._super.apply(this, arguments); }, }); class Parent extends LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.verifySteps(['on_attach_callback']); destroy(parent); 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 LegacyComponent {} MyComponent.template = xml`
component
`; class Parent extends LegacyComponent { constructor() { super(...arguments); this.Children = [MyWidget, MyComponent]; } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.strictEqual(parent.el.innerHTML, '
widget
component
'); }); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } onSomeEvent(ev) { assert.step(ev.detail.value); assert.ok(ev.detail.__targetWidget instanceof MyWidget); } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); await mount(Parent, target); widget.trigger_up('some-event', { value: 'a' }); widget.trigger_up('some_event', { value: 'b' }); // _ are converted to - assert.verifySteps(['a', 'b']); }); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } } Parent.template = xml`
`; 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 = await mount(Parent, target, { env: owl.Component.env }); assert.strictEqual(parent.el.innerHTML, '
'); assert.verifySteps(['some/route 2']); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const env = { services: { math: { sqrt: v => Math.sqrt(v), } } }; const target = testUtils.prepareTarget(); await mount(Parent, target, { env }); }); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const cleanUp = await addMockEnvironmentOwl(Parent, { session: { key: 'value' }, }); const target = testUtils.prepareTarget(); await mount(Parent, target, { env: owl.Component.env }); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; } } Parent.template = xml`
`; 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 = await mount(Parent, target, { env: owl.Component.env }); assert.strictEqual(parent.el.innerHTML, '
'); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget1 = MyWidget1; this.MyWidget2 = MyWidget2; this.state = useState({ flag: true, }); } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.strictEqual(parent.el.innerHTML, '
Hi
'); parent.state.flag = false; await nextTick(); assert.strictEqual(parent.el.innerHTML, '
Hello
'); parent.state.flag = true; await nextTick(); assert.strictEqual(parent.el.innerHTML, '
Hi
'); }); 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; this.state = useState({ flag: true, }); } onSomeEvent(ev) { assert.step(ev.detail.value); } } Parent.template = xml`
`; Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.strictEqual(parent.el.innerHTML, '
Hi
'); 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, '
Hi
'); myWidget.trigger_up('some-event', { value: 'c' }); assert.verifySteps(['a', 'c']); }); QUnit.test("adapter contains the el of sub widget as firstChild (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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; this.state = useState({ name: "GED", }); } } Parent.template = xml` `; Parent.components = { MyWidgetAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); const widgetEl = myWidget.el; assert.strictEqual(target.firstChild, widgetEl); await testUtils.dom.click(widgetEl); parent.state.name = "AAB"; await nextTick(); assert.strictEqual(widgetEl, myWidget.el); await testUtils.dom.click(widgetEl); parent.state.name = "MCM"; await nextTick(); assert.strictEqual(widgetEl, myWidget.el); await testUtils.dom.click(widgetEl); assert.verifySteps(["GED", "AAB", "MCM"]); }); QUnit.test("adapter handles a widget that replaces its el", async function (assert) { assert.expect(10); let renderId = 0; const MyWidget = Widget.extend({ events: { click: "_onClick", }, init: function (parent, name) { this._super.apply(this, arguments); this.name = name; }, start: function () { this.render(); }, render: function () { this._replaceElement("
Click me!
"); this.el.classList.add(`widget_id_${renderId++}`); }, 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 LegacyComponent { constructor() { super(...arguments); this.MyWidget = MyWidget; this.state = useState({ name: "GED", }); } } Parent.template = xml` `; Parent.components = { MyWidgetAdapter }; const target = testUtils.prepareTarget(); const parent = await mount(Parent, target); assert.containsOnce(target, ".widget_id_0"); await testUtils.dom.click(target.querySelector(".widget_id_0")); parent.state.name = "AAB"; await nextTick(); assert.containsNone(target, ".widget_id_0"); assert.containsOnce(target, ".widget_id_1"); await testUtils.dom.click(target.querySelector(".widget_id_1")); parent.state.name = "MCM"; await nextTick(); assert.containsNone(target, ".widget_id_0"); assert.containsNone(target, ".widget_id_1"); assert.containsOnce(target, ".widget_id_2"); await testUtils.dom.click(target.querySelector(".widget_id_2")); assert.verifySteps(["GED", "AAB", "MCM"]); }); QUnit.test("standaloneAdapter can trigger in the DOM and execute action", async (assert) => { assert.expect(3) const done = assert.async(); const MyDialog = Dialog.extend({ async start() { const res = await this._super(...arguments); const btn = document.createElement("button"); btn.classList.add("myButton"); btn.addEventListener("click", () => { this.trigger_up("execute_action", { action_data: {}, env: {}, }); }); this.el.appendChild(btn); return res; } }); const dialogOpened = makeTestPromise(); class MyComp extends Component { setup() { onWillDestroy(() => { this.dialog.destroy(); }) } async spawnDialog() { const parent = standaloneAdapter(); this.dialog = new MyDialog(parent); await this.dialog.open(); dialogOpened.resolve(); } } MyComp.template = xml`