[REF] web,*: remove some legacy test helpers
Part of task~3439226 Part-of: odoo/odoo#129725
This commit is contained in:
@@ -7,9 +7,7 @@ import {
|
||||
dragAndDrop,
|
||||
getFixture,
|
||||
} from '@web/../tests/helpers/utils';
|
||||
import testUtils from 'web.test_utils';
|
||||
import { serializeDateTime } from "@web/core/l10n/dates";
|
||||
const find = testUtils.dom.find;
|
||||
|
||||
let target;
|
||||
|
||||
@@ -222,7 +220,7 @@ QUnit.module('Crm Rainbowman Triggers', {
|
||||
mockRPC: getMockRpc(assert),
|
||||
});
|
||||
|
||||
await dragAndDrop(find(target, ".o_kanban_record", "Lead 6"), target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
await dragAndDrop($(target).find(".o_kanban_record:contains(Lead 6)")[0], target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
assert.verifySteps(['Go, go, go! Congrats for your first deal.']);
|
||||
});
|
||||
|
||||
@@ -234,7 +232,7 @@ QUnit.module('Crm Rainbowman Triggers', {
|
||||
mockRPC: getMockRpc(assert),
|
||||
});
|
||||
|
||||
await dragAndDrop(find(target, ".o_kanban_record", "Lead 2"), target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
await dragAndDrop($(target).find(".o_kanban_record:contains(Lead 2)")[0], target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
assert.verifySteps(['Boom! Team record for the past 30 days.']);
|
||||
});
|
||||
|
||||
@@ -246,7 +244,7 @@ QUnit.module('Crm Rainbowman Triggers', {
|
||||
mockRPC: getMockRpc(assert),
|
||||
});
|
||||
|
||||
await dragAndDrop(find(target, ".o_kanban_record", "Lead 1"), target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
await dragAndDrop($(target).find(".o_kanban_record:contains(Lead 1)")[0], target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
assert.verifySteps(['Yeah! Deal of the last 7 days for the team.']);
|
||||
});
|
||||
|
||||
@@ -258,7 +256,7 @@ QUnit.module('Crm Rainbowman Triggers', {
|
||||
mockRPC: getMockRpc(assert),
|
||||
});
|
||||
|
||||
await dragAndDrop(find(target, ".o_kanban_record", "Lead 8"), target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
await dragAndDrop($(target).find(".o_kanban_record:contains(Lead 8)")[0], target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
assert.verifySteps(['You just beat your personal record for the past 30 days.']);
|
||||
});
|
||||
|
||||
@@ -270,7 +268,7 @@ QUnit.module('Crm Rainbowman Triggers', {
|
||||
mockRPC: getMockRpc(assert),
|
||||
});
|
||||
|
||||
await dragAndDrop(find(target, ".o_kanban_record", "Lead 10"), target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
await dragAndDrop($(target).find(".o_kanban_record:contains(Lead 10)")[0], target.querySelector('.o_kanban_group:nth-of-type(3)'));
|
||||
assert.verifySteps(['You just beat your personal record for the past 7 days.']);
|
||||
});
|
||||
|
||||
@@ -282,7 +280,7 @@ QUnit.module('Crm Rainbowman Triggers', {
|
||||
mockRPC: getMockRpc(assert),
|
||||
});
|
||||
|
||||
await dragAndDrop(find(target, ".o_kanban_record", "Lead 8"), target.querySelector('.o_kanban_group:nth-of-type(2)'));
|
||||
await dragAndDrop($(target).find(".o_kanban_record:contains(Lead 8)")[0], target.querySelector('.o_kanban_group:nth-of-type(2)'));
|
||||
assert.verifySteps(["no rainbowman"]);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ import {
|
||||
getFixture,
|
||||
patchDate,
|
||||
} from '@web/../tests/helpers/utils';
|
||||
import testUtils from 'web.test_utils';
|
||||
const find = testUtils.dom.find;
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
@@ -85,7 +83,7 @@ QUnit.module('Crm Forecast Model Extension', {
|
||||
"2nd column March should contain 2 records");
|
||||
|
||||
// remove the filter
|
||||
await click(find(target, '.o_searchview_facet', "Forecast"), '.o_facet_remove');
|
||||
await click($(target).find(".o_searchview_facet:contains(Forecast)")[0], ".o_facet_remove");
|
||||
|
||||
assert.containsN(target, '.o_kanban_group', 3, "There should be 3 columns");
|
||||
assert.containsN(target, '.o_kanban_group:nth-child(1) .o_kanban_record', 2,
|
||||
@@ -118,7 +116,7 @@ QUnit.module('Crm Forecast Model Extension', {
|
||||
"2nd column March should contain 2 records");
|
||||
|
||||
// remove the filter
|
||||
await click(find(target, '.o_searchview_facet', "Forecast"), '.o_facet_remove');
|
||||
await click($(target).find(".o_searchview_facet:contains(Forecast)")[0], ".o_facet_remove");
|
||||
|
||||
assert.containsN(target, '.o_kanban_group', 3, "There should be 3 columns");
|
||||
assert.containsN(target, '.o_kanban_group:nth-child(1) .o_kanban_record', 2,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import testUtils from "web.test_utils";
|
||||
import { mount } from "@web/../tests/helpers/utils";
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import { getFixture, mount } from "@web/../tests/helpers/utils";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { numberBufferService } from "@point_of_sale/app/utils/number_buffer_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
|
||||
import { Component, useState, xml } from "@odoo/owl";
|
||||
|
||||
QUnit.module("unit tests for NumberBuffer", {
|
||||
@@ -19,7 +18,7 @@ QUnit.module("unit tests for NumberBuffer", {
|
||||
|
||||
QUnit.test("simple fast inputs with capture in between", async function (assert) {
|
||||
assert.expect(3);
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const env = await makeTestEnv();
|
||||
|
||||
class Root extends Component {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import makeTestEnvironment from "web.test_env";
|
||||
import Popover from "web.Popover";
|
||||
import testUtils from "web.test_utils";
|
||||
import { click, mount } from "@web/../tests/helpers/utils";
|
||||
import { click, getFixture, mount } from "@web/../tests/helpers/utils";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
const { useState, xml } = owl;
|
||||
@@ -46,7 +46,7 @@
|
||||
</Popover>
|
||||
</div>`;
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const env = makeTestEnvironment();
|
||||
|
||||
/**
|
||||
@@ -164,7 +164,7 @@
|
||||
<span id="dismissPopovers">💀</span>
|
||||
</div>`;
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const env = makeTestEnvironment();
|
||||
|
||||
const body = document.querySelector('body');
|
||||
@@ -204,7 +204,7 @@
|
||||
`;
|
||||
Parent.components = { Popover };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const env = makeTestEnvironment();
|
||||
|
||||
await mount(Parent, target, { env });
|
||||
@@ -248,7 +248,7 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const env = makeTestEnvironment();
|
||||
|
||||
await mount(Parent, target, { env });
|
||||
|
||||
@@ -4,23 +4,14 @@
|
||||
* Test Utils
|
||||
*
|
||||
* In this module, we define various utility functions to help simulate a mock
|
||||
* environment as close as possible as a real environment. The main function is
|
||||
* certainly createView, which takes a bunch of parameters and give you back an
|
||||
* instance of a view, appended in the dom, ready to be tested.
|
||||
* environment as close as possible as a real environment.
|
||||
*/
|
||||
|
||||
import testUtilsCreate from "web.test_utils_create";
|
||||
import testUtilsControlPanel from "web.test_utils_control_panel";
|
||||
import testUtilsDom from "web.test_utils_dom";
|
||||
import testUtilsFields from "web.test_utils_fields";
|
||||
import testUtilsFile from "web.test_utils_file";
|
||||
import testUtilsForm from "web.test_utils_form";
|
||||
import testUtilsGraph from "web.test_utils_graph";
|
||||
import testUtilsKanban from "web.test_utils_kanban";
|
||||
import testUtilsMock from "web.test_utils_mock";
|
||||
import testUtilsModal from "web.test_utils_modal";
|
||||
import testUtilsPivot from "web.test_utils_pivot";
|
||||
|
||||
import Widget from "web.Widget";
|
||||
|
||||
function deprecated(fn, type) {
|
||||
return function () {
|
||||
@@ -31,6 +22,19 @@
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a widget parent from given parameters.
|
||||
*
|
||||
* @param {Object} params This object will be given to addMockEnvironment, so
|
||||
* any parameters from that method applies
|
||||
* @returns {Promise<Widget>}
|
||||
*/
|
||||
async function createParent(params) {
|
||||
const widget = new Widget();
|
||||
await testUtilsMock.addMockEnvironment(widget, params);
|
||||
return widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function, make a promise with a public resolve function. Note that
|
||||
* this is not standard and should not be used outside of tests...
|
||||
@@ -94,25 +98,6 @@
|
||||
return testUtilsDom.returnAfterNextAnimationFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Awaits for an additionnal rendering frame initiated by the Owl
|
||||
* compatibility layer processing.
|
||||
*
|
||||
* By default a simple "nextTick" will handle the rendering of any widget/
|
||||
* component stuctures having at most 1 switch between the type of
|
||||
* entities (Component > Widget or Widget > Component). However more time
|
||||
* must be spent rendering in case we have additionnal switches. In such
|
||||
* cases this function must be used (1 call for each additionnal switch)
|
||||
* since it will be removed along with the compatiblity layer once the
|
||||
* framework has been entirely converted, and using this helper will make
|
||||
* it easier to wipe it from the code base.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function owlCompatibilityExtraNextTick() {
|
||||
return testUtilsDom.returnAfterNextAnimationFrame();
|
||||
}
|
||||
|
||||
export default {
|
||||
mock: {
|
||||
addMockEnvironment: testUtilsMock.addMockEnvironment,
|
||||
@@ -122,106 +107,19 @@
|
||||
patchDate: testUtilsMock.patchDate,
|
||||
unpatch: testUtilsMock.unpatch,
|
||||
getView: testUtilsMock.getView,
|
||||
patchSetTimeout: testUtilsMock.patchSetTimeout,
|
||||
},
|
||||
controlPanel: {
|
||||
// Generic interactions
|
||||
toggleMenu: testUtilsControlPanel.toggleMenu,
|
||||
toggleMenuItem: testUtilsControlPanel.toggleMenuItem,
|
||||
toggleMenuItemOption: testUtilsControlPanel.toggleMenuItemOption,
|
||||
isItemSelected: testUtilsControlPanel.isItemSelected,
|
||||
isOptionSelected: testUtilsControlPanel.isOptionSelected,
|
||||
getMenuItemTexts: testUtilsControlPanel.getMenuItemTexts,
|
||||
// Button interactions
|
||||
getButtons: testUtilsControlPanel.getButtons,
|
||||
// FilterMenu interactions
|
||||
toggleFilterMenu: testUtilsControlPanel.toggleFilterMenu,
|
||||
toggleAddCustomFilter: testUtilsControlPanel.toggleAddCustomFilter,
|
||||
applyFilter: testUtilsControlPanel.applyFilter,
|
||||
addCondition: testUtilsControlPanel.addCondition,
|
||||
// GroupByMenu interactions
|
||||
toggleGroupByMenu: testUtilsControlPanel.toggleGroupByMenu,
|
||||
toggleAddCustomGroup: testUtilsControlPanel.toggleAddCustomGroup,
|
||||
selectGroup: testUtilsControlPanel.selectGroup,
|
||||
applyGroup: testUtilsControlPanel.applyGroup,
|
||||
// FavoriteMenu interactions
|
||||
toggleFavoriteMenu: testUtilsControlPanel.toggleFavoriteMenu,
|
||||
toggleSaveFavorite: testUtilsControlPanel.toggleSaveFavorite,
|
||||
editFavoriteName: testUtilsControlPanel.editFavoriteName,
|
||||
saveFavorite: testUtilsControlPanel.saveFavorite,
|
||||
deleteFavorite: testUtilsControlPanel.deleteFavorite,
|
||||
// ComparisonMenu interactions
|
||||
toggleComparisonMenu: testUtilsControlPanel.toggleComparisonMenu,
|
||||
// SearchBar interactions
|
||||
getFacetTexts: testUtilsControlPanel.getFacetTexts,
|
||||
removeFacet: testUtilsControlPanel.removeFacet,
|
||||
editSearch: testUtilsControlPanel.editSearch,
|
||||
validateSearch: testUtilsControlPanel.validateSearch,
|
||||
// Action menus interactions
|
||||
toggleActionMenu: testUtilsControlPanel.toggleActionMenu,
|
||||
// Pager interactions
|
||||
pagerPrevious: testUtilsControlPanel.pagerPrevious,
|
||||
pagerNext: testUtilsControlPanel.pagerNext,
|
||||
getPagerValue: testUtilsControlPanel.getPagerValue,
|
||||
getPagerSize: testUtilsControlPanel.getPagerSize,
|
||||
setPagerValue: testUtilsControlPanel.setPagerValue,
|
||||
// View switcher
|
||||
switchView: testUtilsControlPanel.switchView,
|
||||
},
|
||||
dom: {
|
||||
triggerKeypressEvent: testUtilsDom.triggerKeypressEvent,
|
||||
triggerMouseEvent: testUtilsDom.triggerMouseEvent,
|
||||
triggerPositionalMouseEvent: testUtilsDom.triggerPositionalMouseEvent,
|
||||
triggerPositionalTapEvents: testUtilsDom.triggerPositionalTapEvents,
|
||||
dragAndDrop: testUtilsDom.dragAndDrop,
|
||||
find: testUtilsDom.findItem,
|
||||
getNode: testUtilsDom.getNode,
|
||||
openDatepicker: testUtilsDom.openDatepicker,
|
||||
click: testUtilsDom.click,
|
||||
clickFirst: testUtilsDom.clickFirst,
|
||||
clickLast: testUtilsDom.clickLast,
|
||||
triggerEvents: testUtilsDom.triggerEvents,
|
||||
triggerEvent: testUtilsDom.triggerEvent,
|
||||
},
|
||||
form: {
|
||||
clickEdit: testUtilsForm.clickEdit,
|
||||
clickSave: testUtilsForm.clickSave,
|
||||
clickCreate: testUtilsForm.clickCreate,
|
||||
clickDiscard: testUtilsForm.clickDiscard,
|
||||
reload: testUtilsForm.reload,
|
||||
},
|
||||
graph: {
|
||||
reload: testUtilsGraph.reload,
|
||||
},
|
||||
kanban: {
|
||||
reload: testUtilsKanban.reload,
|
||||
clickCreate: testUtilsKanban.clickCreate,
|
||||
quickCreate: testUtilsKanban.quickCreate,
|
||||
toggleGroupSettings: testUtilsKanban.toggleGroupSettings,
|
||||
toggleRecordDropdown: testUtilsKanban.toggleRecordDropdown,
|
||||
},
|
||||
modal: {
|
||||
clickButton: testUtilsModal.clickButton,
|
||||
},
|
||||
pivot: {
|
||||
clickMeasure: testUtilsPivot.clickMeasure,
|
||||
toggleMeasuresDropdown: testUtilsPivot.toggleMeasuresDropdown,
|
||||
reload: testUtilsPivot.reload,
|
||||
},
|
||||
fields: {
|
||||
many2one: {
|
||||
createAndEdit: testUtilsFields.clickM2OCreateAndEdit,
|
||||
clickOpenDropdown: testUtilsFields.clickOpenM2ODropdown,
|
||||
clickHighlightedItem: testUtilsFields.clickM2OHighlightedItem,
|
||||
clickItem: testUtilsFields.clickM2OItem,
|
||||
searchAndClickItem: testUtilsFields.searchAndClickM2OItem,
|
||||
},
|
||||
editInput: testUtilsFields.editInput,
|
||||
editSelect: testUtilsFields.editSelect,
|
||||
editAndTrigger: testUtilsFields.editAndTrigger,
|
||||
triggerKey: testUtilsFields.triggerKey,
|
||||
triggerKeydown: testUtilsFields.triggerKeydown,
|
||||
triggerKeyup: testUtilsFields.triggerKeyup,
|
||||
},
|
||||
file: {
|
||||
createFile: testUtilsFile.createFile,
|
||||
@@ -231,19 +129,11 @@
|
||||
inputFiles: testUtilsFile.inputFiles,
|
||||
},
|
||||
|
||||
createComponent: testUtilsCreate.createComponent,
|
||||
createAsyncView: testUtilsCreate.createView,
|
||||
createCalendarView: testUtilsCreate.createCalendarView,
|
||||
createView: testUtilsCreate.createView,
|
||||
createModel: testUtilsCreate.createModel,
|
||||
createParent: testUtilsCreate.createParent,
|
||||
createParent,
|
||||
makeTestPromise: makeTestPromise,
|
||||
makeTestPromiseWithAssert: makeTestPromiseWithAssert,
|
||||
nextMicrotaskTick: nextMicrotaskTick,
|
||||
nextTick: nextTick,
|
||||
owlCompatibilityExtraNextTick,
|
||||
prepareTarget: testUtilsCreate.prepareTarget,
|
||||
returnAfterNextAnimationFrame: testUtilsDom.returnAfterNextAnimationFrame,
|
||||
|
||||
// backward-compatibility
|
||||
addMockEnvironment: deprecated(testUtilsMock.addMockEnvironment, 'mock'),
|
||||
@@ -253,8 +143,5 @@
|
||||
openDatepicker: deprecated(testUtilsDom.openDatepicker, 'dom'),
|
||||
patch: deprecated(testUtilsMock.patch, 'mock'),
|
||||
patchDate: deprecated(testUtilsMock.patchDate, 'mock'),
|
||||
triggerKeypressEvent: deprecated(testUtilsDom.triggerKeypressEvent, 'dom'),
|
||||
triggerMouseEvent: deprecated(testUtilsDom.triggerMouseEvent, 'dom'),
|
||||
triggerPositionalMouseEvent: deprecated(testUtilsDom.triggerPositionalMouseEvent, 'dom'),
|
||||
unpatch: deprecated(testUtilsMock.unpatch, 'mock'),
|
||||
};
|
||||
|
||||
@@ -1,358 +0,0 @@
|
||||
/** @odoo-module alias=web.test_utils_control_panel **/
|
||||
|
||||
import { click, findItem, getNode, triggerEvent } from "web.test_utils_dom";
|
||||
import { editInput, editSelect, editAndTrigger } from "web.test_utils_fields";
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Exported functions
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {(number|string)} menuFinder
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleMenu(el, menuFinder) {
|
||||
const menu = findItem(el, `.dropdown > button`, menuFinder);
|
||||
await click(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {(number|string)} itemFinder
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleMenuItem(el, itemFinder) {
|
||||
const item = findItem(el, `.o_menu_item > a`, itemFinder);
|
||||
await click(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {(number|string)} itemFinder
|
||||
* @param {(number|string)} optionFinder
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleMenuItemOption(el, itemFinder, optionFinder) {
|
||||
const item = findItem(el, `.o_menu_item > a`, itemFinder);
|
||||
const option = findItem(item.parentNode, '.o_item_option > a', optionFinder);
|
||||
await click(option);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {(number|string)} itemFinder
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isItemSelected(el, itemFinder) {
|
||||
const item = findItem(el, `.o_menu_item > a`, itemFinder);
|
||||
return item.classList.contains('selected');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {(number|string)} itemuFinder
|
||||
* @param {(number|string)} optionFinder
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isOptionSelected(el, itemFinder, optionFinder) {
|
||||
const item = findItem(el, `.o_menu_item > a`, itemFinder);
|
||||
const option = findItem(item.parentNode, '.o_item_option > a', optionFinder);
|
||||
return option.classList.contains('selected');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getMenuItemTexts(el) {
|
||||
return [...getNode(el).querySelectorAll(`.dropdown ul .o_menu_item`)].map(
|
||||
e => e.innerText.trim()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {HTMLButtonElement[]}
|
||||
*/
|
||||
function getButtons(el) {
|
||||
return [...getNode(el).querySelector((`div.o_cp_bottom div.o_cp_buttons`)).children];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleFilterMenu(el) {
|
||||
await click(getNode(el).querySelector(`.o_filter_menu button`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleAddCustomFilter(el) {
|
||||
await click(getNode(el).querySelector(`button.o_add_custom_filter`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function applyFilter(el) {
|
||||
await click(getNode(el).querySelector(`div.o_add_filter_menu > button.o_apply_filter`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function addCondition(el) {
|
||||
await click(getNode(el).querySelector(`div.o_add_filter_menu > button.o_add_condition`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleGroupByMenu(el) {
|
||||
await click(getNode(el).querySelector(`.o_group_by_menu button`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleAddCustomGroup(el) {
|
||||
await click(getNode(el).querySelector(`span.o_add_custom_group_by`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {string} fieldName
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function selectGroup(el, fieldName) {
|
||||
await editSelect(
|
||||
getNode(el).querySelector(`select.o_group_by_selector`),
|
||||
fieldName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function applyGroup(el) {
|
||||
await click(getNode(el).querySelector(`div.o_add_group_by_menu > button.o_apply_group_by`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleFavoriteMenu(el) {
|
||||
await click(getNode(el).querySelector(`.o_favorite_menu button`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleSaveFavorite(el) {
|
||||
await click(getNode(el).querySelector(`.o_favorite_menu .o_add_favorite .dropdown-item`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {string} name
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function editFavoriteName(el, name) {
|
||||
await editInput(getNode(el).querySelector(`.o_favorite_menu .o_add_favorite input[type="text"]`), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function saveFavorite(el) {
|
||||
await click(getNode(el).querySelector(`.o_favorite_menu .o_add_favorite button.o_save_favorite`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {(string|number)} favoriteFinder
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function deleteFavorite(el, favoriteFinder) {
|
||||
const favorite = findItem(el, `.o_favorite_menu .o_menu_item`, favoriteFinder);
|
||||
await click(favorite.querySelector('i.fa-trash'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleComparisonMenu(el) {
|
||||
await click(getNode(el).querySelector(`div.o_comparison_menu > button`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function getFacetTexts(el) {
|
||||
return [...getNode(el).querySelectorAll(`.o_searchview .o_searchview_facet`)].map(
|
||||
facet => facet.innerText.trim()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {(string|number)} facetFinder
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function removeFacet(el, facetFinder = 0) {
|
||||
const facet = findItem(el, `.o_searchview .o_searchview_facet`, facetFinder);
|
||||
await click(facet.querySelector('.o_facet_remove'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {string} value
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function editSearch(el, value) {
|
||||
await editInput(getNode(el).querySelector(`.o_searchview_input`), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function validateSearch(el) {
|
||||
await triggerEvent(
|
||||
getNode(el).querySelector(`.o_searchview_input`),
|
||||
'keydown', { key: 'Enter' }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {string} [menuFinder="Action"]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function toggleActionMenu(el, menuFinder = "Action") {
|
||||
const dropdown = findItem(el, `.o_cp_action_menus button`, menuFinder);
|
||||
await click(dropdown);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function pagerPrevious(el) {
|
||||
await click(getNode(el).querySelector(`.o_pager button.o_pager_previous`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function pagerNext(el) {
|
||||
await click(getNode(el).querySelector(`.o_pager button.o_pager_next`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {string}
|
||||
*/
|
||||
function getPagerValue(el) {
|
||||
const pagerValue = getNode(el).querySelector(`.o_pager_counter .o_pager_value`);
|
||||
switch (pagerValue.tagName) {
|
||||
case 'INPUT':
|
||||
return pagerValue.value;
|
||||
case 'SPAN':
|
||||
return pagerValue.innerText.trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @returns {string}
|
||||
*/
|
||||
function getPagerSize(el) {
|
||||
return getNode(el).querySelector(`.o_pager_counter span.o_pager_limit`).innerText.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {string} value
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function setPagerValue(el, value) {
|
||||
let pagerValue = getNode(el).querySelector(`.o_pager_counter .o_pager_value`);
|
||||
if (pagerValue.tagName === 'SPAN') {
|
||||
await click(pagerValue);
|
||||
}
|
||||
pagerValue = getNode(el).querySelector(`.o_pager_counter input.o_pager_value`);
|
||||
if (!pagerValue) {
|
||||
throw new Error("Pager value is being edited and cannot be changed.");
|
||||
}
|
||||
await editAndTrigger(pagerValue, value, ['change', 'blur']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventTarget} el
|
||||
* @param {string} viewType
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function switchView(el, viewType) {
|
||||
await click(getNode(el).querySelector(`button.o_switch_view.o_${viewType}`));
|
||||
}
|
||||
|
||||
export default {
|
||||
// Generic interactions
|
||||
toggleMenu,
|
||||
toggleMenuItem,
|
||||
toggleMenuItemOption,
|
||||
isItemSelected,
|
||||
isOptionSelected,
|
||||
getMenuItemTexts,
|
||||
// Button interactions
|
||||
getButtons,
|
||||
// FilterMenu interactions
|
||||
toggleFilterMenu,
|
||||
toggleAddCustomFilter,
|
||||
applyFilter,
|
||||
addCondition,
|
||||
// GroupByMenu interactions
|
||||
toggleGroupByMenu,
|
||||
toggleAddCustomGroup,
|
||||
selectGroup,
|
||||
applyGroup,
|
||||
// FavoriteMenu interactions
|
||||
toggleFavoriteMenu,
|
||||
toggleSaveFavorite,
|
||||
editFavoriteName,
|
||||
saveFavorite,
|
||||
deleteFavorite,
|
||||
// ComparisonMenu interactions
|
||||
toggleComparisonMenu,
|
||||
// SearchBar interactions
|
||||
getFacetTexts,
|
||||
removeFacet,
|
||||
editSearch,
|
||||
validateSearch,
|
||||
// Action menus interactions
|
||||
toggleActionMenu,
|
||||
// Pager interactions
|
||||
pagerPrevious,
|
||||
pagerNext,
|
||||
getPagerValue,
|
||||
getPagerSize,
|
||||
setPagerValue,
|
||||
// View switcher
|
||||
switchView,
|
||||
};
|
||||
@@ -1,297 +0,0 @@
|
||||
/** @odoo-module alias=web.test_utils_create **/
|
||||
|
||||
/**
|
||||
* Create Test Utils
|
||||
*
|
||||
* This module defines various utility functions to help creating mock widgets
|
||||
*
|
||||
* Note that all methods defined in this module are exported in the main
|
||||
* testUtils file.
|
||||
*/
|
||||
|
||||
import concurrency from "web.concurrency";
|
||||
import { useListener } from "@web/core/utils/hooks";
|
||||
import dom from "web.dom";
|
||||
import testUtilsMock from "web.test_utils_mock";
|
||||
import Widget from "web.Widget";
|
||||
import { destroy, getFixture, mount, useChild } from "@web/../tests/helpers/utils";
|
||||
import { registerCleanup } from "@web/../tests/helpers/cleanup";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
const { Component, useState, xml } = owl;
|
||||
|
||||
/**
|
||||
* Similar as createView, but specific for calendar views. Some calendar
|
||||
* tests need to trigger positional clicks on the DOM produced by fullcalendar.
|
||||
* Those tests must use this helper with option positionalClicks set to true.
|
||||
* This will move the rendered calendar to the body (required to do positional
|
||||
* clicks), and wait for a setTimeout(0) before returning, because fullcalendar
|
||||
* makes the calendar scroll to 6:00 in a setTimeout(0), which might have an
|
||||
* impact according to where we want to trigger positional clicks.
|
||||
*
|
||||
* @param {Object} params @see createView
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.positionalClicks=false]
|
||||
* @returns {Promise<CalendarController>}
|
||||
*/
|
||||
async function createCalendarView(params, options) {
|
||||
const calendar = await createView(params);
|
||||
if (!options || !options.positionalClicks) {
|
||||
return calendar;
|
||||
}
|
||||
const viewElements = [...document.getElementById('qunit-fixture').children];
|
||||
// prepend reset the scrollTop to zero so we restore it manually
|
||||
let fcScroller = document.querySelector('.fc-scroller');
|
||||
const scrollPosition = fcScroller.scrollTop;
|
||||
viewElements.forEach(el => document.body.prepend(el));
|
||||
fcScroller = document.querySelector('.fc-scroller');
|
||||
fcScroller.scrollTop = scrollPosition;
|
||||
|
||||
const destroy = calendar.destroy;
|
||||
calendar.destroy = () => {
|
||||
viewElements.forEach(el => el.remove());
|
||||
destroy();
|
||||
};
|
||||
await concurrency.delay(0);
|
||||
return calendar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simple component environment with a basic Parent component, an
|
||||
* extensible env and a mocked server. The returned value is the instance of
|
||||
* the given constructor.
|
||||
* @param {class} constructor Component class to instantiate
|
||||
* @param {Object} [params = {}]
|
||||
* @param {boolean} [params.debug]
|
||||
* @param {Object} [params.env]
|
||||
* @param {Object} [params.intercepts] object in which the keys represent the
|
||||
* intercepted event names and the values are their callbacks.
|
||||
* @param {Object} [params.props]
|
||||
* @returns {Promise<Component>} instance of `constructor`
|
||||
*/
|
||||
async function createComponent(constructor, params = {}) {
|
||||
if (!constructor) {
|
||||
throw new Error(`Missing argument "constructor".`);
|
||||
}
|
||||
if (!(constructor.prototype instanceof Component)) {
|
||||
throw new Error(`Argument "constructor" must be an Owl Component.`);
|
||||
}
|
||||
const cleanUp = await testUtilsMock.addMockEnvironmentOwl(Component, params);
|
||||
class Parent extends LegacyComponent {
|
||||
setup() {
|
||||
this.Component = constructor;
|
||||
this.state = useState(params.props || {});
|
||||
for (const eventName in params.intercepts || {}) {
|
||||
useListener(eventName, params.intercepts[eventName]);
|
||||
}
|
||||
useChild();
|
||||
}
|
||||
}
|
||||
Parent.template = xml`<t t-component="Component" t-props="state"/>`;
|
||||
|
||||
const target = getFixture();
|
||||
const env = Component.env;
|
||||
const parent = await mount(Parent, target, { env });
|
||||
registerCleanup(cleanUp);
|
||||
registerCleanup(() => {
|
||||
destroy(parent);
|
||||
});
|
||||
return parent.child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a model from given parameters.
|
||||
*
|
||||
* @param {Object} params This object will be given to addMockEnvironment, so
|
||||
* any parameters from that method applies
|
||||
* @param {Class} params.Model the model class to use
|
||||
* @returns {Model}
|
||||
*/
|
||||
async function createModel(params) {
|
||||
const widget = new Widget();
|
||||
|
||||
const model = new params.Model(widget, params);
|
||||
|
||||
await testUtilsMock.addMockEnvironment(widget, params);
|
||||
|
||||
// override the model's 'destroy' so that it calls 'destroy' on the widget
|
||||
// instead, as the widget is the parent of the model and the mockServer.
|
||||
model.destroy = function () {
|
||||
// remove the override to properly destroy the model when it will be
|
||||
// called the second time (by its parent)
|
||||
delete model.destroy;
|
||||
widget.destroy();
|
||||
};
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a widget parent from given parameters.
|
||||
*
|
||||
* @param {Object} params This object will be given to addMockEnvironment, so
|
||||
* any parameters from that method applies
|
||||
* @returns {Promise<Widget>}
|
||||
*/
|
||||
async function createParent(params) {
|
||||
const widget = new Widget();
|
||||
await testUtilsMock.addMockEnvironment(widget, params);
|
||||
return widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a view from various parameters. Here, a view means a javascript
|
||||
* instance of an AbstractView class, such as a form view, a list view or a
|
||||
* kanban view.
|
||||
*
|
||||
* It returns the instance of the view, properly created, with all rpcs going
|
||||
* through a mock method using the data object as source, and already loaded/
|
||||
* started.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {string} params.arch the xml (arch) of the view to be instantiated
|
||||
* @param {any[]} [params.domain] the initial domain for the view
|
||||
* @param {Object} [params.context] the initial context for the view
|
||||
* @param {string[]} [params.groupBy] the initial groupBy for the view
|
||||
* @param {Object[]} [params.favoriteFilters] the favorite filters one would like to have at initialization
|
||||
* @param {integer} [params.fieldDebounce=0] the debounce value to use for the
|
||||
* duration of the test.
|
||||
* @param {AbstractView} params.View the class that will be instantiated
|
||||
* @param {string} params.model a model name, will be given to the view
|
||||
* @param {Object} params.intercepts an object with event names as key, and
|
||||
* callback as value. Each key,value will be used to intercept the event.
|
||||
* Note that this is particularly useful if you want to intercept events going
|
||||
* up in the init process of the view, because there are no other way to do it
|
||||
* after this method returns
|
||||
* @param {Boolean} [params.doNotDisableAHref=false] will not preventDefault on the A elements of the view if true.
|
||||
* Default is false.
|
||||
* @param {Boolean} [params.touchScreen=false] will add the o_touch_device to the webclient (flag used to define a
|
||||
* device with a touch screen. Default value is false
|
||||
* @returns {Promise<AbstractController>} the instance of the view
|
||||
*/
|
||||
async function createView(params) {
|
||||
const target = prepareTarget(params.debug);
|
||||
const widget = new Widget();
|
||||
// reproduce the DOM environment of views
|
||||
const webClient = Object.assign(document.createElement('div'), {
|
||||
className: params.touchScreen ? 'o_web_client o_touch_device' : 'o_web_client',
|
||||
});
|
||||
const actionManager = Object.assign(document.createElement('div'), {
|
||||
className: 'o_action_manager',
|
||||
});
|
||||
const dialogContainer = Object.assign(document.createElement('div'), {
|
||||
className: 'o_dialog_container',
|
||||
});
|
||||
target.prepend(webClient);
|
||||
webClient.append(actionManager);
|
||||
webClient.append(dialogContainer);
|
||||
|
||||
// add mock environment: mock server, session, fieldviewget, ...
|
||||
const mockServer = await testUtilsMock.addMockEnvironment(widget, params);
|
||||
const viewInfo = testUtilsMock.getView(mockServer, params);
|
||||
|
||||
params.server = mockServer;
|
||||
|
||||
// create the view
|
||||
const View = params.View;
|
||||
const modelName = params.model || 'foo';
|
||||
const defaultAction = {
|
||||
res_model: modelName,
|
||||
context: {},
|
||||
type: 'ir.actions.act_window',
|
||||
};
|
||||
const viewOptions = Object.assign({
|
||||
action: Object.assign(defaultAction, params.action),
|
||||
view: { ...viewInfo, fields: mockServer.fieldsGet(params.model) },
|
||||
modelName: modelName,
|
||||
ids: 'res_id' in params ? [params.res_id] : undefined,
|
||||
currentId: 'res_id' in params ? params.res_id : undefined,
|
||||
domain: params.domain || [],
|
||||
context: params.context || {},
|
||||
hasActionMenus: false,
|
||||
}, params.viewOptions);
|
||||
// patch the View to handle the groupBy given in params, as we can't give it
|
||||
// in init (unlike the domain and context which can be set in the action)
|
||||
testUtilsMock.patch(View, {
|
||||
_updateMVCParams() {
|
||||
this._super(...arguments);
|
||||
this.loadParams.groupedBy = params.groupBy || viewOptions.groupBy || [];
|
||||
testUtilsMock.unpatch(View);
|
||||
},
|
||||
});
|
||||
if ('hasSelectors' in params) {
|
||||
viewOptions.hasSelectors = params.hasSelectors;
|
||||
}
|
||||
|
||||
let view;
|
||||
if (viewInfo.type === 'controlpanel' || viewInfo.type === 'search') {
|
||||
// TODO: probably needs to create an helper just for that
|
||||
view = new params.View({ viewInfo, modelName });
|
||||
} else {
|
||||
viewOptions.controlPanelFieldsView = Object.assign(testUtilsMock.getView(mockServer, {
|
||||
arch: params.archs && params.archs[params.model + ',false,search'] || '<search/>',
|
||||
fields: viewInfo.fields,
|
||||
model: params.model,
|
||||
}), { favoriteFilters: params.favoriteFilters });
|
||||
|
||||
view = new params.View(viewInfo, viewOptions);
|
||||
}
|
||||
|
||||
if (params.interceptsPropagate) {
|
||||
for (const name in params.interceptsPropagate) {
|
||||
testUtilsMock.intercept(widget, name, params.interceptsPropagate[name], true);
|
||||
}
|
||||
}
|
||||
|
||||
const viewController = await view.getController(widget);
|
||||
// override the view's 'destroy' so that it calls 'destroy' on the widget
|
||||
// instead, as the widget is the parent of the view and the mockServer.
|
||||
viewController.__destroy = viewController.destroy;
|
||||
viewController.destroy = function () {
|
||||
// remove the override to properly destroy the viewController and its children
|
||||
// when it will be called the second time (by its parent)
|
||||
delete viewController.destroy;
|
||||
widget.destroy();
|
||||
webClient.remove();
|
||||
};
|
||||
|
||||
// render the viewController in a fragment as they must be able to render correctly
|
||||
// without being in the DOM
|
||||
const fragment = document.createDocumentFragment();
|
||||
await viewController.appendTo(fragment);
|
||||
dom.prepend(actionManager, fragment, {
|
||||
callbacks: [{ widget: viewController }],
|
||||
in_DOM: true,
|
||||
});
|
||||
|
||||
if (!params.doNotDisableAHref) {
|
||||
[...viewController.el.getElementsByTagName('A')].forEach(elem => {
|
||||
elem.addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
});
|
||||
});
|
||||
}
|
||||
return viewController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target (fixture or body) of the document and adds event listeners
|
||||
* to intercept custom or DOM events.
|
||||
*
|
||||
* @param {boolean} [debug=false] if true, the widget will be appended in
|
||||
* the DOM. Also, RPCs and uncaught OdooEvent will be logged
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
function prepareTarget(debug = false) {
|
||||
document.body.classList.toggle('debug', debug);
|
||||
return debug ? document.body : document.getElementById('qunit-fixture');
|
||||
}
|
||||
|
||||
export default {
|
||||
createCalendarView,
|
||||
createComponent,
|
||||
createModel,
|
||||
createParent,
|
||||
createView,
|
||||
prepareTarget,
|
||||
};
|
||||
@@ -1,9 +1,6 @@
|
||||
/** @odoo-module alias=web.test_utils_dom **/
|
||||
|
||||
import concurrency from "web.concurrency";
|
||||
import Widget from "web.Widget";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
/**
|
||||
* DOM Test Utils
|
||||
@@ -192,21 +189,6 @@
|
||||
return click(el, Object.assign({}, options, { first: true }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on the last element of a list of elements. Note that if the list has
|
||||
* only one visible element, we trigger an error. In that case, it is better to
|
||||
* use the click helper instead.
|
||||
*
|
||||
* @param {string|EventTarget|EventTarget[]} el (if string: it is a (jquery) selector)
|
||||
* @param {boolean} [options={}] click options
|
||||
* @param {boolean} [options.allowInvisible=false] if true, clicks on the
|
||||
* element event if it is invisible
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function clickLast(el, options) {
|
||||
return click(el, Object.assign({}, options, { last: true }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a drag and drop operation between 2 jquery nodes: $el and $to.
|
||||
* This is a crude simulation, with only the mousedown, mousemove and mouseup
|
||||
@@ -314,94 +296,6 @@
|
||||
return returnAfterNextAnimationFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to retrieve a distinct item from a collection of elements defined
|
||||
* by the given "selector" string. It can either be the index of the item or its
|
||||
* inner text.
|
||||
* @param {Element} el
|
||||
* @param {string} selector
|
||||
* @param {number | string} [elFinder=0]
|
||||
* @returns {Element | null}
|
||||
*/
|
||||
function findItem(el, selector, elFinder = 0) {
|
||||
const elements = [...getNode(el).querySelectorAll(selector)];
|
||||
if (!elements.length) {
|
||||
throw new Error(`No element found with selector "${selector}".`);
|
||||
}
|
||||
switch (typeof elFinder) {
|
||||
case "number": {
|
||||
const match = elements[elFinder];
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
`No element with selector "${selector}" at index ${elFinder}.`
|
||||
);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
case "string": {
|
||||
const match = elements.find(
|
||||
(el) => el.innerText.trim().toLowerCase() === elFinder.toLowerCase()
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
`No element with selector "${selector}" containing "${elFinder}".
|
||||
`);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
default: throw new Error(
|
||||
`Invalid provided element finder: must be a number|string|function.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to extract an HTML EventTarget element from a given
|
||||
* target. The extracted element will depend on the target type:
|
||||
* - Component|Widget -> el
|
||||
* - jQuery -> associated element (must have 1)
|
||||
* - HTMLCollection (or similar) -> first element (must have 1)
|
||||
* - string -> result of document.querySelector with string
|
||||
* - else -> as is
|
||||
* @private
|
||||
* @param {(Component|Widget|jQuery|HTMLCollection|HTMLElement|string)} target
|
||||
* @returns {EventTarget}
|
||||
*/
|
||||
function getNode(target) {
|
||||
let nodes;
|
||||
if (target instanceof Component || target instanceof Widget) {
|
||||
nodes = [target.el];
|
||||
} else if (typeof target === 'string') {
|
||||
nodes = document.querySelectorAll(target);
|
||||
} else if (target === jQuery) { // jQuery (or $)
|
||||
nodes = [document.body];
|
||||
} else if (target.length) { // jQuery instance, HTMLCollection or array
|
||||
nodes = target;
|
||||
} else {
|
||||
nodes = [target];
|
||||
}
|
||||
if (nodes.length !== 1) {
|
||||
throw new Error(`Found ${nodes.length} nodes instead of 1.`);
|
||||
}
|
||||
const node = nodes[0];
|
||||
if (!node) {
|
||||
throw new Error(`Expected a node and got ${node}.`);
|
||||
}
|
||||
if (!_isEventTarget(node)) {
|
||||
throw new Error(`Expected node to be an instance of EventTarget and got ${node.constructor.name}.`);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the datepicker of a given element.
|
||||
*
|
||||
* @param {jQuery} $datepickerEl element to which a datepicker is attached
|
||||
*/
|
||||
async function openDatepicker($datepickerEl) {
|
||||
return click($datepickerEl.find('.o_datepicker_input'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that will be resolved after the nextAnimationFrame after
|
||||
* the next tick
|
||||
@@ -484,122 +378,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a keypress event for a given character
|
||||
*
|
||||
* @param {string} char the character, or 'ENTER'
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function triggerKeypressEvent(char) {
|
||||
let keycode;
|
||||
if (char === 'Enter') {
|
||||
keycode = $.ui.keyCode.ENTER;
|
||||
} else if (char === "Tab") {
|
||||
keycode = $.ui.keyCode.TAB;
|
||||
} else {
|
||||
keycode = char.charCodeAt(0);
|
||||
}
|
||||
return triggerEvent(document.body, 'keypress', {
|
||||
key: char,
|
||||
keyCode: keycode,
|
||||
which: keycode,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* simulate a mouse event with a custom event who add the item position. This is
|
||||
* sometimes necessary because the basic way to trigger an event (such as
|
||||
* $el.trigger('mousemove')); ) is too crude for some uses.
|
||||
*
|
||||
* @param {jQuery|EventTarget} $el
|
||||
* @param {string} type a mouse event type, such as 'mousedown' or 'mousemove'
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function triggerMouseEvent($el, type) {
|
||||
const el = $el instanceof jQuery ? $el[0] : $el;
|
||||
if (!el) {
|
||||
throw new Error(`no target found to trigger MouseEvent`);
|
||||
}
|
||||
const rect = el.getBoundingClientRect();
|
||||
// try to click around the center of the element, biased to the bottom
|
||||
// right as chrome messes up when clicking on the top-left corner...
|
||||
const left = rect.x + Math.ceil(rect.width / 2);
|
||||
const top = rect.y + Math.ceil(rect.height / 2);
|
||||
return triggerEvent(el, type, {which: 1, clientX: left, clientY: top});
|
||||
}
|
||||
|
||||
/**
|
||||
* simulate a mouse event with a custom event on a position x and y. This is
|
||||
* sometimes necessary because the basic way to trigger an event (such as
|
||||
* $el.trigger('mousemove')); ) is too crude for some uses.
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} y
|
||||
* @param {string} type a mouse event type, such as 'mousedown' or 'mousemove'
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
async function triggerPositionalMouseEvent(x, y, type) {
|
||||
const ev = document.createEvent("MouseEvent");
|
||||
const el = document.elementFromPoint(x, y);
|
||||
ev.initMouseEvent(
|
||||
type,
|
||||
true /* bubble */,
|
||||
true /* cancelable */,
|
||||
window, null,
|
||||
x, y, x, y, /* coordinates */
|
||||
false, false, false, false, /* modifier keys */
|
||||
0 /*left button*/, null
|
||||
);
|
||||
el.dispatchEvent(ev);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a "TAP" (touch) event with a custom position x and y.
|
||||
*
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
async function triggerPositionalTapEvents(x, y) {
|
||||
const element = document.elementFromPoint(x, y);
|
||||
const touch = new Touch({
|
||||
identifier: 0,
|
||||
target: element,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
pageX: x,
|
||||
pageY: y,
|
||||
});
|
||||
await triggerEvent(element, 'touchstart', {
|
||||
touches: [touch],
|
||||
targetTouches: [touch],
|
||||
changedTouches: [touch],
|
||||
});
|
||||
await triggerEvent(element, 'touchmove', {
|
||||
touches: [touch],
|
||||
targetTouches: [touch],
|
||||
changedTouches: [touch],
|
||||
});
|
||||
await triggerEvent(element, 'touchend', {
|
||||
changedTouches: [touch],
|
||||
});
|
||||
return element;
|
||||
}
|
||||
|
||||
export default {
|
||||
click,
|
||||
clickFirst,
|
||||
clickLast,
|
||||
dragAndDrop,
|
||||
findItem,
|
||||
getNode,
|
||||
openDatepicker,
|
||||
returnAfterNextAnimationFrame,
|
||||
triggerEvent,
|
||||
triggerEvents,
|
||||
triggerKeypressEvent,
|
||||
triggerMouseEvent,
|
||||
triggerPositionalMouseEvent,
|
||||
triggerPositionalTapEvents,
|
||||
};
|
||||
|
||||
@@ -22,77 +22,6 @@
|
||||
// Public functions
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Autofills the input of a many2one field and clicks on the "Create and Edit" option.
|
||||
*
|
||||
* @param {string} fieldName
|
||||
* @param {string} text Used as default value for the record name
|
||||
* @see clickM2OItem
|
||||
*/
|
||||
async function clickM2OCreateAndEdit(fieldName, text = "ABC") {
|
||||
await clickOpenM2ODropdown(fieldName);
|
||||
const match = document.querySelector(`.o_field_many2one[name=${fieldName}] input`);
|
||||
await editInput(match, text);
|
||||
return clickM2OItem(fieldName, "Create and Edit");
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on the active (highlighted) selection in a m2o dropdown.
|
||||
*
|
||||
* @param {string} fieldName
|
||||
* @param {[string]} selector if set, this will restrict the search for the m2o
|
||||
* input
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function clickM2OHighlightedItem(fieldName, selector) {
|
||||
const m2oSelector = `${selector || ''} .o_field_many2one[name=${fieldName}] input`;
|
||||
const $dropdown = $(m2oSelector).autocomplete('widget');
|
||||
// clicking on an li (no matter which one), will select the focussed one
|
||||
return testUtilsDom.click($dropdown[0].querySelector('li'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on a menuitem in the m2o dropdown. This helper will target an element
|
||||
* which contains some specific text. Note that it assumes that the dropdown
|
||||
* is currently open.
|
||||
*
|
||||
* Example:
|
||||
* testUtils.fields.many2one.clickM2OItem('partner_id', 'George');
|
||||
*
|
||||
* @param {string} fieldName
|
||||
* @param {string} searchText
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function clickM2OItem(fieldName, searchText) {
|
||||
const m2oSelector = `.o_field_many2one[name=${fieldName}] input`;
|
||||
const $dropdown = $(m2oSelector).autocomplete('widget');
|
||||
const $target = $dropdown.find(`li:contains(${searchText})`).first();
|
||||
if ($target.length !== 1 || !$target.is(':visible')) {
|
||||
throw new Error('Menu item should be visible');
|
||||
}
|
||||
$target.mouseenter(); // This is NOT a mouseenter event. See jquery.js:5516 for more headaches.
|
||||
return testUtilsDom.click($target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click to open the dropdown on a many2one
|
||||
*
|
||||
* @param {string} fieldName
|
||||
* @param {[string]} selector if set, this will restrict the search for the m2o
|
||||
* input
|
||||
* @returns {Promise<HTMLInputElement>} the main many2one input
|
||||
*/
|
||||
async function clickOpenM2ODropdown(fieldName, selector) {
|
||||
const m2oSelector = `${selector || ''} .o_field_many2one[name=${fieldName}] input`;
|
||||
const matches = document.querySelectorAll(m2oSelector);
|
||||
if (matches.length !== 1) {
|
||||
throw new Error(`cannot open m2o: selector ${selector} has been found ${matches.length} instead of 1`);
|
||||
}
|
||||
|
||||
await testUtilsDom.click(matches[0]);
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of an element and then, trigger all specified events.
|
||||
* Note that this helper also checks the unicity of the target.
|
||||
@@ -133,51 +62,6 @@
|
||||
return editAndTrigger(el, value, ['input']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a select.
|
||||
*
|
||||
* Note that this helper also checks the unicity of the target.
|
||||
*
|
||||
* Example:
|
||||
* testUtils.fields.editSelect($('selector'), 'somevalue');
|
||||
*
|
||||
* @param {jQuery|EventTarget} el should target an input, textarea or select
|
||||
* @param {string|number} value
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function editSelect(el, value) {
|
||||
return editAndTrigger(el, value, ['change']);
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper is useful to test many2one fields. Here is what it does:
|
||||
* - click to open the dropdown
|
||||
* - enter a search string in the input
|
||||
* - wait for the selection
|
||||
* - click on the requested menuitem, or the active one by default
|
||||
*
|
||||
* Example:
|
||||
* testUtils.fields.many2one.searchAndClickM2OItem('partner_id', {search: 'George'});
|
||||
*
|
||||
* @param {string} fieldName
|
||||
* @param {[Object]} [options = {}]
|
||||
* @param {[string]} [options.selector]
|
||||
* @param {[string]} [options.search]
|
||||
* @param {[string]} [options.item]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function searchAndClickM2OItem(fieldName, options = {}) {
|
||||
const input = await clickOpenM2ODropdown(fieldName, options.selector);
|
||||
if (options.search) {
|
||||
await editInput(input, options.search);
|
||||
}
|
||||
if (options.item) {
|
||||
return clickM2OItem(fieldName, options.item);
|
||||
} else {
|
||||
return clickM2OHighlightedItem(fieldName, options.selector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to trigger a key event on an element.
|
||||
*
|
||||
@@ -222,27 +106,8 @@
|
||||
return triggerKey('down', $el, keyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to trigger a keyup event on an element.
|
||||
*
|
||||
* @param {jQuery} $el
|
||||
* @param {number|string} keyCode @see triggerKey
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function triggerKeyup($el, keyCode) {
|
||||
return triggerKey('up', $el, keyCode);
|
||||
}
|
||||
|
||||
export default {
|
||||
clickM2OCreateAndEdit,
|
||||
clickM2OHighlightedItem,
|
||||
clickM2OItem,
|
||||
clickOpenM2ODropdown,
|
||||
editAndTrigger,
|
||||
editInput,
|
||||
editSelect,
|
||||
searchAndClickM2OItem,
|
||||
triggerKey,
|
||||
triggerKeydown,
|
||||
triggerKeyup,
|
||||
};
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/** @odoo-module alias=web.test_utils_form **/
|
||||
|
||||
/**
|
||||
* Form Test Utils
|
||||
*
|
||||
* This module defines various utility functions to help test form views.
|
||||
*
|
||||
* Note that all methods defined in this module are exported in the main
|
||||
* testUtils file.
|
||||
*/
|
||||
|
||||
import testUtilsDom from "web.test_utils_dom";
|
||||
|
||||
/**
|
||||
* Clicks on the Edit button in a form view, to set it to edit mode. Note that
|
||||
* it checks that the button is visible, so calling this method in edit mode
|
||||
* will fail.
|
||||
*
|
||||
* @param {FormController} form
|
||||
*/
|
||||
function clickEdit(form) {
|
||||
return testUtilsDom.click(form.$buttons.find('.o_form_button_edit'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the Save button in a form view. Note that this method checks that
|
||||
* the Save button is visible.
|
||||
*
|
||||
* @param {FormController} form
|
||||
*/
|
||||
function clickSave(form) {
|
||||
return testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the Create button in a form view. Note that this method checks that
|
||||
* the Create button is visible.
|
||||
*
|
||||
* @param {FormController} form
|
||||
*/
|
||||
function clickCreate(form) {
|
||||
return testUtilsDom.click(form.$buttons.find('.o_form_button_create'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the Discard button in a form view. Note that this method checks that
|
||||
* the Discard button is visible.
|
||||
*
|
||||
* @param {FormController} form
|
||||
*/
|
||||
function clickDiscard(form) {
|
||||
return testUtilsDom.click(form.$buttons.find('.o_form_button_cancel'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads a form view.
|
||||
*
|
||||
* @param {FormController} form
|
||||
* @param {[Object]} params given to the controller reload method
|
||||
*/
|
||||
function reload(form, params) {
|
||||
return form.reload(params);
|
||||
}
|
||||
|
||||
export default {
|
||||
clickEdit: clickEdit,
|
||||
clickSave: clickSave,
|
||||
clickCreate: clickCreate,
|
||||
clickDiscard: clickDiscard,
|
||||
reload: reload,
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
/** @odoo-module alias=web.test_utils_graph **/
|
||||
|
||||
/**
|
||||
* Graph Test Utils
|
||||
*
|
||||
* This module defines various utility functions to help test graph views.
|
||||
*
|
||||
* Note that all methods defined in this module are exported in the main
|
||||
* testUtils file.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Reloads a graph view.
|
||||
*
|
||||
* @param {GraphController} graph
|
||||
* @param {[Object]} params given to the controller reload method
|
||||
*/
|
||||
function reload(graph, params) {
|
||||
return graph.reload(params);
|
||||
}
|
||||
|
||||
export default {
|
||||
reload: reload,
|
||||
};
|
||||
@@ -1,99 +0,0 @@
|
||||
/** @odoo-module alias=web.test_utils_kanban **/
|
||||
|
||||
/**
|
||||
* Kanban Test Utils
|
||||
*
|
||||
* This module defines various utility functions to help testing kanban views.
|
||||
*
|
||||
* Note that all methods defined in this module are exported in the main
|
||||
* testUtils file.
|
||||
*/
|
||||
|
||||
import testUtilsDom from "web.test_utils_dom";
|
||||
import testUtilsFields from "web.test_utils_fields";
|
||||
|
||||
/**
|
||||
* Clicks on the Create button in a kanban view. Note that this method checks that
|
||||
* the Create button is visible.
|
||||
*
|
||||
* @param {KanbanController} kanban
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function clickCreate(kanban) {
|
||||
return testUtilsDom.click(kanban.$buttons.find('.o-kanban-button-new'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the settings menu for a column (in a grouped kanban view)
|
||||
*
|
||||
* @param {jQuery} $column
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function toggleGroupSettings($column) {
|
||||
var $dropdownToggler = $column.find('.o_kanban_config > a.dropdown-toggle');
|
||||
if (!$dropdownToggler.is(':visible')) {
|
||||
$dropdownToggler.css('display', 'block');
|
||||
}
|
||||
return testUtilsDom.click($dropdownToggler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a value in a quickcreate form view (this method assumes that the quick
|
||||
* create feature is active, and a sub form view is open)
|
||||
*
|
||||
* @param {kanbanController} kanban
|
||||
* @param {string|number} value
|
||||
* @param {[string]} fieldName
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function quickCreate(kanban, value, fieldName) {
|
||||
var additionalSelector = fieldName ? ('[name=' + fieldName + ']'): '';
|
||||
var enterEvent = $.Event(
|
||||
'keydown',
|
||||
{
|
||||
which: $.ui.keyCode.ENTER,
|
||||
keyCode: $.ui.keyCode.ENTER,
|
||||
}
|
||||
);
|
||||
return testUtilsFields.editAndTrigger(
|
||||
kanban.$('.o_kanban_quick_create input' + additionalSelector),
|
||||
value,
|
||||
['input', enterEvent]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads a kanban view.
|
||||
*
|
||||
* @param {KanbanController} kanban
|
||||
* @param {[Object]} params given to the controller reload method
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function reload(kanban, params) {
|
||||
return kanban.reload(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the setting dropdown of a kanban record. Note that the template of a
|
||||
* kanban record is not standardized, so this method will fail if the template
|
||||
* does not comply with the usual dom structure.
|
||||
*
|
||||
* @param {jQuery} $record
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function toggleRecordDropdown($record) {
|
||||
var $dropdownToggler = $record.find('.o_dropdown_kanban > a.dropdown-toggle');
|
||||
if (!$dropdownToggler.is(':visible')) {
|
||||
$dropdownToggler.css('display', 'block');
|
||||
}
|
||||
return testUtilsDom.click($dropdownToggler);
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
clickCreate: clickCreate,
|
||||
quickCreate: quickCreate,
|
||||
reload: reload,
|
||||
toggleGroupSettings: toggleGroupSettings,
|
||||
toggleRecordDropdown: toggleRecordDropdown,
|
||||
};
|
||||
@@ -23,7 +23,6 @@ import session from "web.session";
|
||||
import { patchWithCleanup, patchDate } from "@web/../tests/helpers/utils";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { assets } from "@web/core/assets";
|
||||
import { processArch } from "@web/legacy/legacy_load_views";
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
import { uniqueId } from "@web/core/utils/functions";
|
||||
@@ -41,12 +40,10 @@ const DebouncedField = basic_fields.DebouncedField;
|
||||
* @private
|
||||
* @param {Object} params
|
||||
* @param {Bus} [params.bus]
|
||||
* @param {boolean} [params.debug]
|
||||
* @param {Object} [params.env]
|
||||
* @param {Bus} [params.env.bus]
|
||||
* @param {Object} [params.env.dataManager]
|
||||
* @param {Object} [params.env.services]
|
||||
* @param {Object[]} [params.favoriteFilters]
|
||||
* @param {Object} [params.services]
|
||||
* @param {Object} [params.session]
|
||||
* @param {MockServer} [mockServer]
|
||||
@@ -58,8 +55,6 @@ async function _getMockedOwlEnv(params, mockServer) {
|
||||
const database = {parameters: params.translateParameters || {}};
|
||||
|
||||
// build the env
|
||||
const favoriteFilters = params.favoriteFilters;
|
||||
const debug = params.debug;
|
||||
const services = {};
|
||||
const env = Object.assign({}, params.env, {
|
||||
_t: params.env && params.env._t || Object.assign((s => s), { database }),
|
||||
@@ -248,35 +243,6 @@ function _observe(widget) {
|
||||
// Public functions
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* performs a get_view, and mocks the postprocessing done by the
|
||||
* data_manager to return an equivalent structure.
|
||||
*
|
||||
* @param {MockServer} server
|
||||
* @param {Object} params
|
||||
* @param {string} params.model
|
||||
* @returns {Object} an object with 3 keys: arch, fields and viewFields
|
||||
*/
|
||||
function getView(server, params) {
|
||||
var view = server.getView(params);
|
||||
const fields = server.fieldsGet(params.model);
|
||||
// mock the structure produced by the DataManager
|
||||
const models = { [params.model]: fields };
|
||||
for (const modelName of view.models) {
|
||||
models[modelName] = models[modelName] || server.fieldsGet(modelName);
|
||||
}
|
||||
const { arch, viewFields } = processArch(view.arch, view.type, params.model, models);
|
||||
return {
|
||||
arch,
|
||||
fields,
|
||||
model: view.model,
|
||||
toolbar: view.toolbar,
|
||||
type: view.type,
|
||||
viewFields,
|
||||
view_id: view.id,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* intercepts an event bubbling up the widget hierarchy. The event intercepted
|
||||
* must be a "custom event", i.e. an event generated by the method 'trigger_up'.
|
||||
@@ -675,32 +641,11 @@ function unpatch(target) {
|
||||
delete target.__patchID;
|
||||
}
|
||||
|
||||
window.originalSetTimeout = window.setTimeout;
|
||||
function patchSetTimeout() {
|
||||
var original = window.setTimeout;
|
||||
var self = this;
|
||||
window.setTimeout = function (handler, delay) {
|
||||
console.log("calling setTimeout on " + (handler.name || "some function") + "with delay of " + delay);
|
||||
console.trace();
|
||||
var handlerArguments = Array.prototype.slice.call(arguments, 1);
|
||||
return original(function () {
|
||||
handler.bind(self, handlerArguments)();
|
||||
console.log('after doing the action of the setTimeout');
|
||||
}, delay);
|
||||
};
|
||||
|
||||
return function () {
|
||||
window.setTimeout = original;
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
addMockEnvironment: addMockEnvironment,
|
||||
getView: getView,
|
||||
addMockEnvironmentOwl: addMockEnvironmentOwl,
|
||||
intercept: intercept,
|
||||
patchDate: legacyPatchDate,
|
||||
patch: patch,
|
||||
unpatch: unpatch,
|
||||
patchSetTimeout: patchSetTimeout,
|
||||
};
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/** @odoo-module alias=web.test_utils_modal **/
|
||||
|
||||
/**
|
||||
* Modal Test Utils
|
||||
*
|
||||
* This module defines various utility functions to help test pivot views.
|
||||
*
|
||||
* Note that all methods defined in this module are exported in the main
|
||||
* testUtils file.
|
||||
*/
|
||||
|
||||
import { _t } from "web.core";
|
||||
import testUtilsDom from "web.test_utils_dom";
|
||||
|
||||
/**
|
||||
* Click on a button in the footer of a modal (which contains a given string).
|
||||
*
|
||||
* @param {string} text (in english: this method will perform the translation)
|
||||
*/
|
||||
function clickButton(text) {
|
||||
return testUtilsDom.click($(`.modal-footer button:contains(${_t(text)})`));
|
||||
}
|
||||
|
||||
export default { clickButton };
|
||||
@@ -1,54 +0,0 @@
|
||||
/** @odoo-module alias=web.test_utils_pivot **/
|
||||
|
||||
import testUtilsDom from "web.test_utils_dom";
|
||||
|
||||
/**
|
||||
* Pivot Test Utils
|
||||
*
|
||||
* This module defines various utility functions to help test pivot views.
|
||||
*
|
||||
* Note that all methods defined in this module are exported in the main
|
||||
* testUtils file.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Select a measure by clicking on the corresponding dropdown item (in the
|
||||
* control panel 'Measure' submenu).
|
||||
*
|
||||
* Note that this method assumes that the dropdown menu is open.
|
||||
* @see toggleMeasuresDropdown
|
||||
*
|
||||
* @param {PivotController} pivot
|
||||
* @param {string} measure
|
||||
*/
|
||||
function clickMeasure(pivot, measure) {
|
||||
return testUtilsDom.click(pivot.$buttons.find(`.dropdown-item[data-field=${measure}]`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the 'Measure' dropdown menu (in the control panel)
|
||||
*
|
||||
* @see clickMeasure
|
||||
*
|
||||
* @param {PivotController} pivot
|
||||
*/
|
||||
function toggleMeasuresDropdown(pivot) {
|
||||
return testUtilsDom.click(pivot.$buttons.filter('.btn-group:first').find('> button'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads a graph view.
|
||||
*
|
||||
* @param {PivotController} pivot
|
||||
* @param {[Object]} params given to the controller reload method
|
||||
*/
|
||||
function reload(pivot, params) {
|
||||
return pivot.reload(params);
|
||||
}
|
||||
|
||||
export default {
|
||||
clickMeasure: clickMeasure,
|
||||
reload: reload,
|
||||
toggleMeasuresDropdown: toggleMeasuresDropdown,
|
||||
};
|
||||
@@ -62,7 +62,7 @@
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
|
||||
@@ -92,7 +92,7 @@
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
|
||||
@@ -133,7 +133,7 @@
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
await mount(Parent, target);
|
||||
});
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
</div>`;
|
||||
Parent.components = { MyWidgetAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
|
||||
@@ -198,7 +198,7 @@
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
|
||||
@@ -245,7 +245,7 @@
|
||||
</div>`;
|
||||
Parent.components = { MyWidgetAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div>Hello World!</div>');
|
||||
@@ -307,7 +307,7 @@
|
||||
</div>`;
|
||||
Parent.components = { AsyncComponent, MyWidgetAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div>Hi World!</div><div>Hello World!</div>');
|
||||
@@ -353,7 +353,7 @@
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.verifySteps(['on_attach_callback']);
|
||||
@@ -385,7 +385,7 @@
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div>widget</div><div>component</div>');
|
||||
@@ -417,7 +417,7 @@
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
await mount(Parent, target);
|
||||
|
||||
widget.trigger_up('some-event', { value: 'a' });
|
||||
@@ -452,7 +452,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target, { env: owl.Component.env });
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div></div>');
|
||||
@@ -497,7 +497,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
await mount(Parent, target, { env });
|
||||
});
|
||||
|
||||
@@ -524,47 +524,11 @@
|
||||
session: { key: 'value' },
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
await mount(Parent, target, { env: owl.Component.env });
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
QUnit.test("sub widget that calls load_views", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const MyWidget = Widget.extend({
|
||||
willStart: function () {
|
||||
return this.loadViews('some_model', { x: 2 }, [[false, 'list']]);
|
||||
},
|
||||
});
|
||||
class Parent extends LegacyComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.MyWidget = MyWidget;
|
||||
}
|
||||
}
|
||||
Parent.template = xml`
|
||||
<div>
|
||||
<ComponentAdapter Component="MyWidget"/>
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
const cleanUp = await addMockEnvironmentOwl(Parent, {
|
||||
mockRPC: function (route, args) {
|
||||
assert.strictEqual(route, '/web/dataset/call_kw/some_model');
|
||||
assert.deepEqual(args.kwargs.context, { x: 2 });
|
||||
assert.deepEqual(args.kwargs.views, [[false, 'list']]);
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const parent = await mount(Parent, target, { env: owl.Component.env });
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div></div>');
|
||||
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
QUnit.test("sub widgets in a t-if/t-else", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
@@ -595,7 +559,7 @@
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div>Hi</div>');
|
||||
@@ -639,7 +603,7 @@
|
||||
</div>`;
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div>Hi</div>');
|
||||
@@ -708,7 +672,7 @@
|
||||
`;
|
||||
Parent.components = { MyWidgetAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
const widgetEl = myWidget.el;
|
||||
@@ -779,7 +743,7 @@
|
||||
`;
|
||||
Parent.components = { MyWidgetAdapter };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.containsOnce(target, ".widget_id_0");
|
||||
@@ -824,7 +788,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new MyWidget();
|
||||
await widget.appendTo(target);
|
||||
|
||||
@@ -862,7 +826,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new MyWidget();
|
||||
await widget.appendTo(target);
|
||||
|
||||
@@ -914,7 +878,7 @@
|
||||
});
|
||||
|
||||
const widget = new MyWidget();
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const iframe = document.createElement("iframe");
|
||||
target.appendChild(iframe);
|
||||
await widget.appendTo(iframe.contentWindow.document.body);
|
||||
@@ -955,7 +919,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new MyWidget();
|
||||
await widget.appendTo(target);
|
||||
|
||||
@@ -1017,7 +981,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new MyWidget();
|
||||
await widget.appendTo(target);
|
||||
|
||||
@@ -1079,7 +1043,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new MyWidget();
|
||||
const fragment = document.createElement("div");
|
||||
await widget.appendTo(fragment);
|
||||
@@ -1130,7 +1094,7 @@
|
||||
MyComponent.template = xml`<div><MyChildComponent/></div>`;
|
||||
MyComponent.components = { MyChildComponent };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new ComponentWrapper(null, MyComponent, {});
|
||||
await widget.mount(target);
|
||||
|
||||
@@ -1205,7 +1169,7 @@
|
||||
MyComponent.template = xml`<div><MyChildComponent t-props="props" /></div>`;
|
||||
MyComponent.components = { MyChildComponent };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new ComponentWrapper(null, MyComponent, {
|
||||
text: "takeNoMess"
|
||||
});
|
||||
@@ -1264,7 +1228,7 @@
|
||||
MyComponent.template = xml`<div><MyChildComponent t-props="props" /></div>`;
|
||||
MyComponent.components = { MyChildComponent };
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new ComponentWrapper(null, MyComponent, {
|
||||
text: "takeNoMess"
|
||||
});
|
||||
@@ -1336,7 +1300,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new MyWidget();
|
||||
await widget.appendTo(target);
|
||||
|
||||
@@ -1368,7 +1332,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new MyWidget();
|
||||
await widget.appendTo(target);
|
||||
assert.verifySteps([
|
||||
@@ -1425,7 +1389,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new MyWidget();
|
||||
await widget.appendTo(target);
|
||||
|
||||
@@ -1454,7 +1418,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const widget = new MyWidget();
|
||||
await widget.appendTo(target);
|
||||
|
||||
@@ -1495,7 +1459,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = new Parent();
|
||||
await parent.appendTo(target);
|
||||
|
||||
@@ -1569,7 +1533,7 @@
|
||||
Parent.components = { ComponentAdapter };
|
||||
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, target);
|
||||
|
||||
assert.strictEqual(parent.el.innerHTML, '<div><span>Component</span></div>');
|
||||
@@ -1633,7 +1597,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const target = testUtils.prepareTarget();
|
||||
const target = getFixture();
|
||||
const parent = new Parent();
|
||||
await parent.appendTo(target);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { WebClient } from "@web/webclient/webclient";
|
||||
import testUtils from "web.test_utils";
|
||||
import { registerCleanup } from "../../helpers/cleanup";
|
||||
import { makeTestEnv } from "../../helpers/mock_env";
|
||||
import {
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
editInput,
|
||||
getNodesTextContent,
|
||||
} from "../../helpers/utils";
|
||||
import { pagerNext, toggleMenuItem, toggleSearchBarMenu } from "@web/../tests/search/helpers";
|
||||
import { pagerNext, switchView, toggleMenuItem, toggleSearchBarMenu } from "@web/../tests/search/helpers";
|
||||
import { session } from "@web/session";
|
||||
import {
|
||||
createWebClient,
|
||||
@@ -121,8 +120,8 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
patchWithCleanup(session, { home_action_id: 1001 });
|
||||
|
||||
await createWebClient({ serverData });
|
||||
await testUtils.nextTick(); // wait for the navbar to be updated
|
||||
await testUtils.nextTick(); // wait for the action to be displayed
|
||||
await nextTick(); // wait for the navbar to be updated
|
||||
await nextTick(); // wait for the action to be displayed
|
||||
|
||||
assert.containsOnce(target, ".test_client_action");
|
||||
assert.strictEqual(target.querySelector(".o_menu_brand").innerText, "App1");
|
||||
@@ -196,8 +195,8 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
webClient.env.bus.trigger("test:hashchange", {
|
||||
action: "HelloWorldTest",
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
$(target).find(".o_client_action_test").text(),
|
||||
"Hello World",
|
||||
@@ -215,7 +214,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
webClient.env.bus.trigger("test:hashchange", {
|
||||
action: 1,
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.containsOnce(target, ".o_control_panel");
|
||||
assert.containsOnce(target, ".o_kanban_view");
|
||||
@@ -237,8 +236,8 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
id: 2,
|
||||
model: "partner",
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_form_view");
|
||||
assert.deepEqual(getBreadCrumbTexts(target), ["Second record"]);
|
||||
assert.verifySteps(["/web/webclient/load_menus", "get_views", "web_read"]);
|
||||
@@ -260,7 +259,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
Object.assign(browser.location, { hash });
|
||||
await createWebClient({ serverData, mockRPC });
|
||||
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_form_view");
|
||||
assert.deepEqual(getBreadCrumbTexts(target), ["Second record"]);
|
||||
assert.containsNone(target, ".o_menu_brand");
|
||||
@@ -279,7 +278,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
model: "partner",
|
||||
view_type: "form",
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.containsOnce(target, ".o_form_view");
|
||||
assert.verifySteps([
|
||||
@@ -300,7 +299,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
action: 3,
|
||||
view_type: "kanban",
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.containsNone(target, ".o_list_view");
|
||||
assert.containsOnce(target, ".o_kanban_view");
|
||||
@@ -329,13 +328,13 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
id: 2,
|
||||
view_type: "form",
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.containsNone(target, ".o_list_view");
|
||||
assert.containsOnce(target, ".o_form_view");
|
||||
assert.deepEqual(getBreadCrumbTexts(target), ["Partners", "Second record"]);
|
||||
// go back to List
|
||||
await testUtils.dom.click($(target).find(".o_control_panel .breadcrumb a"));
|
||||
await click(target.querySelector(".o_control_panel .breadcrumb a"));
|
||||
assert.containsOnce(target, ".o_list_view");
|
||||
assert.containsNone(target, ".o_form_view");
|
||||
assert.verifySteps([
|
||||
@@ -362,7 +361,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
"Second record",
|
||||
]);
|
||||
// go back to List
|
||||
await testUtils.dom.click($(target).find(".o_control_panel .breadcrumb a:last"));
|
||||
await click(target.querySelector(".o_control_panel .breadcrumb .o_back_button a"));
|
||||
assert.deepEqual(getBreadCrumbTexts(target), ["Partners Action 4", "Partners"]);
|
||||
});
|
||||
|
||||
@@ -401,7 +400,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
action: 3,
|
||||
view_type: "kanban",
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.containsNone(target, ".o_list_view");
|
||||
assert.containsOnce(target, ".o_kanban_view");
|
||||
@@ -411,7 +410,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
id: 4,
|
||||
view_type: "form",
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.containsNone(target, ".o_kanban_view");
|
||||
assert.containsOnce(target, ".o_form_view");
|
||||
@@ -437,7 +436,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
const webClient = await createWebClient({ serverData, mockRPC });
|
||||
// execute action 3 and open the first record in a form view
|
||||
await doAction(webClient, 3);
|
||||
await testUtils.dom.click($(target).find(".o_list_view .o_data_cell:first"));
|
||||
await click(target.querySelector(".o_list_view .o_data_cell"));
|
||||
assert.containsOnce(target, ".o_form_view");
|
||||
assert.deepEqual(getBreadCrumbTexts(target), ["Partners", "First record"]);
|
||||
// switch to record 4
|
||||
@@ -446,7 +445,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
id: 4,
|
||||
view_type: "form",
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_form_view");
|
||||
assert.deepEqual(getBreadCrumbTexts(target), ["Partners", "Fourth record"]);
|
||||
// verify steps to ensure that the whole action hasn't been re-executed
|
||||
@@ -485,7 +484,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
view_type: "list",
|
||||
});
|
||||
assert.verifySteps(["push_state"], "should have pushed the final state");
|
||||
await testUtils.dom.click($(target).find("tr .o_data_cell:first"));
|
||||
await click(target.querySelector("tr .o_data_cell"));
|
||||
await legacyExtraNextTick();
|
||||
currentHash = webClient.env.services.router.current.hash;
|
||||
assert.deepEqual(currentHash, {
|
||||
@@ -660,7 +659,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await doAction(webClient, 3);
|
||||
assert.containsOnce(target, ".o_list_view", "should now display the list view");
|
||||
|
||||
await testUtils.controlPanel.switchView(target, "kanban");
|
||||
await switchView(target, "kanban");
|
||||
await legacyExtraNextTick();
|
||||
assert.containsOnce(target, ".o_kanban_view", "should now display the kanban view");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user