Before this commit: the action menu registry items were rendered regardless of their inner state. The problem is that these item components can choose not to render themselves, leading to a blank dropdown menu if no other item was present. To fix this, the "getProps" function of the registry has been altered to act as validation: the item will not be rendered if the return value is falsy. The function has also been made asynchronous to allow RPC calls. This solution is still flawed and hides a bad design choice which is to register components and not items or item getters; this API is too complex for its use case(s) and adds pointless depth to the DOM. The shape of the registry should be simplified in an improvement in master. closes odoo/odoo#59259 Task: 2325587 X-original-commit: 45c7d881a5d130f6b3222749c8c87b6c6b45e910 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com> Co-authored-by: Julien Mougenot <jum@odoo.com> Co-authored-by: Mohammed Shekha <msh@odoo.com>
252 lines
8.9 KiB
JavaScript
252 lines
8.9 KiB
JavaScript
odoo.define('web.action_menus_tests', function (require) {
|
|
"use strict";
|
|
|
|
const ActionMenus = require('web.ActionMenus');
|
|
const Registry = require('web.Registry');
|
|
const testUtils = require('web.test_utils');
|
|
|
|
const { Component } = owl;
|
|
const cpHelpers = testUtils.controlPanel;
|
|
const { createComponent } = testUtils;
|
|
|
|
QUnit.module('Components', {
|
|
beforeEach() {
|
|
this.action = {
|
|
res_model: 'hobbit',
|
|
};
|
|
this.view = {
|
|
// needed by google_drive module, makes sense to give a view anyway.
|
|
type: 'form',
|
|
};
|
|
this.props = {
|
|
activeIds: [23],
|
|
context: {},
|
|
items: {
|
|
action: [
|
|
{ action: { id: 1 }, name: "What's taters, precious ?", id: 1 },
|
|
],
|
|
print: [
|
|
{ action: { id: 2 }, name: "Po-ta-toes", id: 2 },
|
|
],
|
|
other: [
|
|
{ description: "Boil'em", callback() { } },
|
|
{ description: "Mash'em", callback() { } },
|
|
{ description: "Stick'em in a stew", url: '#stew' },
|
|
],
|
|
},
|
|
};
|
|
// Patch the registry of the action menus
|
|
this.actionMenusRegistry = ActionMenus.registry;
|
|
ActionMenus.registry = new Registry();
|
|
},
|
|
afterEach() {
|
|
ActionMenus.registry = this.actionMenusRegistry;
|
|
},
|
|
}, function () {
|
|
|
|
QUnit.module('ActionMenus');
|
|
|
|
QUnit.test('basic interactions', async function (assert) {
|
|
assert.expect(10);
|
|
|
|
const actionMenus = await createComponent(ActionMenus, {
|
|
env: {
|
|
action: this.action,
|
|
view: this.view,
|
|
},
|
|
props: this.props,
|
|
});
|
|
|
|
const dropdowns = actionMenus.el.getElementsByClassName('o_dropdown');
|
|
assert.strictEqual(dropdowns.length, 2, "ActionMenus should contain 2 menus");
|
|
assert.strictEqual(dropdowns[0].querySelector('.o_dropdown_title').innerText.trim(), "Print");
|
|
assert.strictEqual(dropdowns[1].querySelector('.o_dropdown_title').innerText.trim(), "Action");
|
|
assert.containsNone(actionMenus, '.o_dropdown_menu');
|
|
|
|
await cpHelpers.toggleActionMenu(actionMenus, "Action");
|
|
|
|
assert.containsOnce(actionMenus, '.o_dropdown_menu');
|
|
assert.containsN(actionMenus, '.o_dropdown_menu .o_menu_item', 4);
|
|
const actionsTexts = [...dropdowns[1].querySelectorAll('.o_menu_item')].map(el => el.innerText.trim());
|
|
assert.deepEqual(actionsTexts, [
|
|
"Boil'em",
|
|
"Mash'em",
|
|
"Stick'em in a stew",
|
|
"What's taters, precious ?",
|
|
], "callbacks should appear before actions");
|
|
|
|
await cpHelpers.toggleActionMenu(actionMenus, "Print");
|
|
|
|
assert.containsOnce(actionMenus, '.o_dropdown_menu');
|
|
assert.containsN(actionMenus, '.o_dropdown_menu .o_menu_item', 1);
|
|
|
|
await cpHelpers.toggleActionMenu(actionMenus, "Print");
|
|
|
|
assert.containsNone(actionMenus, '.o_dropdown_menu');
|
|
|
|
actionMenus.destroy();
|
|
});
|
|
|
|
QUnit.test("empty action menus", async function (assert) {
|
|
assert.expect(1);
|
|
|
|
ActionMenus.registry.add("test", { Component, getProps: () => false });
|
|
this.props.items = {};
|
|
|
|
const actionMenus = await createComponent(ActionMenus, {
|
|
env: {
|
|
action: this.action,
|
|
view: this.view,
|
|
},
|
|
props: this.props,
|
|
});
|
|
|
|
assert.containsNone(actionMenus, ".o_cp_action_menus > *");
|
|
|
|
actionMenus.destroy();
|
|
});
|
|
|
|
QUnit.test('execute action', async function (assert) {
|
|
assert.expect(4);
|
|
|
|
const actionMenus = await createComponent(ActionMenus, {
|
|
env: {
|
|
action: this.action,
|
|
view: this.view,
|
|
},
|
|
props: this.props,
|
|
intercepts: {
|
|
'do-action': ev => assert.step('do-action'),
|
|
},
|
|
async mockRPC(route, args) {
|
|
switch (route) {
|
|
case '/web/action/load':
|
|
const expectedContext = {
|
|
active_id: 23,
|
|
active_ids: [23],
|
|
active_model: 'hobbit',
|
|
};
|
|
assert.deepEqual(args.context, expectedContext);
|
|
assert.step('load-action');
|
|
return { context: {}, flags: {} };
|
|
default:
|
|
return this._super(...arguments);
|
|
|
|
}
|
|
},
|
|
});
|
|
|
|
await cpHelpers.toggleActionMenu(actionMenus, "Action");
|
|
await cpHelpers.toggleMenuItem(actionMenus, "What's taters, precious ?");
|
|
|
|
assert.verifySteps(['load-action', 'do-action']);
|
|
|
|
actionMenus.destroy();
|
|
});
|
|
|
|
QUnit.test('execute callback action', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
const callbackPromise = testUtils.makeTestPromise();
|
|
this.props.items.other[0].callback = function (items) {
|
|
assert.strictEqual(items.length, 1);
|
|
assert.strictEqual(items[0].description, "Boil'em");
|
|
callbackPromise.resolve();
|
|
};
|
|
|
|
const actionMenus = await createComponent(ActionMenus, {
|
|
env: {
|
|
action: this.action,
|
|
view: this.view,
|
|
},
|
|
props: this.props,
|
|
async mockRPC(route, args) {
|
|
switch (route) {
|
|
case '/web/action/load':
|
|
throw new Error("No action should be loaded.");
|
|
default:
|
|
return this._super(...arguments);
|
|
}
|
|
},
|
|
});
|
|
|
|
await cpHelpers.toggleActionMenu(actionMenus, "Action");
|
|
await cpHelpers.toggleMenuItem(actionMenus, "Boil'em");
|
|
|
|
await callbackPromise;
|
|
|
|
actionMenus.destroy();
|
|
});
|
|
|
|
QUnit.test('execute print action', async function (assert) {
|
|
assert.expect(4);
|
|
|
|
const actionMenus = await createComponent(ActionMenus, {
|
|
env: {
|
|
action: this.action,
|
|
view: this.view,
|
|
},
|
|
intercepts: {
|
|
'do-action': ev => assert.step('do-action'),
|
|
},
|
|
props: this.props,
|
|
async mockRPC(route, args) {
|
|
switch (route) {
|
|
case '/web/action/load':
|
|
const expectedContext = {
|
|
active_id: 23,
|
|
active_ids: [23],
|
|
active_model: 'hobbit',
|
|
};
|
|
assert.deepEqual(args.context, expectedContext);
|
|
assert.step('load-action');
|
|
return { context: {}, flags: {} };
|
|
default:
|
|
return this._super(...arguments);
|
|
|
|
}
|
|
},
|
|
});
|
|
|
|
await cpHelpers.toggleActionMenu(actionMenus, "Print");
|
|
await cpHelpers.toggleMenuItem(actionMenus, "Po-ta-toes");
|
|
|
|
assert.verifySteps(['load-action', 'do-action']);
|
|
|
|
actionMenus.destroy();
|
|
});
|
|
|
|
QUnit.test('execute url action', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
const actionMenus = await createComponent(ActionMenus, {
|
|
env: {
|
|
action: this.action,
|
|
services: {
|
|
navigate(url) {
|
|
assert.step(url);
|
|
},
|
|
},
|
|
view: this.view,
|
|
},
|
|
props: this.props,
|
|
async mockRPC(route, args) {
|
|
switch (route) {
|
|
case '/web/action/load':
|
|
throw new Error("No action should be loaded.");
|
|
default:
|
|
return this._super(...arguments);
|
|
}
|
|
},
|
|
});
|
|
|
|
await cpHelpers.toggleActionMenu(actionMenus, "Action");
|
|
await cpHelpers.toggleMenuItem(actionMenus, "Stick'em in a stew");
|
|
|
|
assert.verifySteps(['#stew']);
|
|
|
|
actionMenus.destroy();
|
|
});
|
|
});
|
|
});
|