[FIX] *: remove scss 'extend' from dropdown components

This commit removes all the 'extend' initially introduced to avoid code
repetition and ensure visual consistency across Bootstrap and Owl dropdowns.

Despite achieving the desired results, using 'extend' in this context
was seriously impacting the bundle generation time, probably due to an
underestimated amount of Apps' legacy-code applied on these elements.

In order to achieve the same results, the chosen strategy is to add
Bootstrap default classes directly into Owl dropdowns.
Also, it moves code related to bootstrap dropdown in 'webclient.scss',
leaving 'core/dropdown/dropdown.scss' for Owl code only.

Due to the discrepancies between Bootstrap and Owl html
structure, the '.dropdown-item' class could not have been added
directly to Owl's '.o_dropdown_item' itself, without refactoring
the Dropdown component structure.

// ==== Bootstrap 4.6 default Structure ================================
<div class="dropdown-menu">
  <button class="dropdown-item" type="button">Action</button>
  <a class="dropdown-item" href="#">Another action</a>
</div>

// ==== OWL default Structure before this commit =======================
<ul class="o_dropdown_menu">
  <li class="o_dropdown_item">
     <span>Action</span>
  </li>
  <li class="o_dropdown_item">
     <a href="#">Another action</a>
  </li>
</ul>

// ==== OWL Structure after this commit ================================
<div class="o-dropdown--menu dropdown-menu">
  <span class="dropdown-item">Action</span>
  <a class="dropdown-item" href="#">Another action</a>
</div>

// ==== web.assets_backend.css Bundle Generation Comparison ============
With all modules installed (enterprise edition over runbot):
Before this commit, bundle took ~2.5s and ~4s to generate and weighted ~322kB (~2.5MB uncompressed)
After this commit, it takes between ~1.2s and ~1.6s and weights ~257kB (~1.6MB uncompressed)

closes odoo/odoo#77649

X-original-commit: 84715436d87bb05b421bc9ccaacda67d07571690
Related: odoo/enterprise#21370
Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
Co-authored-by: Stefano Rigano <sri@odoo.com>
Co-authored-by: François Georis <fge@odoo.com>
Co-authored-by: Bruno Boi <boi@odoo.com>
This commit is contained in:
Bruno Boi
2021-10-04 07:57:00 +00:00
co-authored by Stefano Rigano François Georis
parent 63099f5b13
commit b63ee52552
103 changed files with 1218 additions and 1081 deletions
@@ -20,7 +20,7 @@ odoo.define('account.dashboard.setup.tour', function (require) {
}, {
trigger: '.o_control_panel button:contains("' + _t('Print') + '")',
}, {
trigger: '.o_control_panel .o_dropdown_menu a:contains("' + _t('Invoices without Payment') + '")',
trigger: '.o_control_panel .dropdown-menu a:contains("' + _t('Invoices without Payment') + '")',
}, {
trigger: 'iframe .o_report_layout_standard h2',
content: 'Primary color is correct',
+2 -2
View File
@@ -161,7 +161,7 @@ tour.register('totp_login_device', {
run: 'click',
}, {
content: "click the Log out button",
trigger: 'li[data-menu=logout] > a',
trigger: '.dropdown-item[data-menu=logout]',
}, {
content: "check that we're back on the login page or go to it",
trigger: 'input#login, a:contains(Log in)'
@@ -280,7 +280,7 @@ tour.register('totp_admin_disables', {
}
}, {
content: "Open Actions menu",
trigger: 'button.o_dropdown_toggler:contains("Action")'
trigger: 'button.dropdown-toggle:contains("Action")'
}, {
content: "Select totp remover",
trigger: 'a.dropdown-item:contains(Disable two-factor authentication)'
@@ -4,12 +4,12 @@
<t t-name="board.AddToBoard" owl="1">
<Dropdown class="o_add_to_board">
<t t-set-slot="toggler">Add to my dashboard</t>
<li class="px-3 py-2">
<div class="px-3 py-2">
<input type="text" class="o_input" autofocus="" t-model.trim="state.name" t-on-keydown="onInputKeydown" />
</li>
<li class="px-3 py-2">
</div>
<div class="px-3 py-2">
<button type="button" class="btn btn-primary" t-on-click="addToBoard">Add</button>
</li>
</div>
</Dropdown>
</t>
+18 -11
View File
@@ -7,6 +7,8 @@ odoo.define("board.dashboard_tests", function (require) {
var testUtils = require("web.test_utils");
var ListRenderer = require("web.ListRenderer");
var pyUtils = require("web.py_utils");
const { browser } = require("@web/core/browser/browser");
const { patchWithCleanup } = require("@web/../tests/helpers/utils");
const { registry } = require("@web/core/registry");
const { makeFakeUserService } = require("@web/../tests/helpers/mock_services");
const {
@@ -844,6 +846,7 @@ odoo.define("board.dashboard_tests", function (require) {
"partner,false,list": '<list><field name="foo"/></list>',
"partner,false,search": "<search></search>",
};
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
const mockRPC = (route, args) => {
if (route === "/board/add_to_dashboard") {
@@ -895,9 +898,9 @@ odoo.define("board.dashboard_tests", function (require) {
// add this action to dashboard
await toggleFavoriteMenu(webClient);
await testUtils.dom.click($(".o_add_to_board button.o_dropdown_toggler"));
await testUtils.dom.triggerEvent($(".o_add_to_board button.dropdown-toggle"), "mouseenter");
await testUtils.fields.editInput($(".o_add_to_board input"), "a name");
await testUtils.dom.click($(".o_add_to_board .o_dropdown_menu button"));
await testUtils.dom.click($(".o_add_to_board .dropdown-menu button"));
testUtils.mock.unpatch(ListController);
});
@@ -906,6 +909,7 @@ odoo.define("board.dashboard_tests", function (require) {
// the second search saved should not be influenced by the first
assert.expect(2);
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
serverData.views = {
"partner,false,list": '<list><field name="foo"/></list>',
"partner,false,search": "<search></search>",
@@ -951,8 +955,8 @@ odoo.define("board.dashboard_tests", function (require) {
// Add it to dashboard
await toggleFavoriteMenu(webClient);
await testUtils.dom.click($(".o_add_to_board button.o_dropdown_toggler"));
await testUtils.dom.click($(".o_add_to_board .o_dropdown_menu button"));
await testUtils.dom.triggerEvent($(".o_add_to_board button.dropdown-toggle"), "mouseenter");
await testUtils.dom.click($(".o_add_to_board .dropdown-menu button"));
// Remove it
await testUtils.dom.click(webClient.el.querySelector(".o_facet_remove"));
@@ -964,11 +968,12 @@ odoo.define("board.dashboard_tests", function (require) {
await applyFilter(webClient);
// Add it to dashboard
await toggleFavoriteMenu(webClient);
await testUtils.dom.click(
webClient.el.querySelector(".o_add_to_board button.o_dropdown_toggler")
await testUtils.dom.triggerEvent(
webClient.el.querySelector(".o_add_to_board button.dropdown-toggle"),
"mouseenter"
);
await testUtils.dom.click(
webClient.el.querySelector(".o_add_to_board .o_dropdown_menu button")
webClient.el.querySelector(".o_add_to_board .dropdown-menu button")
);
});
@@ -976,6 +981,7 @@ odoo.define("board.dashboard_tests", function (require) {
// View domains are to be added to the dashboard domain
assert.expect(1);
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
var view_domain = ["display_name", "ilike", "a"];
var filter_domain = ["display_name", "ilike", "b"];
@@ -1011,12 +1017,13 @@ odoo.define("board.dashboard_tests", function (require) {
await applyFilter(webClient);
// Add it to dashboard
await toggleFavoriteMenu(webClient);
await testUtils.dom.click(
webClient.el.querySelector(".o_add_to_board button.o_dropdown_toggler")
await testUtils.dom.triggerEvent(
webClient.el.querySelector(".o_add_to_board button.dropdown-toggle"),
"mouseenter"
);
// add
await testUtils.dom.click(
webClient.el.querySelector(".o_add_to_board .o_dropdown_menu button")
webClient.el.querySelector(".o_add_to_board .dropdown-menu button")
);
});
@@ -1255,7 +1262,7 @@ odoo.define("board.dashboard_tests", function (require) {
// add the view to the dashboard
await toggleFavoriteMenu(webClient);
await testUtils.dom.click($(".o_add_to_board button.o_dropdown_toggler"));
await testUtils.dom.click($(".o_add_to_board button.dropdown-toggle"));
await testUtils.fields.editInput($(".o_add_to_board input"), "a name");
await testUtils.dom.click($(".o_add_to_board div button"));
@@ -7,7 +7,7 @@ import {
toggleFilterMenu,
toggleGroupByMenu,
toggleMenuItem,
toggleMenuItemOption
toggleMenuItemOption,
} from "@web/../tests/search/helpers";
import { makeView } from "@web/../tests/views/helpers";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
@@ -18,6 +18,7 @@ import AbstractModel from "web.AbstractModel";
import AbstractView from "web.AbstractView";
import { controlPanel as cpHelpers, mock } from "web.test_utils";
import legacyViewRegistry from "web.view_registry";
import { browser } from "@web/core/browser/browser";
const patchDate = mock.patchDate;
@@ -72,6 +73,7 @@ QUnit.module("Views", (hooks) => {
QUnit.test("Forecast graph view", async function (assert) {
assert.expect(5);
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
const unpatchDate = patchDate(2021, 8, 16, 16, 54, 0);
const expectedDomains = [
@@ -167,6 +169,7 @@ QUnit.module("Views", (hooks) => {
async function (assert) {
assert.expect(16);
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
const unpatchDate = patchDate(2021, 8, 16, 16, 54, 0);
const expectedDomains = [
@@ -11,11 +11,11 @@ tour.register('crm_forecast', {
trigger: ".o_app[data-menu-xmlid='crm.crm_menu_root']",
content: "open crm app",
}, {
trigger: '.o_dropdown_toggler[data-menu-xmlid="crm.crm_menu_report"]',
trigger: '.dropdown-toggle[data-menu-xmlid="crm.crm_menu_report"]',
content: 'Open Reporting menu',
run: 'click',
}, {
trigger: '.o_dropdown_item[data-menu-xmlid="crm.crm_menu_forecast"]',
trigger: '.dropdown-item[data-menu-xmlid="crm.crm_menu_forecast"]',
content: 'Open Forecast menu',
run: 'click',
}, {
@@ -35,7 +35,7 @@ tour.register('hr_expense_tour' , {
content: Markup(_t('<p>Select expenses to submit them to your manager</p>')),
position: 'bottom'
}, {
trigger: '.o_dropdown_toggler',
trigger: '.dropdown-toggle',
extra_trigger: ".o_expense_tree",
content: Markup(_t('<p>Click on <b> Action Create Report </b> to submit selected expenses to your manager</p>')),
position: 'right',
@@ -107,7 +107,7 @@
// ------------------------------------------------------------------
// Make hightlight more consistent, due to messaging menu looking quite similar to discuss app in mobile
.o_MessagingMenu.o-is-open {
.o_MessagingMenu.show {
background-color: rgba($black, 0.1);
}
@@ -2,8 +2,8 @@
<templates xml:space="preserve">
<t t-name="mail.MessagingMenu" owl="1">
<li class="o_MessagingMenu" t-att-class="{ 'o-is-open': messagingMenu ? messagingMenu.isOpen : false, 'o-mobile': messaging ? messaging.device.isMobile : false }">
<a class="o_MessagingMenu_toggler" t-att-class="{ 'o-no-notification': messagingMenu ? !messagingMenu.counter : false }" href="#" title="Conversations" role="button" t-att-aria-expanded="messagingMenu and messagingMenu.isOpen ? 'true' : 'false'" aria-haspopup="true" t-on-click="_onClickToggler">
<div class="o_MessagingMenu dropdown" t-att-class="{ 'show': messagingMenu ? messagingMenu.isOpen : false, 'o-mobile': messaging ? messaging.device.isMobile : false }">
<a class="o_MessagingMenu_toggler dropdown-toggle o-no-caret o-dropdown--narrow" t-att-class="{ 'o-no-notification': messagingMenu ? !messagingMenu.counter : false }" href="#" title="Conversations" role="button" t-att-aria-expanded="messagingMenu and messagingMenu.isOpen ? 'true' : 'false'" aria-haspopup="true" t-on-click="_onClickToggler">
<i class="o_MessagingMenu_icon fa fa-comments" role="img" aria-label="Messages"/>
<t t-if="!messagingMenu or !messaging.isInitialized">
<i class="o_MessagingMenu_loading fa fa-circle-o-notch fa-spin"/>
@@ -65,7 +65,7 @@
</t>
</div>
</t>
</li>
</div>
</t>
<t t-name="mail.MessagingMenu.newMessageButton" owl="1">
@@ -201,7 +201,7 @@ QUnit.test('basic rendering', async function (assert) {
await afterNextRender(() => document.querySelector(`.o_MessagingMenu_toggler`).click());
assert.hasClass(
document.querySelector('.o_MessagingMenu'),
"o-is-open",
"show",
"should mark messaging menu as opened"
);
assert.strictEqual(
@@ -280,7 +280,7 @@ QUnit.test('basic rendering', async function (assert) {
await afterNextRender(() => document.querySelector(`.o_MessagingMenu_toggler`).click());
assert.doesNotHaveClass(
document.querySelector('.o_MessagingMenu'),
"o-is-open",
"show",
"should mark messaging menu as closed"
);
});
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="mail.RtcActivityNotice" owl="1">
<li class="o_RtcActivityNotice">
<div class="o_RtcActivityNotice dropdown">
<t t-if="messaging">
<RtcInvitations/>
<t t-if="messaging.rtc.channel">
@@ -19,7 +19,7 @@
</a>
</t>
</t>
</li>
</div>
</t>
</templates>
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="mail.widgets.RtcActivityNotice">
<li class="o_widget_RtcActivityNotice"/>
<div class="o_widget_RtcActivityNotice"/>
</t>
</templates>
+3 -3
View File
@@ -49,13 +49,13 @@
</t>
<t t-name="mail.systray.ActivityMenu">
<li class="o_mail_systray_item">
<a class="dropdown-toggle o-no-caret" data-toggle="dropdown" data-display="static" aria-expanded="false" title="Activities" href="#" role="button">
<div class="o_mail_systray_item dropdown">
<a class="dropdown-toggle o-no-caret o-dropdown--narrow" data-toggle="dropdown" data-display="static" aria-expanded="false" title="Activities" href="#" role="button">
<i class="fa fa-clock-o" role="img" aria-label="Activities"/> <span class="o_notification_counter badge badge-pill"/>
</a>
<div class="o_mail_systray_dropdown dropdown-menu dropdown-menu-right" role="menu">
<div class="o_mail_systray_dropdown_items"/>
</div>
</li>
</div>
</t>
</templates>
+3 -3
View File
@@ -402,16 +402,16 @@ QUnit.test("activity view: no group_by_menu and no comparison_menu", async funct
assert.containsN(
webClient,
".o_search_options .o_dropdown button:visible",
".o_search_options .dropdown button:visible",
2,
"only two elements should be available in view search"
);
assert.isVisible(
$(webClient.el).find(".o_search_options .o_dropdown.o_filter_menu > button"),
$(webClient.el).find(".o_search_options .dropdown.o_filter_menu > button"),
"filter should be available in view search"
);
assert.isVisible(
$(webClient.el).find(".o_search_options .o_dropdown.o_favorite_menu > button"),
$(webClient.el).find(".o_search_options .dropdown.o_favorite_menu > button"),
"favorites should be available in view search"
);
});
@@ -40,10 +40,10 @@ QUnit.test("Manage Messages", async function (assert) {
const wc = await createWebClient({ serverData, mockRPC });
await doAction(wc, 3, { viewType: "form", props: { resId: 5 } });
await legacyExtraNextTick();
await click(wc.el, ".o_debug_manager .o_dropdown_toggler");
await click(wc.el, ".o_debug_manager .dropdown-toggle");
const dropdownItems = wc.el.querySelectorAll(
".o_debug_manager .o_dropdown_menu .o_dropdown_item"
".o_debug_manager .dropdown-menu .dropdown-item"
);
assert.strictEqual(dropdownItems.length, 1);
assert.strictEqual(
@@ -156,7 +156,7 @@ tour.register('sale_timesheet_tour', {
content: 'Click on the Configuration menu.',
run: 'click',
}, {
trigger: 'li[data-menu-xmlid="project.menu_projects_config"]',
trigger: '.dropdown-item[data-menu-xmlid="project.menu_projects_config"]',
content: 'Select Configuration > Projects.',
run: 'click',
}, {
@@ -238,7 +238,7 @@ tour.register('sale_timesheet_tour', {
trigger: '.o_form_button_save',
content: 'Save Project',
}, {
trigger: 'li[data-menu-xmlid="project.menu_projects"]',
trigger: '.dropdown-item[data-menu-xmlid="project.menu_projects"]',
content: 'Select Projects',
}, {
// an invisible element cannot be used as a trigger so this small hack is mandatory for the next step
@@ -8,13 +8,13 @@
</button>
<t t-name="warehouseFilter">
<div id="warehouse_filter" class="btn-group o_dropdown o_stock_report_warehouse_filter"
<div id="warehouse_filter" class="btn-group dropdown o_stock_report_warehouse_filter"
t-if="displayWarehouseFilter">
<button type="button" class="o_dropdown_toggler btn btn-secondary dropdown-toggle"
<button type="button" class="dropdown-toggle btn btn-secondary dropdown-toggle"
data-toggle="dropdown">
<span class="fa fa-home"/> Warehouse: <t t-esc="active_warehouse['name']"/>
</button>
<div class="dropdown-menu o_dropdown_menu o_filter_menu" role="menu">
<div class="dropdown-menu o_filter_menu" role="menu">
<t t-foreach="warehouses" t-as="wh">
<a role="menuitem" class="dropdown-item warehouse_filter"
data-filter="warehouses" t-att-data-warehouse-id="wh['id']"
@@ -1,5 +1,5 @@
.o_dialog {
.o_debug_manager .o_dropdown_toggler {
.o_debug_manager .dropdown-toggle {
padding: 0 4px;
margin: 2px 10px 2px 0;
}
@@ -5,19 +5,18 @@
<Dropdown class="o_debug_manager"
beforeOpen="getElements"
position="'bottom-end'"
tag="'li'"
togglerClass="'btn btn-link'">
togglerClass="`o-dropdown--narrow ${env.inDialog?'btn btn-link':''}`">
<t t-set-slot="toggler">
<span class="fa fa-bug"/>
<i class="fa fa-bug"/>
</t>
<t t-foreach="elements" t-as="element" t-key="element_index">
<t t-if="element.type == 'item'">
<DropdownItem t-on-dropdown-item-selected.stop="element.callback">
<a t-if="element.href" t-att-href="element.href" t-esc="element.description" t-on-click.prevent=""/>
<span t-else="" t-esc="element.description"/>
</DropdownItem>
</t>
<li t-if="element.type == 'separator'" role="separator" class="dropdown-divider"/>
<DropdownItem
t-if="element.type == 'item'"
t-on-dropdown-item-selected.stop="element.callback"
href="element.href"
t-esc="element.description"
/>
<div t-if="element.type == 'separator'" role="separator" class="dropdown-divider"/>
<t t-if="element.type == 'component'" t-component="element.Component" t-props="element.props"/>
</t>
</Dropdown>
+12 -11
View File
@@ -10,6 +10,13 @@ const { Component, core, hooks, useState, QWeb } = owl;
const { EventBus } = core;
const { onWillStart, useExternalListener, useRef, useSubEnv } = hooks;
const DIRECTION_CARET_CLASS = {
bottom: "dropdown",
top: "dropup",
left: "dropleft",
right: "dropright",
};
/**
* @typedef DropdownState
* @property {boolean} open
@@ -61,11 +68,12 @@ export class Dropdown extends Component {
// Set up nested dropdowns ---------------------------------------------
this.hasParentDropdown = this.env.inDropdown;
useSubEnv({ inDropdown: true });
this.rootTag = this.props.tag || (this.hasParentDropdown ? "li" : "div");
// Set up key navigation -----------------------------------------------
useDropdownNavigation();
// Set up toggler and positioning --------------------------------------
this.togglerRef = useRef("togglerRef");
if (this.props.toggler === "parent") {
// Add parent click listener to handle toggling
useEffect(
@@ -91,8 +99,8 @@ export class Dropdown extends Component {
/** @type {string} **/
let position =
this.props.position || (this.hasParentDropdown ? "right-start" : "bottom-start");
let [direction, variant = "middle"] = position.split("-");
if (localization.direction === "rtl") {
let [direction, variant = "middle"] = position.split("-");
if (["bottom", "top"].includes(direction)) {
variant = variant === "start" ? "end" : "start";
} else {
@@ -100,6 +108,7 @@ export class Dropdown extends Component {
}
position = [direction, variant].join("-");
}
this.directionCaretClass = DIRECTION_CARET_CLASS[direction];
const positioningOptions = {
popper: "menuRef",
@@ -112,8 +121,7 @@ export class Dropdown extends Component {
usePosition(() => this.el.parentElement, positioningOptions);
} else {
// Position menu relatively to inner toggler
const togglerRef = useRef("togglerRef");
usePosition(() => togglerRef.el, positioningOptions);
usePosition(() => this.togglerRef.el, positioningOptions);
}
}
}
@@ -243,9 +251,6 @@ export class Dropdown extends Component {
* NB: only if its siblings dropdown group is opened and if not a sub dropdown.
*/
onTogglerMouseEnter() {
if (this.hasParentDropdown) {
return;
}
if (this.state.groupIsOpen && !this.state.open) {
this.open();
}
@@ -297,10 +302,6 @@ Dropdown.props = {
type: Function,
optional: true,
},
tag: {
type: String,
optional: true,
},
togglerClass: {
type: String,
optional: true,
+60 -133
View File
@@ -1,144 +1,71 @@
// = Default Boostrap Classes customizations
// ----------------------------------------------------------------------------
.dropdown-menu {
max-height: 70vh;
overflow: auto;
background-clip: border-box;
box-shadow: $o-dropdown-box-shadow;
}
.dropdown-item {
&.active, &.selected {
position: relative;
font-weight: $font-weight-bold;
&:focus, &:hover {
background-color: $dropdown-link-hover-bg;
}
&:not(.o_dropdown_item_active_noarrow) {
&:before {
@include o-position-absolute(0);
transform: translate(-1.5em, 90%);
font: .7em/1em FontAwesome;
color: $primary;
content: "\f00c";
}
&.disabled:before {
color: $dropdown-link-disabled-color;
}
}
}
&.selected { // Legacy
// FIXME: does commenting this break anything?
// @extend .active;
}
}
.dropdown-header {
font-weight: $font-weight-bold;
padding-bottom: .1em;
&:not(:first-child) {
margin-top: .3em;
}
}
.dropdown-divider:first-child {
display: none;
}
.o_rtl {
.dropdown-menu {
@extend .dropdown-menu-right;
}
}
// = Extend Owl custom classes to visually match Boostrap ones
// ----------------------------------------------------------------------------
.o_dropdown {
@extend .dropdown;
&_toggler {
@extend .dropdown-toggle;
}
&_menu {
@extend .dropdown-menu;
& > li.o_dropdown {
&.focus > .o_dropdown_toggler {
background-color: $dropdown-link-hover-bg;
}
& > .o_dropdown_toggler {
&:after {
@include o-position-absolute($right: 0, $top: 0);
transform: translate(-.6em, .6em) #{"/*rtl:translate(.6em, .6em) scaleX(-1)*/"};
font: .9em/1em FontAwesome;
content: "\f0da";
}
}
// Correctly align sub dropdowns items with its parent's
& > .o_dropdown_menu {
&.o-popper-position {
&--rs, &--ls {
margin-top: calc(-.5rem - 1px);
}
&--re, &--le {
margin-top: calc(.5rem - 2px);
}
}
}
}
// Since wrapping entry's content is the norm, @extend bootstrap
// '.dropdown-item' classes to the inner element (eg. <a> or <span>)
// rather that on 'o_dropdown_item'.
.o_dropdown_item {
> :first-child {
@extend .dropdown-item;
}
&.selected > :first-child {
// FIXME: does commenting this break anything?
//@extend .active;
}
&.disabled > :first-child {
// FIXME: does commenting this break anything?
//@extend .disabled;
}
&.focus {
background-color: $dropdown-link-hover-bg;
}
}
}
&_menu_group {
@extend .dropdown-header;
}
}
// = Owl components specific rules
// -----------------------------------------------------------------------------
.o_dropdown_item:not(.disabled):not(:disabled) {
// Needed 'cause <DropdownItem> generate <li> html tags that for browsers are
.dropdown-menu {
.dropdown-toggle:focus, .dropdown-item:focus {
background-color: transparent;
}
.dropdown-toggle.focus, .dropdown-item.focus {
background-color: $dropdown-link-hover-bg;
}
}
.dropdown-item:not(.disabled):not(:disabled) {
// Needed 'cause <DropdownItem> generate <span> html tags that for browsers are
// normally not clickable.
cursor: pointer;
}
button.o_dropdown_toggler {
> span {
min-width: max-content;
}
&%--state-active {
button.dropdown-toggle {
&.active, &:hover, &:focus, &:active {
outline: none;
box-shadow: none !important;
}
@include hover-focus-active() {
@extend %--state-active;
&.dropdown-item {
&::after, &::before {
// Changes the placement of bootstrap dropdown carets for subdropdowns
@include o-position-absolute($right: 0, $top: 0);
transform: translate(-.6em, .6em) #{"/*rtl:translate(.6em, .6em) scaleX(-1)*/"};
}
}
}
// = New Owl Dropdown specific rules
// -----------------------------------------------------------------------------
.o-dropdown {
&--menu {
// Makes sure the dropdown menus are already in a
// fixed position before the positioning computations begin.
position: fixed;
}
/*!rtl:begin:ignore*/
&.dropup > .o-dropdown--menu,
&.dropdown > .o-dropdown--menu,
&.dropleft > .o-dropdown--menu,
&.dropright > .o-dropdown--menu {
// Also makes sure that left/right stuffs are not polluted
// by bootstrap rules processed by rtlcss
left: auto;
right: auto;
margin-left: 0;
margin-right: 0;
}
/*!rtl:end:ignore*/
&--no-caret {
> .dropdown-toggle {
&::before, &::after {
content: normal;
}
}
}
// Correctly align sub dropdowns items with its parent's
.o-dropdown > .o-dropdown--menu.o-popper-position {
&--rs, &--ls {
margin-top: calc(-.5rem - 1px);
}
&--re, &--le {
margin-top: calc(.5rem - 2px);
}
}
}
@@ -2,29 +2,28 @@
<templates xml:space="preserve">
<t t-name="web.Dropdown" owl="1">
<t
t-tag="rootTag"
class="o_dropdown"
t-att-class="{ show: state.open, o_dropdown_item: hasParentDropdown and rootTag === 'li' }"
<div
class="o-dropdown dropdown"
t-att-class="{ show: state.open, 'o-dropdown--no-caret': !hasParentDropdown }"
t-attf-class="{{ directionCaretClass || '' }}"
t-on-dropdown-item-selected="onItemSelected"
>
<button
t-if="props.toggler !== 'parent'"
t-attf-class="o_dropdown_toggler {{ props.togglerClass || '' }}"
class="dropdown-toggle"
t-attf-class="{{props.togglerClass || ''}} {{hasParentDropdown ? 'dropdown-item' : ''}}"
t-on-click="onTogglerClick"
t-on-mouseenter="onTogglerMouseEnter"
t-att-title="props.title"
t-att-data-hotkey="props.hotkey"
t-ref="togglerRef"
>
<span class="d-flex align-items-center">
<t t-slot="toggler" />
</span>
<t t-slot="toggler" />
</button>
<ul t-if="state.open" class="o_dropdown_menu d-block" t-att-class="props.menuClass" t-ref="menuRef">
<div t-if="state.open" class="o-dropdown--menu dropdown-menu d-block" t-att-class="props.menuClass" t-ref="menuRef">
<t t-slot="default" />
</ul>
</t>
</div>
</div>
</t>
</templates>
@@ -27,8 +27,11 @@ export const ParentClosingMode = {
export class DropdownItem extends Component {
/**
* Triggers a custom DropdownItemSelectedEvent
* @param {MouseEvent} ev
*/
onClick() {
onClick(ev) {
ev.preventDefault();
/** @type DropdownItemSelectedEventDetail */
const detail = {
payload: this.props.payload,
@@ -54,6 +57,10 @@ DropdownItem.props = {
type: String,
optional: true,
},
href: {
type: String,
optional: true,
},
title: {
type: String,
optional: true,
@@ -2,14 +2,16 @@
<templates xml:space="preserve">
<t t-name="web.DropdownItem" owl="1">
<li
class="o_dropdown_item"
<t
t-tag="props.href ? 'a' : 'span'"
t-att-href="props.href"
class="dropdown-item"
t-on-click="onClick"
t-att-title="props.title"
t-att-data-hotkey="props.hotkey"
>
<t t-slot="default" />
</li>
</t>
</t>
</templates>
@@ -10,6 +10,7 @@ import { scrollTo } from "../utils/scrolling";
* el: HTMLElement,
* isActive: boolean,
* makeOnlyActive: ()=>void,
* navTarget: HTMLElement,
* isSubDropdown: boolean,
* isSubDropdownOpen: boolean,
* closeSubDropdown: ()=>void,
@@ -21,7 +22,7 @@ const { hooks } = owl;
const { useComponent, useRef } = hooks;
const ACTIVE_MENU_ELEMENT_CLASS = "focus";
const MENU_ELEMENTS_SELECTORS = [":scope > li.o_dropdown_item", ":scope > li.o_dropdown"];
const MENU_ELEMENTS_SELECTORS = [":scope > .dropdown-item", ":scope > .dropdown"];
const NEXT_ACTIVE_INDEX_FNS = {
FIRST: () => 0,
LAST: (list) => list.length - 1,
@@ -33,12 +34,30 @@ export function useDropdownNavigation() {
/** @type {import("./dropdown").Dropdown} */
const comp = useComponent();
// As this navigation hook relies on clicking ".o_dropdown_toggler" elements,
// As this navigation hook relies on clicking ".dropdown-toggle" elements,
// it is incompatible with a toggler="parent" strategy for subdropdowns.
if (comp.hasParentDropdown && comp.props.toggler === "parent") {
throw new Error("A nested Dropdown must use its standard toggler");
}
// Needed to avoid unwanted mouseclick behavior on a subdropdown toggler.
const originalOnTogglerClick = comp.onTogglerClick.bind(comp);
comp.onTogglerClick = (ev) => {
if (comp.hasParentDropdown && !ev.__fromDropdownNavigation) {
return;
}
originalOnTogglerClick();
};
// Needed to avoid unwanted mouseenter behavior on a subdropdown toggler.
const originalOnTogglerMouseEnter = comp.onTogglerMouseEnter.bind(comp);
comp.onTogglerMouseEnter = () => {
if (comp.hasParentDropdown) {
return;
}
originalOnTogglerMouseEnter();
};
// Needed to avoid unwanted selection when the mouse pointer is not in use
// but still somewhere in the middle of the dropdown menu list.
let mouseSelectionActive = true;
@@ -56,20 +75,17 @@ export function useDropdownNavigation() {
/** @type {NodeListOf<HTMLElement>} */
const queryResult = menuRef.el.querySelectorAll(MENU_ELEMENTS_SELECTORS.join());
for (const el of queryResult) {
const isSubDropdown = el.classList.contains("o_dropdown");
const isSubDropdown = el.classList.contains("dropdown");
const isSubDropdownOpen = () => el.classList.contains("show");
const togglerClick = () => {
const toggler = el.querySelector(":scope > .o_dropdown_toggler");
if (toggler) {
toggler.dispatchEvent(new MouseEvent("click", { bubbles: false }));
}
};
const navTarget = isSubDropdown ? el.querySelector(":scope > .dropdown-toggle") : el;
let subDropdownTimeout;
const closeSubDropdown = () => {
browser.clearTimeout(subDropdownTimeout);
subDropdownTimeout = browser.setTimeout(() => {
if (isSubDropdownOpen()) {
togglerClick();
const ev = new MouseEvent("click", { bubbles: false });
ev.__fromDropdownNavigation = true;
navTarget.dispatchEvent(ev);
}
}, 200);
};
@@ -78,7 +94,9 @@ export function useDropdownNavigation() {
subDropdownTimeout = browser.setTimeout(
() => {
if (!isSubDropdownOpen()) {
togglerClick();
const ev = new MouseEvent("click", { bubbles: false });
ev.__fromDropdownNavigation = true;
navTarget.dispatchEvent(ev);
}
},
immediate ? 0 : 200
@@ -90,22 +108,23 @@ export function useDropdownNavigation() {
if (menuElement.el === el) {
continue;
}
menuElement.el.classList.remove(ACTIVE_MENU_ELEMENT_CLASS);
menuElement.navTarget.classList.remove(ACTIVE_MENU_ELEMENT_CLASS);
if (menuElement.isSubDropdown) {
menuElement.closeSubDropdown();
}
}
// Make myself active
el.classList.add(ACTIVE_MENU_ELEMENT_CLASS);
navTarget.classList.add(ACTIVE_MENU_ELEMENT_CLASS);
};
/** @type {MenuElement} */
const menuElement = {
el,
get isActive() {
return el.classList.contains(ACTIVE_MENU_ELEMENT_CLASS);
return navTarget.classList.contains(ACTIVE_MENU_ELEMENT_CLASS);
},
makeOnlyActive,
navTarget,
get isSubDropdownOpen() {
return isSubDropdownOpen();
},
@@ -117,12 +136,11 @@ export function useDropdownNavigation() {
// Set up selection listeners
const elementListeners = {
click: makeOnlyActive,
mouseenter: () => {
if (!mouseSelectionActive) {
mouseSelectionActive = true;
} else {
menuElement.makeOnlyActive();
makeOnlyActive();
if (isSubDropdown) {
openSubDropdown();
}
@@ -130,18 +148,18 @@ export function useDropdownNavigation() {
},
};
for (const [eventType, listener] of Object.entries(elementListeners)) {
menuElement.el.addEventListener(eventType, listener);
navTarget.addEventListener(eventType, listener);
}
addedListeners.push([menuElement, elementListeners]);
addedListeners.push([navTarget, elementListeners]);
}
return () => {
menuElements = [];
mouseSelectionActive = true;
// Clear mouse selection listeners
for (const [menuElement, listeners] of addedListeners) {
for (const [navTarget, listeners] of addedListeners) {
for (const [eventType, listener] of Object.entries(listeners)) {
menuElement.el.removeEventListener(eventType, listener);
navTarget.removeEventListener(eventType, listener);
}
}
};
@@ -191,6 +209,16 @@ export function useDropdownNavigation() {
menuElement.openSubDropdown(true);
}
};
const selectActiveMenuElement = () => {
const menuElement = getActiveMenuElement();
if (menuElement) {
if (menuElement.isSubDropdown) {
menuElement.openSubDropdown(true);
} else {
menuElement.navTarget.click();
}
}
};
let hotkeyRemoves = [];
const hotkeyCallbacks = {
home: () => setActiveMenuElement("FIRST"),
@@ -201,16 +229,7 @@ export function useDropdownNavigation() {
arrowup: () => setActiveMenuElement("PREV"),
arrowleft: localization.direction === "rtl" ? openSubDropdown : closeSubDropdown,
arrowright: localization.direction === "rtl" ? closeSubDropdown : openSubDropdown,
enter: () => {
const menuElement = getActiveMenuElement();
if (menuElement) {
if (menuElement.isSubDropdown) {
menuElement.openSubDropdown(true);
} else {
menuElement.el.click();
}
}
},
enter: selectActiveMenuElement,
escape: comp.close,
};
useEffect(
@@ -333,6 +333,18 @@ var FieldMany2One = AbstractField.extend({
html: true,
minLength: 0,
delay: this.AUTOCOMPLETE_DELAY,
classes: {
"ui-autocomplete": "dropdown-menu",
},
create: function() {
$(this).data('ui-autocomplete')._renderMenu = function(ulWrapper, entries) {
var render = this;
$.each(entries, function(index, entry) {
render._renderItemData(ulWrapper, entry);
});
$(ulWrapper).find( "li > a" ).addClass( "dropdown-item" );
}
},
});
this.$input.autocomplete("option", "position", { my : "left top", at: "left bottom" });
this.autocomplete_bound = true;
@@ -193,7 +193,7 @@ var GraphController = AbstractController.extend({
this.groupByMenu.el.classList.add('o_group_by_menu');
}
// Update button classes accordingly to the current mode
const buttons = actionsContainer.querySelectorAll('.o_dropdown_toggler');
const buttons = actionsContainer.querySelectorAll('.dropdown-toggle');
for (const button of buttons) {
button.classList.remove('btn-secondary');
if (this.isEmbedded) {
@@ -33,8 +33,6 @@ $danger: $o-danger !default;
//
// Quickly modify global styling by enabling or disabling optional features.
$enable-caret: false !default; // Disable pseudo element caret on .dropdown-toggle.
// Body
//
// Settings for the `<body>` element.
@@ -1,7 +1,6 @@
// = jQueryUI dropdown adaptations
// ----------------------------------------------------------------------------
.ui-widget.ui-autocomplete {
@extend .dropdown-menu;
top: 0; // Compute initial position
min-width: 0; // Compute initial width
@@ -10,11 +9,10 @@
z-index: $zindex-modal + 1;
.ui-menu-item {
padding: 0;
> a {
@extend .dropdown-item;
&.dropdown-item {
padding: $o-dropdown-vpadding $o-dropdown-hpadding;
}
&.ui-state-active {
margin: 0;
border: none;
+37 -40
View File
@@ -44,6 +44,7 @@
height: $o-navbar-height;
cursor: pointer;
user-select: none;
background: transparent;
@include o-hover-text-color(rgba($white, .9), $white);
}
@@ -61,8 +62,6 @@
%-main-navbar-entry-active {
@include plain-hover-focus() {
background: $-backend-entry-hover-bg;
outline: 1px solid $o-navbar-inverse-link-hover-bg;
outline-offset: -1px;
color: $white;
}
}
@@ -77,54 +76,53 @@
// = Owl <Dropdown> components
// --------------------------------------------------------------------------
.o_dropdown .o_dropdown_toggler {
@extend %-main-navbar-entry-base;
@extend %-main-navbar-entry-padding;
@extend %-main-navbar-entry-bg-hover;
border: 0;
white-space: nowrap;
&,.o_menu_sections, .o_menu_systray {
&, &:focus {
background: none;
}
}
.o-dropdown {
&.show > .o_dropdown_toggler {
@extend %-main-navbar-entry-active;
}
}
.o_nav_entry {
padding: 0;
@extend %-main-navbar-entry-base;
@extend %-main-navbar-entry-padding;
}
.o_dropdown .o_dropdown_menu, .dropdown-menu {
margin-top: 0;
border-top: 0;
@include border-top-radius(0);
}
.o_dropdown_menu_group_entry.o_dropdown_item {
> *:first-child {
padding-left: $o-dropdown-hpadding * 1.5;
.dropdown .dropdown-toggle {
@extend %-main-navbar-entry-base;
@extend %-main-navbar-entry-padding;
@extend %-main-navbar-entry-bg-hover;
border: 0;
white-space: nowrap;
}
+ .o_dropdown_item:not(.o_dropdown_menu_group_entry) {
margin-top: .8em;
.dropdown {
&.show > .dropdown-toggle {
@extend %-main-navbar-entry-active;
}
}
.o_nav_entry {
@extend %-main-navbar-entry-base;
@extend %-main-navbar-entry-padding;
@extend %-main-navbar-entry-bg-hover;
}
.dropdown-menu {
margin-top: 0;
border-top: 0;
@include border-top-radius(0);
}
.o_dropdown_menu_group_entry {
> .dropdown-item {
padding-left: $o-dropdown-hpadding * 1.5;
}
+ .dropdown-item:not(.o_dropdown_menu_group_entry) {
margin-top: .8em;
}
}
}
// = Legacy elements adaptations
// --------------------------------------------------------------------------
> ul {
> li > a, > li > label:only-child, .o_dropdown_toggler {
> .o_menu_sections, .o_menu_systray {
> div > a, > div > label:only-child, .dropdown-toggle, .dropdown-toggle.o-dropdown--narrow {
padding: 0 $dropdown-item-padding-x * .5;
}
> li {
> div {
&, > a {
@extend %-main-navbar-entry-base;
}
@@ -138,7 +136,6 @@
}
}
}
// = Main Navbar Elements
// --------------------------------------------------------------------------
.o_menu_toggle, .o_navbar_apps_menu {
+7 -1
View File
@@ -383,9 +383,15 @@
padding: 0;
> a {
@extend .dropdown-item;
margin: auto auto auto (-$link-padding-gap);
padding: 3px $link-padding-gap;
color: $dropdown-link-color;
display: block;
&:hover {
background-color: $dropdown-link-hover-bg;
color: $dropdown-link-hover-color;
}
}
&:last-child {
+6 -6
View File
@@ -152,9 +152,9 @@
</t>
<t t-name="web.DropdownMenu" owl="1">
<div class="btn-group o_dropdown" t-att-class="{ show: state.open }">
<div class="btn-group dropdown" t-att-class="{ show: state.open }">
<button type="button"
class="o_dropdown_toggler btn btn-secondary"
class="dropdown-toggle btn btn-secondary o-no-caret"
t-attf-class="{{ icon || displayCaret || displayChevron ? 'd-flex align-items-center ' : ''}} {{ displayCaret || displayChevron ? 'pr-1 ' : ''}}"
t-att-aria-expanded="state.open ? 'true' : 'false'"
tabindex="-1"
@@ -168,7 +168,7 @@
<i t-if="displayCaret" t-attf-class="o_dropdown_caret fa fa-fw fa-caret-{{ state.open ? 'down' : 'right' }}"/>
<i t-elif="displayChevron" t-attf-class="o_dropdown_chevron fa fa-fw ml-auto fa-chevron-{{ state.open ? 'down' : 'right' }}"/>
</button>
<ul t-if="state.open" t-attf-class="o_dropdown_menu dropdown-menu {{ dropdownMenuAlignClass }} show" role="menu"
<ul t-if="state.open" t-attf-class="dropdown-menu {{ dropdownMenuAlignClass }} show" role="menu"
t-on-item-selected="_onItemSelected"
>
<t t-set="currentGroup" t-value="null"/>
@@ -370,7 +370,7 @@
t-on-keydown="_onSearchKeydown"
t-on-input="_onSearchInput"
/>
<ul t-if="state.sources.length" class="o_dropdown_menu o_searchview_autocomplete dropdown-menu show" role="menu">
<ul t-if="state.sources.length" class="dropdown-menu o_searchview_autocomplete dropdown-menu show" role="menu">
<li t-foreach="state.sources" t-as="src" t-key="src.id"
t-att-id="src.id"
class="o_menu_item"
@@ -423,7 +423,7 @@
<t t-set="buttonLabel">View %s</t>
<button type="button"
t-att-accesskey="view.accessKey"
t-attf-class="btn btn-secondary fa fa-lg o_switch_view o_{{ view.type }} {{ view.icon }}"
t-attf-class="btn btn-light fa fa-lg o_switch_view o_{{ view.type }} {{ view.icon }}"
t-att-class="{ active: env.view.type === view.type }"
t-att-aria-label="sprintf(buttonLabel.toString(), view.type)"
t-att-data-tooltip="view.name"
@@ -760,7 +760,7 @@
<t t-name="PivotView.buttons">
<div class="btn-group" role="toolbar" aria-label="Main actions">
<button class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<button class="btn btn-primary dropdown-toggle o-no-caret" data-toggle="dropdown" aria-expanded="false">
Measures <i class="fa fa-caret-down ml-1"/>
</button>
<div class="dropdown-menu o_pivot_measures_list" role="menu">
+1 -1
View File
@@ -93,7 +93,7 @@
<li class="dropdown-divider" role="separator"/>
</t>
<t t-if="(item.fieldType === 'date') || (item.fieldType === 'datetime')">
<Dropdown tag="'li'" togglerClass="'o_menu_item dropdown-item'">
<Dropdown togglerClass="'o_menu_item dropdown-item'">
<t t-set-slot="toggler">
<t t-esc="item.description"/>
</t>
@@ -2,22 +2,19 @@
<templates xml:space="preserve">
<t t-name="web.ComparisonMenu" owl="1">
<Dropdown class="o_comparison_menu btn-group"
togglerClass="'btn btn-secondary'"
t-on-dropdown-item-selected="onComparisonSelected"
>
<Dropdown class="o_comparison_menu btn-group" togglerClass="'btn btn-light'" t-on-dropdown-item-selected="onComparisonSelected">
<t t-set-slot="toggler">
<i class="small mr-1" t-att-class="icon"/>
<span class="o_dropdown_title">Comparison</span>
</t>
<t t-foreach="items" t-as="item" t-key="item.id">
<DropdownItem class="o_menu_item dropdown-item"
<DropdownItem
class="o_menu_item"
t-att-class="{ selected: item.isActive }"
payload="{ itemId: item.id }"
parentClosingMode="'none'"
>
<t t-esc="item.description"/>
</DropdownItem>
t-esc="item.description"
/>
</t>
</Dropdown>
</t>
@@ -61,7 +61,7 @@
top: 5px;
right: 10px;
}
.o_dropdown_toggler {
.dropdown-toggle {
margin-right: 15px;
}
}
@@ -86,10 +86,15 @@
}
}
> .o_cp_switch_buttons > .btn:first-child {
margin-left: $o-horizontal-padding;
@include media-breakpoint-down(sm) {
margin-left: 0;
> .o_cp_switch_buttons {
> .btn:first-child {
margin-left: $o-horizontal-padding;
@include media-breakpoint-down(sm) {
margin-left: 0;
}
}
.dropdown-menu {
right: 0;
}
}
}
@@ -33,7 +33,7 @@
<t t-if="(env.config.viewSwitcherEntries or []).length">
<nav class="btn-group o_cp_switch_buttons">
<t t-foreach="env.config.viewSwitcherEntries" t-as="view" t-key="view.type">
<button class="btn btn-secondary fa fa-lg o_switch_view "
<button class="btn btn-light fa fa-lg o_switch_view "
t-attf-class="o_{{view.type}} {{view.icon}} {{view.active ? 'active' : ''}}"
t-att-data-tooltip="view.name"
t-on-click="onViewClicked(view.type)"
@@ -6,7 +6,7 @@
<t t-set-slot="toggler">
Save current search
</t>
<li class="px-3 py-2">
<div class="px-3 py-2">
<input type="text"
class="o_input"
autofocus=""
@@ -20,12 +20,12 @@
<CheckBox value="state.isShared" t-on-change="onShareCheckboxChange">
Share with all users
</CheckBox>
</li>
<li class="px-3 py-2">
</div>
<div class="px-3 py-2">
<button class="o_save_favorite btn btn-primary" t-on-click="saveFavorite">
Save
</button>
</li>
</div>
</Dropdown>
</t>
@@ -2,10 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.FavoriteMenu" owl="1">
<Dropdown class="o_favorite_menu btn-group"
togglerClass="'btn btn-secondary'"
t-on-dropdown-item-selected="onFavoriteSelected"
>
<Dropdown class="o_favorite_menu btn-group" togglerClass="'btn btn-light'" t-on-dropdown-item-selected="onFavoriteSelected">
<t t-set-slot="toggler">
<i class="small mr-1" t-att-class="icon"/>
<span class="o_dropdown_title">Favorites</span>
@@ -13,10 +10,10 @@
<t t-set="currentGroup" t-value="null"/>
<t t-foreach="items" t-as="item" t-key="item.id or item.key">
<t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
<li role="separator" class="dropdown-divider"/>
<div role="separator" class="dropdown-divider"/>
</t>
<t t-if="item.type ==='favorite'">
<DropdownItem class="o_menu_item dropdown-item"
<DropdownItem class="o_menu_item"
t-att-class="{ selected: item.isActive }"
payload="{ itemId: item.id }"
parentClosingMode="'none'"
@@ -7,7 +7,7 @@
Add Custom Filter
</t>
<t t-foreach="conditions" t-as="condition" t-key="condition_index">
<li class=" o_filter_condition dropdown-item-text">
<div class=" o_filter_condition dropdown-item-text">
<t t-set="fieldType" t-value="fields[condition.field].type"/>
<t t-set="selectedOperator" t-value="OPERATORS[FIELD_TYPES[fieldType]][condition.operator]"/>
<span t-if="!condition_first" class="o_or_filter">or</span>
@@ -89,9 +89,9 @@
title="Delete"
t-on-click="onRemoveCondition(condition_index)"
/>
</li>
</div>
</t>
<DropdownItem class="px-3 py-2" t-att-class="{ o_dropdown_item: false }">
<div class="px-3 py-2">
<button type="button"
class="btn btn-primary o_apply_filter mr-2"
t-on-click="onApply"
@@ -105,7 +105,7 @@
<i class="fa fa-plus-circle"/>
<t>Add a condition</t>
</button>
</DropdownItem>
</div>
</Dropdown>
</t>
@@ -2,10 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.FilterMenu" owl="1">
<Dropdown class="o_filter_menu btn-group"
togglerClass="'btn btn-secondary'"
t-on-dropdown-item-selected="onFilterSelected"
>
<Dropdown class="o_filter_menu btn-group" togglerClass="'btn btn-light'" t-on-dropdown-item-selected="onFilterSelected">
<t t-set-slot="toggler">
<i class="small mr-1" t-att-class="icon"/>
<span class="o_dropdown_title">Filters</span>
@@ -13,44 +10,42 @@
<t t-set="currentGroup" t-value="null"/>
<t t-foreach="items" t-as="item" t-key="item.id">
<t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
<li class="dropdown-divider" role="separator"/>
<div class="dropdown-divider" role="separator"/>
</t>
<t t-if="item.options">
<Dropdown tag="'li'" togglerClass="'o_menu_item dropdown-item' + (item.isActive ? ' selected' : '')">
<Dropdown togglerClass="'o_menu_item' + (item.isActive ? ' selected' : '')">
<t t-set-slot="toggler">
<t t-esc="item.description"/>
</t>
<t t-set="subGroup" t-value="null"/>
<t t-foreach="item.options" t-as="option" t-key="option.id">
<t t-if="subGroup !== null and subGroup !== option.groupNumber">
<li class="dropdown-divider" role="separator"/>
<div class="dropdown-divider" role="separator"/>
</t>
<DropdownItem class="o_item_option dropdown-item"
<DropdownItem class="o_item_option"
t-att-class="{ selected: option.isActive }"
payload="{ itemId: item.id, optionId: option.id }"
parentClosingMode="'none'"
>
<t t-esc="option.description"/>
</DropdownItem>
t-esc="option.description"
/>
<t t-set="subGroup" t-value="option.groupNumber"/>
</t>
</Dropdown>
</t>
<t t-else="">
<DropdownItem class="o_menu_item dropdown-item"
<DropdownItem class="o_menu_item"
t-att-class="{ selected: item.isActive }"
payload="{ itemId: item.id }"
parentClosingMode="'none'"
>
<t t-esc="item.description"/>
</DropdownItem>
t-esc="item.description"
/>
</t>
<t t-set="currentGroup" t-value="item.groupNumber"/>
</t>
<t t-if="items.length">
<li role="separator" class="dropdown-divider"/>
<div role="separator" class="dropdown-divider"/>
</t>
<CustomFilterItem t-on-dropdown-item-selected.stop=""/>
<CustomFilterItem/>
</Dropdown>
</t>
@@ -5,19 +5,19 @@
<t t-set-slot="toggler">
Add Custom Group
</t>
<li class="px-3 py-2">
<div class="px-3 py-2">
<select class="w-100 o_input" t-model="state.fieldName">
<option t-foreach="props.fields" t-as="field" t-key="field.name"
t-att-value="field.name"
t-esc="field.string"
/>
</select>
</li>
<DropdownItem class="px-3 py-2" t-att-class="{ o_dropdown_item: false }">
</div>
<div class="px-3 py-2">
<button class="btn btn-primary w-100" t-on-click="onApply">
Apply
</button>
</DropdownItem>
</div>
</Dropdown>
</t>
@@ -3,10 +3,10 @@
<t t-name="web.GroupByMenu" owl="1">
<Dropdown class="o_group_by_menu btn-group"
togglerClass="'btn btn-secondary'"
togglerClass="'btn btn-light'"
t-props="dropdownProps"
t-on-dropdown-item-selected="onGroupBySelected"
>
>
<t t-set-slot="toggler">
<i class="small mr-1" t-att-class="icon"/>
<span class="o_dropdown_title">Group By<t t-if="props.showCaretDown"> <i class="fa fa-caret-down ml-1"/></t></span>
@@ -14,43 +14,41 @@
<t t-set="currentGroup" t-value="null"/>
<t t-foreach="items" t-as="item" t-key="item.id">
<t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
<li class="dropdown-divider" role="separator"/>
<div class="dropdown-divider" role="separator"/>
</t>
<t t-if="item.options">
<Dropdown tag="'li'" togglerClass="'o_menu_item dropdown-item' + (item.isActive ? ' selected' : '')">
<Dropdown togglerClass="'o_menu_item' + (item.isActive ? ' selected' : '')">
<t t-set-slot="toggler">
<t t-esc="item.description"/>
</t>
<t t-set="subGroup" t-value="null"/>
<t t-foreach="item.options" t-as="option" t-key="option.id">
<t t-if="subGroup !== null and subGroup !== option.groupNumber">
<li class="dropdown-divider" role="separator"/>
<div class="dropdown-divider" role="separator"/>
</t>
<DropdownItem class="o_item_option dropdown-item"
<DropdownItem class="o_item_option"
t-att-class="{ selected: option.isActive }"
payload="{ itemId: item.id, optionId: option.id }"
parentClosingMode="'none'"
>
<t t-esc="option.description"/>
</DropdownItem>
t-esc="option.description"
/>
<t t-set="subGroup" t-value="option.groupNumber"/>
</t>
</Dropdown>
</t>
<t t-else="">
<DropdownItem class="o_menu_item dropdown-item"
<DropdownItem class="o_menu_item"
t-att-class="{ selected: item.isActive }"
payload="{ itemId: item.id }"
parentClosingMode="'none'"
>
<t t-esc="item.description"/>
</DropdownItem>
t-esc="item.description"
/>
</t>
<t t-set="currentGroup" t-value="item.groupNumber"/>
</t>
<t t-if="fields.length">
<li t-if="items.length" role="separator" class="dropdown-divider"/>
<CustomGroupByItem fields="fields" t-on-dropdown-item-selected.stop="" t-on-add-custom-group="onAddCustomGroup"/>
<div t-if="items.length" role="separator" class="dropdown-divider"/>
<CustomGroupByItem fields="fields" t-on-add-custom-group="onAddCustomGroup"/>
</t>
</Dropdown>
</t>
@@ -47,7 +47,7 @@
</t>
<t t-name="web.SearchBar.Items" owl="1">
<ul class="o_dropdown_menu o_searchview_autocomplete dropdown-menu show" role="menu">
<ul class="dropdown-menu o_searchview_autocomplete dropdown-menu show" role="menu">
<t t-foreach="items" t-as="item" t-key="item.id">
<li class="o_menu_item"
t-att-class="{ o_indent: item.isChild, o_selection_focus: item_index === state.focusedIndex}"
@@ -53,7 +53,6 @@
}" t-attf-style="{{ isXAxis ? undefined : 'padding-left: ' + getPadding(cell) + 'px;' }}" t-att-data-tooltip="cell.label" data-tooltip-position="right" t-on-click.prevent="onHeaderClick(cell, isXAxis ? 'col' : 'row')">
<t t-if="cell.isLeaf and !cell.isFolded">
<PivotGroupByMenu
tag="'span'"
toggler="'parent'"
cell="cell"
customGroupBys="model.metaData.customGroupBys"
@@ -15,7 +15,7 @@
}
table {
th > .o_dropdown > .o_dropdown_toggler {
th > .dropdown > .dropdown-toggle {
background: none!important;
border: none!important;
padding: 0!important;
@@ -156,12 +156,12 @@
* Make sure the apps menu is open (community only)
*/
async function ensureAppsMenu() {
const appsMenu = document.querySelector(".o_navbar_apps_menu .o_dropdown_menu");
const appsMenu = document.querySelector(".o_navbar_apps_menu .dropdown-menu");
if (!appsMenu) {
const toggler = document.querySelector(".o_navbar_apps_menu .o_dropdown_toggler");
const toggler = document.querySelector(".o_navbar_apps_menu .dropdown-toggle");
await triggerClick(toggler, "apps menu toggle button");
await waitForCondition(() =>
document.querySelector(".o_navbar_apps_menu .o_dropdown_menu")
document.querySelector(".o_navbar_apps_menu .dropdown-menu")
);
}
}
@@ -173,14 +173,14 @@
*/
async function getNextMenu() {
const menus = document.querySelectorAll(
".o_menu_sections > .o_dropdown > .o_dropdown_toggler, .o_menu_sections > .o_dropdown_item"
".o_menu_sections > .dropdown > .dropdown-toggle, .o_menu_sections > .dropdown-item"
);
if (menuIndex === menus.length) {
menuIndex = 0;
return; // all menus done
}
let menu = menus[menuIndex];
if (menu.classList.contains("o_dropdown_toggler")) {
if (menu.classList.contains("dropdown-toggle")) {
// the current menu is a dropdown toggler -> open it and pick a menu inside the dropdown
if (!menu.nextSibling) {
// might already be opened if the last menu was blacklisted
@@ -191,7 +191,7 @@
menuIndex = 0; // empty More menu has no dropdown (FIXME?)
return;
}
const items = dropdown.querySelectorAll(".o_dropdown_item");
const items = dropdown.querySelectorAll(".dropdown-item");
menu = items[subMenuIndex];
if (subMenuIndex === items.length - 1) {
// this is the last item, so go to the next menu
@@ -220,7 +220,7 @@
apps = document.querySelectorAll(".o_apps .o_app");
} else {
await ensureAppsMenu();
apps = document.querySelectorAll(".o_navbar_apps_menu .o_dropdown_item");
apps = document.querySelectorAll(".o_navbar_apps_menu .dropdown-item");
}
const app = apps[appIndex];
appIndex++;
@@ -401,10 +401,10 @@
app = document.querySelector(`a.o_app.o_menuitem[data-menu-xmlid="${xmlId}"]`);
} else {
await triggerClick(
document.querySelector(".o_navbar_apps_menu .o_dropdown_toggler")
document.querySelector(".o_navbar_apps_menu .dropdown-toggle")
);
app = document.querySelector(
`.o_navbar_apps_menu .o_dropdown_item[data-menu-xmlid="${xmlId}"]`
`.o_navbar_apps_menu .dropdown-item[data-menu-xmlid="${xmlId}"]`
);
}
if (!app) {
@@ -17,8 +17,7 @@ export class MenuDropdown extends Dropdown {
useEffect(
() => {
if (this.props.xmlid) {
const toggler = this.el.querySelector("button.o_dropdown_toggler");
toggler.dataset.menuXmlid = this.props.xmlid;
this.togglerRef.el.dataset.menuXmlid = this.props.xmlid;
}
},
() => []
@@ -35,6 +34,9 @@ export class MenuItem extends DropdownItem {
super.setup();
useEffect(
() => {
if (this.props.payload.id) {
this.el.dataset.section = this.props.payload.id;
}
if (this.props.payload.xmlid) {
this.el.dataset.menuXmlid = this.props.payload.xmlid;
}
@@ -148,9 +150,9 @@ export class NavBar extends Component {
// Hide from normal menu
s.classList.add("d-none");
// Show inside "more" menu
const sectionId = s
.querySelector("[data-section]")
.getAttribute("data-section");
const sectionId =
s.dataset.section ||
s.querySelector("[data-section]").getAttribute("data-section");
const currentAppSection = this.currentAppSections.find(
(appSection) => appSection.id.toString() === sectionId
);
@@ -14,13 +14,13 @@
</t>
<!-- App Brand -->
<a
<MenuItem
t-if="currentApp"
t-att-href="getMenuItemHref(currentApp)"
href="getMenuItemHref(currentApp)"
payload="currentApp"
t-esc="currentApp.name"
class="o_menu_brand"
t-ref="menuBrand"
t-on-click.prevent="menuService.selectMenu(currentApp)"
/>
<!-- Current App Sections -->
@@ -29,7 +29,7 @@
</t>
<!-- Systray -->
<ul class="o_menu_systray" role="menu">
<div class="o_menu_systray" role="menu">
<t t-foreach="systrayItems" t-as="item" t-key="item_index">
<NotUpdatable>
<ErrorHandler onError="error => handleItemError(error, item)">
@@ -37,7 +37,7 @@
</ErrorHandler>
</NotUpdatable>
</t>
</ul>
</div>
</nav>
</header>
</t>
@@ -53,15 +53,15 @@
t-key="app.id"
class="o_app"
t-att-class="{ focus: menuService.getCurrentApp() === app }"
href="getMenuItemHref(app)"
payload="app"
>
<a t-att-href="getMenuItemHref(app)" t-esc="app.name" t-on-click.prevent="" />
</MenuItem>
t-esc="app.name"
/>
</Dropdown>
</t>
<t t-name="web.NavBar.SectionsMenu" owl="1">
<ul class="o_menu_sections" t-ref="appSubMenus" role="menu">
<div class="o_menu_sections" t-ref="appSubMenus" role="menu">
<t t-foreach="sections" t-as="section" t-key="section.id">
<t
@@ -77,18 +77,17 @@
</t>
<t t-if="!section.childrenTree.length">
<MenuItem title="section.name" class="o_nav_entry" payload="section" hotkey="hotkey">
<a
t-att-href="getMenuItemHref(section)"
t-on-click.prevent=""
t-att-data-section="section.id"
t-esc="section.name"
/>
</MenuItem>
<MenuItem
title="section.name"
class="o_nav_entry"
payload="section"
href="getMenuItemHref(section)"
hotkey="hotkey"
t-esc="section.name"
/>
</t>
<t t-else="">
<MenuDropdown
tag="'li'"
hotkey="hotkey"
title="section.name"
xmlid="section.xmlid"
@@ -110,44 +109,52 @@
</t>
</t>
</ul>
</div>
</t>
<t t-name="web.NavBar.SectionsMenu.Dropdown.MenuSlot" owl="1">
<t t-foreach="items" t-as="item" t-key="item.id">
<MenuItem t-if="!item.childrenTree.length" payload="item">
<a t-att-href="getMenuItemHref(item)" t-esc="item.name" t-on-click.prevent="" />
</MenuItem>
<MenuItem
t-if="!item.childrenTree.length"
payload="item"
href="getMenuItemHref(item)"
class="dropdown-item"
t-esc="item.name"
/>
<t t-else="">
<div class="o_dropdown_menu_group" t-esc="item.name" />
<div class="dropdown-menu_group dropdown-header" t-esc="item.name" />
<MenuItem
t-foreach="item.childrenTree"
t-as="subItem"
t-key="subItem.id"
class="o_dropdown_menu_group_entry"
payload="subItem">
<a t-att-href="getMenuItemHref(subItem)" t-esc="subItem.name" t-on-click.prevent="" />
</MenuItem>
class="o_dropdown_menu_group_entry dropdown-item"
payload="subItem"
href="getMenuItemHref(subItem)"
t-esc="subItem.name"
/>
</t>
</t>
</t>
<t t-name="web.NavBar.SectionsMenu.MoreDropdown" owl="1">
<Dropdown tag="'li'" class="o_menu_sections_more" title="'More Menu'" hotkey="hotkey">
<Dropdown class="o_menu_sections_more" title="'More Menu'" hotkey="hotkey">
<t t-set-slot="toggler">
<i class="fa fa-plus" />
</t>
<t t-foreach="sections" t-as="section" t-key="section.id">
<t t-if="!section.childrenTree.length">
<MenuItem class="o_more_dropdown_section" payload="section">
<a t-att-href="getMenuItemHref(section)" t-esc="section.name" t-on-click.prevent="" />
</MenuItem>
<MenuItem
class="o_more_dropdown_section"
payload="section"
href="getMenuItemHref(section)"
t-esc="section.name"
/>
</t>
<t t-else="">
<div
class="o_more_dropdown_section o_more_dropdown_section_group bg-200 font-weight-bold"
class="o_more_dropdown_section o_more_dropdown_section_group dropdown-header bg-100"
t-esc="section.name"
/>
<t t-call="web.NavBar.SectionsMenu.Dropdown.MenuSlot">
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.SwitchCompanyMenu" owl="1">
<Dropdown class="o_switch_company_menu" position="'bottom-end'" tag="'li'">
<Dropdown class="o_switch_company_menu" position="'bottom-end'">
<t t-set-slot="toggler">
<span t-attf-class="{{env.isSmall ? 'fa fa-building-o' : 'oe_topbar_name'}}">
<t t-if="!env.isSmall"><t t-esc="currentCompany.name"/></t>
@@ -2,18 +2,18 @@
<templates xml:space="preserve">
<t t-name="web.UserMenu" owl="1">
<Dropdown class="o_user_menu" tag="'li'" t-on-dropdown-item-selected.stop="onDropdownItemSelected">
<Dropdown class="o_user_menu" t-on-dropdown-item-selected.stop="onDropdownItemSelected">
<t t-set-slot="toggler">
<img class="rounded-circle o_user_avatar" t-att-src="source" alt="User"/> <span class="oe_topbar_name"><t t-esc="user.name"/><t t-if="env.debug" t-esc="' (' + user.db.name + ')'"/></span>
</t>
<t t-foreach="getElements()" t-as="element" t-key="element_index">
<t t-if="!element.hide">
<t t-if="element.type == 'item'">
<UserMenuItem payload="{ callback: element.callback, id: element.id }">
<a t-if="element.href" t-att-href="element.href" t-esc="element.description" t-on-click.prevent="" />
<span t-else="" t-esc="element.description"/>
</UserMenuItem>
</t>
<UserMenuItem
t-if="element.type == 'item'"
payload="{ callback: element.callback, id: element.id }"
href="element.href"
t-esc="element.description"
/>
<div t-if="element.type == 'separator'" role="separator" class="dropdown-divider"/>
</t>
</t>
@@ -142,6 +142,53 @@ div.o_boolean_toggle.custom-control.custom-checkbox {
}
}
// == Boostrap Dropdown
// ----------------------------------------------------------------------------
.dropdown-menu {
max-height: 70vh;
overflow: auto;
background-clip: border-box;
box-shadow: $o-dropdown-box-shadow;
}
.dropdown-item {
&.active, &.selected {
position: relative;
font-weight: $font-weight-bold;
&:focus, &:hover {
background-color: $dropdown-link-hover-bg;
}
&:not(.dropdown-item_active_noarrow) {
&:before {
@include o-position-absolute(0);
transform: translate(-1.5em, 90%);
font: .7em/1em FontAwesome;
color: $primary;
content: "\f00c";
}
&.disabled:before {
color: $dropdown-link-disabled-color;
}
}
}
}
.dropdown-header {
font-weight: $font-weight-bold;
padding-bottom: .1em;
&:not(:first-child) {
margin-top: .3em;
}
}
.dropdown-divider:first-child {
display: none;
}
//== Printing improvements
@media print {
.table-responsive {
@@ -103,18 +103,16 @@ QUnit.module("DebugMenu", (hooks) => {
const debugManager = await mount(DebugMenuParent, { env, target });
registerCleanup(() => debugManager.destroy());
let debugManagerEl = debugManager.el;
await click(debugManager.el.querySelector("button.o_dropdown_toggler"));
await click(debugManager.el.querySelector("button.dropdown-toggle"));
debugManagerEl = debugManager.el;
assert.containsN(debugManagerEl, "ul.o_dropdown_menu li.o_dropdown_item", 3);
assert.containsOnce(debugManagerEl, "li.dropdown-divider");
const children = [...(debugManagerEl.querySelector("ul.o_dropdown_menu").children || [])];
assert.containsN(debugManagerEl, ".dropdown-menu .dropdown-item", 3);
assert.containsOnce(debugManagerEl, ".dropdown-divider");
const children = [...(debugManagerEl.querySelector(".dropdown-menu").children || [])];
assert.deepEqual(
children.map((el) => el.tagName),
["LI", "LI", "LI", "LI"]
["SPAN", "SPAN", "DIV", "SPAN"]
);
const items =
[...debugManagerEl.querySelectorAll("ul.o_dropdown_menu li.o_dropdown_item span")] ||
[];
const items = [...debugManagerEl.querySelectorAll(".dropdown-menu .dropdown-item")] || [];
assert.deepEqual(
items.map((el) => el.textContent),
["Item 2", "Item 1", "Item 3"]
@@ -161,10 +159,8 @@ QUnit.module("DebugMenu", (hooks) => {
const env = await makeTestEnv(testConfig);
const debugManager = await mount(DebugMenuParent, { env, target });
registerCleanup(() => debugManager.destroy());
await click(debugManager.el.querySelector("button.o_dropdown_toggler"));
const items = [
...debugManager.el.querySelectorAll("ul.o_dropdown_menu li.o_dropdown_item span"),
];
await click(debugManager.el.querySelector("button.dropdown-toggle"));
const items = [...debugManager.el.querySelectorAll(".dropdown-menu .dropdown-item")];
assert.deepEqual(
items.map((el) => el.textContent),
["Item 1", "Item 2", "Item 3", "Item 4"]
@@ -242,14 +238,10 @@ QUnit.module("DebugMenu", (hooks) => {
assert.containsOnce(target, ".o_dialog .o_debug_manager .fa-bug");
await click(target, ".o_dialog .o_debug_manager button");
const debugManagerEl = target.querySelector(".o_debug_manager");
assert.containsN(debugManagerEl, "ul.o_dropdown_menu li.o_dropdown_item", 2);
assert.containsN(debugManagerEl, ".dropdown-menu .dropdown-item", 2);
// Check that global debugManager elements are not displayed (global_1)
const items =
[
...debugManagerEl.querySelectorAll(
"ul.o_dropdown_menu li.o_dropdown_item span"
),
] || [];
[...debugManagerEl.querySelectorAll(".dropdown-menu .dropdown-item")] || [];
assert.deepEqual(
items.map((el) => el.textContent),
["Item 1", "Item 2"]
@@ -285,9 +277,9 @@ QUnit.module("DebugMenu", (hooks) => {
const env = await makeTestEnv(testConfig);
const debugManager = await mount(DebugMenuParent, { env, target });
registerCleanup(() => debugManager.destroy());
await click(debugManager.el.querySelector("button.o_dropdown_toggler"));
assert.containsOnce(debugManager.el, "ul.o_dropdown_menu li.o_dropdown_item");
const item = debugManager.el.querySelector("ul.o_dropdown_menu li.o_dropdown_item span");
await click(debugManager.el.querySelector("button.dropdown-toggle"));
assert.containsOnce(debugManager.el, ".dropdown-menu .dropdown-item");
const item = debugManager.el.querySelector(".dropdown-menu .dropdown-item");
assert.strictEqual(item.textContent, "Regenerate Assets Bundles");
await click(item);
assert.verifySteps(["ir.attachment/search", "ir.attachment/unlink", "reloadPage"]);
@@ -336,7 +328,7 @@ QUnit.module("DebugMenu", (hooks) => {
const webClient = await createWebClient({ serverData, mockRPC });
await click(webClient.el.querySelector(".o_debug_manager button"));
await click(webClient.el.querySelector(".o_debug_manager .o_dropdown_item"));
await click(webClient.el.querySelector(".o_debug_manager .dropdown-item"));
await legacyExtraNextTick();
assert.containsOnce(webClient, ".modal .o_list_view");
@@ -380,7 +372,7 @@ QUnit.module("DebugMenu", (hooks) => {
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 1234);
await click(webClient.el.querySelector(".o_debug_manager button"));
await click(webClient.el.querySelector(".o_debug_manager .o_dropdown_item"));
await click(webClient.el.querySelector(".o_debug_manager .dropdown-item"));
await legacyExtraNextTick();
assert.containsOnce(webClient, ".modal .o_form_view");
assert.strictEqual(
@@ -416,7 +408,7 @@ QUnit.module("DebugMenu", (hooks) => {
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 1);
await click(webClient.el.querySelector(".o_debug_manager button"));
await click(webClient.el.querySelector(".o_debug_manager .o_dropdown_item"));
await click(webClient.el.querySelector(".o_debug_manager .dropdown-item"));
await legacyExtraNextTick();
assert.containsOnce(webClient, ".modal .o_form_view");
assert.strictEqual(
@@ -446,7 +438,7 @@ QUnit.module("DebugMenu", (hooks) => {
// opens a form view in a dialog without a control panel.
await doAction(webClient, 5);
await click(webClient.el.querySelector(".o_dialog .o_debug_manager button"));
assert.containsNone(webClient, ".o_debug_manager .o_dropdown_item");
assert.containsNone(webClient, ".o_debug_manager .dropdown-item");
}
);
});
+220 -185
View File
@@ -1,19 +1,22 @@
/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { registry } from "@web/core/registry";
import { uiService } from "@web/core/ui/ui_service";
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { DropdownItem } from "../../src/core/dropdown/dropdown_item";
import { registerCleanup } from "../helpers/cleanup";
import { makeTestEnv } from "../helpers/mock_env";
import { makeFakeLocalizationService } from "../helpers/mock_services";
import {
click,
getFixture,
makeDeferred,
mouseEnter,
nextTick,
patchWithCleanup,
triggerHotkey,
} from "../helpers/utils";
import { registerCleanup } from "../helpers/cleanup";
import { makeFakeLocalizationService } from "../helpers/mock_services";
const { mount } = owl;
const serviceRegistry = registry.category("services");
@@ -28,6 +31,10 @@ QUnit.module("Components", ({ beforeEach }) => {
serviceRegistry.add("ui", uiService);
target = getFixture();
registerCleanup(() => parent.destroy());
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
});
QUnit.module("Dropdown");
@@ -39,31 +46,44 @@ QUnit.module("Components", ({ beforeEach }) => {
parent = await mount(Parent, { env, target });
assert.strictEqual(
parent.el.outerHTML,
'<div class="o_dropdown"><button class="o_dropdown_toggler "><span class="d-flex align-items-center"></span></button></div>'
'<div class="o-dropdown dropdown o-dropdown--no-caret"><button class="dropdown-toggle "></button></div>'
);
assert.containsOnce(parent.el, "button.o_dropdown_toggler");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, "button.dropdown-toggle");
assert.containsNone(parent.el, ".dropdown-menu");
});
QUnit.test("can be rendered (custom tag)", async (assert) => {
class Parent extends owl.Component {}
Parent.template = owl.tags.xml`<Dropdown tag="'ged'"/>`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.strictEqual(
parent.el.outerHTML,
'<ged class="o_dropdown"><button class="o_dropdown_toggler "><span class="d-flex align-items-center"></span></button></ged>'
);
assert.containsOnce(parent.el, "button.o_dropdown_toggler");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
});
QUnit.test("DropdownItem can be rendered", async (assert) => {
QUnit.test("DropdownItem can be rendered as <span/>", async (assert) => {
class Parent extends owl.Component {}
Parent.template = owl.tags.xml`<DropdownItem>coucou</DropdownItem>`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.strictEqual(parent.el.outerHTML, '<li class="o_dropdown_item">coucou</li>');
assert.strictEqual(parent.el.outerHTML, '<span class="dropdown-item">coucou</span>');
});
QUnit.test("DropdownItem (with href prop) can be rendered as <a/>", async (assert) => {
class Parent extends owl.Component {}
Parent.template = owl.tags.xml`<DropdownItem href="'#'">coucou</DropdownItem>`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.strictEqual(parent.el.outerHTML, '<a href="#" class="dropdown-item">coucou</a>');
});
QUnit.test("DropdownItem: prevents click default when item clicked", async (assert) => {
assert.expect(2);
// A DropdownItem should preventDefault a click as it may take the shape
// of an <a/> tag with an [href] attribute and e.g. could change the url when clicked.
patchWithCleanup(DropdownItem.prototype, {
onClick(ev) {
assert.ok(!ev.defaultPrevented);
this._super(...arguments);
assert.ok(ev.defaultPrevented);
},
});
class Parent extends owl.Component {}
Parent.template = owl.tags.xml`<DropdownItem/>`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(target, ".dropdown-item");
});
QUnit.test("can be styled", async (assert) => {
@@ -75,14 +95,14 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.o_dropdown_toggler");
assert.hasClass(parent.el, "o_dropdown one");
const toggler = parent.el.querySelector("button");
assert.hasClass(toggler, "o_dropdown_toggler two");
const menu = parent.el.querySelector("ul");
assert.hasClass(menu, "o_dropdown_menu three");
const item = parent.el.querySelector("li");
assert.hasClass(item, "o_dropdown_item four");
await click(parent.el, "button.dropdown-toggle");
assert.hasClass(parent.el, "dropdown one");
const toggler = parent.el.querySelector(".dropdown-toggle");
assert.hasClass(toggler, "two");
const menu = parent.el.querySelector(".dropdown-menu");
assert.hasClass(menu, "three");
const item = parent.el.querySelector(".dropdown-item");
assert.hasClass(item, "four");
});
QUnit.test("menu can be toggled", async (assert) => {
@@ -100,14 +120,14 @@ QUnit.module("Components", ({ beforeEach }) => {
Parent.template = owl.tags.xml`<Dropdown beforeOpen="beforeOpen"/>`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.o_dropdown_toggler");
await click(parent.el, "button.dropdown-toggle");
assert.verifySteps(["beforeOpen"]);
assert.containsNone(parent.el, "ul.o_dropdown_menu");
assert.containsNone(parent.el, ".dropdown-menu");
beforeOpenProm.resolve();
await nextTick();
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
await click(parent.el, "button.o_dropdown_toggler");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
await click(parent.el, "button.dropdown-toggle");
assert.containsNone(parent.el, ".dropdown-menu");
});
QUnit.test("initial open state can be true", async (assert) => {
@@ -124,7 +144,7 @@ QUnit.module("Components", ({ beforeEach }) => {
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.verifySteps(["beforeOpen"]);
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
});
QUnit.test("close on outside click", async (assert) => {
@@ -137,10 +157,10 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.o_dropdown_toggler");
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
await click(parent.el, "button.dropdown-toggle");
assert.containsOnce(parent.el, ".dropdown-menu");
await click(parent.el, "div.outside");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
assert.containsNone(parent.el, ".dropdown-menu");
});
QUnit.test("close on item selection", async (assert) => {
@@ -152,9 +172,9 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.o_dropdown_toggler");
await click(parent.el, "ul.o_dropdown_menu li");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
await click(parent.el, "button.dropdown-toggle");
await click(parent.el, ".dropdown-menu .dropdown-item");
assert.containsNone(parent.el, ".dropdown-menu");
});
QUnit.test("payload received on item selection", async (assert) => {
@@ -170,8 +190,8 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.o_dropdown_toggler");
await click(parent.el, "ul.o_dropdown_menu li");
await click(parent.el, "button.dropdown-toggle");
await click(parent.el, ".dropdown-menu .dropdown-item");
});
QUnit.test("multi-level dropdown: can be rendered and toggled", async (assert) => {
@@ -185,10 +205,10 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.o_dropdown_toggler:last-child");
await click(parent.el, "button.o_dropdown_toggler:last-child");
await click(parent.el, "button.o_dropdown_toggler:last-child");
assert.containsN(parent.el, "ul.o_dropdown_menu", 3);
await click(parent.el, "button.dropdown-toggle:last-child");
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
assert.containsN(parent.el, ".dropdown-menu", 3);
});
QUnit.test("multi-level dropdown: initial open state can be true", async (assert) => {
@@ -202,7 +222,7 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.containsN(parent.el, "ul.o_dropdown_menu", 3);
assert.containsN(parent.el, ".dropdown-menu", 3);
});
QUnit.test("multi-level dropdown: close on outside click", async (assert) => {
@@ -219,12 +239,12 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.o_dropdown_toggler:last-child");
await click(parent.el, "button.o_dropdown_toggler:last-child");
await click(parent.el, "button.o_dropdown_toggler:last-child");
assert.containsN(parent.el, "ul.o_dropdown_menu", 3);
await click(parent.el, "button.dropdown-toggle:last-child");
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
assert.containsN(parent.el, ".dropdown-menu", 3);
await click(parent.el, "div.outside");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
assert.containsNone(parent.el, ".dropdown-menu");
});
QUnit.test("multi-level dropdown: close on item selection", async (assert) => {
@@ -238,11 +258,17 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.o_dropdown_toggler:last-child");
await click(parent.el, "button.o_dropdown_toggler:last-child");
assert.containsN(parent.el, "ul.o_dropdown_menu", 2);
await click(parent.el, ".o_dropdown_item:not(.o_dropdown)");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
await click(parent.el, "button.dropdown-toggle:last-child");
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
assert.containsN(parent.el, ".dropdown-menu", 2);
assert.containsN(parent.el, ".dropdown-item", 2);
assert.containsOnce(
parent.el,
".dropdown-menu > .dropdown > .dropdown-toggle.dropdown-item"
);
assert.containsOnce(parent.el, ".dropdown-menu > .dropdown-item");
await click(parent.el, ".dropdown-menu > .dropdown-item");
assert.containsNone(parent.el, ".dropdown-menu");
});
QUnit.test("multi-level dropdown: parent closing modes on item selection", async (assert) => {
@@ -260,28 +286,28 @@ QUnit.module("Components", ({ beforeEach }) => {
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
// Open the 2-level dropdowns
await click(parent.el, "button.o_dropdown_toggler:last-child");
await click(parent.el, "button.o_dropdown_toggler:last-child");
assert.containsN(parent.el, "ul.o_dropdown_menu", 2);
await click(parent.el, "button.dropdown-toggle:last-child");
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
assert.containsN(parent.el, ".dropdown-menu", 2);
// Select item (parentClosingMode=none)
await click(parent.el, "li.item1");
assert.containsN(parent.el, "ul.o_dropdown_menu", 2);
await click(parent.el, ".item1");
assert.containsN(parent.el, ".dropdown-menu", 2);
// Select item (parentClosingMode=closest)
await click(parent.el, "li.item2");
assert.containsN(parent.el, "ul.o_dropdown_menu", 1);
await click(parent.el, ".item2");
assert.containsN(parent.el, ".dropdown-menu", 1);
// Reopen second level dropdown
await click(parent.el, "button.o_dropdown_toggler:last-child");
assert.containsN(parent.el, "ul.o_dropdown_menu", 2);
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
assert.containsN(parent.el, ".dropdown-menu", 2);
// Select item (parentClosingMode=all)
await click(parent.el, "li.item3");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
await click(parent.el, ".item3");
assert.containsNone(parent.el, ".dropdown-menu");
// Reopen the 2-level dropdowns
await click(parent.el, "button.o_dropdown_toggler:last-child");
await click(parent.el, "button.o_dropdown_toggler:last-child");
assert.containsN(parent.el, "ul.o_dropdown_menu", 2);
await click(parent.el, "button.dropdown-toggle:last-child");
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
assert.containsN(parent.el, ".dropdown-menu", 2);
// Select item (default should be parentClosingMode=all)
await click(parent.el, "li.item4");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
await click(parent.el, ".item4");
assert.containsNone(parent.el, ".dropdown-menu");
});
QUnit.test("multi-level dropdown: payload bubbles on item selection", async (assert) => {
@@ -300,11 +326,11 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
await click(parent.el, "button.o_dropdown_toggler:last-child");
await click(parent.el, "button.o_dropdown_toggler:last-child");
await click(parent.el, "button.dropdown-toggle:last-child");
await mouseEnter(parent.el, "button.dropdown-toggle:last-child");
// As two listeners are defined in the template,
// clicking once the item would execute the handler twice.
await click(parent.el, ".o_dropdown_item:not(.o_dropdown)");
await click(parent.el, ".dropdown-menu > .dropdown-item");
});
QUnit.test("multi-level dropdown: recursive template can be rendered", async (assert) => {
@@ -364,7 +390,7 @@ QUnit.module("Components", ({ beforeEach }) => {
env.qweb.addTemplate("recursive.Template", recursiveTemplate);
parent = await mount(Parent, { env, target });
assert.deepEqual(
[...parent.el.querySelectorAll("button,.o_dropdown_item:not(.o_dropdown)")].map(
[...parent.el.querySelectorAll("button,.dropdown-menu > .dropdown-item")].map(
(el) => el.textContent
),
[
@@ -413,34 +439,34 @@ QUnit.module("Components", ({ beforeEach }) => {
const one = parent.el.querySelector(".one");
await click(one, "button");
assert.verifySteps([]);
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(one, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
assert.containsOnce(one, ".dropdown-menu");
// Hover on TWO
const two = parent.el.querySelector(".two");
two.querySelector("button").dispatchEvent(new MouseEvent("mouseenter"));
assert.verifySteps(["beforeOpen"]);
await nextTick();
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsNone(two, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
assert.containsNone(two, ".dropdown-menu");
beforeOpenProm.resolve();
await nextTick();
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(two, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
assert.containsOnce(two, ".dropdown-menu");
// Hover on THREE
const three = parent.el.querySelector(".three");
three.querySelector("button").dispatchEvent(new MouseEvent("mouseenter"));
await nextTick();
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(three, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
assert.containsOnce(three, ".dropdown-menu");
// Click on OUTSIDE
await click(parent.el, "div.outside");
assert.containsNone(parent.el, "ul.o_dropdown_menu");
assert.containsNone(parent.el, ".dropdown-menu");
// Hover on ONE, TWO, THREE
one.querySelector("button").dispatchEvent(new MouseEvent("mouseenter"));
two.querySelector("button").dispatchEvent(new MouseEvent("mouseenter"));
three.querySelector("button").dispatchEvent(new MouseEvent("mouseenter"));
await nextTick();
assert.containsNone(parent.el, "ul.o_dropdown_menu");
assert.containsNone(parent.el, ".dropdown-menu");
}
);
@@ -459,13 +485,13 @@ QUnit.module("Components", ({ beforeEach }) => {
parent = await mount(Parent, { env, target });
// Click on FOO
await click(parent.el, ".foo button");
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
// Hover on BAR1
const bar1 = parent.el.querySelector(".bar1");
bar1.querySelector("button").dispatchEvent(new MouseEvent("mouseenter"));
await nextTick();
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsNone(bar1, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
assert.containsNone(bar1, ".dropdown-menu");
}
);
@@ -484,16 +510,16 @@ QUnit.module("Components", ({ beforeEach }) => {
parent = await mount(Parent, { env, target });
// Click on BAR1
await click(parent.el, ".bar1 button");
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
// Click on FOO
await click(parent.el, ".foo button");
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
// Hover on BAR1
const bar1 = parent.el.querySelector(".bar1");
bar1.querySelector("button").dispatchEvent(new MouseEvent("mouseenter"));
await nextTick();
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsNone(bar1, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
assert.containsNone(bar1, ".dropdown-menu");
}
);
@@ -511,23 +537,23 @@ QUnit.module("Components", ({ beforeEach }) => {
parent = await mount(Parent, { env, target });
// Click on one
await click(parent.el, ".one button");
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
// Click on two
await click(parent.el, ".two button");
assert.containsN(parent.el, "ul.o_dropdown_menu", 2);
assert.containsN(parent.el, ".dropdown-menu", 2);
// Click on one again
await click(parent.el, ".one button");
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsNone(parent.el.querySelector(".one"), "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
assert.containsNone(parent.el.querySelector(".one"), ".dropdown-menu");
// Hover on one
const one = parent.el.querySelector(".one");
one.querySelector("button").dispatchEvent(new MouseEvent("mouseenter"));
await nextTick();
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsNone(parent.el.querySelector(".one"), "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
assert.containsNone(parent.el.querySelector(".one"), ".dropdown-menu");
// Click outside
await click(parent.el, "div.outside");
assert.containsOnce(parent.el, "ul.o_dropdown_menu");
assert.containsOnce(parent.el, ".dropdown-menu");
});
QUnit.test("dropdowns keynav", async (assert) => {
@@ -547,20 +573,20 @@ QUnit.module("Components", ({ beforeEach }) => {
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.containsNone(parent.el, ".o_dropdown_menu", "menu is closed at start");
assert.containsNone(parent.el, ".dropdown-menu", "menu is closed at start");
triggerHotkey("m", true);
await nextTick();
assert.containsOnce(
parent.el,
".o_dropdown_menu",
".dropdown-menu",
"menu is opened after pressing the toggler hotkey"
);
// Navigate with arrows
assert.containsNone(
parent.el,
".o_dropdown_menu > .focus",
".dropdown-menu > .focus",
"menu should not have any active items"
);
@@ -585,41 +611,41 @@ QUnit.module("Components", ({ beforeEach }) => {
for (const step of scenarioSteps) {
triggerHotkey(step.hotkey);
await nextTick();
assert.hasClass(parent.el.querySelector(".o_dropdown_menu > .focus"), step.expected);
assert.hasClass(parent.el.querySelector(".dropdown-menu > .focus"), step.expected);
}
// Select last one activated in previous scenario (item1)
triggerHotkey("enter");
await nextTick();
assert.containsNone(parent.el, ".o_dropdown_menu", "menu is closed after item selection");
assert.containsNone(parent.el, ".dropdown-menu", "menu is closed after item selection");
// Reopen dropdown
triggerHotkey("m", true);
await nextTick();
assert.containsOnce(
parent.el,
".o_dropdown_menu",
".dropdown-menu",
"menu is opened after pressing the toggler hotkey"
);
// Select second item through data-hotkey attribute
triggerHotkey("2", true);
await nextTick();
assert.containsNone(parent.el, ".o_dropdown_menu", "menu is closed after item selection");
assert.containsNone(parent.el, ".dropdown-menu", "menu is closed after item selection");
// Reopen dropdown
triggerHotkey("m", true);
await nextTick();
assert.containsOnce(
parent.el,
".o_dropdown_menu",
".dropdown-menu",
"menu is opened after pressing the toggler hotkey"
);
// Close dropdown with keynav
triggerHotkey("escape");
await nextTick();
assert.containsNone(parent.el, ".o_dropdown_menu", "menu is closed after item selection");
assert.containsNone(parent.el, ".dropdown-menu", "menu is closed after item selection");
assert.verifySteps(["1", "2"], "items should have been selected in this order");
});
@@ -639,16 +665,16 @@ QUnit.module("Components", ({ beforeEach }) => {
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.containsOnce(parent, ".o_dropdown");
assert.containsNone(parent, ".o_dropdown .o_dropdown_menu");
assert.containsNone(parent, ".o_dropdown button.o_dropdown_toggler");
assert.containsOnce(parent, ".dropdown");
assert.containsNone(parent, ".dropdown .dropdown-menu");
assert.containsNone(parent, ".dropdown button.dropdown-toggle");
await click(parent.el, ".my_custom_toggler");
assert.containsOnce(parent, ".o_dropdown .o_dropdown_menu");
assert.containsN(parent, ".o_dropdown .o_dropdown_menu .o_dropdown_item", 2);
assert.containsOnce(parent, ".dropdown .dropdown-menu");
assert.containsN(parent, ".dropdown .dropdown-menu .dropdown-item", 2);
});
QUnit.test("multi-level dropdown: keynav", async (assert) => {
assert.expect(126);
assert.expect(125);
class Parent extends owl.Component {
onItemSelected(ev) {
const { payload } = ev.detail;
@@ -657,51 +683,21 @@ QUnit.module("Components", ({ beforeEach }) => {
}
Parent.template = owl.tags.xml`
<Dropdown class="first" hotkey="'1'" t-on-dropdown-item-selected="onItemSelected">
<DropdownItem class="first-first" payload="{val:'first-first'}" hotkey="'a'">O</DropdownItem>
<Dropdown tag="'li'" class="second" hotkey="'2'">
<DropdownItem class="second-first" payload="{val:'second-first'}" hotkey="'b'">O</DropdownItem>
<Dropdown tag="'li'" class="third" hotkey="'3'">
<DropdownItem class="third-first" payload="{val:'third-first'}" hotkey="'c'">O</DropdownItem>
<DropdownItem class="third-last" payload="{val:'third-last'}" hotkey="'d'">O</DropdownItem>
<DropdownItem class="first-first" payload="{val:'first-first'}">O</DropdownItem>
<Dropdown class="second">
<DropdownItem class="second-first" payload="{val:'second-first'}">O</DropdownItem>
<Dropdown class="third">
<DropdownItem class="third-first" payload="{val:'third-first'}">O</DropdownItem>
<DropdownItem class="third-last" payload="{val:'third-last'}">O</DropdownItem>
</Dropdown>
<DropdownItem class="second-last" payload="{val:'second-last'}" hotkey="'e'">O</DropdownItem>
<DropdownItem class="second-last" payload="{val:'second-last'}">O</DropdownItem>
</Dropdown>
<DropdownItem class="first-last" payload="{val:'first-last'}" hotkey="'f'">O</DropdownItem>
<DropdownItem class="first-last" payload="{val:'first-last'}">O</DropdownItem>
</Dropdown>
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.containsNone(parent.el, ".o_dropdown_menu", "menus are closed at start");
// Open through hotkeys
triggerHotkey("alt+2");
await nextTick();
assert.containsNone(parent.el, ".o_dropdown_menu", "no menu is opened");
triggerHotkey("alt+1");
await nextTick();
assert.containsOnce(parent.el, ".o_dropdown_menu", "first menu is opened");
triggerHotkey("alt+2");
await nextTick();
assert.containsN(parent.el, ".o_dropdown_menu", 2, "second menu is opened");
triggerHotkey("alt+3");
await nextTick();
assert.containsN(parent.el, ".o_dropdown_menu", 3, "both menus are opened");
// Close through hotkeys
triggerHotkey("escape");
await nextTick();
assert.containsN(parent.el, ".o_dropdown_menu", 2, "third menu is closed");
triggerHotkey("escape");
await nextTick();
assert.containsOnce(parent.el, ".o_dropdown_menu", "second menu is closed");
triggerHotkey("escape");
await nextTick();
assert.containsNone(parent.el, ".o_dropdown_menu", "both menus are closed");
assert.containsNone(parent.el, ".dropdown-menu", "menus are closed at start");
// Highlighting and selecting items
const scenarioSteps = [
@@ -743,15 +739,15 @@ QUnit.module("Components", ({ beforeEach }) => {
{ hotkey: "enter", highlighted: ["second", "third", "third-first"] },
{ hotkey: "enter", selected: "third-first" },
{ hotkey: "alt+1" },
{ hotkey: "alt+e", selected: false },
{ hotkey: "alt+f", selected: "first-last" },
{ hotkey: "alt+1" },
{ hotkey: "alt+2", highlighted: ["second", "second-first"] },
{ hotkey: "alt+3", highlighted: ["second", "third", "third-first"] },
{ hotkey: "alt+d", selected: "third-last" },
{ hotkey: "alt+1" },
{ hotkey: "alt+2", highlighted: ["second", "second-first"] },
{ hotkey: "alt+e", selected: "second-last" },
{ hotkey: "arrowup", highlighted: ["first-last"] },
{ hotkey: "arrowup", highlighted: ["second"] },
{ hotkey: "arrowright", highlighted: ["second", "second-first"] },
{ hotkey: "arrowup", highlighted: ["second", "second-last"] },
{ hotkey: "arrowup", highlighted: ["second", "third"] },
{ hotkey: "arrowright", highlighted: ["second", "third", "third-first"] },
{ hotkey: "escape", highlighted: ["second", "third"] },
{ hotkey: "escape", highlighted: ["second"] },
{ hotkey: "escape", highlighted: [] },
];
for (const [stepIndex, step] of scenarioSteps.entries()) {
@@ -759,7 +755,9 @@ QUnit.module("Components", ({ beforeEach }) => {
await nextTick();
if (step.highlighted !== undefined) {
let index = 0;
const activeElements = parent.el.querySelectorAll(".focus");
const activeElements = [...parent.el.querySelectorAll(".focus")].map((el) =>
el.classList.contains("dropdown-toggle") ? el.parentElement : el
);
assert.ok(
activeElements.length === step.highlighted.length,
`step ${stepIndex}: all active elements to check are found`
@@ -781,7 +779,7 @@ QUnit.module("Components", ({ beforeEach }) => {
Parent.template = owl.tags.xml`
<Dropdown class="first" hotkey="'1'">
<DropdownItem class="first-first">O</DropdownItem>
<Dropdown tag="'li'" class="second">
<Dropdown class="second">
<DropdownItem class="second-first">O</DropdownItem>
</Dropdown>
</Dropdown>
@@ -789,7 +787,7 @@ QUnit.module("Components", ({ beforeEach }) => {
serviceRegistry.add("localization", makeFakeLocalizationService({ direction: "rtl" }));
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.containsNone(parent.el, ".o_dropdown_menu", "menus are closed at start");
assert.containsNone(parent.el, ".dropdown-menu", "menus are closed at start");
// Highlighting and selecting items
const scenarioSteps = [
@@ -805,7 +803,9 @@ QUnit.module("Components", ({ beforeEach }) => {
await nextTick();
if (step.highlighted !== undefined) {
let index = 0;
const activeElements = parent.el.querySelectorAll(".focus");
const activeElements = [...parent.el.querySelectorAll(".focus")].map((el) =>
el.classList.contains("dropdown-toggle") ? el.parentElement : el
);
assert.ok(
activeElements.length === step.highlighted.length,
`step ${stepIndex}: all active elements to check are found`
@@ -817,6 +817,35 @@ QUnit.module("Components", ({ beforeEach }) => {
}
});
QUnit.test(
"multi-level dropdown: mouseentering a dropdown item should close any subdropdown",
async (assert) => {
assert.expect(4);
class Parent extends owl.Component {}
Parent.template = owl.tags.xml`
<Dropdown togglerClass="'main'">
<Dropdown togglerClass="'sub'" />
<DropdownItem class="item" />
</Dropdown>
`;
env = await makeTestEnv();
parent = await mount(Parent, { env, target });
assert.containsNone(parent.el, ".dropdown-menu", "menus are closed at start");
// Open main dropdown
await click(parent.el, ".main");
assert.containsOnce(parent.el, ".dropdown-menu", "1st menu is opened");
// Mouse enter sub dropdown
await mouseEnter(parent.el, ".sub");
assert.containsN(parent.el, ".dropdown-menu", 2, "all menus are opened");
// Mouse enter the adjacent dropdown item
await mouseEnter(parent.el, ".item");
assert.containsOnce(parent.el, ".dropdown-menu", "only 1st menu is opened");
}
);
QUnit.test("multi-level dropdown: unsubscribe all keynav when root close", async (assert) => {
assert.expect(14);
class Parent extends owl.Component {}
@@ -840,35 +869,41 @@ QUnit.module("Components", ({ beforeEach }) => {
},
});
parent = await mount(Parent, { env, target });
assert.containsNone(parent.el, ".o_dropdown_menu", "menus are closed at start");
assert.containsNone(parent.el, ".dropdown-menu", "menus are closed at start");
assert.strictEqual(hotkeyRegistrationsCount, 0, "no hotkey registered");
// Open dropdowns one by one
await click(parent.el, ".first");
assert.containsOnce(parent.el, ".o_dropdown_menu", "1st menu is opened");
assert.containsOnce(parent.el, ".dropdown-menu", "1st menu is opened");
assert.strictEqual(hotkeyRegistrationsCount, 10, "1st menu hotkeys registered");
await click(parent.el, ".second");
assert.containsN(parent.el, ".o_dropdown_menu", 2, "2nd menu is also opened");
await mouseEnter(parent.el, ".second");
assert.containsN(parent.el, ".dropdown-menu", 2, "2nd menu is also opened");
assert.strictEqual(hotkeyRegistrationsCount, 20, "2nd menu hotkeys also registered");
await click(parent.el, ".third");
assert.containsN(parent.el, ".o_dropdown_menu", 3, "3rd menu is also opened");
await mouseEnter(parent.el, ".third");
assert.containsN(parent.el, ".dropdown-menu", 3, "3rd menu is also opened");
assert.strictEqual(hotkeyRegistrationsCount, 30, "3rd menu hotkeys also registered");
// Close second
await click(parent.el, ".second");
assert.containsN(parent.el, ".o_dropdown_menu", 1, "only 1st menu stay opened");
assert.strictEqual(hotkeyRegistrationsCount, 10, "only 1st menu hotkeys registered");
// Close third
triggerHotkey("escape");
await nextTick();
assert.containsN(parent.el, ".dropdown-menu", 2, "two menus still opened");
assert.strictEqual(hotkeyRegistrationsCount, 20, "two menus hotkeys registered");
// Reopen second
await click(parent.el, ".second");
assert.containsN(parent.el, ".o_dropdown_menu", 2, "2nd menu is also opened");
assert.strictEqual(hotkeyRegistrationsCount, 20, "2nd menu hotkeys also registered");
await mouseEnter(parent.el, ".third");
assert.containsN(parent.el, ".dropdown-menu", 3, "3rd menu is also opened");
assert.strictEqual(hotkeyRegistrationsCount, 30, "3rd menu hotkeys also registered");
// Close first
await click(parent.el, ".first");
assert.containsNone(parent.el, ".o_dropdown_menu", "all menus are now closed");
// Close third, second and first
triggerHotkey("escape");
await nextTick();
triggerHotkey("escape");
await nextTick();
triggerHotkey("escape");
await nextTick();
assert.containsNone(parent.el, ".dropdown-menu", "all menus are now closed");
assert.strictEqual(hotkeyRegistrationsCount, 0, "no hotkey registration left");
});
});
+4
View File
@@ -228,6 +228,10 @@ export function click(el, selector) {
return triggerEvent(el, selector, "click", { bubbles: true, cancelable: true });
}
export async function mouseEnter(el, selector) {
return triggerEvent(el, selector, "mouseenter");
}
/**
* Triggers an hotkey properly disregarding the operating system.
*
@@ -56,16 +56,16 @@ odoo.define('web.action_menus_tests', function (require) {
props: this.props,
});
const dropdowns = actionMenus.el.getElementsByClassName('o_dropdown');
const dropdowns = actionMenus.el.getElementsByClassName('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');
assert.containsNone(actionMenus, '.dropdown-menu');
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Action");
assert.containsOnce(actionMenus, '.o_dropdown_menu');
assert.containsN(actionMenus, '.o_dropdown_menu .o_menu_item', 4);
assert.containsOnce(actionMenus, '.dropdown-menu');
assert.containsN(actionMenus, '.dropdown-menu .o_menu_item', 4);
const actionsTexts = [...dropdowns[1].querySelectorAll('.o_menu_item')].map(el => el.innerText.trim());
assert.deepEqual(actionsTexts, [
"Boil'em",
@@ -76,12 +76,12 @@ odoo.define('web.action_menus_tests', function (require) {
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Print");
assert.containsOnce(actionMenus, '.o_dropdown_menu');
assert.containsN(actionMenus, '.o_dropdown_menu .o_menu_item', 1);
assert.containsOnce(actionMenus, '.dropdown-menu');
assert.containsN(actionMenus, '.dropdown-menu .o_menu_item', 1);
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Print");
assert.containsNone(actionMenus, '.o_dropdown_menu');
assert.containsNone(actionMenus, '.dropdown-menu');
actionMenus.destroy();
});
@@ -42,7 +42,7 @@ odoo.define('web.dropdown_menu_tests', function (require) {
});
assert.strictEqual(dropdown.el.querySelector('button').innerText.trim(), "Dropdown");
assert.containsNone(dropdown, 'ul.o_dropdown_menu');
assert.containsNone(dropdown, '.dropdown-menu');
await testUtils.dom.click(dropdown.el.querySelector('button'));
@@ -57,12 +57,12 @@ odoo.define('web.dropdown_menu_tests', function (require) {
for (const dropdownEl of dropdownElements) {
await testUtils.dom.click(dropdownEl);
}
assert.containsOnce(dropdown, 'ul.o_dropdown_menu',
assert.containsOnce(dropdown, '.dropdown-menu',
"Clicking on any item of the dropdown should not close it");
await testUtils.dom.click(document.body);
assert.containsNone(dropdown, 'ul.o_dropdown_menu',
assert.containsNone(dropdown, '.dropdown-menu',
"Clicking outside of the dropdown should close it");
dropdown.destroy();
@@ -349,7 +349,7 @@ odoo.define('web.dropdown_menu_tests', function (require) {
await navigate('Escape', true); // Close the dropdown
assert.containsNone(dropdown, 'ul.o_dropdown_menu', "Dropdown should be folded");
assert.containsNone(dropdown, '.dropdown-menu', "Dropdown should be folded");
dropdown.destroy();
});
@@ -373,25 +373,25 @@ odoo.define('web.dropdown_menu_tests', function (require) {
const parent = new Parent();
await parent.mount(testUtils.prepareTarget(), { position: 'first-child' });
const [menu1, menu2] = parent.el.querySelectorAll('.o_dropdown');
const [menu1, menu2] = parent.el.querySelectorAll('.dropdown');
assert.containsNone(parent, '.o_dropdown_menu');
assert.containsNone(parent, '.dropdown-menu');
await testUtils.dom.click(menu1.querySelector('button'));
assert.containsOnce(parent, '.o_dropdown_menu');
assert.containsOnce(parent, '.o_dropdown.first .o_dropdown_menu');
assert.containsOnce(parent, '.dropdown-menu');
assert.containsOnce(parent, '.dropdown.first .dropdown-menu');
await testUtils.dom.click(menu2.querySelector('button'));
assert.containsOnce(parent, '.o_dropdown_menu');
assert.containsOnce(parent, '.o_dropdown.second .o_dropdown_menu');
assert.containsOnce(parent, '.dropdown-menu');
assert.containsOnce(parent, '.dropdown.second .dropdown-menu');
await testUtils.dom.click(menu2.querySelector('.o_menu_item a'));
await testUtils.dom.click(menu1.querySelector('button'));
assert.containsOnce(parent, '.o_dropdown_menu');
assert.containsOnce(parent, '.o_dropdown.first .o_dropdown_menu');
assert.containsOnce(parent, '.dropdown-menu');
assert.containsOnce(parent, '.dropdown.first .dropdown-menu');
parent.destroy();
});
@@ -419,7 +419,7 @@ odoo.define('web.dropdown_menu_tests', function (require) {
const parent = new Parent();
await parent.mount(testUtils.prepareTarget(), { position: "first-child" });
const menu = parent.el.querySelector(".o_dropdown");
const menu = parent.el.querySelector(".dropdown");
assert.doesNotHaveClass(menu, "show", "dropdown should not be open");
await testUtils.dom.click(menu.querySelector("button"));
@@ -1,6 +1,8 @@
odoo.define('web.comparison_menu_tests', function (require) {
"use strict";
const { browser } = require('@web/core/browser/browser');
const { patchWithCleanup } = require('@web/../tests/helpers/utils');
const cpHelpers = require('@web/../tests/search/helpers');
const {
createControlPanel,
@@ -27,6 +29,9 @@ odoo.define('web.comparison_menu_tests', function (require) {
fields: this.fields,
searchMenuTypes,
};
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
});
},
}, function () {
@@ -42,8 +47,8 @@ odoo.define('web.comparison_menu_tests', function (require) {
};
const controlPanel = await createControlPanel(params);
assert.containsOnce(controlPanel, ".o_dropdown.o_filter_menu");
assert.containsNone(controlPanel, ".o_dropdown.o_comparison_menu");
assert.containsOnce(controlPanel, ".dropdown.o_filter_menu");
assert.containsNone(controlPanel, ".dropdown.o_comparison_menu");
await cpHelpers.toggleFilterMenu(controlPanel);
await cpHelpers.toggleMenuItem(controlPanel, "Birthday");
@@ -55,7 +60,7 @@ odoo.define('web.comparison_menu_tests', function (require) {
await cpHelpers.toggleComparisonMenu(controlPanel);
const comparisonOptions = [...controlPanel.el.querySelectorAll(
'.o_comparison_menu li'
'.o_comparison_menu .o_menu_item'
)];
assert.strictEqual(comparisonOptions.length, 2);
assert.deepEqual(
@@ -10,6 +10,10 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
const cpHelpers = require('@web/../tests/search/helpers');
const { createComponent } = testUtils;
const toggleAddCustomFilterStandalone = async (el) => {
await cpHelpers.toggleMenu(el, "Add Custom Filter");
};
QUnit.module('Components', {
beforeEach: function () {
this.fields = {
@@ -42,7 +46,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
assert.hasClass(cfi.el, 'o_add_custom_filter_menu');
assert.strictEqual(cfi.el.children.length, 1);
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
// Single condition
assert.containsOnce(cfi, '.o_filter_condition');
@@ -82,7 +86,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
});
// Open custom filter form
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
// Retrieve second selectable values for field and operator dropdowns
const fieldSecondValue = cfi.el.querySelector('.o_generator_menu_field option:nth-of-type(2)').value;
@@ -137,7 +141,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
domain: '[["binary_field","!=",False]]',
type: 'filter',
}];
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_field'), 'binary_field');
await cpHelpers.applyFilter(cfi);
@@ -147,7 +151,6 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
domain: '[["binary_field","=",False]]',
type: 'filter',
}];
await cpHelpers.toggleAddCustomFilter(cfi);
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_field'), 'binary_field');
await cpHelpers.editConditionOperator(cfi, 0, '=');
await cpHelpers.applyFilter(cfi);
@@ -180,7 +183,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
domain: '[["color","=","black"]]',
type: 'filter',
}];
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_field'), 'color');
await cpHelpers.applyFilter(cfi);
@@ -190,7 +193,6 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
domain: '[["color","=","white"]]',
type: 'filter',
}];
await cpHelpers.toggleAddCustomFilter(cfi);
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_field'), 'color');
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_value select'), 'white');
await cpHelpers.applyFilter(cfi);
@@ -199,7 +201,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
});
QUnit.test('adding a simple filter works', async function (assert) {
assert.expect(6);
assert.expect(7);
delete this.fields.date_field;
@@ -221,13 +223,14 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
env: { searchModel },
});
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_field'), 'boolean_field');
await cpHelpers.applyFilter(cfi);
// The only thing visible should be the button 'Add Custome Filter';
assert.strictEqual(cfi.el.children.length, 1);
assert.containsOnce(cfi, 'button.o_dropdown_toggler');
// The only things visible should be the button 'Add Custom Filter' and the menu;
assert.strictEqual(cfi.el.children.length, 2);
assert.containsOnce(cfi, 'button.dropdown-toggle');
assert.containsOnce(cfi, '.dropdown-menu');
cfi.destroy();
});
@@ -262,7 +265,6 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
async function testValue(operator, value) {
// open filter menu generator, select ID field, switch operator, type value, then click apply
await cpHelpers.toggleAddCustomFilter(cfi);
await cpHelpers.editConditionField(cfi, 0, 'id_field');
await cpHelpers.editConditionOperator(cfi, 0, operator);
await cpHelpers.editConditionValue(cfi, 0,
@@ -271,6 +273,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
await cpHelpers.applyFilter(cfi);
}
await toggleAddCustomFilterStandalone(cfi);
for (const domain of [...expectedDomains]) {
await testValue(domain[0][1], domain[0][2]);
}
@@ -314,7 +317,6 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
async function testValue(value) {
// open filter menu generator, select trululu field and enter string `a`, then click apply
await cpHelpers.toggleAddCustomFilter(cfi);
await cpHelpers.editConditionField(cfi, 0, 'many2one_field');
await cpHelpers.editConditionValue(cfi, 0,
value
@@ -322,6 +324,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
await cpHelpers.applyFilter(cfi);
}
await toggleAddCustomFilterStandalone(cfi);
for (const value of testedValues) {
await testValue(value);
}
@@ -359,7 +362,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
env: { searchModel },
});
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_field'), 'date_time_field');
assert.strictEqual(cfi.el.querySelector('.o_generator_menu_field').value, 'date_time_field');
@@ -402,7 +405,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
env: { searchModel },
});
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_field'), 'date_time_field');
assert.strictEqual(cfi.el.querySelector('.o_generator_menu_field').value, 'date_time_field');
@@ -428,7 +431,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
},
});
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
await cpHelpers.addCondition(cfi);
await cpHelpers.editConditionField(cfi, 0, "float_field");
@@ -477,7 +480,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
},
});
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
await cpHelpers.addCondition(cfi);
await cpHelpers.editConditionField(cfi, 0, "float_field");
@@ -542,7 +545,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
env: { searchModel },
});
await cpHelpers.toggleAddCustomFilter(cfi);
await toggleAddCustomFilterStandalone(cfi);
await cpHelpers.addCondition(cfi);
await cpHelpers.addCondition(cfi);
await cpHelpers.addCondition(cfi);
@@ -29,21 +29,21 @@ odoo.define('web.groupby_menu_generator_tests', function (require) {
assert.hasClass(cgi.el, 'o_add_custom_group_menu');
assert.strictEqual(cgi.el.children.length, 1);
await testUtils.dom.click(cgi.el.querySelector('.o_add_custom_group_menu .o_dropdown_toggler'));
await testUtils.dom.click(cgi.el.querySelector('.o_add_custom_group_menu .dropdown-toggle'));
// Single select node with a single option
assert.containsOnce(cgi, 'li > select');
assert.strictEqual(cgi.el.querySelector('li > select option').innerText.trim(),
assert.containsOnce(cgi, 'div > select');
assert.strictEqual(cgi.el.querySelector('div > select option').innerText.trim(),
"Super Date");
// Button apply
assert.containsOnce(cgi, 'li > button.btn.btn-primary');
assert.containsOnce(cgi, 'div > button.btn.btn-primary');
cgi.destroy();
});
QUnit.test('select a field name in Add Custom Group menu properly trigger the corresponding field', async function (assert) {
assert.expect(4);
assert.expect(5);
const fields = [
{ sortable: true, name: 'candlelight', string: 'Candlelight', type: 'boolean' },
@@ -61,12 +61,13 @@ odoo.define('web.groupby_menu_generator_tests', function (require) {
env: { searchModel },
});
await testUtils.dom.click(cgi.el.querySelector('.o_add_custom_group_menu .o_dropdown_toggler'));
await testUtils.dom.click(cgi.el.querySelector('li > button.btn.btn-primary'));
await testUtils.dom.click(cgi.el.querySelector('.o_add_custom_group_menu .dropdown-toggle'));
await testUtils.dom.click(cgi.el.querySelector('div > button.btn.btn-primary'));
// The only thing visible should be the button 'Add Custome Group';
assert.strictEqual(cgi.el.children.length, 1);
assert.containsOnce(cgi, '.o_dropdown_toggler');
// The only things visible should be the button 'Add Custome Group' and the dropdown menu;
assert.strictEqual(cgi.el.children.length, 2);
assert.containsOnce(cgi, '.dropdown-toggle');
assert.containsOnce(cgi, '.dropdown-menu');
cgi.destroy();
});
@@ -1,6 +1,9 @@
odoo.define('web.favorite_menu_tests', function (require) {
"use strict";
const { browser } = require('@web/core/browser/browser');
const { patchWithCleanup } = require('@web/../tests/helpers/utils');
const FormView = require('web.FormView');
const testUtils = require('web.test_utils');
@@ -19,6 +22,9 @@ odoo.define('web.favorite_menu_tests', function (require) {
float_field: { string: "Float", type: "float", group_operator: 'sum' },
foo: { string: "Foo", type: "char", store: true, sortable: true },
};
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
});
},
}, function () {
@@ -39,7 +45,7 @@ odoo.define('web.favorite_menu_tests', function (require) {
await cpHelpers.toggleFavoriteMenu(controlPanel);
assert.containsNone(controlPanel, '.dropdown-divider');
assert.containsOnce(controlPanel, '.o_add_favorite');
assert.strictEqual(controlPanel.el.querySelector('.o_add_favorite > button span').innerText.trim(),
assert.strictEqual(controlPanel.el.querySelector('.o_add_favorite > button').innerText.trim(),
"Save current search");
await cpHelpers.toggleSaveFavorite(controlPanel);
@@ -288,7 +294,7 @@ odoo.define('web.favorite_menu_tests', function (require) {
// confirm deletion
await testUtils.dom.click(document.querySelector('div.o_dialog footer button'));
assert.deepEqual(cpHelpers.getFacetTexts(controlPanel), []);
const itemEls = controlPanel.el.querySelectorAll('.o_favorite_menu .o_dropdown_item');
const itemEls = controlPanel.el.querySelectorAll('.o_favorite_menu .dropdown-item');
assert.deepEqual([...itemEls].map(e => e.innerText.trim()), ["Save current search"]);
controlPanel.destroy();
@@ -1,6 +1,8 @@
odoo.define('web.filter_menu_tests', function (require) {
"use strict";
const { browser } = require('@web/core/browser/browser');
const { patchWithCleanup } = require('@web/../tests/helpers/utils');
const testUtils = require('web.test_utils');
const cpHelpers = require('@web/../tests/search/helpers');
@@ -15,6 +17,9 @@ odoo.define('web.filter_menu_tests', function (require) {
date_field: { string: "Date", type: "date", store: true, sortable: true, searchable: true },
foo: { string: "Foo", type: "char", store: true, sortable: true },
};
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
});
},
}, function () {
@@ -52,7 +57,7 @@ odoo.define('web.filter_menu_tests', function (require) {
await cpHelpers.toggleFilterMenu(controlPanel);
assert.containsOnce(controlPanel, '.o_menu_item');
assert.containsOnce(controlPanel, '.dropdown-divider');
assert.containsOnce(controlPanel, 'li.o_add_custom_filter_menu');
assert.containsOnce(controlPanel, 'div.o_add_custom_filter_menu');
controlPanel.destroy();
});
@@ -225,7 +230,7 @@ odoo.define('web.filter_menu_tests', function (require) {
await cpHelpers.toggleFilterMenu(controlPanel);
await cpHelpers.toggleMenuItem(controlPanel, "Date");
const optionEls = controlPanel.el.querySelectorAll('li.o_item_option');
const optionEls = controlPanel.el.querySelectorAll('span.o_item_option');
// default filter should be activated with the global default period 'this_month'
const { domain } = controlPanel.getQuery();
@@ -1,6 +1,8 @@
odoo.define('web.groupby_menu_tests', function (require) {
"use strict";
const { browser } = require('@web/core/browser/browser');
const { patchWithCleanup } = require('@web/../tests/helpers/utils');
const testUtils = require('web.test_utils');
const cpHelpers = require('@web/../tests/search/helpers');
@@ -18,6 +20,9 @@ odoo.define('web.groupby_menu_tests', function (require) {
foo: { string: "Foo", type: "char", store: true, sortable: true },
m2m: { string: "Many2Many", type: "many2many", store: true},
};
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
});
},
}, function () {
@@ -210,7 +215,7 @@ odoo.define('web.groupby_menu_tests', function (require) {
await cpHelpers.toggleGroupByMenu(controlPanel);
await cpHelpers.toggleMenuItem(controlPanel, 0);
const optionEls = controlPanel.el.querySelectorAll('li.o_item_option');
const optionEls = controlPanel.el.querySelectorAll('span.o_item_option');
// default groupby should be activated with the default inteval 'week'
const { groupBy } = controlPanel.getQuery();
@@ -8,6 +8,8 @@ import Registry from "web.Registry";
import SearchBar from "web.SearchBar";
import { registry } from "@web/core/registry";
import * as cpHelpers from "@web/../tests/search/helpers";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { browser } from "@web/core/browser/browser";
let serverData;
QUnit.module("Search Bar (legacy)", (hooks) => {
@@ -74,6 +76,7 @@ QUnit.module("Search Bar (legacy)", (hooks) => {
QUnit.test("navigation with facets", async function (assert) {
assert.expect(4);
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
const webClient = await createWebClient({ serverData });
await doAction(webClient, 1);
@@ -10,6 +10,8 @@ var StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin');
var testUtils = require('web.test_utils');
var Widget = require('web.Widget');
const { browser } = require('@web/core/browser/browser');
const { patchWithCleanup } = require('@web/../tests/helpers/utils');
const cpHelpers = require('@web/../tests/search/helpers');
var createView = testUtils.createView;
@@ -544,6 +546,12 @@ QUnit.module('fields', {}, function () {
display_name: "Partner 9",
});
this.data.partner.fields.datetime.searchable = true;
// add custom filter needs this
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
});
var form = await createView({
View: FormView,
model: 'partner',
@@ -3126,21 +3134,21 @@ QUnit.module('fields', {}, function () {
let filterMenuCss = '.o_search_options > .o_filter_menu';
let groupByMenuCss = '.o_search_options > .o_group_by_menu';
await testUtils.dom.click(document.querySelector(`${filterMenuCss} > .o_dropdown_toggler`));
await testUtils.dom.click(document.querySelector(`${filterMenuCss} > .dropdown-toggle`));
assert.hasClass(document.querySelector(filterMenuCss), 'show');
assert.isVisible(document.querySelector(`${filterMenuCss} > .o_dropdown_menu`),
assert.isVisible(document.querySelector(`${filterMenuCss} > .dropdown-menu`),
"the filter dropdown menu should be visible");
assert.doesNotHaveClass(document.querySelector(groupByMenuCss), 'show');
assert.isNotVisible(document.querySelector(`${groupByMenuCss} > .o_dropdown_menu`),
assert.isNotVisible(document.querySelector(`${groupByMenuCss} > .dropdown-menu`),
"the Group by dropdown menu should be not visible");
await testUtils.dom.click(document.querySelector(`${groupByMenuCss} > .o_dropdown_toggler`));
await testUtils.dom.click(document.querySelector(`${groupByMenuCss} > .dropdown-toggle`));
assert.hasClass(document.querySelector(groupByMenuCss), 'show');
assert.isVisible(document.querySelector(`${groupByMenuCss} > .o_dropdown_menu`),
assert.isVisible(document.querySelector(`${groupByMenuCss} > .dropdown-menu`),
"the group by dropdown menu should be visible");
assert.doesNotHaveClass(document.querySelector(filterMenuCss), 'show');
assert.isNotVisible(document.querySelector(`${filterMenuCss} > .o_dropdown_menu`),
assert.isNotVisible(document.querySelector(`${filterMenuCss} > .dropdown-menu`),
"the filter dropdown menu should be not visible");
$fakeDialog.remove();
@@ -14,7 +14,7 @@ odoo.define('web.test_utils_control_panel', function (require) {
* @returns {Promise}
*/
async function toggleMenu(el, menuFinder) {
const menu = findItem(el, `.o_dropdown > button`, menuFinder);
const menu = findItem(el, `.dropdown > button`, menuFinder);
await click(menu);
}
@@ -67,7 +67,7 @@ odoo.define('web.test_utils_control_panel', function (require) {
* @returns {string[]}
*/
function getMenuItemTexts(el) {
return [...getNode(el).querySelectorAll(`.o_dropdown ul .o_menu_item`)].map(
return [...getNode(el).querySelectorAll(`.dropdown ul .o_menu_item`)].map(
e => e.innerText.trim()
);
}
@@ -6252,8 +6252,8 @@ QUnit.module('Views', {
},
});
assert.containsNone(form, '.o_cp_action_menus .o_dropdown:contains(Print)');
assert.containsOnce(form, '.o_cp_action_menus .o_dropdown:contains(Action)');
assert.containsNone(form, '.o_cp_action_menus .dropdown:contains(Print)');
assert.containsOnce(form, '.o_cp_action_menus .dropdown:contains(Action)');
await testUtils.controlPanel.toggleActionMenu(form);
@@ -5,7 +5,8 @@ const searchUtils = require('web.searchUtils');
const GraphView = require('web.GraphView');
const testUtils = require('web.test_utils');
const { sortBy } = require('web.utils');
const { legacyExtraNextTick } = require("@web/../tests/helpers/utils");
const { browser } = require('@web/core/browser/browser');
const { legacyExtraNextTick, patchWithCleanup } = require("@web/../tests/helpers/utils");
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
const legacyViewRegistry = require("web.view_registry");
const { registry } = require("@web/core/registry");
@@ -1615,6 +1616,8 @@ QUnit.module('Views', {
this.unpatchDate = patchDate(2016, 11, 20, 1, 0, 0);
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
const graph = await createView({
View: GraphView,
model: "foo",
@@ -228,7 +228,7 @@ QUnit.module('Views', {
assert.ok(list.$('tbody td.o_list_record_selector').length, 'should have at least one record');
await testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input'));
assert.containsNone(list.el, 'div.o_control_panel .o_cp_action_menus .o_dropdown_menu');
assert.containsNone(list.el, 'div.o_control_panel .o_cp_action_menus .dropdown-menu');
list.destroy();
});
@@ -1405,15 +1405,15 @@ QUnit.module('Views', {
await testUtils.dom.click(list.$('.o_group_header:first')); // open first group
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
assert.containsOnce(list.el, 'div.o_control_panel .o_cp_action_menus');
assert.containsNone(list.el, 'div.o_control_panel .o_cp_action_menus .o_dropdown',
assert.containsNone(list.el, 'div.o_control_panel .o_cp_action_menus .dropdown',
"should not have dropdown as delete item is not there");
// reload withoud grouping by m2m
await list.reload({groupBy: []});
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
assert.containsOnce(list.el, 'div.o_control_panel .o_cp_action_menus');
assert.containsOnce(list.el, 'div.o_control_panel .o_cp_action_menus .o_dropdown');
await testUtils.dom.click(list.$('div.o_control_panel .o_cp_action_menus .o_dropdown button'));
assert.containsOnce(list.el, 'div.o_control_panel .o_cp_action_menus .dropdown');
await testUtils.dom.click(list.$('div.o_control_panel .o_cp_action_menus .dropdown button'));
assert.deepEqual(
[...list.el.querySelectorAll('.o_cp_action_menus .o_menu_item')].map((el) => el.innerText),
["Delete"]
@@ -2007,7 +2007,7 @@ QUnit.module('Views', {
"create button should be invisible");
assert.isVisible($(webClient.el).find('.o_list_button_save'), "save button should be visible");
await testUtils.dom.click($(webClient.el).find('.o_dropdown_toggler:contains("Group By")'));
await testUtils.dom.click($(webClient.el).find('.dropdown-toggle:contains("Group By")'));
await testUtils.dom.click($(webClient.el).find('.o_group_by_menu .o_menu_item:contains("candle")'));
assert.isNotVisible($(webClient.el).find('.o_list_button_add'), "create button should be invisible");
@@ -3957,8 +3957,8 @@ QUnit.module('Views', {
await click(webClient.el.querySelector('tbody td.o_list_record_selector input'));
assert.containsOnce(webClient, '.o_cp_action_menus', 'sidebar should be visible');
await click(webClient.el.querySelector('.o_cp_action_menus .o_dropdown_toggler'));
const archiveItem = [...webClient.el.querySelectorAll('.o_cp_action_menus .o_dropdown_menu li > a')]
await click(webClient.el.querySelector('.o_cp_action_menus .dropdown-toggle'));
const archiveItem = [...webClient.el.querySelectorAll('.o_cp_action_menus .dropdown-menu li > a')]
.filter((elem) => elem.textContent === 'Archive');
await click(archiveItem[0]);
assert.strictEqual(document.querySelectorAll('.modal').length, 1,
@@ -9497,7 +9497,7 @@ QUnit.module('Views', {
// delete a record
await testUtils.dom.click(list.$('tbody .o_data_row:first td.o_list_record_selector:first input'));
checkSearchRead = true;
await testUtils.dom.click(list.$('.o_dropdown_toggler:contains(Action)'));
await testUtils.dom.click(list.$('.dropdown-toggle:contains(Action)'));
await testUtils.dom.click(list.$('a:contains(Delete)'));
await testUtils.dom.click($('body .modal button span:contains(Ok)'));
assert.strictEqual(list.$('.o_pager_counter').text().trim(), '1-3 / 3',
@@ -9547,7 +9547,7 @@ QUnit.module('Views', {
// delete a record
await testUtils.dom.click(list.$('tbody .o_data_row:first td.o_list_record_selector:first input'));
checkSearchRead = true;
await testUtils.dom.click(list.$('.o_dropdown_toggler:contains(Action)'));
await testUtils.dom.click(list.$('.dropdown-toggle:contains(Action)'));
await testUtils.dom.click(list.$('a:contains(Delete)'));
await testUtils.dom.click($('body .modal button span:contains(Ok)'));
@@ -9597,7 +9597,7 @@ QUnit.module('Views', {
assert.containsOnce(list, '.o_cp_action_menus', 'sidebar should be available');
// archive all records of current page
await testUtils.dom.click(list.$('.o_cp_action_menus .o_dropdown_toggler:contains(Action)'));
await testUtils.dom.click(list.$('.o_cp_action_menus .dropdown-toggle:contains(Action)'));
await testUtils.dom.click(list.$('a:contains(Archive)'));
assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed');
@@ -6,6 +6,8 @@ var PivotView = require('web.PivotView');
const PivotController = require("web.PivotController");
var testUtils = require('web.test_utils');
var testUtilsDom = require('web.test_utils_dom');
const { browser } = require('@web/core/browser/browser');
const { patchWithCleanup } = require('@web/../tests/helpers/utils');
const { registry } = require('@web/core/registry');
const legacyViewRegistry = require('web.view_registry');
@@ -120,6 +122,7 @@ QUnit.module('Views', {
};
serverData = { models: this.data };
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
},
}, function () {
QUnit.module('Legacy PivotView');
@@ -622,24 +625,24 @@ QUnit.module('Views', {
// click on closed header to open dropdown
await testUtils.dom.click(pivot.el.querySelector('tbody .o_pivot_header_cell_closed'));
assert.containsOnce(pivot, ".o_legacy_pivot .o_dropdown_menu");
assert.containsOnce(pivot, ".o_legacy_pivot .dropdown-menu");
assert.strictEqual(
pivot.el.querySelector(".o_legacy_pivot .o_dropdown_menu").innerText.replace(/\s/g, ""),
pivot.el.querySelector(".o_legacy_pivot .dropdown-menu").innerText.replace(/\s/g, ""),
"CompanyTypeCustomerDateOtherProductProductbar"
);
// open the Date sub dropdown
await testUtils.dom.click(pivot.el.querySelector(".o_legacy_pivot .o_dropdown_menu .o_dropdown_toggler.o_menu_item"));
await testUtils.dom.triggerEvent(pivot.el.querySelector(".o_legacy_pivot .dropdown-menu .dropdown-toggle.o_menu_item"), "mouseenter");
assert.strictEqual(
pivot.el
.querySelector(".o_legacy_pivot .o_dropdown_menu .o_dropdown_menu")
.querySelector(".o_legacy_pivot .dropdown-menu .dropdown-menu")
.innerText.replace(/\s/g, ""),
"YearQuarterMonthWeekDay"
);
await testUtils.dom.click(
pivot.el.querySelectorAll(
".o_legacy_pivot .o_dropdown_menu .o_dropdown_menu .o_dropdown_item"
".o_legacy_pivot .dropdown-menu .dropdown-menu .dropdown-item"
)[2]
);
@@ -667,9 +670,9 @@ QUnit.module('Views', {
// open dropdown to zoom into first row
await testUtils.dom.click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
// click on date by day
await testUtils.dom.click(pivot.el.querySelector("tbody .o_dropdown_menu .o_dropdown_toggler"));
await testUtils.dom.triggerEvent(pivot.el.querySelector("tbody .dropdown-menu .dropdown-toggle"), "mouseenter");
await testUtils.dom.click(
pivot.el.querySelector("tbody .o_dropdown_menu .o_dropdown_menu li:nth-child(5)")
pivot.el.querySelector("tbody .dropdown-menu .dropdown-menu span:nth-child(5)")
);
// open dropdown to zoom into second row
@@ -702,7 +705,7 @@ QUnit.module('Views', {
// unfold it
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelector(".o_dropdown_menu li:nth-child(5)"));
await testUtils.dom.click(pivot.el.querySelector(".dropdown-menu span:nth-child(5)"));
assert.containsN(pivot, 'thead tr', 3);
pivot.destroy();
@@ -727,7 +730,7 @@ QUnit.module('Views', {
// unfold it
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed:nth(1)'));
await testUtils.dom.click(pivot.el.querySelector(".o_dropdown_menu li:nth-child(1)"));
await testUtils.dom.click(pivot.el.querySelector(".dropdown-menu span:nth-child(1)"));
assert.containsN(pivot, 'thead tr', 4);
values = ['12', '3', '17', '32'];
assert.strictEqual(getCurrentValues(pivot), values.join(','));
@@ -761,22 +764,22 @@ QUnit.module('Views', {
// open group by dropdown
await cpHelpers.toggleGroupByMenu(pivot.el);
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item', 3,
"should have 2 dropdown items in searchview groupby");
assert.containsOnce(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_add_custom_group_menu',
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item', 3,
"should have 3 dropdown items in searchview groupby");
assert.containsOnce(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_add_custom_group_menu',
"should have custom group generator in searchview groupby");
// click on closed header to open dropdown
await testUtils.dom.click(pivot.el.querySelector('tbody tr:last-child .o_pivot_header_cell_closed'));
assert.containsN(pivot, '.o_pivot_field_menu .dropdown-item', 3,
assert.containsN(pivot, '.o_pivot_field_menu .o_menu_item', 3,
"should have 3 dropdown items same as searchview groupby");
assert.containsOnce(pivot, '.o_pivot_field_menu .o_add_custom_group_menu',
"should have custom group generator same as searchview groupby");
// check custom groupby selection has groupable fields only
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .o_dropdown_toggler'));
assert.containsN(pivot, '.o_pivot_field_menu .o_add_custom_group_menu .o_dropdown_menu option', 6,
await testUtils.dom.triggerEvent(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .dropdown-toggle'), "mouseenter");
assert.containsN(pivot, '.o_pivot_field_menu .o_add_custom_group_menu .dropdown-menu option', 6,
"should have 6 fields in custom groupby");
const optionDescriptions = [...pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .o_dropdown_menu option')]
const optionDescriptions = [...pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .dropdown-menu option')]
.map(option => option.innerText.trim());
assert.deepEqual(
optionDescriptions,
@@ -817,14 +820,14 @@ QUnit.module('Views', {
// open group by dropdown
await cpHelpers.toggleGroupByMenu(pivot.el);
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item', 2,
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item', 2,
"should have 2 dropdown items in searchview groupby");
// click on closed header to open dropdown
await testUtils.dom.click(pivot.el.querySelector('tbody tr:last-child .o_pivot_header_cell_closed'));
assert.containsN(
pivot.el,
".o_dropdown_menu .o_menu_item",
".dropdown-menu .o_menu_item",
2,
"should have 2 dropdown items in pivot groupby"
);
@@ -832,19 +835,19 @@ QUnit.module('Views', {
await cpHelpers.toggleGroupByMenu(pivot.el);
await cpHelpers.toggleAddCustomGroup(pivot.el);
await cpHelpers.applyGroup(pivot.el);
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item', 3,
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item', 3,
"should have 3 dropdown items in searchview groupby now");
// click on closed header to open dropdown
await testUtils.dom.click(pivot.el.querySelector('tbody tr:last-child .o_pivot_header_cell_closed'));
// applying groupby/custom groupby will update pivot groupby dropdown while reverse is not true
assert.containsN(pivot, '.o_pivot_field_menu .dropdown-item', 3,
assert.containsN(pivot, '.o_pivot_field_menu .o_menu_item', 3,
"should have 3 dropdown items same as searchview groupby");
// add a custom group in pivot groupby
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .o_dropdown_toggler'));
await testUtils.dom.triggerEvent(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .dropdown-toggle'), "mouseenter");
await testUtils.fields.editSelect(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu select'), 'date');
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .o_dropdown_menu button'));
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .dropdown-menu button'));
// click on closed header to open groupby selection dropdown
await testUtils.dom.click(pivot.el.querySelector('tbody tr:last-child .o_pivot_header_cell_closed'));
assert.containsN(pivot, '.o_pivot_field_menu .o_menu_item', 4,
@@ -852,7 +855,7 @@ QUnit.module('Views', {
// applying custom groupby in pivot groupby dropdown will not update search dropdown
await cpHelpers.toggleGroupByMenu(pivot.el);
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item', 3,
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item', 3,
"should have 3 dropdown items in searchview groupby dropdown");
pivot.destroy();
@@ -882,11 +885,11 @@ QUnit.module('Views', {
// open group by dropdown
await cpHelpers.toggleGroupByMenu(pivot.el);
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item', 2,
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item', 2,
"should have 2 dropdown items in searchview groupby");
await cpHelpers.toggleAddCustomGroup(pivot.el);
await cpHelpers.applyGroup(pivot.el);
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item', 3,
assert.containsN(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item', 3,
"should have 3 dropdown items in searchview groupby now");
// click on closed header to open dropdown
@@ -895,7 +898,7 @@ QUnit.module('Views', {
assert.deepEqual([...items].map((el) => el.innerText), ["bar", "product", "Company Type"]);
assert.containsN(
pivot,
"tbody .o_dropdown_menu .dropdown-divider", 2,
"tbody .dropdown-menu .dropdown-divider", 2,
"pivot groupby menu should have two separators"
);
assert.hasClass(
@@ -940,7 +943,7 @@ QUnit.module('Views', {
// click on closed header to open dropdown and apply groupby on date field
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .o_dropdown_toggler'));
await testUtils.dom.triggerEvent(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .dropdown-toggle'), "mouseenter");
await testUtils.fields.editSelect(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu select'), 'date');
checkReadGroup = true;
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .btn-primary'));
@@ -967,7 +970,7 @@ QUnit.module('Views', {
// open group by dropdown
await cpHelpers.toggleGroupByMenu(pivot.el);
assert.containsNone(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item',
assert.containsNone(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item',
"should not have any dropdown item in searchview groupby");
// click on closed header to open dropdown
await testUtils.dom.click(pivot.el.querySelector('tbody tr:last-child .o_pivot_header_cell_closed'));
@@ -1013,9 +1016,9 @@ QUnit.module('Views', {
// open group by dropdown
await cpHelpers.toggleGroupByMenu(pivot.el);
assert.containsOnce(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item',
assert.containsOnce(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item',
"should have 1 dropdown item in searchview groupby");
assert.containsNone(pivot, '.o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_add_custom_group_menu',
assert.containsNone(pivot, '.o_control_panel .o_cp_bottom_right .dropdown-menu .o_add_custom_group_menu',
"should not have custom group generator in searchview groupby");
// click on closed header to open dropdown
@@ -1051,7 +1054,7 @@ QUnit.module('Views', {
// click on closed header to open dropdown and apply groupby on date field
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .o_dropdown_toggler'));
await testUtils.dom.triggerEvent(pivot.$('.o_pivot_field_menu .o_add_custom_group_menu .dropdown-toggle'), "mouseenter");
assert.containsOnce(pivot, '.o_pivot_field_menu .o_add_custom_group_menu',
"should have custom group generator in groupby dropdown");
@@ -1203,7 +1206,7 @@ QUnit.module('Views', {
// tries to open a field selection menu, to make sure it was not
// removed from the dom.
await testUtils.dom.clickFirst(pivot.$('.o_pivot_header_cell_closed'));
assert.containsOnce(pivot, '.o_dropdown_menu',
assert.containsOnce(pivot, '.o_legacy_pivot .dropdown-menu',
"the field selector menu exists");
pivot.destroy();
});
@@ -1488,7 +1491,7 @@ QUnit.module('Views', {
// expand header on field customer
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed:nth(1)'));
await testUtils.dom.click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[1]);
await testUtils.dom.click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
assert.deepEqual(pivot.getOwnedQueryParams(), {
context: {
pivot_column_groupby: ['date:day', 'customer'],
@@ -1499,7 +1502,7 @@ QUnit.module('Views', {
// expand row on field product_id
await testUtils.dom.click(pivot.el.querySelector('tbody .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_item")[4]);
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-item")[4]);
assert.deepEqual(pivot.getOwnedQueryParams(), {
context: {
pivot_column_groupby: ['date:day', 'customer'],
@@ -1571,7 +1574,7 @@ QUnit.module('Views', {
}, "context should be correct");
await testUtils.dom.click(pivot.el.querySelector('tbody .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.$('.o_dropdown_menu li:nth-child(5)'));
await testUtils.dom.click(pivot.$('.dropdown-menu span:nth-child(5)'));
assert.deepEqual(pivot.getOwnedQueryParams(), {
context: {
@@ -1582,7 +1585,7 @@ QUnit.module('Views', {
}, "context should be correct");
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.$('.o_dropdown_menu li:nth-child(2)'));
await testUtils.dom.click(pivot.$('.dropdown-menu span:nth-child(2)'));
assert.deepEqual(pivot.getOwnedQueryParams(), {
context: {
@@ -1696,7 +1699,7 @@ QUnit.module('Views', {
// Set a column groupby
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[1]);
await testUtils.dom.click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
// Set a domain
await pivot.update({domain: [['product_id', '=', 37]], groupBy: [], context: {}});
@@ -1711,7 +1714,7 @@ QUnit.module('Views', {
// Set a column groupby
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[4]);
await testUtils.dom.click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[4]);
// Set a domain
await pivot.update({domain: [['product_id', '=', 41]], groupBy: [], context: {}});
@@ -1749,7 +1752,7 @@ QUnit.module('Views', {
// expand a col group
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed:nth(1)'));
await testUtils.dom.click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[1]);
await testUtils.dom.click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
values = [
"29", "1", "2", "32",
@@ -1760,7 +1763,7 @@ QUnit.module('Views', {
// expand a row group
await testUtils.dom.click(pivot.el.querySelector('tbody tr:last-child .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_item")[3]);
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-item")[3]);
values = [
"29", "1", "2", "32",
@@ -1803,7 +1806,7 @@ QUnit.module('Views', {
// Set a column groupby
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[1]);
await testUtils.dom.click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
// Set a domain for empty results
await pivot.update({domain: [['id', '=', false]]});
@@ -1877,21 +1880,21 @@ QUnit.module('Views', {
});
await testUtils.dom.click(pivot.el.querySelector('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelector('thead .o_dropdown_menu .o_dropdown_toggler'));
await testUtils.dom.click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_menu .o_dropdown_item")[3]);
await testUtils.dom.triggerEvent(pivot.el.querySelector('thead .dropdown-menu .dropdown-toggle'), "mouseenter");
await testUtils.dom.click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-menu .dropdown-item")[3]);
// close and reopen row groupings after changing value
this.data.partner.records.find(r => r.product_id === 37).date = '2016-10-27';
await testUtils.dom.click(pivot.el.querySelector('tbody .o_pivot_header_cell_opened'));
await testUtils.dom.click(pivot.el.querySelector('tbody .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_item")[4]);
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-item")[4]);
assert.strictEqual(pivot.el.querySelectorAll('.o_pivot_cell_value')[4].innerText, ''); // xphone December 2016
// invert axis, and reopen column groupings
await testUtils.dom.click(pivot.el.querySelector('.o_cp_buttons .o_pivot_flip_button'));
await testUtils.dom.click(pivot.el.querySelector('thead .o_pivot_header_cell_opened'));
await testUtils.dom.click(pivot.el.querySelector('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[4]);
await testUtils.dom.click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[4]);
assert.strictEqual(pivot.el.querySelectorAll('.o_pivot_cell_value')[3].innerText, ''); // December 2016 xphone
pivot.destroy();
@@ -2380,9 +2383,9 @@ QUnit.module('Views', {
});
await testUtils.dom.click(pivot.el.querySelector('tbody .o_pivot_header_cell_closed'));
// click on date by month
await testUtils.dom.click(pivot.el.querySelector("tbody .o_dropdown_menu .o_dropdown_toggler"));
await testUtils.dom.triggerEvent(pivot.el.querySelector("tbody .dropdown-menu .dropdown-toggle"), "mouseenter");
await testUtils.dom.click(
pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_menu .o_dropdown_item")[3]
pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-menu .dropdown-item")[3]
);
assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '2211',
@@ -2426,7 +2429,7 @@ QUnit.module('Views', {
await testUtils.dom.click(pivot.el.querySelector('tbody .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .o_dropdown_item")[4]);
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .dropdown-item")[4]);
assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '211',
'should have loaded the proper data');
@@ -2447,11 +2450,11 @@ QUnit.module('Views', {
// Set a column groupby
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("thead .o_dropdown_item")[1]);
await testUtils.dom.click(pivot.el.querySelectorAll("thead .dropdown-item")[1]);
// Set a Row groupby
await testUtils.dom.click(pivot.el.querySelector('tbody .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .o_dropdown_item")[4]);
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .dropdown-item")[4]);
// Set a domain
await testUtils.pivot.reload(pivot, {domain: [['product_id', '=', 41]]});
@@ -2681,7 +2684,7 @@ QUnit.module('Views', {
// with data, with row groupby
await testUtils.dom.click(pivot.$('.o_legacy_pivot .o_pivot_header_cell_closed').eq(2));
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_item")[4]);
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-item")[4]);
values = [
"19", "0", "-100%", "0", "13", "100%", "19", "13", "-31.58%",
"19", "0", "-100%", "0", "1" , "100%", "19", "1", "-94.74%",
@@ -3309,7 +3312,7 @@ QUnit.module('Views', {
// Set a Row groupby
await testUtils.dom.click(pivot.el.querySelector('tbody tr:nth-child(2) .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .o_dropdown_item")[4]);
await testUtils.dom.click(pivot.el.querySelectorAll("tbody .dropdown-item")[4]);
assert.strictEqual(
pivot.$('th').slice(0, 2).text(),
@@ -3374,13 +3377,13 @@ QUnit.module('Views', {
},
});
await testUtils.dom.click(pivot.el.querySelector("thead th.o_pivot_header_cell_closed"));
await testUtils.dom.click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[5]);
await testUtils.dom.click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[5]);
// Click on the two dropdown
await testUtils.dom.click(pivot.el.querySelectorAll("thead th.o_pivot_header_cell_closed")[0]);
await testUtils.dom.click(pivot.el.querySelectorAll("thead th.o_pivot_header_cell_closed")[1]);
assert.containsOnce(pivot, 'thead .o_dropdown_menu', 'Only one dropdown should be displayed at a time');
assert.containsOnce(pivot, 'thead .dropdown-menu', 'Only one dropdown should be displayed at a time');
pivot.destroy();
});
@@ -1,6 +1,6 @@
/** @odoo-module **/
import { legacyExtraNextTick } from "@web/../tests/helpers/utils";
import { legacyExtraNextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import {
getFacetTexts,
removeFacet,
@@ -23,6 +23,7 @@ import AbstractView from "web.AbstractView";
import ActionModel from "web.ActionModel";
import { mock } from "web.test_utils";
import legacyViewRegistry from "web.view_registry";
import { browser } from "@web/core/browser/browser";
const serviceRegistry = registry.category("services");
const viewRegistry = registry.category("views");
@@ -258,6 +259,7 @@ QUnit.module("Views", (hooks) => {
async function (assert) {
assert.expect(5);
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
const webClient = await createWebClient({
serverData,
mockRPC(_, args) {
@@ -7,6 +7,8 @@ var testUtils = require('web.test_utils');
var Widget = require('web.Widget');
var FormView = require('web.FormView');
const { browser } = require('@web/core/browser/browser');
const { patchWithCleanup } = require('@web/../tests/helpers/utils');
const cpHelpers = require('@web/../tests/search/helpers');
var createView = testUtils.createView;
@@ -526,6 +528,11 @@ QUnit.module('Views', {
},
});
// save favorite needs this
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
});
var parent = await createParent({
data: this.data,
archs: {
@@ -1,6 +1,7 @@
/** @odoo-module **/
import { patchDate } from "@web/../tests/helpers/utils";
import { patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
import { browser } from "@web/core/browser/browser";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import {
getFacetTexts,
@@ -35,6 +36,10 @@ QUnit.module("Search", (hooks) => {
},
};
setupControlPanelServiceRegistry();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
});
QUnit.module("Comparison");
@@ -49,8 +54,8 @@ QUnit.module("Search", (hooks) => {
searchMenuTypes: ["filter", "comparison"],
searchViewId: false,
});
assert.containsOnce(controlPanel, ".o_dropdown.o_filter_menu");
assert.containsNone(controlPanel, ".o_dropdown.o_comparison_menu");
assert.containsOnce(controlPanel, ".dropdown.o_filter_menu");
assert.containsNone(controlPanel, ".dropdown.o_comparison_menu");
await toggleFilterMenu(controlPanel);
await toggleMenuItem(controlPanel, "Birthday");
await toggleMenuItemOption(controlPanel, "Birthday", "January");
@@ -63,7 +68,9 @@ QUnit.module("Search", (hooks) => {
"COMPARISON"
);
await toggleComparisonMenu(controlPanel);
const comparisonOptions = [...controlPanel.el.querySelectorAll(".o_comparison_menu li")];
const comparisonOptions = [
...controlPanel.el.querySelectorAll(".o_comparison_menu .dropdown-item"),
];
assert.strictEqual(comparisonOptions.length, 2);
assert.deepEqual(
comparisonOptions.map((e) => e.innerText.trim()),
@@ -1,6 +1,7 @@
/** @odoo-module **/
import { triggerEvent } from "@web/../tests/helpers/utils";
import { patchWithCleanup, triggerEvent } from "@web/../tests/helpers/utils";
import { browser } from "@web/core/browser/browser";
import { dialogService } from "@web/core/dialog/dialog_service";
import { registry } from "@web/core/registry";
import { ControlPanel } from "@web/search/control_panel/control_panel";
@@ -62,6 +63,10 @@ QUnit.module("Search", (hooks) => {
setupControlPanelFavoriteMenuRegistry();
setupControlPanelServiceRegistry();
serviceRegistry.add("dialog", dialogService);
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
});
QUnit.module("CustomFavoriteItem");
@@ -4,6 +4,7 @@ import { registerCleanup } from "@web/../tests/helpers/cleanup";
import { click, patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
import { localization } from "@web/core/l10n/localization";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import { browser } from "@web/core/browser/browser";
import {
addCondition,
applyFilter,
@@ -84,6 +85,10 @@ QUnit.module("Search", (hooks) => {
},
};
setupControlPanelServiceRegistry();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
});
QUnit.module("CustomFilterItem");
@@ -305,13 +310,13 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(getDomain(controlPanel), []);
assert.containsNone(controlPanel, ".o_menu_item");
assert.containsOnce(controlPanel, ".o_add_custom_filter_menu button.o_dropdown_toggler");
assert.containsOnce(controlPanel, ".o_add_custom_filter_menu button.dropdown-toggle");
// the 'Add Custom Filter' menu should be closed;
assert.containsNone(controlPanel, ".o_add_custom_filter_menu ul.o_dropdown_menu");
assert.containsNone(controlPanel, ".o_add_custom_filter_menu .dropdown-menu");
await toggleAddCustomFilter(controlPanel);
// the 'Add Custom Filter' menu should be open;
assert.containsOnce(controlPanel, ".o_add_custom_filter_menu ul.o_dropdown_menu");
assert.containsOnce(controlPanel, ".o_add_custom_filter_menu .dropdown-menu");
await applyFilter(controlPanel);
@@ -319,9 +324,9 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(getDomain(controlPanel), [["boolean_field", "=", true]]);
assert.containsOnce(controlPanel, ".o_menu_item");
assert.containsOnce(controlPanel, ".o_add_custom_filter_menu button.o_dropdown_toggler");
// the 'Add Custom Filter' menu should be closed;
assert.containsNone(controlPanel, ".o_add_custom_filter_menu ul.o_dropdown_menu");
assert.containsOnce(controlPanel, ".o_add_custom_filter_menu button.dropdown-toggle");
// the 'Add Custom Filter' menu should still be opened;
assert.containsOnce(controlPanel, ".o_add_custom_filter_menu .dropdown-menu");
});
QUnit.test("selection field: default and updated value", async function (assert) {
@@ -1,6 +1,8 @@
/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import { patchWithCleanup } from "../helpers/utils";
import {
applyGroup,
getFacetTexts,
@@ -34,6 +36,10 @@ QUnit.module("Search", (hooks) => {
},
};
setupControlPanelServiceRegistry();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
});
QUnit.module("CustomGroupByItem");
@@ -54,12 +60,12 @@ QUnit.module("Search", (hooks) => {
const customGroupByItem = controlPanel.el.querySelector(".o_add_custom_group_menu");
assert.strictEqual(customGroupByItem.innerText.trim(), "Add Custom Group");
assert.containsOnce(customGroupByItem, "button.o_dropdown_toggler");
assert.containsNone(customGroupByItem, "ul.o_dropdown_menu");
assert.containsOnce(customGroupByItem, "button.dropdown-toggle");
assert.containsNone(customGroupByItem, ".dropdown-menu");
await toggleAddCustomGroup(controlPanel);
assert.containsOnce(customGroupByItem, "ul.o_dropdown_menu");
assert.containsOnce(customGroupByItem, ".dropdown-menu");
assert.deepEqual(
[...controlPanel.el.querySelectorAll(".o_add_custom_group_menu select option")].map(
@@ -92,7 +98,7 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(
[
...controlPanel.el.querySelectorAll(
".o_add_custom_group_menu .o_dropdown_menu select option"
".o_add_custom_group_menu .dropdown-menu select option"
),
].map((el) => el.innerText),
["Foo"]
@@ -156,16 +162,16 @@ QUnit.module("Search", (hooks) => {
await toggleAddCustomGroup(controlPanel);
// Single select node with a single option
assert.containsOnce(controlPanel, ".o_add_custom_group_menu .o_dropdown_menu select");
assert.containsOnce(controlPanel, ".o_add_custom_group_menu .dropdown-menu select");
assert.strictEqual(
controlPanel.el
.querySelector(".o_add_custom_group_menu .o_dropdown_menu select option")
.querySelector(".o_add_custom_group_menu .dropdown-menu select option")
.innerText.trim(),
"Super Date"
);
// Button apply
assert.containsOnce(controlPanel, ".o_add_custom_group_menu .o_dropdown_menu .btn");
assert.containsOnce(controlPanel, ".o_add_custom_group_menu .dropdown-menu .btn");
});
QUnit.test(
@@ -192,8 +198,8 @@ QUnit.module("Search", (hooks) => {
await applyGroup(controlPanel);
assert.containsOnce(controlPanel, ".o_group_by_menu .o_menu_item");
assert.containsOnce(controlPanel, ".o_add_custom_group_menu .o_dropdown_toggler");
assert.containsNone(controlPanel, ".o_add_custom_group_menu .o_dropdown_menu");
assert.containsOnce(controlPanel, ".o_add_custom_group_menu .dropdown-toggle");
assert.containsOnce(controlPanel, ".o_add_custom_group_menu .dropdown-menu");
assert.deepEqual(getFacetTexts(controlPanel), ["Candlelight"]);
}
);
@@ -84,10 +84,10 @@ QUnit.module("Search", (hooks) => {
await toggleFavoriteMenu(controlPanel);
assert.containsOnce(
controlPanel,
"div.o_favorite_menu > .o_dropdown_menu",
"div.o_favorite_menu > .dropdown-menu",
"the menu should be opened"
);
assert.containsNone(controlPanel, ".o_dropdown_menu *", "the menu should be empty");
assert.containsNone(controlPanel, ".dropdown-menu *", "the menu should be empty");
}
);
@@ -117,11 +117,11 @@ QUnit.module("Search", (hooks) => {
await toggleFavoriteMenu(controlPanel);
assert.containsOnce(
controlPanel,
"div.o_favorite_menu > .o_dropdown_menu",
"div.o_favorite_menu > .dropdown-menu",
"the menu should be opened"
);
assert.containsNone(controlPanel, ".o_dropdown_menu .dropdown-divider");
assert.containsOnce(controlPanel, ".o_dropdown_menu .o_add_favorite");
assert.containsNone(controlPanel, ".dropdown-menu .dropdown-divider");
assert.containsOnce(controlPanel, ".dropdown-menu .o_add_favorite");
});
QUnit.test(
@@ -182,7 +182,7 @@ QUnit.module("Search", (hooks) => {
assert.deepEqual(getFacetTexts(webClient), ["My favorite"]);
assert.hasClass(
webClient.el.querySelector(".o_favorite_menu li.o_menu_item"),
webClient.el.querySelector(".o_favorite_menu .o_menu_item"),
"selected"
);
@@ -1,6 +1,7 @@
/** @odoo-module **/
import { patchDate } from "@web/../tests/helpers/utils";
import { patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
import { browser } from "@web/core/browser/browser";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import {
getFacetTexts,
@@ -45,6 +46,10 @@ QUnit.module("Search", (hooks) => {
},
};
setupControlPanelServiceRegistry();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
});
QUnit.module("FilterMenu");
@@ -147,7 +152,7 @@ QUnit.module("Search", (hooks) => {
await toggleFilterMenu(controlPanel);
await toggleMenuItem(controlPanel, "Date");
const optionEls = controlPanel.el.querySelectorAll(".o_dropdown .o_item_option");
const optionEls = controlPanel.el.querySelectorAll(".dropdown .o_item_option");
// default filter should be activated with the global default period 'this_month'
assert.deepEqual(getDomain(controlPanel), [
@@ -1,6 +1,8 @@
/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { ControlPanel } from "@web/search/control_panel/control_panel";
import { patchWithCleanup } from "../helpers/utils";
import {
getFacetTexts,
isItemSelected,
@@ -34,6 +36,10 @@ QUnit.module("Search", (hooks) => {
},
};
setupControlPanelServiceRegistry();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
});
QUnit.module("GroupByMenu");
+26 -18
View File
@@ -10,7 +10,7 @@ import { viewService } from "@web/views/view_service";
import { actionService } from "@web/webclient/actions/action_service";
import { registerCleanup } from "../helpers/cleanup";
import { makeTestEnv } from "../helpers/mock_env";
import { click, getFixture, triggerEvent } from "../helpers/utils";
import { click, getFixture, mouseEnter, triggerEvent } from "../helpers/utils";
const serviceRegistry = registry.category("services");
const favoriteMenuRegistry = registry.category("favoriteMenu");
@@ -72,18 +72,26 @@ const findItem = (target, selector, finder = 0) => {
/** Menu (generic) */
export const toggleMenu = async (el, menuFinder) => {
const menu = findItem(el, `.o_dropdown button.o_dropdown_toggler`, menuFinder);
const menu = findItem(el, `.dropdown button.dropdown-toggle`, menuFinder);
await click(menu);
};
export const toggleMenuItem = async (el, itemFinder) => {
const item = findItem(el, `.o_menu_item`, itemFinder);
await click(item);
if (item.classList.contains("dropdown-toggle")) {
await mouseEnter(item);
} else {
await click(item);
}
};
export const toggleMenuItemOption = async (el, itemFinder, optionFinder) => {
const item = findItem(el, `.o_menu_item`, itemFinder);
const option = findItem(item.parentNode, ".o_item_option", optionFinder);
await click(option);
if (option.classList.contains("dropdown-toggle")) {
await mouseEnter(option);
} else {
await click(option);
}
};
export const isItemSelected = (el, itemFinder) => {
const item = findItem(el, `.o_menu_item`, itemFinder);
@@ -96,17 +104,17 @@ export const isOptionSelected = (el, itemFinder, optionFinder) => {
};
export const getMenuItemTexts = (target) => {
const el = getNode(target);
return [...el.querySelectorAll(`.o_dropdown ul .o_menu_item`)].map((e) => e.innerText.trim());
return [...el.querySelectorAll(`.dropdown ul .o_menu_item`)].map((e) => e.innerText.trim());
};
/** Filter menu */
export const toggleFilterMenu = async (el) => {
await click(findItem(el, `.o_filter_menu button.o_dropdown_toggler`));
await click(findItem(el, `.o_filter_menu button.dropdown-toggle`));
};
export const toggleAddCustomFilter = async (el) => {
await click(findItem(el, `.o_add_custom_filter_menu .o_dropdown_toggler`));
await mouseEnter(findItem(el, `.o_add_custom_filter_menu .dropdown-toggle`));
};
export const editConditionField = async (el, index, fieldName) => {
@@ -135,11 +143,11 @@ export const editConditionValue = async (el, index, value, valueIndex = 0) => {
};
export const applyFilter = async (el) => {
await click(findItem(el, `.o_add_custom_filter_menu .o_dropdown_menu button.o_apply_filter`));
await click(findItem(el, `.o_add_custom_filter_menu .dropdown-menu button.o_apply_filter`));
};
export const addCondition = async (el) => {
await click(findItem(el, `.o_add_custom_filter_menu .o_dropdown_menu button.o_add_condition`));
await click(findItem(el, `.o_add_custom_filter_menu .dropdown-menu button.o_add_condition`));
};
export async function removeCondition(el, index) {
@@ -150,27 +158,27 @@ export async function removeCondition(el, index) {
/** Group by menu */
export const toggleGroupByMenu = async (el) => {
await click(findItem(el, `.o_group_by_menu .o_dropdown_toggler`));
await click(findItem(el, `.o_group_by_menu .dropdown-toggle`));
};
export const toggleAddCustomGroup = async (el) => {
await click(findItem(el, `.o_add_custom_group_menu .o_dropdown_toggler`));
await mouseEnter(findItem(el, `.o_add_custom_group_menu .dropdown-toggle`));
};
export const selectGroup = async (el, fieldName) => {
const select = findItem(el, `.o_add_custom_group_menu .o_dropdown_menu select`);
const select = findItem(el, `.o_add_custom_group_menu .dropdown-menu select`);
select.value = fieldName;
await triggerEvent(select, null, "change");
};
export const applyGroup = async (el) => {
await click(findItem(el, `.o_add_custom_group_menu .o_dropdown_menu .btn`));
await click(findItem(el, `.o_add_custom_group_menu .dropdown-menu .btn`));
};
/** Favorite menu */
export const toggleFavoriteMenu = async (el) => {
await click(findItem(el, `.o_favorite_menu .o_dropdown_toggler`));
await click(findItem(el, `.o_favorite_menu .dropdown-toggle`));
};
export const deleteFavorite = async (el, favoriteFinder) => {
@@ -179,26 +187,26 @@ export const deleteFavorite = async (el, favoriteFinder) => {
};
export const toggleSaveFavorite = async (el) => {
await click(findItem(el, `.o_favorite_menu .o_add_favorite .o_dropdown_toggler`));
await mouseEnter(findItem(el, `.o_favorite_menu .o_add_favorite .dropdown-toggle`));
};
export const editFavoriteName = async (el, name) => {
const input = findItem(
el,
`.o_favorite_menu .o_add_favorite .o_dropdown_menu input[type="text"]`
`.o_favorite_menu .o_add_favorite .dropdown-menu input[type="text"]`
);
input.value = name;
await triggerEvent(input, null, "input");
};
export const saveFavorite = async (el) => {
await click(findItem(el, `.o_favorite_menu .o_add_favorite .o_dropdown_menu button`));
await click(findItem(el, `.o_favorite_menu .o_add_favorite .dropdown-menu button`));
};
/** Comparison menu */
export const toggleComparisonMenu = async (el) => {
await click(findItem(el, `.o_comparison_menu button.o_dropdown_toggler`));
await click(findItem(el, `.o_comparison_menu button.dropdown-toggle`));
};
/** Search bar */
@@ -22,6 +22,8 @@ import { dialogService } from "@web/core/dialog/dialog_service";
import { registry } from "@web/core/registry";
import { BORDER_WHITE, DEFAULT_BG } from "@web/views/graph/colors";
import { GraphArchParser } from "@web/views/graph/graph_arch_parser";
import { browser } from "@web/core/browser/browser";
import { patchWithCleanup } from "../helpers/utils";
const serviceRegistry = registry.category("services");
@@ -277,6 +279,7 @@ QUnit.module("Views", (hooks) => {
setupControlPanelServiceRegistry();
setupControlPanelFavoriteMenuRegistry();
serviceRegistry.add("dialog", dialogService);
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
});
QUnit.module("GraphView");
@@ -2478,7 +2481,7 @@ QUnit.module("Views", (hooks) => {
});
await toggleMenu(graph, "Measures");
assert.deepEqual(
[...graph.el.querySelectorAll(".o_cp_bottom_left li.o_menu_item")].map(
[...graph.el.querySelectorAll(".o_cp_bottom_left .o_menu_item")].map(
(el) => el.innerText
),
["Foo", "Revenue", "Count"]
@@ -2523,7 +2526,7 @@ QUnit.module("Views", (hooks) => {
});
await toggleMenu(graph, "Measures");
assert.deepEqual(
[...graph.el.querySelectorAll(".o_cp_bottom_left li.o_menu_item")].map(
[...graph.el.querySelectorAll(".o_cp_bottom_left .o_menu_item")].map(
(el) => el.innerText
),
["Bouh", "Foo", "Revenue", "Count"]
@@ -2900,13 +2903,13 @@ QUnit.module("Views", (hooks) => {
await toggleMenu(graph, "Measures");
assert.containsN(
graph,
"li.o_menu_item",
".o_menu_item",
3,
"there should be three menu item in the measures dropdown (count, revenue and foo)"
);
assert.containsOnce(graph, 'li.o_menu_item:contains("Revenue")');
assert.containsOnce(graph, 'li.o_menu_item:contains("Foo")');
assert.containsOnce(graph, 'li.o_menu_item:contains("Count")');
assert.containsOnce(graph, '.o_menu_item:contains("Revenue")');
assert.containsOnce(graph, '.o_menu_item:contains("Foo")');
assert.containsOnce(graph, '.o_menu_item:contains("Count")');
});
QUnit.test("graph view with invisible attribute on field", async function (assert) {
@@ -2924,11 +2927,11 @@ QUnit.module("Views", (hooks) => {
await toggleMenu(graph, "Measures");
assert.containsN(
graph,
"li.o_menu_item",
".o_menu_item",
2,
"there should be only two menu item in the measures dropdown (count and foo)"
);
assert.containsNone(graph, 'li.o_menu_item:contains("Revenue")');
assert.containsNone(graph, '.o_menu_item:contains("Revenue")');
});
QUnit.test("graph view sort by measure", async function (assert) {
+128 -132
View File
@@ -14,6 +14,7 @@ import {
patchWithCleanup,
triggerEvent,
triggerEvents,
mouseEnter,
} from "../helpers/utils";
import {
applyGroup,
@@ -35,6 +36,7 @@ import {
} from "../search/helpers";
import { createWebClient, doAction } from "../webclient/helpers";
import { makeView } from "./helpers";
import { browser } from "@web/core/browser/browser";
const serviceRegistry = registry.category("services");
@@ -189,6 +191,7 @@ QUnit.module("Views", (hooks) => {
serviceRegistry.add("dialog", dialogService);
serviceRegistry.add("localization", makeFakeLocalizationService());
serviceRegistry.add("user", makeFakeUserService());
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
});
QUnit.module("PivotView");
@@ -257,9 +260,9 @@ QUnit.module("Views", (hooks) => {
</pivot>`,
});
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
assert.strictEqual(
pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_menu .o_dropdown_item").innerText,
pivot.el.querySelector(".o_cp_bottom_left .dropdown-menu .dropdown-item").innerText,
"BAR"
);
assert.strictEqual(
@@ -316,12 +319,12 @@ QUnit.module("Views", (hooks) => {
// there should be only one displayed measure as the other one is invisible
assert.containsOnce(pivot, ".o_pivot_measure_row");
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
// there should be only one measure besides count, as the other one is invisible
assert.containsN(pivot, ".o_cp_bottom_left .o_dropdown_menu .dropdown-item", 2);
assert.containsN(pivot, ".o_cp_bottom_left .dropdown-menu .dropdown-item", 2);
// the invisible field souldn't be in the groupable fields neither
await click(pivot.el.querySelector(".o_pivot_header_cell_closed"));
assert.containsNone(pivot, '.o_dropdown_menu a[data-field="foo2"]');
assert.containsNone(pivot, '.dropdown-menu a[data-field="foo2"]');
});
QUnit.test('pivot view without "string" attribute', async function (assert) {
@@ -382,10 +385,10 @@ QUnit.module("Views", (hooks) => {
</pivot>`,
});
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
assert.containsOnce(
pivot,
".o_cp_bottom_left .o_dropdown_menu .dropdown-item:contains(Computed and not stored)"
".o_cp_bottom_left .dropdown-menu .dropdown-item:contains(Computed and not stored)"
);
assert.strictEqual(
pivot.el.querySelector(".o_pivot_measure_row").innerText,
@@ -772,11 +775,9 @@ QUnit.module("Views", (hooks) => {
},
});
await click(pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_toggler"));
assert.containsOnce(pivot.el, ".o_cp_bottom_left .o_dropdown_menu .o_dropdown_item");
const measure = pivot.el.querySelector(
".o_cp_bottom_left .o_dropdown_menu .o_dropdown_item"
);
await click(pivot.el.querySelector(".o_cp_bottom_left .dropdown-toggle"));
assert.containsOnce(pivot.el, ".o_cp_bottom_left .dropdown-menu .dropdown-item");
const measure = pivot.el.querySelector(".o_cp_bottom_left .dropdown-menu .dropdown-item");
assert.strictEqual(measure.innerText, "Count");
assert.hasClass(measure, "selected", "The count measure should be selected");
});
@@ -873,25 +874,23 @@ QUnit.module("Views", (hooks) => {
// click on closed header to open dropdown
await click(pivot.el, "tbody .o_pivot_header_cell_closed");
assert.containsOnce(pivot, ".o_pivot .o_dropdown_menu");
assert.containsOnce(pivot, ".o_pivot .dropdown-menu");
assert.strictEqual(
pivot.el.querySelector(".o_pivot .o_dropdown_menu").innerText.replace(/\s/g, ""),
pivot.el.querySelector(".o_pivot .dropdown-menu").innerText.replace(/\s/g, ""),
"CompanyTypeCustomerDateOtherProductProductbarAddCustomGroup"
);
// open the Date sub dropdown
await click(pivot.el, ".o_pivot .o_dropdown_menu .o_dropdown_toggler.o_menu_item");
await mouseEnter(pivot.el, ".o_pivot .dropdown-menu .dropdown-toggle.o_menu_item");
assert.strictEqual(
pivot.el
.querySelector(".o_pivot .o_dropdown_menu .o_dropdown_menu")
.querySelector(".o_pivot .dropdown-menu .dropdown-menu")
.innerText.replace(/\s/g, ""),
"YearQuarterMonthWeekDay"
);
await click(
pivot.el.querySelectorAll(
".o_pivot .o_dropdown_menu .o_dropdown_menu .o_dropdown_item"
)[2]
pivot.el.querySelectorAll(".o_pivot .dropdown-menu .dropdown-menu .dropdown-item")[2]
);
assert.containsN(pivot, "tbody tr", 4, "should have 4 rows: one for header, 3 for data");
@@ -919,9 +918,9 @@ QUnit.module("Views", (hooks) => {
// open dropdown to zoom into first row
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
// click on date by day
await click(pivot.el.querySelector("tbody .o_dropdown_menu .o_dropdown_toggler"));
await mouseEnter(pivot.el.querySelector("tbody .dropdown-menu .dropdown-toggle"));
await click(
pivot.el.querySelector("tbody .o_dropdown_menu .o_dropdown_menu li:nth-child(5)")
pivot.el.querySelector("tbody .dropdown-menu .dropdown-menu span:nth-child(5)")
);
// open dropdown to zoom into second row
@@ -957,7 +956,7 @@ QUnit.module("Views", (hooks) => {
// unfold it
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
await click(pivot.el.querySelector(".o_dropdown_menu li:nth-child(5)"));
await click(pivot.el.querySelector(".dropdown-menu span:nth-child(5)"));
assert.containsN(pivot, "thead tr", 3);
});
@@ -981,7 +980,7 @@ QUnit.module("Views", (hooks) => {
// unfold it
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed:last-child"));
await click(pivot.el.querySelector(".o_dropdown_menu li:nth-child(1)"));
await click(pivot.el.querySelector(".dropdown-menu span:nth-child(1)"));
assert.containsN(pivot, "thead tr", 4);
values = ["12", "3", "17", "32"];
assert.strictEqual(getCurrentValues(pivot), values.join(","));
@@ -1015,13 +1014,13 @@ QUnit.module("Views", (hooks) => {
await toggleGroupByMenu(pivot);
assert.containsN(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item",
3,
"should have 3 dropdown items in searchview groupby"
);
assert.containsOnce(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_add_custom_group_menu",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_add_custom_group_menu",
"should have custom group generator in searchview groupby"
);
@@ -1029,26 +1028,26 @@ QUnit.module("Views", (hooks) => {
await click(pivot.el, "tbody tr:last-child .o_pivot_header_cell_closed");
assert.containsN(
pivot,
".o_dropdown_menu > .dropdown-item",
".dropdown-menu > .dropdown-item",
3,
"should have 3 dropdown items same as searchview groupby"
);
assert.containsOnce(
pivot,
".o_dropdown_menu .o_add_custom_group_menu",
".dropdown-menu .o_add_custom_group_menu",
"should have custom group generator same as searchview groupby"
);
// check custom groupby selection has groupable fields only
await click(pivot.el, ".o_dropdown_menu .o_add_custom_group_menu .o_dropdown_toggler");
await mouseEnter(pivot.el, ".dropdown-menu .o_add_custom_group_menu .dropdown-toggle");
assert.containsN(
pivot,
".o_dropdown_menu .o_add_custom_group_menu .o_dropdown_menu option",
".dropdown-menu .o_add_custom_group_menu .dropdown-menu option",
6,
"should have 6 fields in custom groupby"
);
const optionDescriptions = [
...pivot.el.querySelectorAll(
".o_dropdown_menu .o_add_custom_group_menu .o_dropdown_menu option"
".dropdown-menu .o_add_custom_group_menu .dropdown-menu option"
),
].map((option) => option.innerText.trim());
assert.deepEqual(
@@ -1080,7 +1079,7 @@ QUnit.module("Views", (hooks) => {
await toggleGroupByMenu(pivot);
assert.containsN(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item",
2,
"should have 2 dropdown items in searchview groupby"
);
@@ -1089,7 +1088,7 @@ QUnit.module("Views", (hooks) => {
await click(pivot.el, "tbody tr:last-child .o_pivot_header_cell_closed");
assert.containsN(
pivot,
".o_dropdown_menu .o_menu_item",
".dropdown-menu .o_menu_item",
2,
"should have 2 dropdown items in pivot groupby"
);
@@ -1100,28 +1099,28 @@ QUnit.module("Views", (hooks) => {
await applyGroup(pivot);
assert.containsN(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item",
3,
"should have 3 dropdown items in searchview groupby now"
);
await click(pivot.el, "tbody tr:last-child .o_pivot_header_cell_closed");
assert.containsN(
pivot,
".o_dropdown_menu .o_menu_item",
".dropdown-menu .o_menu_item",
2,
"should still have 2 dropdown items in pivot groupby"
);
// add a custom group in pivot groupby
await click(pivot.el, ".o_dropdown_menu .o_add_custom_group_menu .o_dropdown_toggler");
pivot.el.querySelector(".o_dropdown_menu .o_add_custom_group_menu select").value = "date";
await triggerEvent(pivot.el, ".o_dropdown_menu .o_add_custom_group_menu select", "change");
await click(pivot.el, ".o_dropdown_menu .o_add_custom_group_menu .o_dropdown_menu .btn");
await mouseEnter(pivot.el, ".dropdown-menu .o_add_custom_group_menu .dropdown-toggle");
pivot.el.querySelector(".dropdown-menu .o_add_custom_group_menu select").value = "date";
await triggerEvent(pivot.el, ".dropdown-menu .o_add_custom_group_menu select", "change");
await click(pivot.el, ".dropdown-menu .o_add_custom_group_menu .dropdown-menu .btn");
// click on closed header to open groupby selection dropdown
await click(pivot.el, "tbody tr:last-child .o_pivot_header_cell_closed");
assert.containsN(
pivot,
".o_dropdown_menu .o_menu_item",
".dropdown-menu .o_menu_item",
3,
"should have 3 dropdown items in pivot groupby dropdown"
);
@@ -1130,7 +1129,7 @@ QUnit.module("Views", (hooks) => {
await toggleGroupByMenu(pivot);
assert.containsN(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item",
3,
"should still have 3 dropdown items in searchview groupby dropdown"
);
@@ -1159,7 +1158,7 @@ QUnit.module("Views", (hooks) => {
await toggleGroupByMenu(pivot);
assert.containsN(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item",
2,
"should have 2 dropdown items in searchview groupby"
);
@@ -1167,7 +1166,7 @@ QUnit.module("Views", (hooks) => {
await applyGroup(pivot);
assert.containsN(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item",
3,
"should have 3 dropdown items in searchview groupby now"
);
@@ -1181,7 +1180,7 @@ QUnit.module("Views", (hooks) => {
);
assert.containsOnce(
pivot,
"tbody .o_dropdown_menu .dropdown-divider",
"tbody .dropdown-menu .dropdown-divider",
"pivot groupby menu should only have one separator"
);
assert.hasClass(
@@ -1191,17 +1190,14 @@ QUnit.module("Views", (hooks) => {
);
// add a custom group in pivot groupby
await click(pivot.el, ".o_dropdown_menu .o_add_custom_group_menu .o_dropdown_toggler");
await mouseEnter(pivot.el, ".dropdown-menu .o_add_custom_group_menu .dropdown-toggle");
pivot.el.querySelector(".o_add_custom_group_menu select").value = "customer";
await triggerEvent(
pivot.el,
".o_dropdown_menu .o_add_custom_group_menu select",
".dropdown-menu .o_add_custom_group_menu select",
"change"
);
await click(
pivot.el,
".o_dropdown_menu .o_add_custom_group_menu .o_dropdown_menu .btn"
);
await click(pivot.el, ".dropdown-menu .o_add_custom_group_menu .dropdown-menu .btn");
await click(pivot.el.querySelectorAll("tbody .o_pivot_header_cell_closed")[1]);
items = pivot.el.querySelectorAll(".o_menu_item");
@@ -1211,7 +1207,7 @@ QUnit.module("Views", (hooks) => {
);
assert.containsN(
pivot,
"tbody .o_dropdown_menu .dropdown-divider",
"tbody .dropdown-menu .dropdown-divider",
2,
"pivot groupby menu should now have two separators"
);
@@ -1261,9 +1257,9 @@ QUnit.module("Views", (hooks) => {
// click on closed header to open dropdown and apply groupby on date field
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
await click(
await mouseEnter(
pivot.el.querySelector(
"thead .o_dropdown_menu .o_add_custom_group_menu .o_dropdown_toggler "
"thead .dropdown-menu .o_add_custom_group_menu .dropdown-toggle "
)
);
@@ -1291,25 +1287,25 @@ QUnit.module("Views", (hooks) => {
await toggleGroupByMenu(pivot);
assert.containsNone(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item",
"should not have any dropdown item in searchview groupby"
);
assert.containsOnce(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_add_custom_group_menu",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_add_custom_group_menu",
"should have add custom group item in searchview groupby"
);
// click on closed header to open dropdown
await click(pivot.el.querySelectorAll("tbody .o_pivot_header_cell_closed")[1]);
assert.containsN(
pivot,
"tbody .o_dropdown_menu .o_menu_item",
"tbody .dropdown-menu .o_menu_item",
6,
"should have 6 dropdown items i.e. all groupable fields available"
);
assert.containsOnce(
pivot,
".o_dropdown_menu .o_add_custom_group_menu",
".dropdown-menu .o_add_custom_group_menu",
"should have custom group generator in groupby dropdown"
);
});
@@ -1361,12 +1357,12 @@ QUnit.module("Views", (hooks) => {
await toggleGroupByMenu(pivot);
assert.containsOnce(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_menu_item",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_menu_item",
"should have 1 dropdown item in searchview groupby"
);
assert.containsNone(
pivot,
".o_control_panel .o_cp_bottom_right .o_dropdown_menu .o_add_custom_group_menu",
".o_control_panel .o_cp_bottom_right .dropdown-menu .o_add_custom_group_menu",
"should not have custom group generator in searchview groupby"
);
@@ -1374,12 +1370,12 @@ QUnit.module("Views", (hooks) => {
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
assert.containsOnce(
pivot,
"tbody .o_dropdown_menu .o_dropdown_item",
"tbody .dropdown-menu .dropdown-item",
"should have 1 dropdown items"
);
assert.containsNone(
pivot,
".o_dropdown_menu .o_add_custom_group_menu",
".dropdown-menu .o_add_custom_group_menu",
"should not have custom group generator in groupby dropdown"
);
}
@@ -1407,24 +1403,24 @@ QUnit.module("Views", (hooks) => {
// click on closed header to open dropdown
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
assert.containsOnce(pivot, "thead .o_dropdown_menu .o_add_custom_group_menu");
await click(
assert.containsOnce(pivot, "thead .dropdown-menu .o_add_custom_group_menu");
await mouseEnter(
pivot.el.querySelector(
"thead .o_dropdown_menu .o_add_custom_group_menu .o_dropdown_toggler"
"thead .dropdown-menu .o_add_custom_group_menu .dropdown-toggle"
)
);
assert.containsOnce(
pivot,
"thead .o_dropdown_menu .o_add_custom_group_menu .o_dropdown_menu"
"thead .dropdown-menu .o_add_custom_group_menu .dropdown-menu"
);
// click on apply button should close dropdown
await click(
pivot.el.querySelector(
"thead .o_dropdown_menu .o_add_custom_group_menu .o_dropdown_menu .btn-primary"
"thead .dropdown-menu .o_add_custom_group_menu .dropdown-menu .btn-primary"
)
);
assert.containsNone(pivot, "thead .o_dropdown_menu");
assert.containsNone(pivot, "thead .dropdown-menu");
}
);
@@ -1452,16 +1448,16 @@ QUnit.module("Views", (hooks) => {
"should have 3 cells: 1 for the open header, and 2 for data"
);
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
assert.doesNotHaveClass(
$(pivot.el).find(".o_dropdown_item:contains(Count)"),
$(pivot.el).find(".dropdown-item:contains(Count)"),
"selected",
"the __count measure should not be selected"
);
await click($(pivot.el).find(".o_cp_bottom_left .o_dropdown_item:contains(Count)")[0]);
await click($(pivot.el).find(".o_cp_bottom_left .dropdown-item:contains(Count)")[0]);
assert.hasClass(
$(pivot.el).find(".o_cp_bottom_left .o_dropdown_item:contains(Count)"),
$(pivot.el).find(".o_cp_bottom_left .dropdown-item:contains(Count)"),
"selected",
"the __count measure should be selected"
);
@@ -1473,10 +1469,10 @@ QUnit.module("Views", (hooks) => {
);
assert.strictEqual(rpcCount, 2, "should have done 2 rpcs to reload data");
await click($(pivot.el).find(".o_cp_bottom_left .o_dropdown_item:contains(Count)")[0]);
await click($(pivot.el).find(".o_cp_bottom_left .dropdown-item:contains(Count)")[0]);
assert.doesNotHaveClass(
$(pivot.el).find(".o_dropdown_item:contains(Count)")[0],
$(pivot.el).find(".dropdown-item:contains(Count)")[0],
"selected",
"the __count measure should not be selected"
);
@@ -1500,8 +1496,8 @@ QUnit.module("Views", (hooks) => {
assert.containsNone(pivot, ".o_view_nocontent");
assert.containsOnce(pivot, "table");
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
await click($(pivot.el).find(".o_cp_bottom_left .o_dropdown_item:contains(Count)")[0]);
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
await click($(pivot.el).find(".o_cp_bottom_left .dropdown-item:contains(Count)")[0]);
assert.containsOnce(pivot, ".o_view_nocontent");
assert.containsNone(pivot, "table");
@@ -1577,7 +1573,7 @@ QUnit.module("Views", (hooks) => {
// tries to open a field selection menu, to make sure it was not
// removed from the dom.
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
assert.containsOnce(pivot, "tbody .o_dropdown_menu");
assert.containsOnce(pivot, "tbody .dropdown-menu");
});
QUnit.test("tries to restore previous state after domain change", async function (assert) {
@@ -1927,7 +1923,7 @@ QUnit.module("Views", (hooks) => {
// expand header on field customer
await click(pivot.el.querySelectorAll("thead .o_pivot_header_cell_closed")[1]);
await click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[1]);
await click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
expectedContext = {
group_by: [],
pivot_column_groupby: ["date:day", "customer"],
@@ -1941,7 +1937,7 @@ QUnit.module("Views", (hooks) => {
// expand row on field product_id
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_item")[4]);
await click(pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-item")[4]);
expectedContext = {
group_by: [],
pivot_column_groupby: ["date:day", "customer"],
@@ -2042,7 +2038,7 @@ QUnit.module("Views", (hooks) => {
// Group row by product_id
await click(pivot.el, "tbody .o_pivot_header_cell_closed");
await click(pivot.el, ".o_dropdown_menu li:nth-child(5)");
await click(pivot.el, ".dropdown-menu span:nth-child(5)");
expectedContext = {
group_by: [],
pivot_column_groupby: [],
@@ -2056,7 +2052,7 @@ QUnit.module("Views", (hooks) => {
// Group column by customer
await click(pivot.el, "thead .o_pivot_header_cell_closed");
await click(pivot.el, ".o_dropdown_menu li:nth-child(2)");
await click(pivot.el, ".dropdown-menu span:nth-child(2)");
expectedContext = {
group_by: [],
pivot_column_groupby: ["customer"],
@@ -2214,7 +2210,7 @@ QUnit.module("Views", (hooks) => {
// Group row by product_id
await click(webClient.el, "tbody .o_pivot_header_cell_closed");
await click(webClient.el, ".o_dropdown_menu li:nth-child(5)");
await click(webClient.el, ".dropdown-menu span:nth-child(5)");
assert.strictEqual(
webClient.el.querySelector("tbody .o_pivot_header_cell_closed").textContent,
@@ -2507,7 +2503,7 @@ QUnit.module("Views", (hooks) => {
// Set a column groupby
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("thead .o_dropdown_item")[1]);
await click(pivot.el.querySelectorAll("thead .dropdown-item")[1]);
// Set a domain
await toggleFilterMenu(pivot);
@@ -2528,7 +2524,7 @@ QUnit.module("Views", (hooks) => {
// Set a column groupby
await removeFacet(pivot); // remove previously saved favorite
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[4]);
await click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[4]);
// Set a domain
await toggleFilterMenu(pivot);
@@ -2570,14 +2566,14 @@ QUnit.module("Views", (hooks) => {
// expand a col group
await click(pivot.el.querySelectorAll("thead .o_pivot_header_cell_closed")[1]);
await click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[1]);
await click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
values = ["29", "1", "2", "32", "12", "12", "17", "1", "2", "20"];
assert.strictEqual(getCurrentValues(pivot), values.join(","));
// expand a row group
await click(pivot.el.querySelectorAll("tbody .o_pivot_header_cell_closed")[1]);
await click(pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_item")[3]);
await click(pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-item")[3]);
values = ["29", "1", "2", "32", "12", "12", "17", "1", "2", "20", "17", "1", "2", "20"];
assert.strictEqual(getCurrentValues(pivot), values.join(","));
@@ -2644,7 +2640,7 @@ QUnit.module("Views", (hooks) => {
// Set a column groupby
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[1]);
await click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
assert.containsOnce(pivot, "table");
@@ -2750,9 +2746,9 @@ QUnit.module("Views", (hooks) => {
});
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
await click(pivot.el.querySelector("thead .o_dropdown_menu .o_dropdown_toggler"));
await mouseEnter(pivot.el.querySelector("thead .dropdown-menu .dropdown-toggle"));
await click(
pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_menu .o_dropdown_item")[3]
pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-menu .dropdown-item")[3]
);
// close and reopen row groupings after changing value
@@ -2760,14 +2756,14 @@ QUnit.module("Views", (hooks) => {
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_opened"));
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_item")[4]);
await click(pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-item")[4]);
assert.strictEqual(pivot.el.querySelectorAll(".o_pivot_cell_value")[4].innerText, ""); // xphone December 2016
// invert axis, and reopen column groupings
await click(pivot.el.querySelector(".o_cp_bottom_left .o_pivot_flip_button"));
await click(pivot.el.querySelector("thead .o_pivot_header_cell_opened"));
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[4]);
await click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[4]);
assert.strictEqual(pivot.el.querySelectorAll(".o_pivot_cell_value")[3].innerText, ""); // December 2016 xphone
});
@@ -3103,13 +3099,13 @@ QUnit.module("Views", (hooks) => {
},
});
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
assert.containsOnce(pivot, ".o_cp_bottom_left .o_dropdown_item");
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
assert.containsOnce(pivot, ".o_cp_bottom_left .dropdown-item");
assert.strictEqual(
pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_item").innerText,
pivot.el.querySelector(".o_cp_bottom_left .dropdown-item").innerText,
"Count"
);
assert.hasClass(pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_item"), "selected");
assert.hasClass(pivot.el.querySelector(".o_cp_bottom_left .dropdown-item"), "selected");
});
QUnit.test("not use a many2one as a measure by default", async function (assert) {
@@ -3125,13 +3121,13 @@ QUnit.module("Views", (hooks) => {
<field name="date" interval="month" type="col"/>
</pivot>`,
});
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
assert.containsOnce(pivot, ".o_cp_bottom_left .o_dropdown_item");
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
assert.containsOnce(pivot, ".o_cp_bottom_left .dropdown-item");
assert.strictEqual(
pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_item").innerText,
pivot.el.querySelector(".o_cp_bottom_left .dropdown-item").innerText,
"Count"
);
assert.hasClass(pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_item"), "selected");
assert.hasClass(pivot.el.querySelector(".o_cp_bottom_left .dropdown-item"), "selected");
});
QUnit.test(
@@ -3150,14 +3146,14 @@ QUnit.module("Views", (hooks) => {
</pivot>`,
additionalMeasures: ["product_id"],
});
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
assert.containsN(pivot, ".o_cp_bottom_left .o_dropdown_item", 2);
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
assert.containsN(pivot, ".o_cp_bottom_left .dropdown-item", 2);
assert.strictEqual(
pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_item").innerText,
pivot.el.querySelector(".o_cp_bottom_left .dropdown-item").innerText,
"Product"
);
assert.doesNotHaveClass(
pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_item"),
pivot.el.querySelector(".o_cp_bottom_left .dropdown-item"),
"selected"
);
}
@@ -3198,9 +3194,9 @@ QUnit.module("Views", (hooks) => {
});
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
// click on date by month
await click(pivot.el.querySelector("tbody .o_dropdown_menu .o_dropdown_toggler"));
await mouseEnter(pivot.el.querySelector("tbody .dropdown-menu .dropdown-toggle"));
await click(
pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_menu .o_dropdown_item")[3]
pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-menu .dropdown-item")[3]
);
assert.strictEqual(
@@ -3226,8 +3222,8 @@ QUnit.module("Views", (hooks) => {
additionalMeasures: ["product_id"],
});
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
await click(pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_item"));
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
await click(pivot.el.querySelector(".o_cp_bottom_left .dropdown-item"));
assert.strictEqual(
[...pivot.el.querySelectorAll(".o_pivot_cell_value")]
.map((c) => c.innerText)
@@ -3254,7 +3250,7 @@ QUnit.module("Views", (hooks) => {
});
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("tbody .o_dropdown_item")[4]);
await click(pivot.el.querySelectorAll("tbody .dropdown-item")[4]);
assert.strictEqual(
[...pivot.el.querySelectorAll(".o_pivot_cell_value")]
@@ -3297,11 +3293,11 @@ QUnit.module("Views", (hooks) => {
// Set a column groupby
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("thead .o_dropdown_item")[1]);
await click(pivot.el.querySelectorAll("thead .dropdown-item")[1]);
// Set a Row groupby
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("tbody .o_dropdown_item")[4]);
await click(pivot.el.querySelectorAll("tbody .dropdown-item")[4]);
// Add a filter
await toggleFilterMenu(pivot);
@@ -3404,9 +3400,9 @@ QUnit.module("Views", (hooks) => {
</pivot>`,
});
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
assert.strictEqual(
[...pivot.el.querySelectorAll(".o_cp_bottom_left .o_dropdown_item")]
[...pivot.el.querySelectorAll(".o_cp_bottom_left .dropdown-item")]
.map((i) => i.innerText)
.join(""),
"bouhFoomoddZipCount"
@@ -3547,7 +3543,7 @@ QUnit.module("Views", (hooks) => {
// with data, with row groupby
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("tbody .o_dropdown_menu .o_dropdown_item")[4]);
await click(pivot.el.querySelectorAll("tbody .dropdown-menu .dropdown-item")[4]);
values = [
"19",
"0",
@@ -3576,12 +3572,12 @@ QUnit.module("Views", (hooks) => {
];
assert.strictEqual(getCurrentValues(pivot), values.join());
await click(pivot.el.querySelector(".o_cp_bottom_left button.o_dropdown_toggler"));
await click(pivot.el.querySelector(".o_cp_bottom_left button.dropdown-toggle"));
await click(
pivot.el.querySelectorAll(".o_cp_bottom_left .o_dropdown_menu .o_dropdown_item")[0]
pivot.el.querySelectorAll(".o_cp_bottom_left .dropdown-menu .dropdown-item")[0]
);
await click(
pivot.el.querySelectorAll(".o_cp_bottom_left .o_dropdown_menu .o_dropdown_item")[1]
pivot.el.querySelectorAll(".o_cp_bottom_left .dropdown-menu .dropdown-item")[1]
);
values = [
"1",
@@ -3612,10 +3608,10 @@ QUnit.module("Views", (hooks) => {
assert.strictEqual(getCurrentValues(pivot), values.join());
await click(
pivot.el.querySelectorAll(".o_cp_bottom_left .o_dropdown_menu .o_dropdown_item")[2]
pivot.el.querySelectorAll(".o_cp_bottom_left .dropdown-menu .dropdown-item")[2]
);
await click(
pivot.el.querySelectorAll(".o_cp_bottom_left .o_dropdown_menu .o_dropdown_item")[1]
pivot.el.querySelectorAll(".o_cp_bottom_left .dropdown-menu .dropdown-item")[1]
);
values = [
"2",
@@ -4085,21 +4081,21 @@ QUnit.module("Views", (hooks) => {
arch: `<pivot><field name="foo" type="measure"/></pivot>`,
});
assert.containsNone(pivot, ".o_cp_bottom_left .o_dropdown_menu");
assert.containsNone(pivot, ".o_cp_bottom_left .dropdown-menu");
// open the "Measures" menu
await click(pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_toggler"));
assert.containsOnce(pivot, ".o_cp_bottom_left .o_dropdown_menu");
await click(pivot.el.querySelector(".o_cp_bottom_left .dropdown-toggle"));
assert.containsOnce(pivot, ".o_cp_bottom_left .dropdown-menu");
// click on the divider in the "Measures" menu does not crash
await click(pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_menu .dropdown-divider"));
await click(pivot.el.querySelector(".o_cp_bottom_left .dropdown-menu .dropdown-divider"));
// the menu should still be open
assert.containsOnce(pivot, ".o_cp_bottom_left .o_dropdown_menu");
assert.containsOnce(pivot, ".o_cp_bottom_left .dropdown-menu");
// click on the measure list but not on a menu item or the separator
await click(pivot.el.querySelector(".o_cp_bottom_left .o_dropdown_menu"));
await click(pivot.el.querySelector(".o_cp_bottom_left .dropdown-menu"));
// the menu should still be open
assert.containsOnce(pivot, ".o_cp_bottom_left .o_dropdown_menu");
assert.containsOnce(pivot, ".o_cp_bottom_left .dropdown-menu");
});
QUnit.test(
@@ -4523,7 +4519,7 @@ QUnit.module("Views", (hooks) => {
// Set a Row groupby
await click(pivot.el, "tbody tr:nth-child(2) .o_pivot_header_cell_closed");
await click(pivot.el, "tbody .o_dropdown_menu .o_menu_item:nth-child(5)");
await click(pivot.el, "tbody .dropdown-menu .o_menu_item:nth-child(5)");
assert.deepEqual(
[...pivot.el.querySelectorAll("thead th")].map((th) => th.innerText),
@@ -4571,7 +4567,7 @@ QUnit.module("Views", (hooks) => {
// add a col groupby on Product
await click(pivot.el.querySelector("thead th.o_pivot_header_cell_closed"));
await click(pivot.el.querySelectorAll("thead .o_dropdown_menu .o_dropdown_item")[5]);
await click(pivot.el.querySelectorAll("thead .dropdown-menu .dropdown-item")[5]);
// Click on the two header dropdown togglers
await click(pivot.el.querySelectorAll("thead th.o_pivot_header_cell_closed")[0]);
@@ -4579,7 +4575,7 @@ QUnit.module("Views", (hooks) => {
assert.containsOnce(
pivot,
"thead .o_dropdown_menu",
"thead .dropdown-menu",
"Only one dropdown should be displayed at a time"
);
});
@@ -4848,7 +4844,7 @@ QUnit.module("Views", (hooks) => {
// drill down first row group (group by company_type)
await click(webClient.el.querySelector("tbody .o_pivot_header_cell_closed"));
await click(webClient.el.querySelector(".o_pivot .o_dropdown_menu .o_dropdown_item"));
await click(webClient.el.querySelector(".o_pivot .dropdown-menu .dropdown-item"));
assert.strictEqual(getCurrentValues(webClient), ["4", "2", "1", "1", "2"].join(","));
@@ -4947,7 +4943,7 @@ QUnit.module("Views", (hooks) => {
// drill down first row group (group by company_type)
await click(webClient.el.querySelector("tbody .o_pivot_header_cell_closed"));
await click(webClient.el.querySelector(".o_pivot .o_dropdown_menu .o_dropdown_item"));
await click(webClient.el.querySelector(".o_pivot .dropdown-menu .dropdown-item"));
assert.strictEqual(getCurrentValues(webClient), ["32", "12", "12", "20"].join(","));
@@ -5156,7 +5152,7 @@ QUnit.module("Views", (hooks) => {
// Add a groupby (this operation should be ignored as it concerns the old
// table, which will be replaced soon)
await click(pivot.el.querySelector("thead .o_pivot_header_cell_closed"));
await click(pivot.el.querySelector("thead .o_dropdown_menu .o_dropdown_item"));
await click(pivot.el.querySelector("thead .dropdown-menu .dropdown-item"));
assert.strictEqual(getCurrentValues(pivot), ["32", "12", "20"].join(","));
def.resolve();
@@ -5189,7 +5185,7 @@ QUnit.module("Views", (hooks) => {
// Add a groupby, to have a group to expand afterwards
await click(pivot.el.querySelector("tbody .o_pivot_header_cell_closed"));
await click(pivot.el.querySelector("tbody .o_dropdown_menu .o_dropdown_item"));
await click(pivot.el.querySelector("tbody .dropdown-menu .dropdown-item"));
assert.strictEqual(getCurrentValues(pivot), ["32", "12", "12", "20"].join(","));
@@ -5331,12 +5327,12 @@ QUnit.module("Views", (hooks) => {
// "Count" is the only measure available
assert.deepEqual(
[...pivot.el.querySelectorAll(".o_cp_bottom_left .o_dropdown_menu li")].map(
[...pivot.el.querySelectorAll(".o_cp_bottom_left .dropdown-menu .o_menu_item")].map(
(e) => e.innerText
),
["Count"]
);
// No separator should be displayed in the menu "Measures"
assert.containsNone(pivot, ".o_cp_bottom_left .o_dropdown_menu div.dropdown-divider");
assert.containsNone(pivot, ".o_cp_bottom_left .dropdown-menu div.dropdown-divider");
});
});
@@ -967,8 +967,8 @@ QUnit.module("ActionManager", (hooks) => {
assert.containsOnce(webClient, ".o_dialog_error");
await click(webClient.el, ".modal-header .close");
assert.containsNone(webClient, ".o_dialog_error");
await click(webClient.el, "nav .o_navbar_apps_menu .o_dropdown_toggler ");
assert.containsN(webClient, ".o_dropdown_item.o_app", 3);
await click(webClient.el, "nav .o_navbar_apps_menu .dropdown-toggle ");
assert.containsN(webClient, ".dropdown-item.o_app", 3);
assert.containsNone(webClient, ".o_menu_brand");
assert.strictEqual(webClient.el.querySelector(".o_action_manager").innerHTML, "");
assert.deepEqual(webClient.env.services.router.current.hash, {
@@ -25,7 +25,7 @@ QUnit.module("ActionManager", (hooks) => {
let urlState = webClient.env.services.router.current;
assert.deepEqual(urlState.hash, {});
await click(webClient.el, ".o_navbar_apps_menu button");
await click(webClient.el, ".o_navbar_apps_menu .o_dropdown_item:nth-child(3)");
await click(webClient.el, ".o_navbar_apps_menu .dropdown-item:nth-child(3)");
await nextTick();
await nextTick();
urlState = webClient.env.services.router.current;
@@ -44,7 +44,7 @@ QUnit.module("ActionManager", (hooks) => {
let urlState = webClient.env.services.router.current;
assert.deepEqual(urlState.hash, {});
await click(webClient.el, ".o_navbar_apps_menu button");
await click(webClient.el, ".o_navbar_apps_menu .o_dropdown_item:nth-child(3)");
await click(webClient.el, ".o_navbar_apps_menu .dropdown-item:nth-child(3)");
await nextTick();
await nextTick();
urlState = webClient.env.services.router.current;
@@ -147,7 +147,7 @@ QUnit.module("ActionManager", (hooks) => {
await doAction(webClient, "client_action_pushes");
await click(webClient.el, ".test_client_action");
await click(webClient.el, ".o_navbar_apps_menu button");
await click(webClient.el, ".o_navbar_apps_menu .o_dropdown_item:nth-child(3)");
await click(webClient.el, ".o_navbar_apps_menu .dropdown-item:nth-child(3)");
await nextTick();
await nextTick();
urlState = webClient.env.services.router.current;
@@ -75,14 +75,10 @@ QUnit.module("ActionManager", (hooks) => {
assert.containsNone(webClient, ".o_cp_action_menus");
await testUtils.dom.clickFirst($(webClient.el).find("input.custom-control-input"));
assert.isVisible(
$(webClient.el).find(
'.o_cp_action_menus button.o_dropdown_toggler:contains("Print")'
)[0]
$(webClient.el).find('.o_cp_action_menus button.dropdown-toggle:contains("Print")')[0]
);
assert.isVisible(
$(webClient.el).find(
'.o_cp_action_menus button.o_dropdown_toggler:contains("Action")'
)[0]
$(webClient.el).find('.o_cp_action_menus button.dropdown-toggle:contains("Action")')[0]
);
});
@@ -1356,7 +1352,7 @@ QUnit.module("ActionManager", (hooks) => {
$(webClient.el).find(".o_control_panel .o_cp_bottom_right button:contains(Group By)")
);
await testUtils.dom.click(
$(webClient.el).find(".o_control_panel .o_group_by_menu li:first")
$(webClient.el).find(".o_control_panel .o_group_by_menu .o_menu_item:first")
);
await legacyExtraNextTick();
assert.containsN(
@@ -1485,6 +1481,7 @@ QUnit.module("ActionManager", (hooks) => {
},
},
};
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
const webClient = await createWebClient({ serverData, legacyParams });
await doAction(webClient, 33);
assert.containsN(webClient, ".o_data_row", 5, "should contain 5 records");
@@ -1575,19 +1572,19 @@ QUnit.module("ActionManager", (hooks) => {
const webClient = await createWebClient({ serverData });
await doAction(webClient, 1);
assert.isVisible(
webClient.el.querySelector(".o_search_options .o_dropdown.o_filter_menu"),
webClient.el.querySelector(".o_search_options .dropdown.o_filter_menu"),
"the search options should be available"
);
await doAction(webClient, 3);
assert.isVisible(
webClient.el.querySelector(".o_search_options .o_dropdown.o_filter_menu"),
webClient.el.querySelector(".o_search_options .dropdown.o_filter_menu"),
"the search options should be available"
);
// go back using the breadcrumbs
await testUtils.dom.click($(webClient.el).find(".o_control_panel .breadcrumb a:first"));
await legacyExtraNextTick();
assert.isVisible(
webClient.el.querySelector(".o_search_options .o_dropdown.o_filter_menu"),
webClient.el.querySelector(".o_search_options .dropdown.o_filter_menu"),
"the search options should be available"
);
}
@@ -2278,12 +2275,12 @@ QUnit.module("ActionManager", (hooks) => {
await doAction(webClient, 1);
assert.containsNone(
webClient.el,
".o_debug_manager .o_dropdown_item:contains('Edit View: Kanban')"
".o_debug_manager .dropdown-item:contains('Edit View: Kanban')"
);
await click(webClient.el.querySelector(".o_debug_manager .o_dropdown_toggler"));
await click(webClient.el.querySelector(".o_debug_manager .dropdown-toggle"));
assert.containsOnce(
webClient.el,
".o_debug_manager .o_dropdown_item:contains('Edit View: Kanban')"
".o_debug_manager .dropdown-item:contains('Edit View: Kanban')"
);
});
@@ -47,7 +47,7 @@ QUnit.test("can be rendered", async (assert) => {
const navbar = await mount(NavBar, { env, target });
assert.containsOnce(
navbar.el,
".o_navbar_apps_menu button.o_dropdown_toggler",
".o_navbar_apps_menu button.dropdown-toggle",
"1 apps menu toggler present"
);
navbar.destroy();
@@ -58,10 +58,10 @@ QUnit.test("dropdown menu can be toggled", async (assert) => {
const target = getFixture();
const navbar = await mount(NavBar, { env, target });
const dropdown = navbar.el.querySelector(".o_navbar_apps_menu");
await click(dropdown, "button.o_dropdown_toggler");
assert.containsOnce(dropdown, "ul.o_dropdown_menu");
await click(dropdown, "button.o_dropdown_toggler");
assert.containsNone(dropdown, "ul.o_dropdown_menu");
await click(dropdown, "button.dropdown-toggle");
assert.containsOnce(dropdown, ".dropdown-menu");
await click(dropdown, "button.dropdown-toggle");
assert.containsNone(dropdown, ".dropdown-menu");
navbar.destroy();
});
@@ -80,8 +80,8 @@ QUnit.test("data-menu-xmlid attribute on AppsMenu items", async (assert) => {
// check apps
const appsMenu = navbar.el.querySelector(".o_navbar_apps_menu");
await click(appsMenu, "button.o_dropdown_toggler");
const menuItems = appsMenu.querySelectorAll("li");
await click(appsMenu, "button.dropdown-toggle");
const menuItems = appsMenu.querySelectorAll("a");
assert.strictEqual(
menuItems[0].dataset.menuXmlid,
"wowl",
@@ -96,17 +96,14 @@ QUnit.test("data-menu-xmlid attribute on AppsMenu items", async (assert) => {
// check menus
env.services.menu.setCurrentMenu(1);
await nextTick();
assert.containsOnce(navbar, ".o_menu_sections .o_dropdown_item[data-menu-xmlid=menu_3]");
assert.containsOnce(navbar, ".o_menu_sections .dropdown-item[data-menu-xmlid=menu_3]");
// check sub menus toggler
assert.containsOnce(
navbar,
".o_menu_sections button.o_dropdown_toggler[data-menu-xmlid=menu_4]"
);
assert.containsOnce(navbar, ".o_menu_sections button.dropdown-toggle[data-menu-xmlid=menu_4]");
// check sub menus
await click(navbar.el.querySelector(".o_menu_sections .o_dropdown_toggler"));
assert.containsOnce(navbar, ".o_menu_sections .o_dropdown_item[data-menu-xmlid=menu_5]");
await click(navbar.el.querySelector(".o_menu_sections .dropdown-toggle"));
assert.containsOnce(navbar, ".o_menu_sections .dropdown-item[data-menu-xmlid=menu_5]");
navbar.destroy();
});
@@ -117,10 +114,10 @@ QUnit.test("navbar can display current active app", async (assert) => {
const navbar = await mount(NavBar, { env, target });
const dropdown = navbar.el.querySelector(".o_navbar_apps_menu");
// Open apps menu
await click(dropdown, "button.o_dropdown_toggler");
await click(dropdown, "button.dropdown-toggle");
assert.containsOnce(
dropdown,
"ul.o_dropdown_menu li.o_dropdown_item:not(.focus)",
".dropdown-menu .dropdown-item:not(.focus)",
"should not show the current active app as the menus service has not loaded an app yet"
);
@@ -129,7 +126,7 @@ QUnit.test("navbar can display current active app", async (assert) => {
await nextTick();
assert.containsOnce(
dropdown,
"ul.o_dropdown_menu li.o_dropdown_item.focus",
".dropdown-menu .dropdown-item.focus",
"should show the current active app"
);
navbar.destroy();
@@ -218,9 +215,9 @@ QUnit.test("can adapt with 'more' menu sections behavior", async (assert) => {
"the displayed menu section should be the 'more' menu"
);
// Open the more menu
await click(navbar.el, ".o_menu_sections_more .o_dropdown_toggler");
await click(navbar.el, ".o_menu_sections_more .dropdown-toggle");
assert.deepEqual(
[...navbar.el.querySelectorAll(".o_dropdown_menu > *")].map((el) => el.textContent),
[...navbar.el.querySelectorAll(".dropdown-menu > *")].map((el) => el.textContent),
["Section 10", "Section 11", "Section 12", "Section 120", "Section 121", "Section 122"],
"'more' menu should contain all hidden sections in correct order"
);
@@ -401,23 +398,23 @@ QUnit.test("'more' menu sections properly updated on app change", async (assert)
);
// Open the more menu
await click(navbar.el, ".o_menu_sections_more .o_dropdown_toggler");
await click(navbar.el, ".o_menu_sections_more .dropdown-toggle");
assert.deepEqual(
[...navbar.el.querySelectorAll(".o_dropdown_menu > *")].map((el) => el.textContent),
[...navbar.el.querySelectorAll(".dropdown-menu > *")].map((el) => el.textContent),
["Section 10", "Section 11", "Section 12", "Section 120", "Section 121", "Section 122"],
"'more' menu should contain App1 sections"
);
// Close the more menu
await click(navbar.el, ".o_menu_sections_more .o_dropdown_toggler");
await click(navbar.el, ".o_menu_sections_more .dropdown-toggle");
// Set App2 menu
env.services.menu.setCurrentMenu(2);
await nextTick();
// Open the more menu
await click(navbar.el, ".o_menu_sections_more .o_dropdown_toggler");
await click(navbar.el, ".o_menu_sections_more .dropdown-toggle");
assert.deepEqual(
[...navbar.el.querySelectorAll(".o_dropdown_menu > *")].map((el) => el.textContent),
[...navbar.el.querySelectorAll(".dropdown-menu > *")].map((el) => el.textContent),
["Section 20", "Section 21", "Section 22", "Section 220", "Section 221", "Section 222"],
"'more' menu should contain App2 sections"
);
@@ -56,25 +56,25 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
const scMenu = await createSwitchCompanyMenu();
assert.strictEqual(scMenu.el.tagName.toUpperCase(), "LI");
assert.strictEqual(scMenu.el.tagName.toUpperCase(), "DIV");
assert.hasClass(scMenu.el, "o_switch_company_menu");
assert.strictEqual(scMenu.el.textContent, "Hermit");
await click(scMenu.el.querySelector(".o_dropdown_toggler"));
await click(scMenu.el.querySelector(".dropdown-toggle"));
assert.containsN(scMenu, ".toggle_company", 3);
assert.containsN(scMenu, ".log_into", 3);
assert.containsOnce(scMenu.el, ".fa-check-square");
assert.containsN(scMenu.el, ".fa-square-o", 2);
assert.strictEqual(
scMenu.el.querySelector(".fa-check-square").closest(".o_dropdown_item").textContent,
scMenu.el.querySelector(".fa-check-square").closest(".dropdown-item").textContent,
"Hermit"
);
assert.strictEqual(
scMenu.el.querySelector(".fa-square-o").closest(".o_dropdown_item").textContent,
scMenu.el.querySelector(".fa-square-o").closest(".dropdown-item").textContent,
"Herman's"
);
assert.strictEqual(
scMenu.el.querySelector(".o_dropdown_menu").textContent,
scMenu.el.querySelector(".dropdown-menu").textContent,
"HermitHerman'sHeroes TM"
);
});
@@ -96,7 +96,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
await click(scMenu.el.querySelector(".o_dropdown_toggler"));
await click(scMenu.el.querySelector(".dropdown-toggle"));
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
@@ -107,7 +107,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
* [ ] Company 3
*/
await click(scMenu.el.querySelectorAll(".toggle_company")[1]);
assert.containsOnce(scMenu.el, ".o_dropdown_menu", "dropdown is still opened");
assert.containsOnce(scMenu.el, ".dropdown-menu", "dropdown is still opened");
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 1);
await prom;
@@ -131,7 +131,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
await click(scMenu.el.querySelector(".o_dropdown_toggler"));
await click(scMenu.el.querySelector(".dropdown-toggle"));
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
@@ -144,7 +144,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
await click(scMenu.el.querySelectorAll(".toggle_company")[0]);
await click(scMenu.el.querySelectorAll(".toggle_company")[1]);
await click(scMenu.el.querySelectorAll(".toggle_company")[2]);
assert.containsOnce(scMenu.el, ".o_dropdown_menu", "dropdown is still opened");
assert.containsOnce(scMenu.el, ".dropdown-menu", "dropdown is still opened");
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 1);
@@ -170,7 +170,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
await click(scMenu.el.querySelector(".o_dropdown_toggler"));
await click(scMenu.el.querySelector(".dropdown-toggle"));
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
@@ -181,7 +181,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
* [ ] Company 3
*/
await click(scMenu.el.querySelectorAll(".toggle_company")[0]);
assert.containsOnce(scMenu.el, ".o_dropdown_menu", "dropdown is still opened");
assert.containsOnce(scMenu.el, ".dropdown-menu", "dropdown is still opened");
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 0);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 3);
await prom;
@@ -203,7 +203,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
await click(scMenu.el.querySelector(".o_dropdown_toggler"));
await click(scMenu.el.querySelector(".dropdown-toggle"));
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
@@ -214,7 +214,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
* [ ] Company 3
*/
await click(scMenu.el.querySelectorAll(".log_into")[1]);
assert.containsNone(scMenu.el, ".o_dropdown_menu", "dropdown is directly closed");
assert.containsNone(scMenu.el, ".dropdown-menu", "dropdown is directly closed");
assert.verifySteps(["cids=2"]);
});
@@ -234,7 +234,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [3, 1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3);
await click(scMenu.el.querySelector(".o_dropdown_toggler"));
await click(scMenu.el.querySelector(".dropdown-toggle"));
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 1);
@@ -245,7 +245,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
* [x] **Company 3**
*/
await click(scMenu.el.querySelectorAll(".log_into")[1]);
assert.containsNone(scMenu.el, ".o_dropdown_menu", "dropdown is directly closed");
assert.containsNone(scMenu.el, ".dropdown-menu", "dropdown is directly closed");
assert.verifySteps(["cids=2%2C3%2C1"]);
});
@@ -265,7 +265,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [2, 3]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 2);
await click(scMenu.el.querySelector(".o_dropdown_toggler"));
await click(scMenu.el.querySelector(".dropdown-toggle"));
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 1);
@@ -276,7 +276,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
* [x] Company 3 -> log into
*/
await click(scMenu.el.querySelectorAll(".log_into")[2]);
assert.containsNone(scMenu.el, ".o_dropdown_menu", "dropdown is directly closed");
assert.containsNone(scMenu.el, ".dropdown-menu", "dropdown is directly closed");
assert.verifySteps(["cids=3%2C2"]);
});
@@ -295,7 +295,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
await click(scMenu.el.querySelector(".o_dropdown_toggler"));
await click(scMenu.el.querySelector(".dropdown-toggle"));
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
@@ -308,7 +308,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
await click(scMenu.el.querySelectorAll(".toggle_company")[2]);
await click(scMenu.el.querySelectorAll(".toggle_company")[0]);
await click(scMenu.el.querySelectorAll(".log_into")[1]);
assert.containsNone(scMenu.el, ".o_dropdown_menu", "dropdown is directly closed");
assert.containsNone(scMenu.el, ".dropdown-menu", "dropdown is directly closed");
assert.verifySteps(["cids=2"]);
});
});
@@ -99,25 +99,23 @@ QUnit.test("can be rendered", async (assert) => {
);
assert.containsOnce(userMenuEl, "span.oe_topbar_name");
assert.strictEqual(userMenuEl.querySelector(".oe_topbar_name").textContent, "Sauron");
assert.containsNone(userMenuEl, "ul.o_dropdown_menu li.o_dropdown_item");
await click(userMenu.el.querySelector("button.o_dropdown_toggler"));
assert.containsNone(userMenuEl, ".dropdown-menu .dropdown-item");
await click(userMenu.el.querySelector("button.dropdown-toggle"));
userMenuEl = userMenu.el;
assert.containsN(userMenuEl, "ul.o_dropdown_menu li.o_dropdown_item", 3);
assert.containsN(userMenuEl, ".dropdown-menu .dropdown-item", 3);
assert.containsOnce(userMenuEl, "div.dropdown-divider");
const children = [...(userMenuEl.querySelector("ul.o_dropdown_menu").children || [])];
const children = [...(userMenuEl.querySelector(".dropdown-menu").children || [])];
assert.deepEqual(
children.map((el) => el.tagName),
["LI", "LI", "DIV", "LI"]
["SPAN", "SPAN", "DIV", "SPAN"]
);
const items = [...userMenuEl.querySelectorAll("ul.o_dropdown_menu li.o_dropdown_item")] || [];
const items = [...userMenuEl.querySelectorAll(".dropdown-menu .dropdown-item")] || [];
assert.deepEqual(
items.map((el) => el.dataset.menu),
["ring", "bad", "eye"]
);
const spans =
[...userMenuEl.querySelectorAll("ul.o_dropdown_menu li.o_dropdown_item span")] || [];
assert.deepEqual(
spans.map((el) => el.textContent),
items.map((el) => el.textContent),
["Ring", "Bad", "Eye"]
);
for (const item of items) {
@@ -165,9 +163,9 @@ QUnit.test("can execute the callback of settings", async (assert) => {
env = await makeTestEnv(testConfig);
userMenuRegistry.add("profile", preferencesItem);
userMenu = await mount(UserMenu, { env, target });
await click(userMenu.el.querySelector("button.o_dropdown_toggler"));
assert.containsOnce(userMenu.el, "ul.o_dropdown_menu li.o_dropdown_item");
const item = userMenu.el.querySelector("ul.o_dropdown_menu li.o_dropdown_item");
await click(userMenu.el.querySelector("button.dropdown-toggle"));
assert.containsOnce(userMenu.el, ".dropdown-menu .dropdown-item");
const item = userMenu.el.querySelector(".dropdown-menu .dropdown-item");
assert.strictEqual(item.textContent, "Preferences");
await click(item);
assert.verifySteps(["7", "Change My Preferences"]);
@@ -136,11 +136,11 @@
.oe-toolbar li > a.dropdown-item {
color: white;
}
.oe-toolbar .dropdown-item:hover, .dropdown-item:focus {
.oe-toolbar .dropdown-item:hover, .oe-toolbar .dropdown-item:focus {
color: white;
background: #868686;
}
.oe-toolbar .dropdown-item.active, .dropdown-item:active {
.oe-toolbar .dropdown-item.active, .oe-toolbar .dropdown-item:active {
color: white;
background: #555555;
}
@@ -182,7 +182,7 @@ $o-we-toolbar-color-clickable-active: $o-we-bg-darkest;
.dropdown-item {
&.active, &.selected {
&:not(.o_dropdown_item_active_noarrow):before {
&:not(.dropdown-item_active_noarrow):before {
transform: translate(-1.5em, 0);
height: 100%;
display: flex;
@@ -63,10 +63,10 @@ QUnit.module("Tours", (hooks) => {
const debugManager = await mount(DebugMenuParent, { env, target });
registerCleanup(() => debugManager.destroy());
await click(debugManager.el.querySelector("button.o_dropdown_toggler"));
await click(debugManager.el.querySelector("button.dropdown-toggle"));
assert.containsOnce(debugManager.el, ".o_dropdown_item");
await click(debugManager.el.querySelector(".o_dropdown_item"));
assert.containsOnce(debugManager.el, ".dropdown-item");
await click(debugManager.el.querySelector(".dropdown-item"));
assert.verifySteps(["consume"]);
});
});
@@ -18,7 +18,7 @@ odoo.define("website.tour.backend_dashboard", function (require) {
trigger: 'button[data-menu-xmlid="website.menu_dashboard"]',
},
{
trigger: 'li[data-menu-xmlid="website.menu_website_google_analytics"]',
trigger: '.dropdown-item[data-menu-xmlid="website.menu_website_google_analytics"]',
},
{
// Visits section should always be present even when empty / not hooked to anything
@@ -3,14 +3,14 @@
<!-- publish/unpublish button -->
<template id="website_publisher" name="Website Publisher">
<t groups="website.group_website_publisher" t-if="'website_published' in main_object.fields_get() and ('can_publish' not in main_object.fields_get() or main_object.can_publish)">
<li t-attf-class="js_publish_management #{extra_classes} #{main_object.website_published and 'css_published' or 'css_unpublished'}" t-att-data-description="env['ir.model']._get(main_object._name).display_name" t-att-data-id="main_object.id" t-att-data-object="main_object._name" t-att-data-controller="publish_controller">
<div t-attf-class="js_publish_management #{extra_classes} #{main_object.website_published and 'css_published' or 'css_unpublished'}" t-att-data-description="env['ir.model']._get(main_object._name).display_name" t-att-data-id="main_object.id" t-att-data-object="main_object._name" t-att-data-controller="publish_controller">
<label class="o_switch o_switch_danger js_publish_btn m-0" t-att-accesskey="desktop_navbar and 'p'">
<input type="checkbox" t-att-checked="main_object.website_published"/>
<span/>
<span class="css_publish">Unpublished</span>
<span class="css_unpublish">Published</span>
</label>
</li>
</div>
</t>
</template>
@@ -31,22 +31,22 @@
</p>
</div>
<nav groups="base.group_user" t-if="website" id="oe_main_menu_navbar" class="o_main_navbar">
<ul id="oe_applications">
<li class="dropdown active">
<div id="oe_applications">
<div class="dropdown active">
<a class="dropdown-toggle full" data-toggle="dropdown" href="#" accesskey="h">
<i class="fa fa-th-large mr-2"/> WEBSITE
</a>
<div class="dropdown-menu" role="menu">
<!-- This will be populated on hover in JS -->
</div>
</li>
</ul>
</div>
</div>
<button type="button" class="btn fa fa-bars float-right d-block d-md-none o_mobile_menu_toggle" aria-label="Menu" title="Menu" groups="website.group_website_designer"/>
<ul class="o_menu_sections" groups="website.group_website_designer">
<div class="o_menu_sections" groups="website.group_website_designer">
<!-- Content -->
<li t-if="editable" class="dropdown" id="content-menu">
<div t-if="editable" class="dropdown" id="content-menu">
<a id="content-menu-button" class="dropdown-toggle o-no-caret waves" data-toggle="dropdown" data-display="static" href="#">Pages</a>
<div class="dropdown-menu" role="menu">
<a role="menuitem" data-action="edit_menu" href="#" title="Edit Top Menu" class="dropdown-item">Edit Menu</a>
@@ -54,37 +54,37 @@
<div t-if="deletable" role="separator" class="dropdown-divider"/>
<a role="menuitem" href="#" data-action="page_properties" class="dropdown-item" t-if="deletable">Page Properties</a>
</div>
</li>
</div>
<!-- Customize -->
<li class="dropdown" id="customize-menu">
<div class="dropdown" id="customize-menu">
<a class="dropdown-toggle o-no-caret waves" data-toggle="dropdown" data-display="static" href="#">Customize</a>
<div class="dropdown-menu" role="menu">
<a role="menuitem" href="#" data-action="ace" class="dropdown-item" id="html_editor">HTML/CSS/JS Editor</a>
<a role="menuitem" href="/web#action=website.action_website_add_features" class="dropdown-item" id="install_apps">Add Features</a>
</div>
</li>
</div>
<!-- Promote -->
<li class="dropdown" id="promote-menu">
<div class="dropdown" id="promote-menu">
<a class="dropdown-toggle o-no-caret waves" data-toggle="dropdown" data-display="static" href="#">Promote</a>
<div class="dropdown-menu oe_promote_menu" role="menu">
<a role="menuitem" data-action="promote-current-page" href="#" title="Promote page on the web" class="dropdown-item">Optimize SEO</a>
</div>
</li>
</div>
<t t-call="website.website_publisher">
<t t-set="extra_classes" t-translation="off">d-flex d-sm-flex d-md-none</t>
</t>
</ul>
</div>
<ul class="o_menu_systray d-none d-md-flex" groups="website.group_website_publisher">
<div class="o_menu_systray d-none d-md-flex" groups="website.group_website_publisher">
<t t-call="website.website_publisher">
<t t-set="extra_classes" t-value=""/>
<t t-set="desktop_navbar" t-value="true"/>
</t>
<!-- Mobile preview -->
<li class="o_mobile_preview" id="mobile-menu">
<div class="o_mobile_preview" id="mobile-menu">
<a data-action="show-mobile-preview" href="#" accesskey="v"><span title="Mobile preview" role="img" aria-label="Mobile preview" class="fa fa-mobile"/></a>
</li>
<li groups="website.group_multi_website" t-if="multi_website_websites" id="website_switcher">
</div>
<div groups="website.group_multi_website" t-if="multi_website_websites" id="website_switcher">
<a class="dropdown-toggle" data-toggle="dropdown" href="#" accesskey="w">
<i class="fa fa-globe d-lg-none"/>
<span class="d-none d-lg-inline-block">
@@ -105,10 +105,10 @@
</a>
</t>
</div>
</li>
</div>
<!-- Page Edition -->
<li class="o_new_content_menu" id="new-content-menu">
<div class="o_new_content_menu" id="new-content-menu">
<a href="#" accesskey="c"><span class="fa fa-plus mr-2"/>New</a>
<div id="o_new_content_menu_choices" class="o_hidden">
<div class="container pt32 pb32">
@@ -197,11 +197,11 @@
</div>
</div>
</div>
</li>
<li t-if="not translatable" id="edit-page-menu">
</div>
<div t-if="not translatable" id="edit-page-menu">
<a data-action="edit" href="#" accesskey="a"><span class="fa fa-pencil mr-2"/>Edit</a>
</li>
<li t-if="edit_in_backend or ('website_published' in main_object.fields_get() and main_object._name != 'website.page')">
</div>
<div t-if="edit_in_backend or ('website_published' in main_object.fields_get() and main_object._name != 'website.page')">
<a role="button" class="btn btn-primary btn-sm dropdown-toggle css_edit_dynamic" data-toggle="dropdown">
<span class="sr-only">Toggle Dropdown</span>
</a>
@@ -209,14 +209,14 @@
<a role="menuitem" style="text-align: left;" t-attf-href="/web#view_type=form&amp;model=#{main_object._name}&amp;id=#{main_object.id}&amp;action=#{action}&amp;menu_id=#{backend_menu_id}"
class="dropdown-item" title='Edit in backend' id="edit-in-backend">Edit in backend</a>
</div>
</li>
<li t-if="translatable">
</div>
<div t-if="translatable">
<a data-action="translate" href="#">TRANSLATE</a>
</li>
<li t-if="translatable">
</div>
<div t-if="translatable">
<a data-action="edit_master" href="#">or Edit Master</a>
</li>
</ul>
</div>
</div>
</nav>
</xpath>
</template>
@@ -143,7 +143,7 @@
</template>
<template id="contact_edit_options" inherit_id="website.user_navbar" name="Edit Customer Options">
<xpath expr="//li[@id='edit-page-menu']" position="after">
<xpath expr="//div[@id='edit-page-menu']" position="after">
<t t-if="main_object._name == 'res.partner'" t-set="action" t-value="'contacts.action_contacts'"/>
</xpath>
</template>
@@ -13,7 +13,7 @@
<!-- User Navbar - Edit Options -->
<template id="event_edit_options" inherit_id="website.user_navbar" name="Edit Event Options">
<xpath expr="//li[@id='edit-page-menu']" position="after">
<xpath expr="//div[@id='edit-page-menu']" position="after">
<t t-if="main_object._name == 'event.event'" t-set="action" t-value="'event.action_event_view'"/>
</xpath>
</template>
@@ -199,7 +199,7 @@
<!-- User Navbar - Edit Options -->
<template id="event_sponsor_edit_options" inherit_id="website.user_navbar" name="Edit Event Sponsor Options">
<xpath expr="//li[@id='edit-page-menu']" position="after">
<xpath expr="//div[@id='edit-page-menu']" position="after">
<t t-if="main_object._name == 'event.sponsor'"
t-set="action" t-value="'website_event_exhibitor.event_sponsor_action'"/>
</xpath>
@@ -2,7 +2,7 @@
<odoo>
<template id="track_edit_options" inherit_id="website.user_navbar" name="Edit Track Options">
<xpath expr="//li[@id='edit-page-menu']" position="after">
<xpath expr="//div[@id='edit-page-menu']" position="after">
<t t-if="main_object._name == 'event.track'" t-set="action" t-value="'website_event_track.action_event_track'" />
</xpath>
</template>
@@ -2,7 +2,7 @@
<odoo>
<template id="job_edit_options" inherit_id="website.user_navbar" name="Edit Job Options">
<xpath expr="//li[@id='edit-page-menu']" position="after">
<xpath expr="//div[@id='edit-page-menu']" position="after">
<t t-if="main_object._name == 'hr.job'" t-set="action" t-value="'hr_recruitment.action_hr_job'"/>
</xpath>
</template>
@@ -48,7 +48,7 @@ tour.register('shop_cart_recovery', {
},
{
content: "click action",
trigger: '.o_dropdown_toggler:contains("Action")',
trigger: '.dropdown-toggle:contains("Action")',
},
{
content: "click Send a Cart Recovery Email",
+1 -1
View File
@@ -539,7 +539,7 @@
<!-- /shop/product page -->
<template id="product_edit_options" inherit_id="website.user_navbar" name="Edit Product Options">
<xpath expr="//li[@id='edit-page-menu']" position="after">
<xpath expr="//div[@id='edit-page-menu']" position="after">
<t t-if="main_object._name == 'product.template'" t-set="action" t-value="'website_sale.product_template_action_website'" />
</xpath>
</template>
@@ -223,7 +223,7 @@
<div class="o_kanban_slides_card_manage_pane">
<t t-if="widget.deletable">
<div role="menuitem">
<a class="dropdown-item" type="delete">Delete</a>
<a type="delete">Delete</a>
</div>
</t>
<div role="menuitem">

Some files were not shown because too many files have changed in this diff Show More