Before this commit: when user selects company from company switcher, page is reloaded, what if user wants to select multiple companies from dropdown, he have to wait for page reload and need to do same process again. After this commit: when user selects company from company switcher, page will not reloaded as soon as user toggles checkbox, user will given time to select another item or unselect other item from dropdown, so user can select multiple companies or unselect multiple companies. task-2467747 closes odoo-dev/odoo#875 Related: odoo-dev/enterprise#148 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
211 lines
7.2 KiB
JavaScript
211 lines
7.2 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { isMacOS } from "@web/core/browser/feature_detection";
|
|
import { patch, unpatch } from "@web/core/utils/patch";
|
|
import { registerCleanup } from "./cleanup";
|
|
import { download } from "@web/core/network/download";
|
|
|
|
const { Settings } = luxon;
|
|
|
|
/**
|
|
* Patch the native Date object
|
|
*
|
|
* Note that it will be automatically unpatched at the end of the test
|
|
*
|
|
* @param {number} [year]
|
|
* @param {number} [month]
|
|
* @param {number} [day]
|
|
* @param {number} [hours]
|
|
* @param {number} [minutes]
|
|
* @param {number} [seconds]
|
|
*/
|
|
export function patchDate(year, month, day, hours, minutes, seconds) {
|
|
const actualDate = new Date();
|
|
const fakeDate = new Date(year, month, day, hours, minutes, seconds);
|
|
const timeInterval = actualDate.getTime() - fakeDate.getTime();
|
|
const originalNow = Settings.now;
|
|
Settings.now = () => Date.now() - timeInterval;
|
|
registerCleanup(() => {
|
|
Settings.now = originalNow;
|
|
});
|
|
}
|
|
|
|
let nextId = 1;
|
|
|
|
/**
|
|
*
|
|
* @param {Object} obj object to patch
|
|
* @param {Object} patchValue the actual patch description
|
|
* @param {{pure?: boolean}} [options]
|
|
*/
|
|
export function patchWithCleanup(obj, patchValue, options) {
|
|
const patchName = `__test_patch_${nextId++}__`;
|
|
patch(obj, patchName, patchValue, options);
|
|
registerCleanup(() => {
|
|
unpatch(obj, patchName);
|
|
});
|
|
}
|
|
|
|
export function getFixture() {
|
|
if (QUnit.config.debug) {
|
|
return document.body;
|
|
} else {
|
|
return document.querySelector("#qunit-fixture");
|
|
}
|
|
}
|
|
|
|
export async function nextTick() {
|
|
await new Promise((resolve) => window.requestAnimationFrame(resolve));
|
|
await new Promise((resolve) => setTimeout(resolve));
|
|
}
|
|
|
|
export function makeDeferred() {
|
|
/** @type {(value:any)=>void} */
|
|
let resolve;
|
|
/** @type {(reason?:any)=>void} */
|
|
let reject;
|
|
const prom = new Promise((res, rej) => {
|
|
resolve = res;
|
|
reject = rej;
|
|
});
|
|
return Object.assign(prom, { resolve, reject });
|
|
}
|
|
|
|
function findElement(el, selector) {
|
|
let target = el;
|
|
if (selector) {
|
|
const els = el.querySelectorAll(selector);
|
|
if (els.length === 0) {
|
|
throw new Error(`No element found (selector: ${selector})`);
|
|
}
|
|
if (els.length > 1) {
|
|
throw new Error(`Found ${els.length} elements, instead of 1 (selector: ${selector})`);
|
|
}
|
|
target = els[0];
|
|
}
|
|
return target;
|
|
}
|
|
|
|
const keyboardEventBubble = (args) =>
|
|
Object.assign({}, args, { bubbles: true, keyCode: args.which });
|
|
|
|
const mouseEventMapping = (args) =>
|
|
Object.assign({}, args, {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
clientX: args ? args.pageX : undefined,
|
|
clientY: args ? args.pageY : undefined,
|
|
view: window,
|
|
});
|
|
|
|
const mouseEventNoBubble = (args) =>
|
|
Object.assign({}, args, {
|
|
bubbles: false,
|
|
cancelable: false,
|
|
clientX: args ? args.pageX : undefined,
|
|
clientY: args ? args.pageY : undefined,
|
|
view: window,
|
|
});
|
|
|
|
const noBubble = (args) => Object.assign({}, args, { bubbles: false });
|
|
|
|
const onlyBubble = (args) => Object.assign({}, args, { bubbles: true });
|
|
|
|
// TriggerEvent constructor/args processor mapping
|
|
const EVENT_TYPES = {
|
|
auxclick: { constructor: MouseEvent, processParameters: mouseEventMapping },
|
|
click: { constructor: MouseEvent, processParameters: mouseEventMapping },
|
|
contextmenu: { constructor: MouseEvent, processParameters: mouseEventMapping },
|
|
dblclick: { constructor: MouseEvent, processParameters: mouseEventMapping },
|
|
mousedown: { constructor: MouseEvent, processParameters: mouseEventMapping },
|
|
mouseup: { constructor: MouseEvent, processParameters: mouseEventMapping },
|
|
mousemove: { constructor: MouseEvent, processParameters: mouseEventMapping },
|
|
mouseenter: { constructor: MouseEvent, processParameters: mouseEventNoBubble },
|
|
mouseleave: { constructor: MouseEvent, processParameters: mouseEventNoBubble },
|
|
mouseover: { constructor: MouseEvent, processParameters: mouseEventMapping },
|
|
mouseout: { constructor: MouseEvent, processParameters: mouseEventMapping },
|
|
focus: { constructor: FocusEvent, processParameters: noBubble },
|
|
focusin: { constructor: FocusEvent, processParameters: onlyBubble },
|
|
blur: { constructor: FocusEvent, processParameters: noBubble },
|
|
cut: { constructor: ClipboardEvent, processParameters: onlyBubble },
|
|
copy: { constructor: ClipboardEvent, processParameters: onlyBubble },
|
|
paste: { constructor: ClipboardEvent, processParameters: onlyBubble },
|
|
keydown: { constructor: KeyboardEvent, processParameters: keyboardEventBubble },
|
|
keypress: { constructor: KeyboardEvent, processParameters: keyboardEventBubble },
|
|
keyup: { constructor: KeyboardEvent, processParameters: keyboardEventBubble },
|
|
drag: { constructor: DragEvent, processParameters: onlyBubble },
|
|
dragend: { constructor: DragEvent, processParameters: onlyBubble },
|
|
dragenter: { constructor: DragEvent, processParameters: onlyBubble },
|
|
dragstart: { constructor: DragEvent, processParameters: onlyBubble },
|
|
dragleave: { constructor: DragEvent, processParameters: onlyBubble },
|
|
dragover: { constructor: DragEvent, processParameters: onlyBubble },
|
|
drop: { constructor: DragEvent, processParameters: onlyBubble },
|
|
input: { constructor: InputEvent, processParameters: onlyBubble },
|
|
compositionstart: { constructor: CompositionEvent, processParameters: onlyBubble },
|
|
compositionend: { constructor: CompositionEvent, processParameters: onlyBubble },
|
|
};
|
|
|
|
export async function triggerEvent(el, selector, eventType, eventAttrs = {}) {
|
|
let event;
|
|
if (eventType in EVENT_TYPES) {
|
|
const { constructor, processParameters } = EVENT_TYPES[eventType];
|
|
event = new constructor(eventType, processParameters(eventAttrs));
|
|
} else {
|
|
event = new Event(eventType, Object.assign({}, eventAttrs, { bubbles: true }));
|
|
}
|
|
const target = findElement(el, selector);
|
|
target.dispatchEvent(event);
|
|
await nextTick();
|
|
}
|
|
|
|
export async function triggerEvents(el, querySelector, events) {
|
|
for (let e = 0; e < events.length; e++) {
|
|
await triggerEvent(el, querySelector, events[e]);
|
|
}
|
|
}
|
|
|
|
export function click(el, selector) {
|
|
return triggerEvent(el, selector, "click", { bubbles: true, cancelable: true });
|
|
}
|
|
|
|
/**
|
|
* Triggers an hotkey properly disregarding the operating system.
|
|
*
|
|
* @param {string} hotkey
|
|
* @param {boolean} altIsOptional
|
|
* @param {KeyboardEventInit} eventAttrs
|
|
*/
|
|
export function triggerHotkey(hotkey, altIsOptional = false, eventAttrs = {}) {
|
|
eventAttrs.key = hotkey.split("+").pop();
|
|
|
|
if (/shift/i.test(hotkey)) {
|
|
eventAttrs.shiftKey = true;
|
|
}
|
|
|
|
if (/control/i.test(hotkey)) {
|
|
if (isMacOS()) {
|
|
eventAttrs.metaKey = true;
|
|
} else {
|
|
eventAttrs.ctrlKey = true;
|
|
}
|
|
}
|
|
|
|
if (!altIsOptional) {
|
|
if (isMacOS()) {
|
|
eventAttrs.ctrlKey = true;
|
|
} else {
|
|
eventAttrs.altKey = true;
|
|
}
|
|
}
|
|
|
|
window.dispatchEvent(new KeyboardEvent("keydown", eventAttrs));
|
|
}
|
|
|
|
export async function legacyExtraNextTick() {
|
|
return nextTick();
|
|
}
|
|
|
|
export function mockDownload(cb) {
|
|
patchWithCleanup(download, { _download: cb });
|
|
}
|