mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-19 15:52:08 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
|
|
@ -0,0 +1,264 @@
|
|||
odoo.define('web.action_menus_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const ActionMenus = require('web.ActionMenus');
|
||||
const Registry = require('web.Registry');
|
||||
const testUtils = require('web.test_utils');
|
||||
|
||||
const { Component } = owl;
|
||||
const { createComponent } = testUtils;
|
||||
|
||||
QUnit.module('Components', {
|
||||
beforeEach() {
|
||||
this.action = {
|
||||
res_model: 'hobbit',
|
||||
};
|
||||
this.view = {
|
||||
// needed by google_drive module, makes sense to give a view anyway.
|
||||
type: 'form',
|
||||
};
|
||||
this.props = {
|
||||
activeIds: [23],
|
||||
context: {},
|
||||
items: {
|
||||
action: [
|
||||
{ action: { id: 1 }, name: "What's taters, precious ?", id: 1 },
|
||||
],
|
||||
print: [
|
||||
{ action: { id: 2 }, name: "Po-ta-toes", id: 2 },
|
||||
],
|
||||
other: [
|
||||
{ description: "Boil'em", callback() { } },
|
||||
{ description: "Mash'em", callback() { } },
|
||||
{ description: "Stick'em in a stew", url: '#stew' },
|
||||
],
|
||||
},
|
||||
};
|
||||
// Patch the registry of the action menus
|
||||
this.actionMenusRegistry = ActionMenus.registry;
|
||||
ActionMenus.registry = new Registry();
|
||||
},
|
||||
afterEach() {
|
||||
ActionMenus.registry = this.actionMenusRegistry;
|
||||
},
|
||||
}, function () {
|
||||
|
||||
QUnit.module('ActionMenus');
|
||||
|
||||
QUnit.test('basic interactions', async function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
const actionMenus = await createComponent(ActionMenus, {
|
||||
env: {
|
||||
action: this.action,
|
||||
view: this.view,
|
||||
},
|
||||
props: this.props,
|
||||
});
|
||||
|
||||
const dropdowns = actionMenus.el.getElementsByClassName('dropdown');
|
||||
assert.strictEqual(dropdowns.length, 2, "ActionMenus should contain 2 menus");
|
||||
assert.strictEqual(dropdowns[0].querySelector('.o_dropdown_title').innerText.trim(), "Print");
|
||||
assert.strictEqual(dropdowns[1].querySelector('.o_dropdown_title').innerText.trim(), "Action");
|
||||
assert.containsNone(actionMenus, '.o-dropdown-menu');
|
||||
|
||||
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Action");
|
||||
|
||||
assert.containsOnce(actionMenus, '.o-dropdown-menu');
|
||||
assert.containsN(actionMenus, '.o-dropdown-menu .o_menu_item', 4);
|
||||
const actionsTexts = [...dropdowns[1].querySelectorAll('.o_menu_item')].map(el => el.innerText.trim());
|
||||
assert.deepEqual(actionsTexts, [
|
||||
"Boil'em",
|
||||
"Mash'em",
|
||||
"Stick'em in a stew",
|
||||
"What's taters, precious ?",
|
||||
], "callbacks should appear before actions");
|
||||
|
||||
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Print");
|
||||
|
||||
assert.containsOnce(actionMenus, '.o-dropdown-menu');
|
||||
assert.containsN(actionMenus, '.o-dropdown-menu .o_menu_item', 1);
|
||||
|
||||
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Print");
|
||||
|
||||
assert.containsNone(actionMenus, '.o-dropdown-menu');
|
||||
});
|
||||
|
||||
QUnit.test("empty action menus", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
ActionMenus.registry.add("test", { Component, getProps: () => false });
|
||||
this.props.items = {};
|
||||
|
||||
const actionMenus = await createComponent(ActionMenus, {
|
||||
env: {
|
||||
action: this.action,
|
||||
view: this.view,
|
||||
},
|
||||
props: this.props,
|
||||
});
|
||||
|
||||
assert.containsNone(actionMenus, ".o_cp_action_menus > *");
|
||||
});
|
||||
|
||||
QUnit.test('execute action', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const actionMenus = await createComponent(ActionMenus, {
|
||||
env: {
|
||||
action: this.action,
|
||||
view: this.view,
|
||||
},
|
||||
props: this.props,
|
||||
intercepts: {
|
||||
'do-action': ev => assert.step('do-action'),
|
||||
},
|
||||
async mockRPC(route, args) {
|
||||
switch (route) {
|
||||
case '/web/action/load':
|
||||
const expectedContext = {
|
||||
active_id: 23,
|
||||
active_ids: [23],
|
||||
active_model: 'hobbit',
|
||||
};
|
||||
assert.deepEqual(args.context, expectedContext);
|
||||
assert.step('load-action');
|
||||
return { context: {}, flags: {} };
|
||||
default:
|
||||
return this._super(...arguments);
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Action");
|
||||
await testUtils.controlPanel.toggleMenuItem(actionMenus, "What's taters, precious ?");
|
||||
|
||||
assert.verifySteps(['load-action', 'do-action']);
|
||||
});
|
||||
|
||||
QUnit.test('execute callback action', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const callbackPromise = testUtils.makeTestPromise();
|
||||
this.props.items.other[0].callback = function (items) {
|
||||
assert.strictEqual(items.length, 1);
|
||||
assert.strictEqual(items[0].description, "Boil'em");
|
||||
callbackPromise.resolve();
|
||||
};
|
||||
|
||||
const actionMenus = await createComponent(ActionMenus, {
|
||||
env: {
|
||||
action: this.action,
|
||||
view: this.view,
|
||||
},
|
||||
props: this.props,
|
||||
async mockRPC(route, args) {
|
||||
switch (route) {
|
||||
case '/web/action/load':
|
||||
throw new Error("No action should be loaded.");
|
||||
default:
|
||||
return this._super(...arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Action");
|
||||
await testUtils.controlPanel.toggleMenuItem(actionMenus, "Boil'em");
|
||||
|
||||
await callbackPromise;
|
||||
});
|
||||
|
||||
QUnit.test('execute print action', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const actionMenus = await createComponent(ActionMenus, {
|
||||
env: {
|
||||
action: this.action,
|
||||
view: this.view,
|
||||
},
|
||||
intercepts: {
|
||||
'do-action': ev => assert.step('do-action'),
|
||||
},
|
||||
props: this.props,
|
||||
async mockRPC(route, args) {
|
||||
switch (route) {
|
||||
case '/web/action/load':
|
||||
const expectedContext = {
|
||||
active_id: 23,
|
||||
active_ids: [23],
|
||||
active_model: 'hobbit',
|
||||
};
|
||||
assert.deepEqual(args.context, expectedContext);
|
||||
assert.step('load-action');
|
||||
return { context: {}, flags: {} };
|
||||
default:
|
||||
return this._super(...arguments);
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Print");
|
||||
await testUtils.controlPanel.toggleMenuItem(actionMenus, "Po-ta-toes");
|
||||
|
||||
assert.verifySteps(['load-action', 'do-action']);
|
||||
});
|
||||
|
||||
QUnit.test('execute url action', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const actionMenus = await createComponent(ActionMenus, {
|
||||
env: {
|
||||
action: this.action,
|
||||
services: {
|
||||
navigate(url) {
|
||||
assert.step(url);
|
||||
},
|
||||
},
|
||||
view: this.view,
|
||||
},
|
||||
props: this.props,
|
||||
async mockRPC(route, args) {
|
||||
switch (route) {
|
||||
case '/web/action/load':
|
||||
throw new Error("No action should be loaded.");
|
||||
default:
|
||||
return this._super(...arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Action");
|
||||
await testUtils.controlPanel.toggleMenuItem(actionMenus, "Stick'em in a stew");
|
||||
|
||||
assert.verifySteps(['#stew']);
|
||||
});
|
||||
|
||||
QUnit.test('execute action with context', async function (assert) {
|
||||
assert.expect(1);
|
||||
const actionMenus = await createComponent(ActionMenus, {
|
||||
env: {
|
||||
action: this.action,
|
||||
view: this.view,
|
||||
},
|
||||
props: {
|
||||
...this.props,
|
||||
isDomainSelected: true,
|
||||
context: {
|
||||
allowed_company_ids: [112],
|
||||
},
|
||||
},
|
||||
async mockRPC(route, args) {
|
||||
if (route === "/web/dataset/call_kw/hobbit/search"){
|
||||
assert.deepEqual(args.kwargs.context, { allowed_company_ids: [112] }, "The kwargs should contains the right context");
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.controlPanel.toggleActionMenu(actionMenus, "Action");
|
||||
await testUtils.controlPanel.toggleMenuItem(actionMenus, "What's taters, precious ?");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
odoo.define('web.custom_checkbox_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const CustomCheckbox = require('web.CustomCheckbox');
|
||||
const testUtils = require('web.test_utils');
|
||||
|
||||
const { createComponent, dom: testUtilsDom } = testUtils;
|
||||
|
||||
QUnit.module('Components', {}, function () {
|
||||
|
||||
QUnit.module('CustomCheckbox');
|
||||
|
||||
QUnit.test('test checkbox: default values', async function(assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const checkbox = await createComponent(CustomCheckbox, {});
|
||||
|
||||
assert.containsOnce(checkbox.el, 'input');
|
||||
assert.containsNone(checkbox.el, 'input:disabled');
|
||||
assert.containsOnce(checkbox.el, 'label');
|
||||
|
||||
const input = checkbox.el.querySelector('input');
|
||||
assert.notOk(input.checked, 'checkbox should be unchecked');
|
||||
assert.ok(input.id.startsWith('checkbox-comp-'));
|
||||
|
||||
await testUtilsDom.click(checkbox.el.querySelector('label'));
|
||||
assert.ok(input.checked, 'checkbox should be checked');
|
||||
});
|
||||
|
||||
QUnit.test('test checkbox: custom values', async function(assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const checkbox = await createComponent(CustomCheckbox, {
|
||||
props: {
|
||||
id: 'my-form-check',
|
||||
disabled: true,
|
||||
value: true,
|
||||
text: 'checkbox',
|
||||
}
|
||||
});
|
||||
|
||||
assert.containsOnce(checkbox.el, 'input');
|
||||
assert.containsOnce(checkbox.el, 'input:disabled');
|
||||
assert.containsOnce(checkbox.el, 'label');
|
||||
|
||||
const input = checkbox.el.querySelector('input');
|
||||
assert.ok(input.checked, 'checkbox should be checked');
|
||||
assert.strictEqual(input.id, 'my-form-check');
|
||||
assert.ok(input.checked, 'checkbox should be checked');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
odoo.define('web.custom_file_input_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const CustomFileInput = require('web.CustomFileInput');
|
||||
const testUtils = require('web.test_utils');
|
||||
|
||||
const { createComponent } = testUtils;
|
||||
|
||||
QUnit.module('Components', {}, function () {
|
||||
|
||||
// This module cannot be tested as thoroughly as we want it to be:
|
||||
// browsers do not let scripts programmatically assign values to inputs
|
||||
// of type file
|
||||
QUnit.module('CustomFileInput');
|
||||
|
||||
QUnit.test("Upload a file: default props", async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const customFileInput = await createComponent(CustomFileInput, {
|
||||
env: {
|
||||
services: {
|
||||
async httpRequest(route, params) {
|
||||
assert.deepEqual(params, {
|
||||
csrf_token: odoo.csrf_token,
|
||||
ufile: [],
|
||||
});
|
||||
assert.step(route);
|
||||
return '[]';
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const input = customFileInput.el.querySelector('input');
|
||||
|
||||
assert.strictEqual(customFileInput.el.innerText.trim().toUpperCase(), "CHOOSE FILE",
|
||||
"File input total text should match its given inner element's text");
|
||||
assert.strictEqual(input.accept, '*',
|
||||
"Input should accept all files by default");
|
||||
|
||||
await testUtils.dom.triggerEvent(input, 'change');
|
||||
|
||||
assert.notOk(input.multiple, "'multiple' attribute should not be set");
|
||||
assert.verifySteps(['/web/binary/upload']);
|
||||
});
|
||||
|
||||
QUnit.test("Upload a file: custom attachment", async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const customFileInput = await createComponent(CustomFileInput, {
|
||||
env: {
|
||||
services: {
|
||||
async httpRequest(route, params) {
|
||||
assert.deepEqual(params, {
|
||||
id: 5,
|
||||
model: 'res.model',
|
||||
csrf_token: odoo.csrf_token,
|
||||
ufile: [],
|
||||
});
|
||||
assert.step(route);
|
||||
return '[]';
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
accepted_file_extensions: '.png',
|
||||
action: '/web/binary/upload_attachment',
|
||||
id: 5,
|
||||
model: 'res.model',
|
||||
multi_upload: true,
|
||||
},
|
||||
intercepts: {
|
||||
'uploaded': ev => assert.strictEqual(ev.detail.files.length, 0,
|
||||
"'files' property should be an empty array"),
|
||||
},
|
||||
});
|
||||
const input = customFileInput.el.querySelector('input');
|
||||
|
||||
assert.strictEqual(input.accept, '.png', "Input should now only accept pngs");
|
||||
|
||||
await testUtils.dom.triggerEvent(input, 'change');
|
||||
|
||||
assert.ok(input.multiple, "'multiple' attribute should be set");
|
||||
assert.verifySteps(['/web/binary/upload_attachment']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
odoo.define('web.datepicker_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const { DatePicker, DateTimePicker } = require('web.DatePickerOwl');
|
||||
const testUtils = require('web.test_utils');
|
||||
const time = require('web.time');
|
||||
|
||||
const { createComponent } = testUtils;
|
||||
|
||||
QUnit.module('Components', {}, function () {
|
||||
|
||||
QUnit.module('DatePicker (legacy)');
|
||||
|
||||
QUnit.test("basic rendering", async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
const picker = await createComponent(DatePicker, {
|
||||
props: { date: moment('1997-01-09'), onDateTimeChanged: () => {} },
|
||||
});
|
||||
|
||||
|
||||
assert.containsOnce(picker, 'input.o_input.o_datepicker_input');
|
||||
assert.containsOnce(picker, 'span.o_datepicker_button');
|
||||
assert.containsNone(document.body, 'div.bootstrap-datetimepicker-widget');
|
||||
|
||||
const input = picker.el.querySelector('input.o_input.o_datepicker_input');
|
||||
assert.strictEqual(input.value, '01/09/1997',
|
||||
"Value should be the one given")
|
||||
;
|
||||
assert.strictEqual(input.dataset.target, `#${picker.el.id}`,
|
||||
"DatePicker id should match its input target");
|
||||
|
||||
await testUtils.dom.click(input);
|
||||
|
||||
assert.containsOnce(document.body, 'div.bootstrap-datetimepicker-widget .datepicker');
|
||||
assert.containsNone(document.body, 'div.bootstrap-datetimepicker-widget .timepicker');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.datepicker .day.active').dataset.day,
|
||||
'01/09/1997',
|
||||
"Datepicker should have set the correct day"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("pick a date", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const picker = await createComponent(DatePicker, {
|
||||
props: {
|
||||
date: moment('1997-01-09'),
|
||||
onDateTimeChanged: date => {
|
||||
assert.step('datetime-changed');
|
||||
assert.strictEqual(date.format('MM/DD/YYYY'), '02/08/1997',
|
||||
"Event should transmit the correct date");
|
||||
},
|
||||
}
|
||||
});
|
||||
const input = picker.el.querySelector('.o_datepicker_input');
|
||||
|
||||
await testUtils.dom.click(input);
|
||||
await testUtils.dom.click(document.querySelector('.datepicker th.next')); // next month
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
||||
await testUtils.dom.click(document.querySelectorAll('.datepicker table td')[15]); // previous day
|
||||
|
||||
assert.strictEqual(input.value, '02/08/1997');
|
||||
assert.verifySteps(['datetime-changed']);
|
||||
});
|
||||
|
||||
QUnit.test("pick a date with locale", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
// weird shit of moment https://github.com/moment/moment/issues/5600
|
||||
// When month regex returns undefined, january is taken (first month of the default "nameless" locale)
|
||||
const originalLocale = moment.locale();
|
||||
// Those parameters will make Moment's internal compute stuff that are relevant to the bug
|
||||
const months = 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_');
|
||||
const monthsShort = 'janv._févr._mars_avr._mai_juin_juil._août_custSept._oct._nov._déc.'.split('_');
|
||||
moment.defineLocale('frenchForTests', { months, monthsShort, code: 'frTest' , monthsParseExact: true});
|
||||
|
||||
const hasChanged = testUtils.makeTestPromise();
|
||||
const picker = await createComponent(DatePicker, {
|
||||
translateParameters: {
|
||||
date_format: "%d %b, %Y", // Those are important too
|
||||
time_format: "%H:%M:%S",
|
||||
},
|
||||
props: {
|
||||
date: moment('09/01/1997', 'MM/DD/YYYY'),
|
||||
onDateTimeChanged: date => {
|
||||
assert.step('datetime-changed');
|
||||
assert.strictEqual(date.format('MM/DD/YYYY'), '09/02/1997',
|
||||
"Event should transmit the correct date");
|
||||
hasChanged.resolve();
|
||||
},
|
||||
}
|
||||
});
|
||||
const input = picker.el.querySelector('.o_datepicker_input');
|
||||
await testUtils.dom.click(input);
|
||||
|
||||
await testUtils.dom.click(document.querySelectorAll('.datepicker table td')[3]); // next day
|
||||
|
||||
assert.strictEqual(input.value, '02 custSept., 1997');
|
||||
assert.verifySteps(['datetime-changed']);
|
||||
|
||||
moment.locale(originalLocale);
|
||||
moment.updateLocale('frenchForTests', null);
|
||||
});
|
||||
|
||||
QUnit.test("enter a date value", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const picker = await createComponent(DatePicker, {
|
||||
props: {
|
||||
date: moment('1997-01-09'),
|
||||
onDateTimeChanged: date => {
|
||||
assert.step('datetime-changed');
|
||||
assert.strictEqual(date.format('MM/DD/YYYY'), '02/08/1997',
|
||||
"Event should transmit the correct date");
|
||||
},
|
||||
}
|
||||
});
|
||||
const input = picker.el.querySelector('.o_datepicker_input');
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
||||
await testUtils.fields.editAndTrigger(input, '02/08/1997', ['change']);
|
||||
|
||||
assert.verifySteps(['datetime-changed']);
|
||||
|
||||
await testUtils.dom.click(input);
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelector('.datepicker .day.active').dataset.day,
|
||||
'02/08/1997',
|
||||
"Datepicker should have set the correct day"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Date format is correctly set", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
testUtils.mock.patch(time, { getLangDateFormat: () => "YYYY/MM/DD" });
|
||||
const picker = await createComponent(DatePicker, {
|
||||
props: { date: moment('1997-01-09'), onDateTimeChanged: () => {} },
|
||||
});
|
||||
const input = picker.el.querySelector('.o_datepicker_input');
|
||||
|
||||
assert.strictEqual(input.value, '1997/01/09');
|
||||
|
||||
// Forces an update to assert that the registered format is the correct one
|
||||
await testUtils.dom.click(input);
|
||||
|
||||
assert.strictEqual(input.value, '1997/01/09');
|
||||
|
||||
testUtils.mock.unpatch(time);
|
||||
});
|
||||
|
||||
QUnit.module('DateTimePicker (legacy)');
|
||||
|
||||
QUnit.test("basic rendering", async function (assert) {
|
||||
assert.expect(11);
|
||||
|
||||
const picker = await createComponent(DateTimePicker, {
|
||||
props: { date: moment('1997-01-09 12:30:01'), onDateTimeChanged: () => {} },
|
||||
});
|
||||
|
||||
assert.containsOnce(picker, 'input.o_input.o_datepicker_input');
|
||||
assert.containsOnce(picker, 'span.o_datepicker_button');
|
||||
assert.containsNone(document.body, 'div.bootstrap-datetimepicker-widget');
|
||||
|
||||
const input = picker.el.querySelector('input.o_input.o_datepicker_input');
|
||||
assert.strictEqual(input.value, '01/09/1997 12:30:01', "Value should be the one given");
|
||||
assert.strictEqual(input.dataset.target, `#${picker.el.id}`,
|
||||
"DateTimePicker id should match its input target");
|
||||
|
||||
await testUtils.dom.click(input);
|
||||
|
||||
assert.containsOnce(document.body, 'div.bootstrap-datetimepicker-widget .datepicker');
|
||||
assert.containsOnce(document.body, 'div.bootstrap-datetimepicker-widget .timepicker');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.datepicker .day.active').dataset.day,
|
||||
'01/09/1997',
|
||||
"Datepicker should have set the correct day");
|
||||
|
||||
assert.strictEqual(document.querySelector('.timepicker .timepicker-hour').innerText.trim(), '12',
|
||||
"Datepicker should have set the correct hour");
|
||||
assert.strictEqual(document.querySelector('.timepicker .timepicker-minute').innerText.trim(), '30',
|
||||
"Datepicker should have set the correct minute");
|
||||
assert.strictEqual(document.querySelector('.timepicker .timepicker-second').innerText.trim(), '01',
|
||||
"Datepicker should have set the correct second");
|
||||
});
|
||||
|
||||
QUnit.test("pick a date and time", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const picker = await createComponent(DateTimePicker, {
|
||||
props: {
|
||||
date: moment('1997-01-09 12:30:01'),
|
||||
onDateTimeChanged: date => {
|
||||
assert.step('datetime-changed');
|
||||
assert.strictEqual(date.format('MM/DD/YYYY HH:mm:ss'), '02/08/1997 15:45:05',
|
||||
"Event should transmit the correct date");
|
||||
},
|
||||
}
|
||||
});
|
||||
const input = picker.el.querySelector('input.o_input.o_datepicker_input');
|
||||
|
||||
await testUtils.dom.click(input);
|
||||
await testUtils.dom.click(document.querySelector('.datepicker th.next')); // February
|
||||
await testUtils.dom.click(document.querySelectorAll('.datepicker table td')[15]); // 08
|
||||
await testUtils.dom.click(document.querySelector('a[title="Select Time"]'));
|
||||
await testUtils.dom.click(document.querySelector('.timepicker .timepicker-hour'));
|
||||
await testUtils.dom.click(document.querySelectorAll('.timepicker .hour')[15]); // 15h
|
||||
await testUtils.dom.click(document.querySelector('.timepicker .timepicker-minute'));
|
||||
await testUtils.dom.click(document.querySelectorAll('.timepicker .minute')[9]); // 45m
|
||||
await testUtils.dom.click(document.querySelector('.timepicker .timepicker-second'));
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
||||
await testUtils.dom.click(document.querySelectorAll('.timepicker .second')[1]); // 05s
|
||||
|
||||
assert.strictEqual(input.value, '02/08/1997 15:45:05');
|
||||
assert.verifySteps(['datetime-changed']);
|
||||
});
|
||||
|
||||
QUnit.test("pick a date and time with locale", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
// weird shit of moment https://github.com/moment/moment/issues/5600
|
||||
// When month regex returns undefined, january is taken (first month of the default "nameless" locale)
|
||||
const originalLocale = moment.locale();
|
||||
// Those parameters will make Moment's internal compute stuff that are relevant to the bug
|
||||
const months = 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_');
|
||||
const monthsShort = 'janv._févr._mars_avr._mai_juin_juil._août_custSept._oct._nov._déc.'.split('_');
|
||||
moment.defineLocale('frenchForTests', { months, monthsShort, code: 'frTest' , monthsParseExact: true});
|
||||
|
||||
const hasChanged = testUtils.makeTestPromise();
|
||||
const picker = await createComponent(DateTimePicker, {
|
||||
translateParameters: {
|
||||
date_format: "%d %b, %Y", // Those are important too
|
||||
time_format: "%H:%M:%S",
|
||||
},
|
||||
props: {
|
||||
date: moment('09/01/1997 12:30:01', 'MM/DD/YYYY HH:mm:ss'),
|
||||
onDateTimeChanged: date => {
|
||||
assert.step('datetime-changed');
|
||||
assert.strictEqual(date.format('MM/DD/YYYY HH:mm:ss'), '09/02/1997 15:45:05',
|
||||
"Event should transmit the correct date");
|
||||
hasChanged.resolve();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
const input = picker.el.querySelector('input.o_input.o_datepicker_input');
|
||||
|
||||
await testUtils.dom.click(input);
|
||||
await testUtils.dom.click(document.querySelectorAll('.datepicker table td')[3]); // next day
|
||||
await testUtils.dom.click(document.querySelector('a[title="Select Time"]'));
|
||||
await testUtils.dom.click(document.querySelector('.timepicker .timepicker-hour'));
|
||||
await testUtils.dom.click(document.querySelectorAll('.timepicker .hour')[15]); // 15h
|
||||
await testUtils.dom.click(document.querySelector('.timepicker .timepicker-minute'));
|
||||
await testUtils.dom.click(document.querySelectorAll('.timepicker .minute')[9]); // 45m
|
||||
await testUtils.dom.click(document.querySelector('.timepicker .timepicker-second'));
|
||||
|
||||
assert.verifySteps([]);
|
||||
await testUtils.dom.click(document.querySelectorAll('.timepicker .second')[1]); // 05s
|
||||
|
||||
assert.strictEqual(input.value, '02 custSept., 1997 15:45:05');
|
||||
assert.verifySteps(['datetime-changed']);
|
||||
|
||||
await hasChanged;
|
||||
|
||||
moment.locale(originalLocale);
|
||||
moment.updateLocale('frenchForTests', null);
|
||||
});
|
||||
|
||||
QUnit.test("enter a datetime value", async function (assert) {
|
||||
assert.expect(9);
|
||||
|
||||
const picker = await createComponent(DateTimePicker, {
|
||||
props: {
|
||||
date: moment('1997-01-09 12:30:01'),
|
||||
onDateTimeChanged: date => {
|
||||
assert.step('datetime-changed');
|
||||
assert.strictEqual(date.format('MM/DD/YYYY HH:mm:ss'), '02/08/1997 15:45:05',
|
||||
"Event should transmit the correct date");
|
||||
},
|
||||
}
|
||||
});
|
||||
const input = picker.el.querySelector('.o_datepicker_input');
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
||||
await testUtils.fields.editAndTrigger(input, '02/08/1997 15:45:05', ['change']);
|
||||
|
||||
assert.verifySteps(['datetime-changed']);
|
||||
|
||||
await testUtils.dom.click(input);
|
||||
|
||||
assert.strictEqual(input.value, '02/08/1997 15:45:05');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.datepicker .day.active').dataset.day,
|
||||
'02/08/1997',
|
||||
"Datepicker should have set the correct day"
|
||||
);
|
||||
assert.strictEqual(document.querySelector('.timepicker .timepicker-hour').innerText.trim(), '15',
|
||||
"Datepicker should have set the correct hour");
|
||||
assert.strictEqual(document.querySelector('.timepicker .timepicker-minute').innerText.trim(), '45',
|
||||
"Datepicker should have set the correct minute");
|
||||
assert.strictEqual(document.querySelector('.timepicker .timepicker-second').innerText.trim(), '05',
|
||||
"Datepicker should have set the correct second");
|
||||
});
|
||||
|
||||
QUnit.test("Date time format is correctly set", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
testUtils.mock.patch(time, { getLangDatetimeFormat: () => "hh:mm:ss YYYY/MM/DD" });
|
||||
const picker = await createComponent(DateTimePicker, {
|
||||
props: { date: moment('1997-01-09 12:30:01'), onDateTimeChanged: () => {} },
|
||||
});
|
||||
const input = picker.el.querySelector('.o_datepicker_input');
|
||||
|
||||
assert.strictEqual(input.value, '12:30:01 1997/01/09');
|
||||
|
||||
// Forces an update to assert that the registered format is the correct one
|
||||
await testUtils.dom.click(input);
|
||||
|
||||
assert.strictEqual(input.value, '12:30:01 1997/01/09');
|
||||
|
||||
testUtils.mock.unpatch(time);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
odoo.define('web.dropdown_menu_mobile_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const DropdownMenu = require('web.DropdownMenu');
|
||||
const testUtils = require('web.test_utils');
|
||||
|
||||
const { createComponent } = testUtils;
|
||||
|
||||
QUnit.module('Components', {
|
||||
before: function () {
|
||||
this.items = [
|
||||
{
|
||||
isActive: false,
|
||||
description: 'Some Item',
|
||||
id: 1,
|
||||
groupId: 1,
|
||||
groupNumber: 1,
|
||||
options: [
|
||||
{ description: "First Option", groupNumber: 1, id: 1 },
|
||||
{ description: "Second Option", groupNumber: 2, id: 2 },
|
||||
],
|
||||
}, {
|
||||
isActive: true,
|
||||
description: 'Some other Item',
|
||||
id: 2,
|
||||
groupId: 2,
|
||||
groupNumber: 2,
|
||||
},
|
||||
];
|
||||
},
|
||||
}, function () {
|
||||
QUnit.module('DropdownMenu');
|
||||
|
||||
QUnit.test('display dropdown at the right position', async function (assert) {
|
||||
assert.expect(2);
|
||||
const viewPort = testUtils.prepareTarget();
|
||||
viewPort.style.position = 'initial';
|
||||
|
||||
const dropdown = await createComponent(DropdownMenu, {
|
||||
env: {
|
||||
device: {
|
||||
isMobile: true
|
||||
},
|
||||
},
|
||||
props: {
|
||||
items: this.items,
|
||||
title: "Dropdown",
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
assert.containsOnce(dropdown.el, '.dropdown-menu-start',
|
||||
"should display the dropdown menu at the right screen");
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
|
||||
// position the dropdown to the right
|
||||
dropdown.el.parentNode.classList.add('clearfix');
|
||||
dropdown.el.classList.add('float-end');
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
assert.containsOnce(dropdown.el, '.dropdown-menu-end',
|
||||
"should display the dropdown menu at the left screen");
|
||||
|
||||
dropdown.el.parentNode.classList.remove('clearfix');
|
||||
viewPort.style.position = '';
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,430 @@
|
|||
odoo.define('web.dropdown_menu_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const DropdownMenu = require('web.DropdownMenu');
|
||||
const testUtils = require('web.test_utils');
|
||||
const makeTestEnvironment = require("web.test_env");
|
||||
const { mount } = require("@web/../tests/helpers/utils");
|
||||
const { LegacyComponent } = require("@web/legacy/legacy_component");
|
||||
|
||||
const { useState, xml } = owl;
|
||||
const { createComponent } = testUtils;
|
||||
|
||||
QUnit.module('Components', {
|
||||
beforeEach: function () {
|
||||
this.items = [
|
||||
{
|
||||
isActive: false,
|
||||
description: 'Some Item',
|
||||
id: 1,
|
||||
groupId: 1,
|
||||
groupNumber: 1,
|
||||
options: [
|
||||
{ description: "First Option", groupNumber: 1, id: 1 },
|
||||
{ description: "Second Option", groupNumber: 2, id: 2 },
|
||||
],
|
||||
}, {
|
||||
isActive: true,
|
||||
description: 'Some other Item',
|
||||
id: 2,
|
||||
groupId: 2,
|
||||
groupNumber: 2,
|
||||
},
|
||||
];
|
||||
},
|
||||
}, function () {
|
||||
QUnit.module('DropdownMenu');
|
||||
|
||||
QUnit.test('simple rendering and basic interactions', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
const dropdown = await createComponent(DropdownMenu, {
|
||||
props: {
|
||||
items: this.items,
|
||||
title: "Dropdown",
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(dropdown.el.querySelector('button').innerText.trim(), "Dropdown");
|
||||
assert.containsNone(dropdown, '.o-dropdown-menu');
|
||||
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
|
||||
assert.containsN(dropdown, '.dropdown-divider, .dropdown-item', 3,
|
||||
'should have 3 elements counting the divider');
|
||||
const itemEls = dropdown.el.querySelectorAll('.o_menu_item > .dropdown-item');
|
||||
assert.strictEqual(itemEls[0].innerText.trim(), 'Some Item');
|
||||
assert.doesNotHaveClass(itemEls[0], 'selected');
|
||||
assert.hasClass(itemEls[1], 'selected');
|
||||
|
||||
const dropdownElements = dropdown.el.querySelectorAll('.o_menu_item *');
|
||||
for (const dropdownEl of dropdownElements) {
|
||||
await testUtils.dom.click(dropdownEl);
|
||||
}
|
||||
assert.containsOnce(dropdown, '.o-dropdown-menu',
|
||||
"Clicking on any item of the dropdown should not close it");
|
||||
|
||||
await testUtils.dom.click(document.body);
|
||||
|
||||
assert.containsNone(dropdown, '.o-dropdown-menu',
|
||||
"Clicking outside of the dropdown should close it");
|
||||
});
|
||||
|
||||
QUnit.test('only one dropdown rendering at same time (owl vs bootstrap dropdown)', async function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
const bsDropdown = document.createElement('div');
|
||||
bsDropdown.innerHTML = `<div class="dropdown">
|
||||
<button class="btn dropdown-toggle" type="button"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
BS Dropdown button
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="#">BS Action</a>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.append(bsDropdown);
|
||||
|
||||
const dropdown = await createComponent(DropdownMenu, {
|
||||
props: {
|
||||
items: this.items,
|
||||
title: "Dropdown",
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
|
||||
assert.hasClass(dropdown.el.querySelector('.o-dropdown-menu'), 'show');
|
||||
assert.doesNotHaveClass(bsDropdown.querySelector('.dropdown-menu'), 'show');
|
||||
|
||||
assert.isVisible(dropdown.el.querySelector('.o-dropdown-menu'),
|
||||
"owl dropdown menu should be visible");
|
||||
assert.isNotVisible(bsDropdown.querySelector('.dropdown-menu'),
|
||||
"bs dropdown menu should not be visible");
|
||||
|
||||
await testUtils.dom.click(bsDropdown.querySelector('.btn.dropdown-toggle'));
|
||||
|
||||
assert.doesNotHaveClass(dropdown.el, 'show');
|
||||
assert.containsNone(dropdown.el, '.o-dropdown-menu',
|
||||
"owl dropdown menu should not be set inside the dom");
|
||||
|
||||
assert.hasClass(bsDropdown.querySelector('.dropdown-menu'), 'show');
|
||||
assert.isVisible(bsDropdown.querySelector('.dropdown-menu'),
|
||||
"bs dropdown menu should be visible");
|
||||
|
||||
await testUtils.dom.click(document.body);
|
||||
|
||||
assert.doesNotHaveClass(dropdown.el, 'show');
|
||||
assert.containsNone(dropdown.el, '.o-dropdown-menu',
|
||||
"owl dropdown menu should not be set inside the dom");
|
||||
|
||||
assert.doesNotHaveClass(bsDropdown.querySelector('.dropdown-menu'), 'show');
|
||||
assert.isNotVisible(bsDropdown.querySelector('.dropdown-menu'),
|
||||
"bs dropdown menu should not be visible");
|
||||
|
||||
bsDropdown.remove();
|
||||
});
|
||||
|
||||
QUnit.test('click on an item without options should toggle it', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
delete this.items[0].options;
|
||||
|
||||
const dropdown = await createComponent(DropdownMenu, {
|
||||
props: { items: this.items },
|
||||
intercepts: {
|
||||
'item-selected': function (ev) {
|
||||
assert.strictEqual(ev.detail.item.id, 1);
|
||||
this.state.items[0].isActive = !this.state.items[0].isActive;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
|
||||
const firstItemEl = dropdown.el.querySelector('.o_menu_item > a');
|
||||
assert.doesNotHaveClass(firstItemEl, 'selected');
|
||||
await testUtils.dom.click(firstItemEl);
|
||||
assert.hasClass(firstItemEl, 'selected');
|
||||
assert.isVisible(firstItemEl);
|
||||
await testUtils.dom.click(firstItemEl);
|
||||
assert.doesNotHaveClass(firstItemEl, 'selected');
|
||||
assert.isVisible(firstItemEl);
|
||||
});
|
||||
|
||||
QUnit.test('click on an item should not change url', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
delete this.items[0].options;
|
||||
|
||||
const initialHref = window.location.href;
|
||||
const dropdown = await createComponent(DropdownMenu, {
|
||||
props: { items: this.items },
|
||||
});
|
||||
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
await testUtils.dom.click(dropdown.el.querySelector('.o_menu_item > a'));
|
||||
assert.strictEqual(window.location.href, initialHref,
|
||||
"the url should not have changed after a click on an item");
|
||||
});
|
||||
|
||||
QUnit.test('options rendering', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const dropdown = await createComponent(DropdownMenu, {
|
||||
props: { items: this.items },
|
||||
});
|
||||
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
assert.containsN(dropdown, '.dropdown-divider, .dropdown-item', 3);
|
||||
|
||||
const firstItemEl = dropdown.el.querySelector('.o_menu_item > a');
|
||||
assert.hasClass(firstItemEl.querySelector('i'), 'o_icon_right fa fa-caret-right');
|
||||
// open options menu
|
||||
await testUtils.dom.click(firstItemEl);
|
||||
assert.hasClass(firstItemEl.querySelector('i'), 'o_icon_right fa fa-caret-down');
|
||||
assert.containsN(dropdown, '.dropdown-divider, .dropdown-item', 6);
|
||||
|
||||
// close options menu
|
||||
await testUtils.dom.click(firstItemEl);
|
||||
assert.hasClass(firstItemEl.querySelector('i'), 'o_icon_right fa fa-caret-right');
|
||||
assert.containsN(dropdown, '.dropdown-divider, .dropdown-item', 3);
|
||||
});
|
||||
|
||||
QUnit.test('close menu closes also submenus', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const dropdown = await createComponent(DropdownMenu, {
|
||||
props: { items: this.items },
|
||||
});
|
||||
|
||||
// open dropdown menu
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
// open options menu of first item
|
||||
await testUtils.dom.click(dropdown.el.querySelector('.o_menu_item a'));
|
||||
|
||||
assert.containsN(dropdown, '.dropdown-divider, .dropdown-item', 6);
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
assert.containsN(dropdown, '.dropdown-divider, .dropdown-item', 3);
|
||||
});
|
||||
|
||||
QUnit.test('click on an option should trigger the event "item_option_clicked" with appropriate data', async function (assert) {
|
||||
assert.expect(18);
|
||||
|
||||
let eventNumber = 0;
|
||||
const dropdown = await createComponent(DropdownMenu, {
|
||||
props: { items: this.items },
|
||||
intercepts: {
|
||||
'item-selected': function (ev) {
|
||||
eventNumber++;
|
||||
const { option } = ev.detail;
|
||||
assert.strictEqual(ev.detail.item.id, 1);
|
||||
if (eventNumber === 1) {
|
||||
assert.strictEqual(option.id, 1);
|
||||
this.state.items[0].isActive = true;
|
||||
this.state.items[0].options[0].isActive = true;
|
||||
}
|
||||
if (eventNumber === 2) {
|
||||
assert.strictEqual(option.id, 2);
|
||||
this.state.items[0].options[1].isActive = true;
|
||||
}
|
||||
if (eventNumber === 3) {
|
||||
assert.strictEqual(option.id, 1);
|
||||
this.state.items[0].options[0].isActive = false;
|
||||
}
|
||||
if (eventNumber === 4) {
|
||||
assert.strictEqual(option.id, 2);
|
||||
this.state.items[0].isActive = false;
|
||||
this.state.items[0].options[1].isActive = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// open dropdown menu
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
assert.containsN(dropdown, '.dropdown-divider, .o_menu_item', 3);
|
||||
|
||||
// open menu options of first item
|
||||
await testUtils.dom.click(dropdown.el.querySelector('.o_menu_item > a'));
|
||||
let optionELs = dropdown.el.querySelectorAll('.o_menu_item .o_item_option > a');
|
||||
|
||||
// click on first option
|
||||
await testUtils.dom.click(optionELs[0]);
|
||||
assert.hasClass(dropdown.el.querySelector('.o_menu_item > a'), 'selected');
|
||||
optionELs = dropdown.el.querySelectorAll('.o_menu_item .o_item_option > a');
|
||||
assert.hasClass(optionELs[0], 'selected');
|
||||
assert.doesNotHaveClass(optionELs[1], 'selected');
|
||||
|
||||
// click on second option
|
||||
await testUtils.dom.click(optionELs[1]);
|
||||
assert.hasClass(dropdown.el.querySelector('.o_menu_item > a'), 'selected');
|
||||
optionELs = dropdown.el.querySelectorAll('.o_menu_item .o_item_option > a');
|
||||
assert.hasClass(optionELs[0], 'selected');
|
||||
assert.hasClass(optionELs[1], 'selected');
|
||||
|
||||
// click again on first option
|
||||
await testUtils.dom.click(optionELs[0]);
|
||||
// click again on second option
|
||||
await testUtils.dom.click(optionELs[1]);
|
||||
assert.doesNotHaveClass(dropdown.el.querySelector('.o_menu_item > a'), 'selected');
|
||||
optionELs = dropdown.el.querySelectorAll('.o_menu_item .o_item_option > a');
|
||||
assert.doesNotHaveClass(optionELs[0], 'selected');
|
||||
assert.doesNotHaveClass(optionELs[1], 'selected');
|
||||
});
|
||||
|
||||
QUnit.test('keyboard navigation', async function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
// Shorthand method to trigger a specific keydown.
|
||||
// Note that BootStrap handles some of the navigation moves (up and down)
|
||||
// so we need to give the event the proper "which" property. We also give
|
||||
// it when it's not required to check if it has been correctly prevented.
|
||||
async function navigate(key, global) {
|
||||
const which = {
|
||||
Enter: 13,
|
||||
Escape: 27,
|
||||
ArrowLeft: 37,
|
||||
ArrowUp: 38,
|
||||
ArrowRight: 39,
|
||||
ArrowDown: 40,
|
||||
}[key];
|
||||
const target = global ? document.body : document.activeElement;
|
||||
await testUtils.dom.triggerEvent(target, 'keydown', { key, which });
|
||||
if (key === 'Enter') {
|
||||
// Pressing "Enter" on a focused element triggers a click (HTML5 specs)
|
||||
await testUtils.dom.click(target);
|
||||
}
|
||||
}
|
||||
|
||||
const dropdown = await createComponent(DropdownMenu, {
|
||||
props: { items: this.items },
|
||||
});
|
||||
|
||||
// Initialize active element (start at toggle button)
|
||||
dropdown.el.querySelector('button').focus();
|
||||
await testUtils.dom.click(dropdown.el.querySelector('button'));
|
||||
|
||||
await navigate('ArrowDown'); // Go to next item
|
||||
|
||||
assert.strictEqual(document.activeElement, dropdown.el.querySelector('.o_menu_item a'));
|
||||
assert.containsNone(dropdown, '.o_item_option');
|
||||
|
||||
await navigate('ArrowRight'); // Unfold first item's options (w/ Right)
|
||||
|
||||
assert.strictEqual(document.activeElement, dropdown.el.querySelector('.o_menu_item a'));
|
||||
assert.containsN(dropdown, '.o_item_option', 2);
|
||||
|
||||
await navigate('ArrowDown'); // Go to next option
|
||||
|
||||
assert.strictEqual(document.activeElement, dropdown.el.querySelector('.o_item_option a'));
|
||||
|
||||
await navigate('ArrowLeft'); // Fold first item's options (w/ Left)
|
||||
|
||||
assert.strictEqual(document.activeElement, dropdown.el.querySelector('.o_menu_item a'));
|
||||
assert.containsNone(dropdown, '.o_item_option');
|
||||
|
||||
await navigate('Enter'); // Unfold first item's options (w/ Enter)
|
||||
|
||||
assert.strictEqual(document.activeElement, dropdown.el.querySelector('.o_menu_item a'));
|
||||
assert.containsN(dropdown, '.o_item_option', 2);
|
||||
|
||||
await navigate('ArrowDown'); // Go to next option
|
||||
await navigate('Escape'); // Fold first item's options (w/ Escape)
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.strictEqual(dropdown.el.querySelector('.o_menu_item a'), document.activeElement);
|
||||
assert.containsNone(dropdown, '.o_item_option');
|
||||
|
||||
await navigate('Escape', true); // Close the dropdown
|
||||
|
||||
assert.containsNone(dropdown, '.o-dropdown-menu', "Dropdown should be folded");
|
||||
});
|
||||
|
||||
QUnit.test('interactions between multiple dropdowns', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
const items = this.items;
|
||||
class Parent extends LegacyComponent {
|
||||
setup() {
|
||||
this.state = useState({ items });
|
||||
}
|
||||
}
|
||||
Parent.components = { DropdownMenu };
|
||||
Parent.template = xml`
|
||||
<div>
|
||||
<DropdownMenu title="'First'" items="state.items"/>
|
||||
<DropdownMenu title="'Second'" items="state.items"/>
|
||||
</div>`;
|
||||
const env = makeTestEnvironment();
|
||||
const target = testUtils.prepareTarget();
|
||||
|
||||
const parent = await mount(Parent, target, { env });
|
||||
|
||||
const [menu1, menu2] = parent.el.querySelectorAll('.dropdown');
|
||||
|
||||
assert.containsNone(parent, '.o-dropdown-menu');
|
||||
|
||||
await testUtils.dom.click(menu1.querySelector('button'));
|
||||
|
||||
assert.containsOnce(parent, '.o-dropdown-menu');
|
||||
const [first, second] = parent.el.querySelectorAll(".dropdown");
|
||||
assert.containsOnce(first, '.o-dropdown-menu');
|
||||
|
||||
await testUtils.dom.click(menu2.querySelector('button'));
|
||||
|
||||
assert.containsOnce(parent, '.o-dropdown-menu');
|
||||
assert.containsOnce(second, '.o-dropdown-menu');
|
||||
|
||||
await testUtils.dom.click(menu2.querySelector('.o_menu_item a'));
|
||||
await testUtils.dom.click(menu1.querySelector('button'));
|
||||
|
||||
assert.containsOnce(parent, '.o-dropdown-menu');
|
||||
assert.containsOnce(first, '.o-dropdown-menu');
|
||||
});
|
||||
|
||||
QUnit.test("dropdown doesn't get close on mousedown inside and mouseup outside dropdown", async function (assert) {
|
||||
// In this test, we simulate a case where the user clicks inside a dropdown menu item
|
||||
// (e.g. in the input of the 'Save current search' item in the Favorites menu), keeps
|
||||
// the click pressed, moves the cursor outside the dropdown and releases the click
|
||||
// (i.e. mousedown and focus inside the item, mouseup and click outside the dropdown).
|
||||
// In this case, we want to keep the dropdown menu open.
|
||||
assert.expect(5);
|
||||
|
||||
const items = this.items;
|
||||
class Parent extends LegacyComponent {
|
||||
setup() {
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
Parent.components = { DropdownMenu };
|
||||
Parent.template = xml`
|
||||
<div>
|
||||
<DropdownMenu title="'First'" items="items"/>
|
||||
</div>`;
|
||||
const env = makeTestEnvironment();
|
||||
const target = testUtils.prepareTarget();
|
||||
|
||||
const parent = await mount(Parent, target, { env });
|
||||
|
||||
const menu = parent.el.querySelector(".dropdown");
|
||||
assert.doesNotHaveClass(menu, "show", "dropdown should not be open");
|
||||
|
||||
await testUtils.dom.click(menu.querySelector("button"));
|
||||
assert.hasClass(menu, "show", "dropdown should be open");
|
||||
|
||||
const firstItemEl = menu.querySelector(".o_menu_item > a");
|
||||
// open options menu
|
||||
await testUtils.dom.click(firstItemEl);
|
||||
assert.hasClass(firstItemEl.querySelector("i"), "o_icon_right fa fa-caret-down");
|
||||
|
||||
// force the focus inside the dropdown item and click outside
|
||||
firstItemEl.parentElement.querySelector(".o_menu_item_options .o_item_option a").focus();
|
||||
await testUtils.dom.triggerEvents(parent.el, "click");
|
||||
assert.hasClass(menu, "show", "dropdown should still be open");
|
||||
assert.hasClass(firstItemEl.querySelector("i"), "o_icon_right fa fa-caret-down");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
odoo.define('web.pager_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const Pager = require('web.Pager');
|
||||
const testUtils = require('web.test_utils');
|
||||
const { LegacyComponent } = require("@web/legacy/legacy_component");
|
||||
|
||||
const { createComponent } = testUtils;
|
||||
|
||||
const { xml, useState } = owl;
|
||||
|
||||
class PagerController extends LegacyComponent {
|
||||
setup() {
|
||||
this.state = useState({ ...this.props });
|
||||
}
|
||||
async updateProps(nextProps) {
|
||||
Object.assign(this.state, nextProps);
|
||||
await testUtils.nextTick();
|
||||
}
|
||||
}
|
||||
PagerController.template = xml`<Pager t-props="state" />`;
|
||||
PagerController.components = { Pager };
|
||||
|
||||
QUnit.module('Components', {}, function () {
|
||||
|
||||
QUnit.module('Legacy Pager');
|
||||
|
||||
QUnit.test('basic interactions', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pager = await createComponent(PagerController, {
|
||||
props: {
|
||||
currentMinimum: 1,
|
||||
limit: 4,
|
||||
size: 10,
|
||||
onPagerChanged: function (detail) {
|
||||
pager.updateProps(detail);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(testUtils.controlPanel.getPagerValue(pager), "1-4",
|
||||
"currentMinimum should be set to 1");
|
||||
|
||||
await testUtils.controlPanel.pagerNext(pager);
|
||||
|
||||
assert.strictEqual(testUtils.controlPanel.getPagerValue(pager), "5-8",
|
||||
"currentMinimum should now be 5");
|
||||
});
|
||||
|
||||
QUnit.test('edit the pager', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const pager = await createComponent(PagerController, {
|
||||
props: {
|
||||
currentMinimum: 1,
|
||||
limit: 4,
|
||||
size: 10,
|
||||
onPagerChanged: function (detail) {
|
||||
pager.updateProps(detail);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.dom.click(pager.el.querySelector('.o_pager_value'));
|
||||
|
||||
assert.containsOnce(pager, 'input',
|
||||
"the pager should contain an input");
|
||||
assert.strictEqual(testUtils.controlPanel.getPagerValue(pager), "1-4",
|
||||
"the input should have correct value");
|
||||
|
||||
// change the limit
|
||||
await testUtils.controlPanel.setPagerValue(pager, "1-6");
|
||||
|
||||
assert.containsNone(pager, 'input',
|
||||
"the pager should not contain an input anymore");
|
||||
assert.strictEqual(testUtils.controlPanel.getPagerValue(pager), "1-6",
|
||||
"the limit should have been updated");
|
||||
});
|
||||
|
||||
QUnit.test("keydown on pager with same value", async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
const pager = await createComponent(PagerController, {
|
||||
props: {
|
||||
currentMinimum: 1,
|
||||
limit: 4,
|
||||
size: 10,
|
||||
onPagerChanged: () => assert.step("pager-changed"),
|
||||
},
|
||||
});
|
||||
|
||||
// Enter edit mode
|
||||
await testUtils.dom.click(pager.el.querySelector('.o_pager_value'));
|
||||
|
||||
assert.containsOnce(pager, "input");
|
||||
assert.strictEqual(testUtils.controlPanel.getPagerValue(pager), "1-4");
|
||||
assert.verifySteps([]);
|
||||
|
||||
// Exit edit mode
|
||||
await testUtils.dom.triggerEvent(pager.el.querySelector('input'), "keydown", { key: "Enter" });
|
||||
|
||||
assert.containsNone(pager, "input");
|
||||
assert.strictEqual(testUtils.controlPanel.getPagerValue(pager), "1-4");
|
||||
assert.verifySteps(["pager-changed"]);
|
||||
});
|
||||
|
||||
QUnit.test('pager value formatting', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
const pager = await createComponent(PagerController, {
|
||||
props: {
|
||||
currentMinimum: 1,
|
||||
limit: 4,
|
||||
size: 10,
|
||||
onPagerChanged: (detail) => {
|
||||
pager.updateProps(detail);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(testUtils.controlPanel.getPagerValue(pager), "1-4", "Initial value should be correct");
|
||||
|
||||
async function inputAndAssert(input, expected, reason) {
|
||||
await testUtils.controlPanel.setPagerValue(pager, input);
|
||||
assert.strictEqual(testUtils.controlPanel.getPagerValue(pager), expected,
|
||||
`Pager value should be "${expected}" when given "${input}": ${reason}`);
|
||||
}
|
||||
|
||||
await inputAndAssert("4-4", "4", "values are squashed when minimum = maximum");
|
||||
await inputAndAssert("1-11", "1-10", "maximum is floored to size when out of range");
|
||||
await inputAndAssert("20-15", "10", "combination of the 2 assertions above");
|
||||
await inputAndAssert("6-5", "10", "fallback to previous value when minimum > maximum");
|
||||
await inputAndAssert("definitelyValidNumber", "10", "fallback to previous value if not a number");
|
||||
await inputAndAssert(" 1 , 2 ", "1-2", "value is normalized and accepts several separators");
|
||||
await inputAndAssert("3 8", "3-8", "value accepts whitespace(s) as a separator");
|
||||
});
|
||||
|
||||
QUnit.test('pager disabling', async function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
const reloadPromise = testUtils.makeTestPromise();
|
||||
const pager = await createComponent(PagerController, {
|
||||
props: {
|
||||
currentMinimum: 1,
|
||||
limit: 4,
|
||||
size: 10,
|
||||
// The goal here is to test the reactivity of the pager; in a
|
||||
// typical views, we disable the pager after switching page
|
||||
// to avoid switching twice with the same action (double click).
|
||||
onPagerChanged: async function (detail) {
|
||||
// 1. Simulate a (long) server action
|
||||
await reloadPromise;
|
||||
// 2. Update the view with loaded data
|
||||
pager.updateProps(detail);
|
||||
},
|
||||
},
|
||||
});
|
||||
const pagerButtons = pager.el.querySelectorAll('button');
|
||||
|
||||
// Click and check button is disabled
|
||||
await testUtils.controlPanel.pagerNext(pager);
|
||||
assert.ok(pager.el.querySelector('button.o_pager_next').disabled);
|
||||
// Try to edit the pager value
|
||||
await testUtils.dom.click(pager.el.querySelector('.o_pager_value'));
|
||||
|
||||
assert.strictEqual(pagerButtons.length, 2, "the two buttons should be displayed");
|
||||
assert.ok(pagerButtons[0].disabled, "'previous' is disabled");
|
||||
assert.ok(pagerButtons[1].disabled, "'next' is disabled");
|
||||
assert.strictEqual(pager.el.querySelector('.o_pager_value').tagName, 'SPAN',
|
||||
"pager edition is prevented");
|
||||
|
||||
// Server action is done
|
||||
reloadPromise.resolve();
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.strictEqual(pagerButtons.length, 2, "the two buttons should be displayed");
|
||||
assert.notOk(pagerButtons[0].disabled, "'previous' is enabled");
|
||||
assert.notOk(pagerButtons[1].disabled, "'next' is enabled");
|
||||
assert.strictEqual(testUtils.controlPanel.getPagerValue(pager), "5-8", "value has been updated");
|
||||
|
||||
await testUtils.dom.click(pager.el.querySelector('.o_pager_value'));
|
||||
|
||||
assert.strictEqual(pager.el.querySelector('.o_pager_value').tagName, 'INPUT',
|
||||
"pager edition is re-enabled");
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue