[IMP] mail, web: allow ui size patching during tests
Until now, when we add to patch the ui size, we would have patched one of the following:
- config.device.{isMobile,size_class}
- (wowl) env.isSmall / env.services.ui.{isSmall/size}
- (legacy) env.device.isMobile
- (legacy/wowl) browser.innerWidth
The issue is that patching one does not affect the others resulting in an incoherent state.
In order to ease ui size patching, the `patchUiSize` method has been introduced. This method
take either a size or a width as parameter (eg. SIZES.SM, 1920) and affect all the properties above.
task-2582313
closes odoo/odoo#91540
Related: odoo/enterprise#27428
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from '@web/core/browser/browser';
|
||||
import { getMediaQueryLists, MEDIAS_BREAKPOINTS, SIZES } from '@web/core/ui/ui_service';
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
|
||||
import config from 'web.config';
|
||||
|
||||
/**
|
||||
* Return the width corresponding to the given size. If an upper and lower bound
|
||||
* are defined, returns the lower bound: this is an arbitrary choice that should
|
||||
* not impact anything. A test should pass the `width` parameter instead of `size`
|
||||
* if it needs a specific width to be set.
|
||||
*
|
||||
* @param {number} size
|
||||
* @returns {number} The width corresponding to the given size.
|
||||
*/
|
||||
function getWidthFromSize(size) {
|
||||
const { minWidth, maxWidth } = MEDIAS_BREAKPOINTS[size];
|
||||
return minWidth ? minWidth : maxWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size corresponding to the given width.
|
||||
*
|
||||
* @param {number} width
|
||||
* @returns {number} The size corresponding to the given width.
|
||||
*/
|
||||
function getSizeFromWidth(width) {
|
||||
return MEDIAS_BREAKPOINTS.findIndex(({ minWidth, maxWidth }) => {
|
||||
if (!maxWidth) {
|
||||
return width >= minWidth;
|
||||
}
|
||||
if (!minWidth) {
|
||||
return width <= maxWidth;
|
||||
}
|
||||
return width >= minWidth && width <= maxWidth;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch legacy objects referring to the ui size. This function must be removed
|
||||
* when switching to the new environment in the discuss app. This will impact
|
||||
* env.browser.innerWidth, env.device.isMobile and config.device.{size_class/isMobile}.
|
||||
*
|
||||
* @param {number} size
|
||||
* @param {number} width
|
||||
*/
|
||||
function legacyPatchUiSize(height, size, width) {
|
||||
const legacyEnv = owl.Component.env;
|
||||
patchWithCleanup(legacyEnv, {
|
||||
browser: {
|
||||
...legacyEnv.browser,
|
||||
innerWidth: width,
|
||||
innerHeight: height || browser.innerHeight,
|
||||
},
|
||||
device: {
|
||||
...legacyEnv.device,
|
||||
isMobile: size <= SIZES.SM,
|
||||
}
|
||||
});
|
||||
patchWithCleanup(config, {
|
||||
device: {
|
||||
...config.device,
|
||||
size_class: size,
|
||||
isMobile: size <= SIZES.SM,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust ui size either from given size (mapped to window breakpoints) or
|
||||
* width. This will impact not only config.device.{size_class/isMobile} but
|
||||
* uiService.{isSmall/size}, (wowl/legacy) browser.innerWidth, (wowl)
|
||||
* env.isSmall and (legacy) env.device.isMobile. When a size is given, the browser
|
||||
* width is set according to the breakpoints that are used by the webClient.
|
||||
*
|
||||
* @param {Object} params parameters to configure the ui size.
|
||||
* @param {number|undefined} [params.size]
|
||||
* @param {number|undefined} [params.width]
|
||||
* @param {number|undefined} [params.height]
|
||||
*/
|
||||
function patchUiSize({ height, size, width }) {
|
||||
if (!size && !width || size && width) {
|
||||
throw new Error('Either size or width must be given to the patchUiSize function');
|
||||
}
|
||||
size = size === undefined ? getSizeFromWidth(width) : size;
|
||||
width = width || getWidthFromSize(size);
|
||||
const MEDIAS = getMediaQueryLists();
|
||||
|
||||
patchWithCleanup(browser, {
|
||||
innerWidth: width,
|
||||
innerHeight: height || browser.innerHeight,
|
||||
});
|
||||
patchWithCleanup(window.MediaQueryList.prototype, {
|
||||
get matches() {
|
||||
return this.media === MEDIAS[size].media;
|
||||
},
|
||||
});
|
||||
legacyPatchUiSize(height, size, width);
|
||||
}
|
||||
|
||||
export {
|
||||
patchUiSize,
|
||||
SIZES
|
||||
};
|
||||
@@ -653,7 +653,7 @@ async function start(param0 = {}) {
|
||||
const {
|
||||
autoOpenDiscuss = false,
|
||||
discuss = {},
|
||||
env: providedEnv,
|
||||
env: providedEnv = {},
|
||||
hasWebClient = false,
|
||||
hasTimeControl = false,
|
||||
hasView = false,
|
||||
@@ -684,7 +684,18 @@ async function start(param0 = {}) {
|
||||
const { debug = false } = param0;
|
||||
initCallbacks.forEach(callback => callback(param0));
|
||||
const testSetupDoneDeferred = makeDeferred();
|
||||
let env = Object.assign(providedEnv || {});
|
||||
let env = {
|
||||
...providedEnv,
|
||||
browser: {
|
||||
...providedEnv.browser,
|
||||
innerWidth: browser.innerWidth,
|
||||
innerHeight: browser.innerHeight,
|
||||
},
|
||||
device: {
|
||||
...providedEnv.device,
|
||||
isMobile: owl.Component.env.device.isMobile,
|
||||
},
|
||||
};
|
||||
env.session = Object.assign(
|
||||
{
|
||||
is_bound: Promise.resolve(),
|
||||
|
||||
+3
-21
@@ -1,13 +1,11 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patchUiSize } from '@mail/../tests/helpers/patch_ui_size';
|
||||
import {
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import { browser } from '@web/core/browser/browser';
|
||||
import { patchWithCleanup } from '@web/../tests/helpers/utils';
|
||||
|
||||
QUnit.module('mail', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss_mobile_mailbox_selection', {}, function () {
|
||||
@@ -16,17 +14,9 @@ QUnit.module('discuss_mobile_mailbox_selection_tests.js');
|
||||
QUnit.test('select another mailbox', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
patchWithCleanup(browser, {
|
||||
innerHeight: 640,
|
||||
innerWidth: 360,
|
||||
});
|
||||
patchUiSize({ height: 360, width: 640 });
|
||||
const { click, messaging } = await start({
|
||||
autoOpenDiscuss: true,
|
||||
env: {
|
||||
device: {
|
||||
isMobile: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
@@ -77,10 +67,7 @@ QUnit.test('auto-select "Inbox" when discuss had channel as active thread', asyn
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create();
|
||||
|
||||
patchWithCleanup(browser, {
|
||||
innerHeight: 640,
|
||||
innerWidth: 360,
|
||||
});
|
||||
patchUiSize({ height: 360, width: 640 });
|
||||
const { click, messaging } = await start({
|
||||
autoOpenDiscuss: true,
|
||||
discuss: {
|
||||
@@ -88,11 +75,6 @@ QUnit.test('auto-select "Inbox" when discuss had channel as active thread', asyn
|
||||
active_id: mailChannelId1,
|
||||
},
|
||||
},
|
||||
env: {
|
||||
device: {
|
||||
isMobile: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.hasClass(
|
||||
document.querySelector('.o_MobileMessagingNavbar_tab[data-tab-id="channel"]'),
|
||||
|
||||
+18
-56
@@ -1,6 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { makeDeferred } from '@mail/utils/deferred';
|
||||
import { patchUiSize, SIZES } from '@mail/../tests/helpers/patch_ui_size';
|
||||
import {
|
||||
afterNextRender,
|
||||
nextAnimationFrame,
|
||||
@@ -8,9 +9,6 @@ import {
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import { browser } from '@web/core/browser/browser';
|
||||
import { patchWithCleanup } from '@web/../tests/helpers/utils';
|
||||
|
||||
import { file, dom } from 'web.test_utils';
|
||||
const { createFile, inputFiles } = file;
|
||||
const { triggerEvent } = dom;
|
||||
@@ -236,9 +234,7 @@ QUnit.test('open chat from "new message" chat window should open chat in place o
|
||||
},
|
||||
]);
|
||||
const imSearchDef = makeDeferred();
|
||||
patchWithCleanup(browser, {
|
||||
innerWidth: 1920,
|
||||
});
|
||||
patchUiSize({ width: 1920 });
|
||||
const { click, createMessagingMenuComponent } = await start({
|
||||
async mockRPC(route, args) {
|
||||
const res = await this._super(...arguments);
|
||||
@@ -629,13 +625,8 @@ QUnit.test('Mobile: opening a chat window should not update channel state on the
|
||||
}],
|
||||
],
|
||||
});
|
||||
const { click, createMessagingMenuComponent } = await start({
|
||||
env: {
|
||||
device: {
|
||||
isMobile: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const { click, createMessagingMenuComponent } = await start();
|
||||
await createMessagingMenuComponent();
|
||||
await click(`.o_MessagingMenu_toggler`);
|
||||
await click(`.o_NotificationList_preview`);
|
||||
@@ -664,13 +655,8 @@ QUnit.test('Mobile: closing a chat window should not update channel state on the
|
||||
}],
|
||||
],
|
||||
});
|
||||
const { click, createMessagingMenuComponent } = await start({
|
||||
env: {
|
||||
device: {
|
||||
isMobile: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const { click, createMessagingMenuComponent } = await start();
|
||||
await createMessagingMenuComponent();
|
||||
await click(`.o_MessagingMenu_toggler`);
|
||||
await click(`.o_NotificationList_preview`);
|
||||
@@ -711,13 +697,8 @@ QUnit.test("Mobile: chat window shouldn't open automatically after receiving a n
|
||||
uuid: 'channel-10-uuid',
|
||||
},
|
||||
];
|
||||
const { messaging } = await start({
|
||||
env: {
|
||||
device: {
|
||||
isMobile: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const { messaging } = await start();
|
||||
|
||||
// simulate receiving a message
|
||||
messaging.rpc({
|
||||
@@ -858,9 +839,7 @@ QUnit.test('focus next visible chat window when closing current chat window with
|
||||
],
|
||||
},
|
||||
]);
|
||||
patchWithCleanup(browser, {
|
||||
innerWidth: 1920,
|
||||
});
|
||||
patchUiSize({ width: 1920 });
|
||||
await start();
|
||||
assert.containsN(
|
||||
document.body,
|
||||
@@ -1041,9 +1020,7 @@ QUnit.test('open 2 different chat windows: enough screen width [REQUIRE FOCUS]',
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const [mailChannelId1, mailChannelId2] = pyEnv['mail.channel'].create([{ name: 'mailChannel1' }, { name: 'mailChannel2' }]);
|
||||
patchWithCleanup(browser, {
|
||||
innerWidth: 1920, // enough to fit at least 2 chat windows
|
||||
});
|
||||
patchUiSize({ width: 1920 }); // enough to fit at least 2 chat windows
|
||||
const { click, createMessagingMenuComponent, messaging } = await start();
|
||||
await createMessagingMenuComponent();
|
||||
await click(`.o_MessagingMenu_toggler`);
|
||||
@@ -1267,9 +1244,7 @@ QUnit.test('open 2 folded chat windows: check shift operations are available', a
|
||||
channel_type: "chat",
|
||||
};
|
||||
pyEnv['mail.channel'].create([channel, chat]);
|
||||
patchWithCleanup(browser, {
|
||||
innerWidth: 900,
|
||||
});
|
||||
patchUiSize({ width: 900 });
|
||||
const { click } = await start();
|
||||
|
||||
assert.containsN(
|
||||
@@ -1377,9 +1352,7 @@ QUnit.test('open 3 different chat windows: not enough screen width', async funct
|
||||
{ name: 'mailChannel2' },
|
||||
{ name: 'mailChannel3' },
|
||||
]);
|
||||
patchWithCleanup(browser, {
|
||||
innerWidth: 900, // enough to fit 2 chat windows but not 3
|
||||
});
|
||||
patchUiSize({ width: 900 }); // enough to fit 2 chat windows but not 3
|
||||
const { click, createMessagingMenuComponent, messaging } = await start();
|
||||
await createMessagingMenuComponent();
|
||||
|
||||
@@ -1633,9 +1606,7 @@ QUnit.test('chat window: TAB cycle with 3 open chat windows [REQUIRE FOCUS]', as
|
||||
],
|
||||
},
|
||||
]);
|
||||
patchWithCleanup(browser, {
|
||||
innerWidth: 1920,
|
||||
});
|
||||
patchUiSize({ width: 1920 });
|
||||
await start();
|
||||
assert.containsN(
|
||||
document.body,
|
||||
@@ -1830,13 +1801,8 @@ QUnit.test('chat window: post message on non-mailing channel with "CTRL-Enter" k
|
||||
}],
|
||||
],
|
||||
});
|
||||
const { click, createMessagingMenuComponent, insertText } = await start({
|
||||
env: {
|
||||
device: {
|
||||
isMobile: true, // here isMobile is used for the small screen size, not actually for the mobile devices
|
||||
},
|
||||
},
|
||||
});
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const { click, createMessagingMenuComponent, insertText } = await start();
|
||||
await createMessagingMenuComponent();
|
||||
|
||||
await click(`.o_MessagingMenu_toggler`);
|
||||
@@ -2046,9 +2012,7 @@ QUnit.test('chat window does not fetch messages if hidden', async function (asse
|
||||
name: "Channel #12",
|
||||
},
|
||||
]);
|
||||
patchWithCleanup(browser, {
|
||||
innerWidth: 900,
|
||||
});
|
||||
patchUiSize({ width: 900 });
|
||||
const { click, messaging } = await start({
|
||||
mockRPC(route, args) {
|
||||
if (route === '/mail/channel/messages') {
|
||||
@@ -2449,9 +2413,7 @@ QUnit.test('should not have chat window hidden menu in mobile (transition from 2
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const [mailChannelId1, mailChannelId2] = pyEnv['mail.channel'].create([{ name: 'mailChannel1' }, { name: 'mailChannel1' }]);
|
||||
patchWithCleanup(browser, {
|
||||
innerWidth: 600, // enough to fit 1 chat window + hidden menu
|
||||
});
|
||||
patchUiSize({ width: 600 }); // enough to fit 1 chat window + hidden menu
|
||||
const { click, createMessagingMenuComponent, messaging } = await start();
|
||||
await createMessagingMenuComponent();
|
||||
// open, from systray menu, chat windows of channels with id 1, 2
|
||||
@@ -2465,7 +2427,7 @@ QUnit.test('should not have chat window hidden menu in mobile (transition from 2
|
||||
}).localId
|
||||
}"]
|
||||
`);
|
||||
await click('.o_MessagingMenu_toggler');
|
||||
await click('.o_ChatWindowHeader_commandBack');
|
||||
await click(`
|
||||
.o_MessagingMenu_dropdownMenu
|
||||
.o_NotificationList_preview[data-thread-local-id="${
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { makeDeferred } from '@mail/utils/deferred';
|
||||
import { patchUiSize, SIZES } from '@mail/../tests/helpers/patch_ui_size';
|
||||
import {
|
||||
afterNextRender,
|
||||
nextAnimationFrame,
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import config from 'web.config';
|
||||
import fieldRegistry from 'web.field_registry';
|
||||
import FormView from 'web.FormView';
|
||||
import { dom, nextTick } from 'web.test_utils';
|
||||
@@ -775,6 +775,7 @@ QUnit.test('Form view not scrolled when switching record', async function (asser
|
||||
});
|
||||
pyEnv['mail.message'].create(messages);
|
||||
|
||||
patchUiSize({ size: SIZES.LG });
|
||||
const { click } = await this.createView({
|
||||
hasView: true,
|
||||
// View params
|
||||
@@ -795,12 +796,6 @@ QUnit.test('Form view not scrolled when switching record', async function (asser
|
||||
currentId: resPartnerId1,
|
||||
ids: [resPartnerId1, resPartnerId2],
|
||||
},
|
||||
config: {
|
||||
device: { size_class: config.device.SIZES.LG },
|
||||
},
|
||||
env: {
|
||||
device: { size_class: config.device.SIZES.LG },
|
||||
},
|
||||
});
|
||||
|
||||
const controllerContentEl = document.querySelector('.o_content');
|
||||
|
||||
@@ -38,6 +38,35 @@ export function useActiveElement(refName) {
|
||||
);
|
||||
}
|
||||
|
||||
// window size handling
|
||||
export const MEDIAS_BREAKPOINTS = [
|
||||
{ maxWidth: 474 },
|
||||
{ minWidth: 475, maxWidth: 575 },
|
||||
{ minWidth: 576, maxWidth: 767 },
|
||||
{ minWidth: 769, maxWidth: 991 },
|
||||
{ minWidth: 992, maxWidth: 1199 },
|
||||
{ minWidth: 1200, maxWidth: 1533 },
|
||||
{ minWidth: 1534 },
|
||||
];
|
||||
|
||||
/**
|
||||
* Create the MediaQueryList used both by the uiService and config from
|
||||
* `MEDIA_BREAKPOINTS`.
|
||||
*
|
||||
* @returns {MediaQueryList[]}
|
||||
*/
|
||||
export function getMediaQueryLists() {
|
||||
return MEDIAS_BREAKPOINTS.map(({ minWidth, maxWidth }) => {
|
||||
if (!maxWidth) {
|
||||
return window.matchMedia(`(min-width: ${minWidth}px)`);
|
||||
}
|
||||
if (!minWidth) {
|
||||
return window.matchMedia(`(max-width: ${maxWidth}px)`);
|
||||
}
|
||||
return window.matchMedia(`(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`);
|
||||
});
|
||||
}
|
||||
|
||||
export const uiService = {
|
||||
start(env) {
|
||||
let ui = {};
|
||||
@@ -109,15 +138,7 @@ export const uiService = {
|
||||
});
|
||||
|
||||
// window size handling
|
||||
const MEDIAS = [
|
||||
window.matchMedia("(max-width: 474px)"),
|
||||
window.matchMedia("(min-width: 475px) and (max-width: 575px)"),
|
||||
window.matchMedia("(min-width: 576px) and (max-width: 767px)"),
|
||||
window.matchMedia("(min-width: 768px) and (max-width: 991px)"),
|
||||
window.matchMedia("(min-width: 992px) and (max-width: 1199px)"),
|
||||
window.matchMedia("(min-width: 1200px) and (max-width: 1533px)"),
|
||||
window.matchMedia("(min-width: 1534px)"),
|
||||
];
|
||||
const MEDIAS = getMediaQueryLists();
|
||||
function getSize() {
|
||||
return MEDIAS.findIndex((media) => media.matches);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ odoo.define('web.config', function (require) {
|
||||
|
||||
const Bus = require('web.Bus');
|
||||
const { hasTouch, isAndroid, isIOS, isMobileOS } = require('@web/core/browser/feature_detection');
|
||||
const { getMediaQueryLists } = require('@web/core/ui/ui_service');
|
||||
|
||||
const bus = new Bus();
|
||||
|
||||
@@ -87,16 +88,7 @@ var config = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
var medias = [
|
||||
window.matchMedia('(max-width: 474px)'),
|
||||
window.matchMedia('(min-width: 475px) and (max-width: 575px)'),
|
||||
window.matchMedia('(min-width: 576px) and (max-width: 767px)'),
|
||||
window.matchMedia('(min-width: 768px) and (max-width: 991px)'),
|
||||
window.matchMedia('(min-width: 992px) and (max-width: 1199px)'),
|
||||
window.matchMedia('(min-width: 1200px) and (max-width: 1533px)'),
|
||||
window.matchMedia('(min-width: 1534px)'),
|
||||
];
|
||||
const medias = getMediaQueryLists();
|
||||
|
||||
/**
|
||||
* Return the current size class
|
||||
|
||||
Reference in New Issue
Block a user