summaryrefslogtreecommitdiff
path: root/addons/web/static/tests
diff options
context:
space:
mode:
authorAaron Bohy <aab@odoo.com>2020-01-14 09:03:21 +0000
committerAaron Bohy <aab@odoo.com>2020-01-21 07:17:31 +0000
commit671a5471cd891ac87a2cffc8a0e7ebac1b519725 (patch)
tree058f8ad61c5789d385f97b9502b506679d67f380 /addons/web/static/tests
parent45246a3b96e0b41e4072c2703473185abe28d840 (diff)
[IMP] web: Owl compatibility: add ComponentAdapter
This commit adds the ComponentAdapter, an Owl component meant to be used as universal adapter for Owl components that embed Odoo legacy widgets. This component will be a precious tool during the transition phase of converting our JS codebase from the legacy (widget) framework to Owl.
Diffstat (limited to 'addons/web/static/tests')
-rw-r--r--addons/web/static/tests/helpers/test_env.js21
-rw-r--r--addons/web/static/tests/helpers/test_utils.js1
-rw-r--r--addons/web/static/tests/helpers/test_utils_mock.js76
-rw-r--r--addons/web/static/tests/owl_compatibility_tests.js674
4 files changed, 763 insertions, 9 deletions
diff --git a/addons/web/static/tests/helpers/test_env.js b/addons/web/static/tests/helpers/test_env.js
index 417350ef65b..fabe52ff444 100644
--- a/addons/web/static/tests/helpers/test_env.js
+++ b/addons/web/static/tests/helpers/test_env.js
@@ -1,8 +1,10 @@
odoo.define('web.test_env', async function (require) {
"use strict";
- const { buildQuery } = require("web.rpc");
+ const AbstractStorageService = require('web.AbstractStorageService');
const Bus = require("web.Bus");
+ const RamStorage = require('web.RamStorage');
+ const { buildQuery } = require("web.rpc");
const session = require('web.session');
/**
@@ -45,6 +47,10 @@ odoo.define('web.test_env', async function (require) {
*/
function makeTestEnvironment(env = {}, providedRPC = null) {
const proxiedEnv = _proxify(env, 'env');
+ const RamStorageService = AbstractStorageService.extend({
+ storage: new RamStorage(),
+ });
+ let testEnv = {};
const defaultEnv = {
_t: env._t || (s => s),
_lt: env._lt || (s => s),
@@ -54,11 +60,22 @@ odoo.define('web.test_env', async function (require) {
}, env.device),
qweb: new owl.QWeb({ templates: session.owlTemplates }),
services: Object.assign({
+ ajax: { // for legacy subwidgets
+ rpc() {
+ const prom = testEnv.session.rpc(...arguments);
+ prom.abort = function () {
+ throw new Error("Can't abort this request");
+ };
+ return prom;
+ },
+ },
getCookie() { },
rpc(params, options) {
const query = buildQuery(params);
return testEnv.session.rpc(query.route, query.params, options);
},
+ local_storage: new RamStorageService(),
+ session_storage: new RamStorageService(),
}, env.services),
session: Object.assign({
rpc(route, params, options) {
@@ -69,7 +86,7 @@ odoo.define('web.test_env', async function (require) {
},
}, env.session),
};
- const testEnv = Object.assign(proxiedEnv, defaultEnv);
+ testEnv = Object.assign(proxiedEnv, defaultEnv);
return testEnv;
}
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index 447c39240a7..a9c07973566 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -121,6 +121,7 @@ return Promise.all([
return {
mock: {
addMockEnvironment: testUtilsMock.addMockEnvironment,
+ getMockedOwlEnv: testUtilsMock.getMockedOwlEnv,
intercept: testUtilsMock.intercept,
patch: testUtilsMock.patch,
patchDate: testUtilsMock.patchDate,
diff --git a/addons/web/static/tests/helpers/test_utils_mock.js b/addons/web/static/tests/helpers/test_utils_mock.js
index b4c722b7de1..fe45bbb0e8e 100644
--- a/addons/web/static/tests/helpers/test_utils_mock.js
+++ b/addons/web/static/tests/helpers/test_utils_mock.js
@@ -10,14 +10,15 @@ odoo.define('web.test_utils_mock', function (require) {
* testUtils file.
*/
-var basic_fields = require('web.basic_fields');
-var config = require('web.config');
-var core = require('web.core');
-var dom = require('web.dom');
-var MockServer = require('web.MockServer');
-var session = require('web.session');
+const basic_fields = require('web.basic_fields');
+const config = require('web.config');
+const core = require('web.core');
+const dom = require('web.dom');
+const makeTestEnvironment = require('web.test_env');
+const MockServer = require('web.MockServer');
+const session = require('web.session');
-var DebouncedField = basic_fields.DebouncedField;
+const DebouncedField = basic_fields.DebouncedField;
//------------------------------------------------------------------------------
@@ -360,6 +361,66 @@ function fieldsViewGet(server, params) {
}
/**
+ * Returns a mocked environment to be used by OWL components in tests.
+ *
+ * @param {Object} [params]
+ * @param {Object} [params.actions] the actions given to the mock server
+ * @param {Object} [params.archs] this archs given to the mock server
+ * @param {Object} [params.data] the business data given to the mock server
+ * @param {boolean} [params.debug]
+ * @param {function} [params.mockRPC]
+ * @returns {Object}
+ */
+function getMockedOwlEnv(params) {
+ params = params || {};
+ let Server = MockServer;
+ if (params.mockRPC) {
+ Server = MockServer.extend({ _performRpc: params.mockRPC });
+ }
+ const server = new Server(params.data, {
+ actions: params.actions,
+ archs: params.archs,
+ debug: params.debug,
+ });
+ const env = {
+ dataManager: {
+ load_action: (actionID, context) => {
+ return server.performRpc('/web/action/load', {
+ kwargs: {
+ action_id: actionID,
+ additional_context: context,
+ },
+ });
+ },
+ load_views: (params, options) => {
+ return server.performRpc('/web/dataset/call_kw/' + params.model, {
+ args: [],
+ kwargs: {
+ context: params.context,
+ options: options,
+ views: params.views_descr,
+ },
+ method: 'load_views',
+ model: params.model,
+ }).then(function (views) {
+ return _.mapObject(views, viewParams => {
+ return fieldsViewGet(server, viewParams);
+ });
+ });
+ },
+ load_filters: params => {
+ if (params.debug) {
+ console.log('[mock] load_filters', params);
+ }
+ return Promise.resolve([]);
+ },
+ },
+ session: params.session || {},
+ };
+ return makeTestEnvironment(env, server.performRpc.bind(server));
+}
+
+/**
* intercepts an event bubbling up the widget hierarchy. The event intercepted
* must be a "custom event", i.e. an event generated by the method 'trigger_up'.
*
@@ -573,6 +634,7 @@ function patchSetTimeout() {
return {
addMockEnvironment: addMockEnvironment,
fieldsViewGet: fieldsViewGet,
+ getMockedOwlEnv: getMockedOwlEnv,
intercept: intercept,
patchDate: patchDate,
patch: patch,
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 00000000000..b82eeb04857
--- /dev/null
+++ b/addons/web/static/tests/owl_compatibility_tests.js
@@ -0,0 +1,674 @@
+odoo.define('web.OwlCompatibilityTests', function (require) {
+ "use strict";
+
+ const { ComponentAdapter } = require('web.OwlCompatibility');
+ const testUtils = require('web.test_utils');
+ const Widget = require('web.Widget');
+
+ const getMockedOwlEnv = testUtils.mock.getMockedOwlEnv;
+ const makeTestPromise = testUtils.makeTestPromise;
+ const nextTick = testUtils.nextTick;
+
+ const { Component, tags, useState } = owl;
+ const { xml } = tags;
+
+ 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 {
+ update(nextProps) {
+ return this.widget.update(nextProps.name);
+ }
+ render() {
+ 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 {
+ update(nextProps) {
+ return this.widget.update(nextProps.name);
+ }
+ render() {
+ 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.env = getMockedOwlEnv({
+ mockRPC: function (route, args) {
+ assert.step(`${route} ${args.val}`);
+ return Promise.resolve();
+ },
+ });
+ 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></div>');
+ assert.verifySteps(['some/route 2']);
+
+ parent.destroy();
+ });
+
+ 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;
+ }
+ }
+ const env = getMockedOwlEnv();
+ env.services.math = {
+ sqrt: v => Math.sqrt(v),
+ };
+ Parent.env = env;
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ 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.env = getMockedOwlEnv({
+ session: { key: 'value' },
+ });
+ Parent.template = xml`
+ <div>
+ <ComponentAdapter Component="MyWidget"/>
+ </div>`;
+ Parent.components = { ComponentAdapter };
+
+ const target = testUtils.prepareTarget();
+ const parent = new Parent();
+ await parent.mount(target);
+
+ parent.destroy();
+ });
+
+ 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.env = getMockedOwlEnv({
+ 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();
+ },
+ });
+ 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></div>');
+
+ parent.destroy();
+ });
+
+ 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();
+ });
+ });
+});