Odoo18-Base/addons/web/static/tests/views/fields/daterange_field_tests.js
2025-03-10 10:52:11 +07:00

1357 lines
51 KiB
JavaScript

/** @odoo-module **/
import { getPickerCell, getTimePickers } from "@web/../tests/core/datetime/datetime_test_helpers";
import {
click,
clickSave,
editInput,
getFixture,
makeDeferred,
nextTick,
patchDate,
patchTimeZone,
triggerEvent,
triggerScroll,
} from "@web/../tests/helpers/utils";
import { pagerNext } from "@web/../tests/search/helpers";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
const getInputs = () => target.querySelectorAll(".o_field_daterange input");
/**
* @param {HTMLElement} el
*/
const isHiddenByCSS = (el) => {
const style = getComputedStyle(el);
return style.visibility === "hidden" || style.opacity === "0";
};
let serverData;
let target;
QUnit.module("Fields", (hooks) => {
hooks.beforeEach(() => {
target = getFixture();
serverData = {
models: {
partner: {
fields: {
date: { string: "A date", type: "date", searchable: true },
datetime: { string: "A datetime", type: "datetime", searchable: true },
datetime_end: { string: "Datetime End", type: "datetime" },
bool_field: { string: "A boolean", type: "boolean" },
},
records: [
{
id: 1,
date: "2017-02-03",
datetime: "2017-02-08 10:00:00",
},
],
},
},
};
setupViewRegistries();
// Date field should not have an offset as they are ignored.
// However, in the test environement, a UTC timezone is set to run all tests. And if any code does not use the safe timezone method
// provided by the framework (which happens in this case inside the date range picker lib), unexpected behavior kicks in as the timezone
// of the dev machine collides with the timezone set by the test env.
// To avoid failing test on dev's local machines, a hack is to apply an timezone offset greater than the difference between UTC and the dev's
// machine timezone. For belgium, > 60 is enough. For India, > 5h30 is required, hence 330.
patchTimeZone(330);
});
QUnit.module("DateRangeField");
QUnit.test("Datetime field - interaction with the datepicker", async (assert) => {
serverData.models.partner.records[0].datetime_end = "2017-03-13 00:00:00";
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
});
// Check date range picker initialization
assert.containsOnce(getFixture(), ".o_field_daterange");
assert.containsNone(getFixture(), ".o_datetime_picker");
// open the first one
const daterange = target.querySelector(".o_field_daterange");
await click(daterange.querySelector("input[data-field=datetime]"));
let datepicker = document.querySelector(".o_datetime_picker");
assert.isVisible(datepicker, "first date range picker should be opened");
assert.strictEqual(
datepicker.querySelector(".o_date_item_cell.o_select_start").textContent,
"8",
"active start date should be '8' in date range picker"
);
let [hourSelectStart, minuteSelectStart] = getTimePickers().at(0);
assert.strictEqual(
hourSelectStart.value,
"15",
"active start date hour should be '15' in date range picker"
);
assert.strictEqual(
minuteSelectStart.value,
"30",
"active start date minute should be '30' in date range picker"
);
assert.strictEqual(
datepicker.querySelector(".o_date_item_cell.o_select_end").textContent,
"13",
"active end date should be '13' in date range picker"
);
let [hourSelectEnd, minuteSelectEnd] = getTimePickers().at(1);
assert.strictEqual(
hourSelectEnd.value,
"5",
"active end date hour should be '5' in date range picker"
);
assert.strictEqual(
minuteSelectEnd.value,
"30",
"active end date minute should be '30' in date range picker"
);
assert.containsN(
minuteSelectStart,
"option",
12,
"minute selection should contain 12 options (1 for each 5 minutes)"
);
// Close picker
await click(document.querySelector(".o_form_view_container"));
assert.containsNone(getFixture(), ".o_datetime_picker", "datepicker should be closed");
// Try to check with end date
await click(daterange.querySelector("input[data-field=datetime_end]"));
datepicker = document.querySelector(".o_datetime_picker");
assert.isVisible(datepicker, "first date range picker should be opened");
assert.strictEqual(
datepicker.querySelector(".o_date_item_cell.o_select_start").textContent,
"8",
"active start date should be '8' in date range picker"
);
[hourSelectStart, minuteSelectStart] = getTimePickers().at(0);
assert.strictEqual(
hourSelectStart.value,
"15",
"active start date hour should be '15' in date range picker"
);
assert.strictEqual(
minuteSelectStart.value,
"30",
"active start date minute should be '30' in date range picker"
);
assert.strictEqual(
datepicker.querySelector(".o_date_item_cell.o_select_end").textContent,
"13",
"active end date should be '13' in date range picker"
);
[hourSelectEnd, minuteSelectEnd] = getTimePickers().at(1);
assert.strictEqual(
hourSelectEnd.value,
"5",
"active end date hour should be '5' in date range picker"
);
assert.strictEqual(
minuteSelectEnd.value,
"30",
"active end date minute should be '30' in date range picker"
);
assert.containsN(
minuteSelectStart,
"option",
12,
"minute selection should contain 12 options (1 for each 5 minutes)"
);
// Select a new range and check that inputs are updated
await click(getPickerCell("8").at(0)); // 02/08/2017
await click(getPickerCell("9").at(0)); // 02/09/2017
assert.equal(
target.querySelector("input[data-field=datetime]").value,
"02/08/2017 15:30:00"
);
assert.equal(
target.querySelector("input[data-field=datetime_end]").value,
"02/09/2017 05:30:00"
);
// Save
await clickSave(target);
// Check date after save
assert.strictEqual(
target.querySelector("input[data-field=datetime]").value,
"02/08/2017 15:30:00"
);
assert.strictEqual(
target.querySelector("input[data-field=datetime_end]").value,
"02/09/2017 05:30:00"
);
});
QUnit.test("Date field - interaction with the datepicker", async (assert) => {
serverData.models.partner.fields.date_end = { string: "Date End", type: "date" };
serverData.models.partner.records[0].date_end = "2017-02-08";
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="date" widget="daterange" options="{'end_date_field': 'date_end'}"/>
</form>`,
});
// Check date range picker initialization
assert.containsOnce(getFixture(), ".o_field_daterange");
assert.containsNone(getFixture(), ".o_datetime_picker");
// open the first one
await click(target.querySelector("input[data-field=date]"));
let datepicker = document.querySelector(".o_datetime_picker");
assert.isVisible(datepicker, "first date range picker should be opened");
assert.strictEqual(
datepicker.querySelector(".o_select_start").textContent,
"3",
"active start date should be '3' in date range picker"
);
assert.strictEqual(
datepicker.querySelector(".o_select_end").textContent,
"8",
"active end date should be '8' in date range picker"
);
// Change date
await click(getPickerCell("12").at(1));
await click(getPickerCell("16").at(0));
// Close picker
await click(document.querySelector(".o_form_view"));
// Check date after change
assert.isNotVisible(datepicker, "date range picker should be closed");
assert.strictEqual(
target.querySelector("input[data-field=date]").value,
"02/16/2017",
"the date should be '02/16/2017'"
);
assert.strictEqual(
target.querySelector("input[data-field=date_end]").value,
"03/12/2017",
"'the date should be '03/12/2017'"
);
// Try to change range with end date
await click(target.querySelector("input[data-field=date_end]"));
datepicker = document.querySelector(".o_datetime_picker");
assert.isVisible(datepicker, "date range picker should be opened");
assert.strictEqual(
datepicker.querySelector(".o_select_start").textContent,
"16",
"start date should be a 16 in date range picker"
);
assert.strictEqual(
datepicker.querySelector(".o_select_end").textContent,
"12",
"end date should be a 12 in date range picker"
);
// Change date
await click(getPickerCell("13").at(0));
await click(getPickerCell("18").at(1));
// Close picker
await click(document.querySelector(".o_form_view"));
// Check date after change
assert.isNotVisible(datepicker, "date range picker should be closed");
assert.strictEqual(
target.querySelector("input[data-field=date]").value,
"02/13/2017",
"the start date should be '02/13/2017'"
);
assert.strictEqual(
target.querySelector("input[data-field=date_end]").value,
"03/18/2017",
"the end date should be '03/18/2017'"
);
// Save
await clickSave(target);
// Check date after save
assert.strictEqual(
target.querySelector("input[data-field=date]").value,
"02/13/2017",
"the start date should be '02/13/2017' after save"
);
assert.strictEqual(
target.querySelector("input[data-field=date_end]").value,
"03/18/2017",
"the end date should be '03/18/2017' after save"
);
});
QUnit.test(
"date picker should still be present when scrolling outside of it",
async (assert) => {
serverData.models.partner.records[0].datetime_end = "2017-03-13 00:00:00";
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
});
await click(target.querySelector("input[data-field=datetime]"));
assert.isVisible(
document.querySelector(".o_datetime_picker"),
"date range picker should be opened"
);
await triggerScroll(target, { top: 50 });
assert.isVisible(
document.querySelector(".o_datetime_picker"),
"date range picker should still be opened"
);
}
);
QUnit.test("DateRangeField with label opens datepicker on click", async (assert) => {
serverData.models.partner.fields.date_end = { string: "Date End", type: "date" };
serverData.models.partner.records[0].date_end = "2017-02-08";
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<label for="date" string="Daterange" />
<field name="date" widget="daterange" options="{'end_date_field': 'date_end'}"/>
</form>`,
});
await click(target.querySelector("label.o_form_label"));
assert.isVisible(
document.querySelector(".o_datetime_picker"),
"date range picker should be opened"
);
});
QUnit.test(
"Datetime field manually input value should send utc value to server",
async (assert) => {
assert.expect(4);
serverData.models.partner.records[0].datetime_end = "2017-03-13 00:00:00";
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
resId: 1,
mockRPC(route, args) {
if (args.method === "web_save") {
assert.deepEqual(args.args[1], {
datetime: "2017-02-08 06:00:00",
datetime_end: "2017-03-13 00:00:00",
});
}
},
});
// check date display correctly in readonly
assert.strictEqual(
target.querySelector(".o_field_daterange input").value,
"02/08/2017 15:30:00",
"the start date should be correctly displayed in readonly"
);
assert.strictEqual(
target.querySelectorAll(".o_field_daterange input")[1].value,
"03/13/2017 05:30:00",
"the end date should be correctly displayed in readonly"
);
// update input for Datetime
await editInput(target, "input[data-field=datetime]", "02/08/2017 11:30:00");
// save form
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_field_daterange input").value,
"02/08/2017 11:30:00",
"the start date should be correctly displayed after manual update"
);
}
);
QUnit.test("Daterange field keyup should not erase end date", async (assert) => {
serverData.models.partner.records[0].datetime_end = "2017-03-13 00:00:00";
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
resId: 1,
});
// check date display correctly in readonly
assert.strictEqual(
target.querySelector(".o_field_daterange input").value,
"02/08/2017 15:30:00",
"the start date should be correctly displayed"
);
assert.strictEqual(
target.querySelectorAll(".o_field_daterange input")[1].value,
"03/13/2017 05:30:00",
"the end date should be correctly displayed"
);
// reveal the o_datetime_picker
await click(target.querySelector("input[data-field=datetime]"));
// the keyup event should not be handled by o_datetime_picker
await triggerEvent(target, "input[data-field=datetime]", "keyup", {
key: "ArrowLeft",
});
assert.strictEqual(
target.querySelector(".o_field_daterange input").value,
"02/08/2017 15:30:00",
"the start date should be correctly displayed after onkeyup"
);
assert.strictEqual(
target.querySelectorAll(".o_field_daterange input")[1].value,
"03/13/2017 05:30:00",
"the end date should be correctly displayed after onkeyup"
);
});
// TODO: check if this test is still relevant
// QUnit.test(
// "DateRangeField manually input wrong value should show toaster",
// async (assert)=> {
// serverData.models.partner.fields.date_end = { string: "Date End", type: "date" };
// serverData.models.partner.records[0].date_end = "2017-02-08";
// await makeView({
// type: "form",
// resModel: "partner",
// serverData,
// arch: `
// <form>
// <field name="date" widget="daterange" options="{'end_date_field': 'date_end'}"/>
// </form>`,
// resId: 1,
// });
// await editInput(target, "input[data-field=date]", "blabla");
// // click outside daterange field
// await click(target);
// assert.hasClass(
// target.querySelector(".o_field_daterange"),
// "o_field_invalid",
// "date field should be displayed as invalid"
// );
// // update input date with right value
// await editInput(target, "input[data-field=date]", "02/08/2017");
// assert.doesNotHaveClass(
// target.querySelector(".o_field_daterange[name='date']"),
// "o_field_invalid",
// "date field should not be displayed as invalid now"
// );
// // again enter wrong value and try to save should raise invalid fields value
// await editInput(target, "input[data-field=date]", "blabla");
// await clickSave(target);
// assert.strictEqual(
// target.querySelector(".o_notification_title").textContent,
// "Invalid fields: "
// );
// assert.strictEqual(
// target.querySelector(".o_notification_content").innerHTML,
// "<ul><li>A date</li></ul>"
// );
// assert.hasClass(target.querySelector(".o_notification"), "border-danger");
// }
// );
QUnit.test("Render with initial empty value: date field", async (assert) => {
// 2014-08-14 12:34:56 -> the day E. Zuckerman, who invented pop-up ads, has apologised.
patchDate(2014, 7, 14, 12, 34, 56);
serverData.models.partner.fields.date_end = { string: "Date End", type: "date" };
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="date" widget="daterange" options="{'end_date_field': 'date_end'}"/>
</form>`,
});
await click(target, "input[data-field=date]");
assert.containsOnce(target, ".o_datetime_picker", "check that the datepicker is opened");
// Select a value (today)
await click(target.querySelector(".o_today"));
assert.strictEqual(
target.querySelectorAll(".o_field_daterange input")[0].value,
"08/14/2014",
"start date should be set properly"
);
// Add an end date
await click(target.querySelector(".o_add_date"));
assert.strictEqual(
target.querySelectorAll(".o_field_daterange input")[0].value,
target.querySelectorAll(".o_field_daterange input")[1].value,
"the end date should be set to the same value as the start date"
);
});
QUnit.test("Render with initial empty value: datetime field", async (assert) => {
// 2014-08-14 12:34:56 -> the day E. Zuckerman, who invented pop-up ads, has apologised.
patchDate(2014, 7, 14, 12, 34, 56);
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
});
await click(target, "input[data-field=datetime]");
assert.containsOnce(target, ".o_datetime_picker", "check that the datepicker is opened");
assert.containsNone(target, ".o_add_date");
// Select a value (today)
await click(target.querySelector(".o_today"));
assert.strictEqual(
target.querySelectorAll(".o_field_daterange input")[0].value,
"08/14/2014 12:00:00",
"start date should be set properly"
);
assert.notOk(isHiddenByCSS(target.querySelector(".o_add_date")));
assert.strictEqual(
target.querySelector(".o_add_date").innerText.trim().toLowerCase(),
"add end date"
);
// Add an end date
await click(target.querySelector(".o_add_date"));
const [startInput, endInput] = target.querySelectorAll(".o_field_daterange input");
assert.strictEqual(
startInput.value,
endInput.value,
"the end date should be set to the same value as the start date"
);
});
QUnit.test("Render with initial empty value and optional start date", async (assert) => {
patchDate(2014, 7, 14, 12, 34, 56);
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="datetime_end" widget="daterange" options="{'start_date_field': 'datetime'}"/>
</form>`,
});
await click(target, "input[data-field=datetime_end]");
assert.containsOnce(target, ".o_datetime_picker", "check that the datepicker is opened");
assert.containsNone(target, ".o_add_date");
// Select a value (today)
await click(target.querySelector(".o_today"));
assert.strictEqual(
target.querySelectorAll(".o_field_daterange input")[0].value,
"08/14/2014 12:00:00",
"end date should be set properly"
);
assert.notOk(isHiddenByCSS(target.querySelector(".o_add_date")));
assert.strictEqual(
target.querySelector(".o_add_date").innerText.trim().toLowerCase(),
"add start date"
);
// Add an end date
await click(target.querySelector(".o_add_date"));
const [startInput, endInput] = target.querySelectorAll(".o_field_daterange input");
assert.strictEqual(
startInput.value,
endInput.value,
"the end date should be set to the same value as the start date"
);
});
QUnit.test("initial empty date with optional start date", async (assert) => {
patchDate(2014, 7, 14, 12, 34, 56);
serverData.models.partner.records[0].datetime = "2017-03-13 00:00:00";
serverData.models.partner.records[0].datetime_end = false;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="datetime_end" widget="daterange" options="{'start_date_field': 'datetime'}"/>
</form>`,
resId: 1,
});
assert.ok(isHiddenByCSS(target.querySelector(".o_add_date")));
target.querySelector(".o_field_daterange input").focus();
await nextTick();
assert.notOk(isHiddenByCSS(target.querySelector(".o_add_date")));
assert.containsNone(target, ".o_datetime_picker");
assert.strictEqual(
target.querySelector(".o_add_date").innerText.trim().toLowerCase(),
"add end date"
);
// Add an end date
await click(target.querySelector(".o_add_date"));
const [startInput, endInput] = target.querySelectorAll(".o_field_daterange input");
assert.containsOnce(target, ".o_datetime_picker");
assert.strictEqual(
startInput.value,
endInput.value,
"the end date should be set to the same value as the start date"
);
});
QUnit.test("initial empty date with optional end date", async (assert) => {
// 2014-08-14 12:34:56 -> the day E. Zuckerman, who invented pop-up ads, has apologised.
patchDate(2014, 7, 14, 12, 34, 56);
serverData.models.partner.records[0].datetime = false;
serverData.models.partner.records[0].datetime_end = "2017-03-13 00:00:00";
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
resId: 1,
});
assert.ok(isHiddenByCSS(target.querySelector(".o_add_date")));
target.querySelector(".o_field_daterange input").focus();
await nextTick();
assert.notOk(isHiddenByCSS(target.querySelector(".o_add_date")));
assert.strictEqual(
target.querySelector(".o_add_date").innerText.trim().toLowerCase(),
"add start date"
);
// Add a start date
await click(target.querySelector(".o_add_date"));
const [startInput, endInput] = target.querySelectorAll(".o_field_daterange input");
assert.strictEqual(
startInput.value,
endInput.value,
"the start date should be set to the same value as the end date"
);
});
QUnit.test("Datetime field - open datepicker and switch page", async (assert) => {
serverData.models.partner.records[0].datetime_end = "2017-03-13 00:00:00";
serverData.models.partner.records.push({
id: 2,
date: "2017-03-04",
datetime: "2017-03-10 11:00:00",
datetime_end: "2017-04-15 00:00:00",
});
await makeView({
type: "form",
resModel: "partner",
resId: 1,
resIds: [1, 2],
serverData,
arch: `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
});
// Check date range picker initialization
assert.containsOnce(getFixture(), ".o_field_daterange");
assert.containsNone(getFixture(), ".o_datetime_picker");
// open datepicker
await click(target.querySelector("input[data-field=datetime]"));
let datepicker = document.querySelector(".o_datetime_picker");
assert.isVisible(datepicker, "date range picker should be opened");
// Start date: id=1
assert.strictEqual(
datepicker.querySelector(".o_select_start").textContent,
"8",
"active start date should be '8' in date range picker"
);
let [hourSelectStart, minuteSelectStart] = getTimePickers().at(0);
assert.strictEqual(
hourSelectStart.value,
"15",
"active start date hour should be '15' in date range picker"
);
assert.strictEqual(
minuteSelectStart.value,
"30",
"active start date minute should be '30' in date range picker"
);
// End date: id=1
assert.strictEqual(
datepicker.querySelector(".o_select_end").textContent,
"13",
"active end date should be '13' in date range picker"
);
let [hourSelectEnd, minuteSelectEnd] = getTimePickers().at(1);
assert.strictEqual(
hourSelectEnd.value,
"5",
"active end date hour should be '5' in date range picker"
);
assert.strictEqual(
minuteSelectEnd.value,
"30",
"active end date minute should be '30' in date range picker"
);
// Close picker
await click(document.querySelector(".o_form_view"));
assert.isNotVisible(datepicker, "date range picker should be closed");
await pagerNext(target);
// Check date range picker initialization
assert.containsOnce(getFixture(), ".o_field_daterange");
assert.containsNone(getFixture(), ".o_datetime_picker");
// open date range picker
await click(target.querySelector("input[data-field=datetime]"));
datepicker = document.querySelector(".o_datetime_picker");
assert.isVisible(datepicker, "first date range picker should be opened");
// Start date: id=2
assert.strictEqual(
datepicker.querySelector(".o_select_start").textContent,
"10",
"active start date should be '10' in date range picker"
);
[hourSelectStart, minuteSelectStart] = getTimePickers().at(0);
assert.strictEqual(
hourSelectStart.value,
"16",
"active start date hour should be '16' in date range picker"
);
assert.strictEqual(
minuteSelectStart.value,
"30",
"active start date minute should be '30' in date range picker"
);
// End date id=2
assert.strictEqual(
datepicker.querySelector(".o_select_end").textContent,
"15",
"active end date should be '15' in date range picker"
);
[hourSelectEnd, minuteSelectEnd] = getTimePickers().at(1);
assert.strictEqual(
hourSelectEnd.value,
"5",
"active end date hour should be '5' in date range picker"
);
assert.strictEqual(
minuteSelectEnd.value,
"30",
"active end date minute should be '30' in date range picker"
);
});
QUnit.test("related end date, both start date and end date empty", async (assert) => {
serverData.models.partner.records[0].datetime = false;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "");
assert.containsNone(target, ".o_add_date");
await editInput(getInputs()[0], null, "06/06/2023 12:00:00");
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "06/06/2023 12:00:00");
assert.strictEqual(target.querySelector(".o_add_date").textContent.trim(), "Add end date");
await click(target, ".o_add_date");
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "06/06/2023 12:00:00");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[0].value, "06/06/2023 12:00:00");
assert.containsNone(target, ".o_add_date");
});
QUnit.test("required: related end date, both start date and end date empty", async (assert) => {
serverData.models.partner.records[0].datetime = false;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="bool_field"/>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}" required="bool_field"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "");
assert.containsNone(target, ".o_add_date");
await click(target, ".o_field_boolean input");
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[1].value, "");
assert.containsNone(target, ".o_add_date");
await editInput(getInputs()[0], null, "06/06/2023 12:00:00");
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "06/06/2023 12:00:00");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[1].value, "");
assert.containsNone(target, ".o_add_date");
await editInput(getInputs()[1], null, "07/07/2023 13:00:00");
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "06/06/2023 12:00:00");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[1].value, "07/07/2023 13:00:00");
assert.containsNone(target, ".o_add_date");
await editInput(getInputs()[0], null, "");
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[1].value, "07/07/2023 13:00:00");
assert.containsNone(target, ".o_add_date");
// Open the picker, this checks that props validation for the picker isn't
// broken by required being present
await click(getInputs()[0]);
});
QUnit.test("related start date, both start date and end date empty", async (assert) => {
serverData.models.partner.records[0].datetime = false;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime_end" widget="daterange" options="{'start_date_field': 'datetime'}"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime_end");
assert.strictEqual(getInputs()[0].value, "");
assert.containsNone(target, ".o_add_date");
});
QUnit.test("related end date, start date set and end date empty", async (assert) => {
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(target.querySelector(".o_add_date").textContent.trim(), "Add end date");
});
QUnit.test("related start date, start date set and end date empty", async (assert) => {
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime_end" widget="daterange" options="{'start_date_field': 'datetime'}"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(target.querySelector(".o_add_date").textContent.trim(), "Add end date");
});
QUnit.test("related end date, start date empty and end date set", async (assert) => {
const recordData = serverData.models.partner.records[0];
recordData.datetime_end = recordData.datetime;
recordData.datetime = false;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime_end");
assert.strictEqual(
target.querySelector(".o_add_date").textContent.trim(),
"Add start date"
);
});
QUnit.test("related start date, start date empty and end date set", async (assert) => {
const recordData = serverData.models.partner.records[0];
recordData.datetime_end = recordData.datetime;
recordData.datetime = false;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime_end" widget="daterange" options="{'start_date_field': 'datetime'}"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime_end");
assert.strictEqual(
target.querySelector(".o_add_date").textContent.trim(),
"Add start date"
);
});
QUnit.test("related end date, both start date and end date set", async (assert) => {
const recordData = serverData.models.partner.records[0];
recordData.datetime_end = recordData.datetime;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.containsNone(target, ".o_add_date");
});
QUnit.test("related start date, both start date and end date set", async (assert) => {
const recordData = serverData.models.partner.records[0];
recordData.datetime_end = recordData.datetime;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime_end" widget="daterange" options="{'start_date_field': 'datetime'}"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "02/08/2017 15:30:00");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[0].value, "02/08/2017 15:30:00");
assert.containsNone(target, ".o_add_date");
await editInput(getInputs()[0], null, "");
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime_end");
assert.strictEqual(getInputs()[0].value, "02/08/2017 15:30:00");
assert.strictEqual(
target.querySelector(".o_add_date").textContent.trim(),
"Add start date"
);
await editInput(getInputs()[0], null, "");
assert.strictEqual(getInputs().length, 1);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime_end");
assert.strictEqual(getInputs()[0].value, "");
assert.containsNone(target, ".o_add_date");
});
QUnit.test("related start date, required, both start date and end date set", async (assert) => {
serverData.models.partner.fields.date_end = { string: "Some Date", type: "date" };
const [firstRecord] = serverData.models.partner.records;
firstRecord.date_end = firstRecord.date;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="date" widget="daterange" options="{'start_date_field': 'date_end'}" required="1" />
</form>`,
resId: 1,
});
assert.strictEqual(getInputs()[0].value, "02/03/2017");
assert.containsOnce(target, ".fa-long-arrow-right");
assert.strictEqual(getInputs()[1].value, "02/03/2017");
});
QUnit.test("list daterange with start date and empty end date", async (assert) => {
serverData.models.partner.fields.date_end = { string: "Some Date", type: "date" };
await makeView({
type: "list",
resModel: "partner",
serverData,
arch: /* xml */ `
<tree>
<field name="date" widget="daterange" options="{'end_date_field': 'date_end'}" />
</tree>`,
});
const arrowIcon = target.querySelector(".fa-long-arrow-right");
const textSiblings = [...arrowIcon.parentNode.childNodes]
.map((node) => {
if (node === arrowIcon) {
return "->";
} else if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue.trim();
} else {
return node.innerText?.trim();
}
})
.filter(Boolean);
assert.deepEqual(textSiblings, ["02/03/2017", "->"]);
});
QUnit.test("list daterange with empty start date and end date", async (assert) => {
serverData.models.partner.fields.date_end = { string: "Some Date", type: "date" };
const [firstRecord] = serverData.models.partner.records;
[firstRecord.date, firstRecord.date_end] = [firstRecord.date_end, firstRecord.date];
await makeView({
type: "list",
resModel: "partner",
serverData,
arch: /* xml */ `
<tree>
<field name="date" widget="daterange" options="{'end_date_field': 'date_end'}" />
</tree>`,
});
const arrowIcon = target.querySelector(".fa-long-arrow-right");
const textSiblings = [...arrowIcon.parentNode.childNodes]
.map((node) => {
if (node === arrowIcon) {
return "->";
} else if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue.trim();
} else {
return node.innerText?.trim();
}
})
.filter(Boolean);
assert.deepEqual(textSiblings, ["->", "02/03/2017"]);
});
QUnit.test(
"always range: related end date, both start date and end date empty",
async (assert) => {
serverData.models.partner.records[0].datetime = false;
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end', 'always_range': '1'}"/>
</form>`,
resId: 1,
});
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[1].value, "");
assert.containsNone(target, ".o_add_date");
await editInput(getInputs()[0], null, "06/06/2023 12:00:00");
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "06/06/2023 12:00:00");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[1].value, "");
assert.containsNone(target, ".o_add_date");
await editInput(getInputs()[1], null, "07/07/2023 13:00:00");
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "06/06/2023 12:00:00");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[1].value, "07/07/2023 13:00:00");
assert.containsNone(target, ".o_add_date");
await editInput(getInputs()[0], null, "");
assert.strictEqual(getInputs().length, 2);
assert.hasAttrValue(getInputs()[0], "data-field", "datetime");
assert.strictEqual(getInputs()[0].value, "");
assert.hasAttrValue(getInputs()[1], "data-field", "datetime_end");
assert.strictEqual(getInputs()[1].value, "07/07/2023 13:00:00");
assert.containsNone(target, ".o_add_date");
}
);
QUnit.test(
"there is no arrow between the dates with option always_range if nothing is set and it is readonly",
async (assert) => {
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: /* xml */ `
<form>
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end', 'always_range': 'true'}" />
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end', 'always_range': 'true'}" readonly="true" />
</form>`,
});
assert.containsOnce(target, ".fa-long-arrow-right");
}
);
QUnit.test("invalid empty date with optional end date", async (assert) => {
serverData.models.partner.fields.date_end = { string: "Date End", type: "date" };
serverData.models.partner.records[0].date_end = "2017-02-08";
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<label for="date" string="Daterange" />
<field name="date" widget="daterange" options="{'end_date_field': 'date_end','always_range': '1'}" string="Planned Date" required="date_end"/>
<field name="date_end" invisible="1" required="date"/>
</form>`,
resId: 1,
});
await editInput(target, "input[data-field=date_end]", "");
await click(target);
assert.hasClass(
target.querySelector(".o_field_daterange"),
"o_field_invalid",
"date field should be displayed as invalid"
);
});
QUnit.test("invalid empty date with optional start date", async (assert) => {
serverData.models.partner.fields.date_end = { string: "Date End", type: "date" };
serverData.models.partner.records[0].date_end = "2017-02-08";
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<label for="date_end" string="Daterange" />
<field name="date" invisible="1" required="date_end"/>
<field name="date_end" widget="daterange" options="{'start_date_field': 'date','always_range': '1'}" string="Planned Date" required="date"/>
</form>`,
resId: 1,
});
await editInput(target, "input[data-field=date]", "");
await click(target);
assert.hasClass(
target.querySelector(".o_field_daterange"),
"o_field_invalid",
"date field should be displayed as invalid"
);
});
QUnit.test("date values are selected eagerly and do not flicker", async (assert) => {
const getValues = () =>
[...target.querySelectorAll(".o_field_datetime input")].map((input) => input.value);
serverData.models.partner.onchanges = {
datetime: () => {},
};
const def = makeDeferred();
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="datetime" options="{'end_date_field': 'datetime_end'}"/>
</form>`,
resId: 1,
async mockRPC(_route, { method }) {
if (method === "onchange") {
await def;
assert.step(method);
}
},
});
await click(target, ".o_field_datetime input");
await click(getPickerCell("19"));
await click(target, ".o_add_date");
await click($(".btn:contains(Apply)")[0]);
assert.deepEqual(getValues(), ["02/19/2017 15:30:00", "02/19/2017 15:30:00"]);
assert.verifySteps([]);
def.resolve();
await nextTick();
assert.deepEqual(getValues(), ["02/19/2017 15:30:00", "02/19/2017 15:30:00"]);
assert.verifySteps(["onchange"]);
});
QUnit.test(
"update the selected input date after removing the existing date",
async (assert) => {
serverData.models.partner.fields.date_end = { string: "Date End", type: "date" };
serverData.models.partner.records[0].date_end = "2017-02-08";
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="date" widget="daterange" options="{'start_date_field': 'date_end'}" required="1" />
</form>`,
});
await click(target, "input[data-field=date]");
await editInput(target, "input[data-field=date]", null);
await click(getPickerCell("12").at(0));
assert.strictEqual(target.querySelector("input[data-field=date]").value, "02/12/2017");
}
);
});