[IMP] web, mail: field widgets' smart action in command palette

This commit adds the smarts actions:
- Assign to... (many2one_avatar and many2many_avatar)
- Assign/unassign to me  (many2one_avatar and many2many_avatar)
- Set priority... (priority)
- Set kanban state... (state_selection)

These smarts actions are commands with hotkeys accessible from
the form views containing the widget to which they are linked.

closes odoo/odoo#75312

Task-id: 2588238
Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
This commit is contained in:
Francois (fge)
2021-08-30 08:37:01 +00:00
committed by Lucas Perais (lpe)
parent 39636ea698
commit 118ddcace0
10 changed files with 650 additions and 21 deletions
+223 -3
View File
@@ -1,8 +1,9 @@
/** @odoo-module **/
import core from 'web.core';
import fieldRegistry from 'web.field_registry';
import { Many2OneAvatar } from 'web.relational_fields';
import { FieldMany2ManyTagsAvatar, KanbanMany2ManyTagsAvatar, ListMany2ManyTagsAvatar } from 'web.relational_fields';
import { FieldMany2ManyTagsAvatar, KanbanMany2ManyTagsAvatar, ListMany2ManyTagsAvatar, Many2OneAvatar } from 'web.relational_fields';
import session from 'web.session';
const { Component } = owl;
@@ -58,6 +59,124 @@ export const Many2OneAvatarUser = Many2OneAvatar.extend(M2XAvatarMixin, {
'click .o_m2o_avatar > img': '_onAvatarClicked',
}),
on_attach_callback() {
console.log("on_attach_callback ", this.mode)
this._registerCommandAssignTo()
},
on_detach_callback(){
this._unregisterCommandAssignTo()
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
* @private
*/
_registerCommandAssignTo() {
const self = this;
if (self.viewType === "form") {
let provide = async (env, options) => {
if (self.isDestroyed()) {
return
}
const records = await self._searchAssignTo(options.searchValue, 10)
return records.map((record) => ({
name: record[1],
action: () => {
if (self.isDestroyed()) {
return
}
self.reinitialize({ id: record[0], display_name: record[1] });
}
}))
}
let getCommandDefinition = (env) => ({
name: env._t("Assign to ..."),
options: {
activeElement: env.services.ui.getActiveElementOf(self.el),
category: "smart_action",
hotkey: "alt+i",
},
async action() {
return env.services.command.openPalette({
emptyMessageByNamespace: { "default": env._t("No users found") },
placeholder: env._t("Select a user..."),
providers: [{ provide }],
})
},
});
core.bus.trigger("set_legacy_command", "web.Many2OneAvatar.assignTo", getCommandDefinition, self.el);
getCommandDefinition = (env) => ({
name: env._t("Assign/unassign to me"),
options: {
activeElement: env.services.ui.getActiveElementOf(self.el),
category: "smart_action",
hotkey: "alt+shift+i",
},
action() {
if (self.isDestroyed()) {
return
}
if (self.value.res_id === session.user_id[0]) {
self._setValue({
operation: 'DELETE',
ids: [session.user_id[0]],
});
} else {
self.reinitialize({ id: session.user_id[0], display_name: session.name });
}
},
});
core.bus.trigger("set_legacy_command", "web.Many2OneAvatar.assignToMe", getCommandDefinition);
}
},
/**
* @override
* @private
*/
_unregisterCommandAssignTo() {
core.bus.trigger("remove_legacy_command", "web.Many2OneAvatar.assignTo");
core.bus.trigger("remove_legacy_command", "web.Many2OneAvatar.assignToMe");
},
/**
* @override
* @private
*/
_searchAssignTo(searchValue, limit) {
const value = searchValue.trim();
const domain = this.record.getDomain(this.recordParams);
const context = Object.assign(
this.record.getContext(this.recordParams),
this.additionalContext,
);
// Exclude black-listed ids from the domain
const blackListedIds = this._getSearchBlacklist();
if (blackListedIds.length) {
domain.push(['id', 'not in', blackListedIds]);
}
const nameSearch = this._rpc({
model: this.field.relation,
method: "name_search",
kwargs: {
name: value,
args: domain,
operator: "ilike",
limit: limit + 1,
context,
}
});
return this.orderer.add(nameSearch);
},
//----------------------------------------------------------------------
// Handlers
//----------------------------------------------------------------------
@@ -99,7 +218,108 @@ const M2MAvatarMixin = Object.assign(M2XAvatarMixin, {
},
});
export const Many2ManyAvatarUser = FieldMany2ManyTagsAvatar.extend(M2MAvatarMixin, {});
export const Many2ManyAvatarUser = FieldMany2ManyTagsAvatar.extend(M2MAvatarMixin, {
on_attach_callback() {
this._registerCommandAssignTo()
},
on_detach_callback(){
this._unregisterCommandAssignTo()
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
* @private
*/
_registerCommandAssignTo() {
const self = this;
if (self.viewType === "form") {
const provide = async (env, options) => {
if (self.isDestroyed()) {
return
}
const many2one = new Many2OneAvatarUser(self, self.name, self.record, {
mode: 'edit',
noOpen: true,
noCreate: !self.canCreate,
viewType: self.viewType,
attrs: self.attrs,
});
many2one._getSearchBlacklist = function () {
return self.value.res_ids;
};
const records = await many2one._searchAssignTo(options.searchValue, 10)
return records.map((record) => ({
name: record[1],
action: () => {
if (self.isDestroyed()) {
return
}
self._setValue({
operation: 'ADD_M2M',
ids: { id: record[0], display_name: record[1] }
});
},
}))
};
let getCommandDefinition = (env) => ({
name: env._t("Assign to ..."),
options: {
activeElement: env.services.ui.getActiveElementOf(self.el),
category: "smart_action",
hotkey: "alt+i",
},
action() {
return env.services.command.openPalette({
emptyMessageByNamespace: { "default": env._t("No users found") },
placeholder: env._t("Select a user..."),
providers: [{ provide }],
})
},
})
core.bus.trigger("set_legacy_command", "web.FieldMany2ManyTagsAvatar.assignTo", getCommandDefinition)
getCommandDefinition = (env) => ({
name: env._t("Assign/unassign to me"),
options: {
activeElement: env.services.ui.getActiveElementOf(self.el),
category: "smart_action",
hotkey: "alt+shift+i",
},
action() {
if (self.isDestroyed()) {
return
}
if (self.value.res_ids.includes(session.user_id[0])) {
self._setValue({
operation: 'DELETE',
ids: [session.user_id[0]],
});
} else {
self._setValue({
operation: 'ADD_M2M',
ids: { id: session.user_id[0], display_name: session.name }
});
}
},
});
core.bus.trigger("set_legacy_command", "web.Many2OneAvatar.assignToMe", getCommandDefinition);
}
},
/**
* @override
* @private
*/
_unregisterCommandAssignTo() {
core.bus.trigger("remove_legacy_command", "web.FieldMany2ManyTagsAvatar.assignTo");
core.bus.trigger("remove_legacy_command", "web.Many2OneAvatar.assignToMe");
}
});
export const KanbanMany2ManyAvatarUser = KanbanMany2ManyTagsAvatar.extend(M2MAvatarMixin, {});
@@ -1,11 +1,17 @@
/** @odoo-module **/
import { Many2OneAvatarUser } from '@mail/js/m2x_avatar_user';
import { afterEach, beforeEach, start } from '@mail/utils/test_utils';
import { click, legacyExtraNextTick, patchWithCleanup, triggerHotkey } from "@web/../tests/helpers/utils";
import { createWebClient, doAction } from '@web/../tests/webclient/helpers';
import { registry } from "@web/core/registry";
import { makeLegacyCommandService } from "@web/legacy/utils";
import core from 'web.core';
import FormView from 'web.FormView';
import KanbanView from 'web.KanbanView';
import ListView from 'web.ListView';
import { Many2OneAvatarUser } from '@mail/js/m2x_avatar_user';
import session from 'web.session';
import makeTestEnvironment from "web.test_env";
import { dom, mock, nextTick } from 'web.test_utils';
@@ -21,7 +27,7 @@ QUnit.module('mail', {}, function () {
'foo': {
fields: {
user_id: { string: "User", type: 'many2one', relation: 'res.users' },
user_ids: { string: "Users", type: "many2many", relation: 'res.users' },
user_ids: { string: "Users", type: "many2many", relation: 'res.users', default:[] },
},
records: [
{ id: 1, user_id: 11, user_ids: [11, 23], },
@@ -408,4 +414,195 @@ QUnit.module('mail', {}, function () {
form.destroy();
});
QUnit.test('many2one_avatar_user widget edited by the smart action "Assign to..."', async function (assert) {
assert.expect(4);
const legacyEnv = makeTestEnvironment({ bus: core.bus });
const serviceRegistry = registry.category("services");
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
const views = {
'foo,false,form': '<form><field name="user_id" widget="many2one_avatar_user"/></form>',
'foo,false,search': '<search></search>',
};
const models = {
'foo': this.data.foo,
'res.partner': this.data['res.partner'],
'res.users': this.data['res.users'],
}
const serverData = { models, views}
const webClient = await createWebClient({serverData});
await doAction(webClient, {
res_id: 1,
type: 'ir.actions.act_window',
target: 'current',
res_model: 'foo',
'view_mode': 'form',
'views': [[false, 'form']],
});
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Mario")
triggerHotkey("control+k")
await nextTick();
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign to ...ALT + I")
assert.ok(idx >= 0);
await click([...webClient.el.querySelectorAll(".o_command")][idx])
await nextTick();
assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [
"Your Company, Mitchell Admin",
"Public user",
"Mario",
"Luigi",
"Yoshi",
])
await click(webClient.el, "#o_command_3")
await legacyExtraNextTick();
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Luigi")
});
QUnit.test('many2one_avatar_user widget edited by the smart action "Assign to me"', async function (assert) {
assert.expect(4);
patchWithCleanup(session, { user_id: [7] })
const legacyEnv = makeTestEnvironment({ bus: core.bus });
const serviceRegistry = registry.category("services");
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
const views = {
'foo,false,form': '<form><field name="user_id" widget="many2one_avatar_user"/></form>',
'foo,false,search': '<search></search>',
};
const models = {
'foo': this.data.foo,
'res.partner': this.data['res.partner'],
'res.users': this.data['res.users'],
}
const serverData = { models, views}
const webClient = await createWebClient({serverData});
await doAction(webClient, {
res_id: 1,
type: 'ir.actions.act_window',
target: 'current',
res_model: 'foo',
'view_mode': 'form',
'views': [[false, 'form']],
});
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Mario")
triggerHotkey("control+k")
await nextTick();
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign/unassign to meALT + SHIFT + I")
assert.ok(idx >= 0);
// Assign me (Luigi)
triggerHotkey("alt+shift+i")
await legacyExtraNextTick();
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Luigi")
// Unassign me
triggerHotkey("control+k");
await nextTick();
await click([...webClient.el.querySelectorAll(".o_command")][idx])
await legacyExtraNextTick();
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "")
});
QUnit.test('many2many_avatar_user widget edited by the smart action "Assign to..."', async function (assert) {
assert.expect(4);
const legacyEnv = makeTestEnvironment({ bus: core.bus });
const serviceRegistry = registry.category("services");
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
const views = {
'foo,false,form': '<form><field name="user_ids" widget="many2many_avatar_user"/></form>',
'foo,false,search': '<search></search>',
};
const models = {
'foo': this.data.foo,
'res.partner': this.data['res.partner'],
'res.users': this.data['res.users'],
}
const serverData = { models, views}
const webClient = await createWebClient({serverData});
await doAction(webClient, {
res_id: 1,
type: 'ir.actions.act_window',
target: 'current',
res_model: 'foo',
'view_mode': 'form',
'views': [[false, 'form']],
});
let userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent));
assert.deepEqual(userNames, ["Mario", "Yoshi"]);
triggerHotkey("control+k")
await nextTick();
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign to ...ALT + I")
assert.ok(idx >= 0);
await click([...webClient.el.querySelectorAll(".o_command")][idx])
await nextTick();
assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [
"Your Company, Mitchell Admin",
"Public user",
"Luigi"
]);
await click(webClient.el, "#o_command_2");
await legacyExtraNextTick();
userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map(el => el.textContent);
assert.deepEqual(userNames, ["Mario", "Yoshi", "Luigi"]);
});
QUnit.test('many2many_avatar_user widget edited by the smart action "Assign to me"', async function (assert) {
assert.expect(4);
patchWithCleanup(session, { user_id: [7] })
const legacyEnv = makeTestEnvironment({ bus: core.bus });
const serviceRegistry = registry.category("services");
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
const views = {
'foo,false,form': '<form><field name="user_ids" widget="many2many_avatar_user"/></form>',
'foo,false,search': '<search></search>',
};
const models = {
'foo': this.data.foo,
'res.partner': this.data['res.partner'],
'res.users': this.data['res.users'],
}
const serverData = { models, views}
const webClient = await createWebClient({serverData});
await doAction(webClient, {
res_id: 1,
type: 'ir.actions.act_window',
target: 'current',
res_model: 'foo',
'view_mode': 'form',
'views': [[false, 'form']],
});
let userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent));
assert.deepEqual(userNames, ["Mario", "Yoshi"]);
triggerHotkey("control+k");
await nextTick();
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign/unassign to meALT + SHIFT + I");
assert.ok(idx >= 0);
// Assign me (Luigi)
triggerHotkey("alt+shift+i");
await legacyExtraNextTick();
userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent));
assert.deepEqual(userNames, ["Mario", "Yoshi", "Luigi"]);
// Unassign me
triggerHotkey("control+k");
await nextTick();
await click([...webClient.el.querySelectorAll(".o_command")][idx]);
await legacyExtraNextTick();
userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent));
assert.deepEqual(userNames, ["Mario", "Yoshi"]);
});
});
@@ -243,6 +243,7 @@ export const hotkeyService = {
* @param {string} hotkey
* @param {()=>void} callback
* @param {Object} options additional options
* @param {HTMLElement} [options.activeElement=undefined]
* @param {boolean} [options.allowRepeat=false]
* allow registration to perform multiple times when hotkey is held down
* @param {boolean} [options.global=false]
@@ -288,17 +289,19 @@ export const hotkeyService = {
const registration = {
hotkey: hotkey.toLowerCase(),
callback,
activeElement: null,
activeElement: options.activeElement,
allowRepeat: options && options.allowRepeat,
global: options && options.global,
};
registrations.set(token, registration);
// Due to the way elements are mounted in the DOM by Owl (bottom-to-top),
// we need to wait the next micro task tick to set the context owner of the registration.
Promise.resolve().then(() => {
registration.activeElement = ui.activeElement;
});
if (!registrations.activeElement) {
// Due to the way elements are mounted in the DOM by Owl (bottom-to-top),
// we need to wait the next micro task tick to set the context owner of the registration.
Promise.resolve().then(() => {
registration.activeElement = ui.activeElement;
});
}
return token;
}
@@ -318,6 +321,7 @@ export const hotkeyService = {
* @param {() => void} callback
* @param {Object} options
* @param {boolean} [options.allowRepeat=false]
* @param {HTMLElement} [options.activeElement=undefined]
* @param {boolean} [options.global=false]
* @returns {() => void}
*/
@@ -79,10 +79,18 @@ export const uiService = {
activeElems = activeElems.filter((x) => x !== el);
bus.trigger("active-element-changed", ui.activeElement);
}
function getActiveElementOf(el) {
for (const activeElement of [...activeElems].reverse()) {
if (activeElement.contains(el)) {
return activeElement;
}
}
}
Object.assign(ui, {
activateElement,
deactivateElement,
getActiveElementOf,
});
Object.defineProperty(ui, "activeElement", {
get() {
@@ -2504,6 +2504,44 @@ var PriorityWidget = AbstractField.extend({
var checked = this.$("[aria-checked='true']");
return checked.length ? checked : this.$("[data-index='1']");
},
init: function () {
this._super.apply(this, arguments);
console.log("init ", this.mode)
},
on_attach_callback() {
console.log("on_attach_callback ")
const self = this;
if (self.viewType === "form") {
let provide = () => {
return self.field.selection.map((value) => ({
name: value[1],
action: () => {
this._setValue(value[0]);
}
}))
}
let getCommandDefinition = (env) => ({
name: env._t("Set priority..."),
options: {
activeElement: env.services.ui.getActiveElementOf(self.el),
category: "smart_action",
hotkey: "alt+r",
},
action() {
return env.services.command.openPalette({
placeholder: env._t("Set a priority..."),
providers: [{ provide }],
})
},
});
core.bus.trigger("set_legacy_command", "web.PriorityWidget.setPriority", getCommandDefinition);
}
},
on_detach_callback() {
core.bus.trigger("remove_legacy_command", "web.PriorityWidget.setPriority");
},
//--------------------------------------------------------------------------
// Private
@@ -2679,6 +2717,38 @@ var StateSelectionWidget = AbstractField.extend({
return this.$("a[data-toggle='dropdown']");
},
on_attach_callback() {
const self = this;
if (self.viewType === "form") {
let provide = () => {
return self.field.selection.map((value) => ({
name: value[1],
action: () => {
this._setValue(value[0]);
}
}))
}
let getCommandDefinition = (env) => ({
name: env._t("Set kanban state..."),
options: {
activeElement: env.services.ui.getActiveElementOf(self.el),
category: "smart_action",
hotkey: "alt+shift+r",
},
action() {
return env.services.command.openPalette({
placeholder: env._t("Set a kanban state..."),
providers: [{ provide }],
})
},
});
core.bus.trigger("set_legacy_command", "web.StateSelectionWidget.setKanbanState", getCommandDefinition);
}
},
on_detach_callback() {
core.bus.trigger("remove_legacy_command", "web.StateSelectionWidget.setKanbanState");
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -8,6 +8,7 @@ import {
makeLegacySessionService,
makeLegacyDialogMappingService,
makeLegacyCrashManagerService,
makeLegacyCommandService,
} from "./utils";
import * as AbstractService from "web.AbstractService";
import * as legacyEnv from "web.env";
@@ -43,6 +44,8 @@ export const legacySetupProm = new Promise((resolve) => {
serviceRegistry.add("legacy_crash_manager", makeLegacyCrashManagerService(legacyEnv));
const legacyDialogMappingService = makeLegacyDialogMappingService(legacyEnv);
serviceRegistry.add("legacy_dialog_mapping", legacyDialogMappingService);
const legacyCommandService = makeLegacyCommandService(legacyEnv);
serviceRegistry.add("legacy_command", legacyCommandService);
await Promise.all([whenReady(), session.is_bound]);
legacyEnv.qweb.addTemplates(session.owlTemplates);
legacySetupResolver(legacyEnv);
+35
View File
@@ -120,6 +120,41 @@ export function makeLegacyDialogMappingService(legacyEnv) {
};
}
/**
* Deploys a service allowing legacy to add/remove commands.
*
* @param {object} legacyEnv
* @returns a wowl deployable service
*/
export function makeLegacyCommandService(legacyEnv) {
return {
dependencies: ["command"],
start(env) {
const { command } = env.services;
const commandRemoveMap = new Map();
function setLegacyCommand(uniqueId, getCommandDefinition) {
console.log("setLegacyCommand ", uniqueId);
const { name, action, options } = getCommandDefinition(env);
removeLegacyCommand(uniqueId);
commandRemoveMap.set(uniqueId, command.add(name, action, options));
}
function removeLegacyCommand(uniqueId) {
if (commandRemoveMap.has(uniqueId)) {
const removeCommand = commandRemoveMap.get(uniqueId);
removeCommand();
commandRemoveMap.delete(uniqueId);
}
}
legacyEnv.bus.on("set_legacy_command", null, setLegacyCommand);
legacyEnv.bus.on("remove_legacy_command", null, removeLegacyCommand);
},
};
}
export function makeLegacySessionService(legacyEnv, session) {
return {
dependencies: ["user"],
@@ -5,6 +5,7 @@ import { registry } from "@web/core/registry";
const commandCategoryRegistry = registry.category("command_categories");
commandCategoryRegistry
.add("app", {}, { sequence: 10 })
.add("smart_action", {}, { sequence: 15 })
.add("actions", {}, { sequence: 30 })
.add("navbar", {}, { sequence: 40 })
.add("default", {}, { sequence: 100 })
@@ -19,6 +19,7 @@ const { xml } = owl.tags;
/**
* @typedef {{
* activeElement?: HTMLElement;
* category?: string;
* global?: boolean;
* hotkey?: string;
@@ -27,7 +28,6 @@ const { xml } = owl.tags;
/**
* @typedef {Command & CommandOptions & {
* activeElement?: HTMLElement;
* removeHotkey?: ()=>void;
* }} CommandRegistration
*/
@@ -114,13 +114,14 @@ export const commandService = {
throw new Error("A Command must have a name and an action function.");
}
const registration = Object.assign({}, command, options, { activeElement: null });
const registration = Object.assign({}, command, options);
if (registration.hotkey) {
registration.removeHotkey = hotkeyService.add(
registration.hotkey,
registration.action,
{
activeElement: registration.activeElement,
global: registration.global,
}
);
@@ -128,13 +129,14 @@ export const commandService = {
const token = nextToken++;
registeredCommands.set(token, registration);
// Due to the way elements are mounted in the DOM by Owl (bottom-to-top),
// we need to wait the next micro task tick to set the context activate
// element of the subscription.
Promise.resolve().then(() => {
registration.activeElement = ui.activeElement;
});
if (!options.activeElement) {
// Due to the way elements are mounted in the DOM by Owl (bottom-to-top),
// we need to wait the next micro task tick to set the context activate
// element of the subscription.
Promise.resolve().then(() => {
registration.activeElement = ui.activeElement;
});
}
return token;
}
@@ -14,6 +14,11 @@ var session = require('web.session');
var testUtils = require('web.test_utils');
var testUtilsDom = require('web.test_utils_dom');
var field_registry = require('web.field_registry');
const makeTestEnvironment = require("web.test_env");
const { makeLegacyCommandService } = require("@web/legacy/utils");
const { registry } = require("@web/core/registry");
const { legacyExtraNextTick, patchWithCleanup, triggerHotkey, nextTick, click } = require("@web/../tests/helpers/utils");
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
var createView = testUtils.createView;
var patchDate = testUtils.mock.patchDate;
@@ -6564,6 +6569,48 @@ QUnit.module('basic_fields', {
form.destroy();
});
QUnit.test('priority widget edited by the smart action "Set priority..."', async function (assert) {
assert.expect(4);
const legacyEnv = makeTestEnvironment({ bus: core.bus });
const serviceRegistry = registry.category("services");
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
const views = {
'partner,false,form': '<form>' +
'<field name="selection" widget="priority"/>' +
'</form>',
'partner,false,search': '<search></search>',
};
const serverData = { models: this.data, views}
const webClient = await createWebClient({serverData});
await doAction(webClient, {
res_id: 1,
type: 'ir.actions.act_window',
target: 'current',
res_model: 'partner',
'view_mode': 'form',
'views': [[false, 'form']],
});
assert.containsOnce(webClient, ".fa-star")
triggerHotkey("control+k")
await nextTick();
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Set priority...ALT + R")
assert.ok(idx >= 0);
await click([...webClient.el.querySelectorAll(".o_command")][idx])
await nextTick();
assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [
"Normal",
"Blocked",
"Done"
])
await click(webClient.el, "#o_command_2")
await legacyExtraNextTick();
assert.containsN(webClient, ".fa-star", 2)
});
QUnit.module('StateSelection Widget');
QUnit.test('state_selection widget in form view', async function (assert) {
@@ -6821,6 +6868,48 @@ QUnit.module('basic_fields', {
list.destroy();
});
QUnit.test('state_selection edited by the smart action "Set kanban state..."', async function (assert) {
assert.expect(4);
const legacyEnv = makeTestEnvironment({ bus: core.bus });
const serviceRegistry = registry.category("services");
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
const views = {
'partner,false,form': '<form>' +
'<field name="selection" widget="state_selection"/>' +
'</form>',
'partner,false,search': '<search></search>',
};
const serverData = { models: this.data, views}
const webClient = await createWebClient({serverData});
await doAction(webClient, {
res_id: 1,
type: 'ir.actions.act_window',
target: 'current',
res_model: 'partner',
'view_mode': 'form',
'views': [[false, 'form']],
});
assert.containsOnce(webClient, ".o_status_red")
triggerHotkey("control+k")
await nextTick();
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Set kanban state...ALT + SHIFT + R")
assert.ok(idx >= 0);
await click([...webClient.el.querySelectorAll(".o_command")][idx])
await nextTick();
assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [
"Normal",
"Blocked",
"Done"
])
await click(webClient.el, "#o_command_2")
await legacyExtraNextTick();
assert.containsOnce(webClient, ".o_status_green")
});
QUnit.module('FavoriteWidget');