mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 01:12:01 +02:00
vanilla 17.0
This commit is contained in:
parent
d72e748793
commit
a9bcec8e91
1986 changed files with 1613876 additions and 568976 deletions
|
|
@ -34,10 +34,11 @@ QUnit.test("defaults", (assert) => {
|
|||
filtersInfo: {},
|
||||
formViewId: false,
|
||||
hasEditDialog: false,
|
||||
hasQuickCreate: true,
|
||||
quickCreate: true,
|
||||
quickCreateViewId: null,
|
||||
isDateHidden: false,
|
||||
isTimeHidden: false,
|
||||
popoverFields: {},
|
||||
popoverFieldNodes: {},
|
||||
scale: "week",
|
||||
scales: ["day", "week", "month", "year"],
|
||||
showUnusualDays: false,
|
||||
|
|
@ -87,15 +88,33 @@ QUnit.test("hasEditDialog", (assert) => {
|
|||
check(assert, "event_open_popup", "0", "hasEditDialog", false);
|
||||
});
|
||||
|
||||
QUnit.test("hasQuickCreate", (assert) => {
|
||||
check(assert, "quick_add", "", "hasQuickCreate", true);
|
||||
check(assert, "quick_add", "true", "hasQuickCreate", true);
|
||||
check(assert, "quick_add", "True", "hasQuickCreate", true);
|
||||
check(assert, "quick_add", "1", "hasQuickCreate", true);
|
||||
check(assert, "quick_add", "false", "hasQuickCreate", false);
|
||||
check(assert, "quick_add", "False", "hasQuickCreate", false);
|
||||
check(assert, "quick_add", "0", "hasQuickCreate", false);
|
||||
check(assert, "quick_add", "390", "hasQuickCreate", true);
|
||||
QUnit.test("quickCreate", (assert) => {
|
||||
check(assert, "quick_create", "", "quickCreate", true);
|
||||
check(assert, "quick_create", "true", "quickCreate", true);
|
||||
check(assert, "quick_create", "True", "quickCreate", true);
|
||||
check(assert, "quick_create", "1", "quickCreate", true);
|
||||
check(assert, "quick_create", "false", "quickCreate", false);
|
||||
check(assert, "quick_create", "False", "quickCreate", false);
|
||||
check(assert, "quick_create", "0", "quickCreate", false);
|
||||
check(assert, "quick_create", "12", "quickCreate", true);
|
||||
});
|
||||
|
||||
QUnit.test("quickCreateViewId", (assert) => {
|
||||
let arch = parseArch(
|
||||
`<calendar date_start="start_date" quick_create="0" quick_create_view_id="12" />`
|
||||
);
|
||||
assert.strictEqual(arch.quickCreate, false);
|
||||
assert.strictEqual(arch.quickCreateViewId, null);
|
||||
|
||||
arch = parseArch(
|
||||
`<calendar date_start="start_date" quick_create="1" quick_create_view_id="12" />`
|
||||
);
|
||||
assert.strictEqual(arch.quickCreate, true);
|
||||
assert.strictEqual(arch.quickCreateViewId, 12);
|
||||
|
||||
arch = parseArch(`<calendar date_start="start_date" quick_create="1"/>`);
|
||||
assert.strictEqual(arch.quickCreate, true);
|
||||
assert.strictEqual(arch.quickCreateViewId, null);
|
||||
});
|
||||
|
||||
QUnit.test("isDateHidden", (assert) => {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ QUnit.module("CalendarView - CommonPopover", ({ beforeEach }) => {
|
|||
});
|
||||
const dateTimeGroup = target.querySelector(`.list-group`);
|
||||
const dateTimeLabels = dateTimeGroup.textContent.replace(/\s+/g, " ").trim();
|
||||
assert.strictEqual(dateTimeLabels, "July 16, 2021 (All day)");
|
||||
assert.strictEqual(dateTimeLabels, "July 16, 2021");
|
||||
});
|
||||
|
||||
QUnit.test("date duration: is all day and two days duration", async (assert) => {
|
||||
|
|
@ -77,7 +77,7 @@ QUnit.module("CalendarView - CommonPopover", ({ beforeEach }) => {
|
|||
});
|
||||
const dateTimeGroup = target.querySelector(`.list-group`);
|
||||
const dateTimeLabels = dateTimeGroup.textContent.replace(/\s+/g, " ").trim();
|
||||
assert.strictEqual(dateTimeLabels, "July 16-17, 2021 (2 days)");
|
||||
assert.strictEqual(dateTimeLabels, "July 16-17, 2021 2 days");
|
||||
});
|
||||
|
||||
QUnit.test("time duration: 1 hour diff", async (assert) => {
|
||||
|
|
@ -152,10 +152,7 @@ QUnit.module("CalendarView - CommonPopover", ({ beforeEach }) => {
|
|||
});
|
||||
const dateTimeGroup = target.querySelector(`.list-group`);
|
||||
const dateTimeLabels = dateTimeGroup.textContent.replace(/\s+/g, " ").trim();
|
||||
assert.strictEqual(
|
||||
dateTimeLabels,
|
||||
"July 16, 2021 08:00 - 11:15 (3 hours, 15 minutes)"
|
||||
);
|
||||
assert.strictEqual(dateTimeLabels, "July 16, 2021 08:00 - 11:15 (3 hours, 15 minutes)");
|
||||
});
|
||||
|
||||
QUnit.test("isTimeHidden is true", async (assert) => {
|
||||
|
|
@ -177,10 +174,7 @@ QUnit.module("CalendarView - CommonPopover", ({ beforeEach }) => {
|
|||
});
|
||||
const dateTimeGroup = target.querySelector(`.list-group`);
|
||||
const dateTimeLabels = dateTimeGroup.textContent.replace(/\s+/g, " ").trim();
|
||||
assert.strictEqual(
|
||||
dateTimeLabels,
|
||||
"July 16, 2021 08:00 - 11:15 (3 hours, 15 minutes)"
|
||||
);
|
||||
assert.strictEqual(dateTimeLabels, "July 16, 2021 08:00 - 11:15 (3 hours, 15 minutes)");
|
||||
});
|
||||
|
||||
QUnit.test("canDelete is true", async (assert) => {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,9 @@ QUnit.module("CalendarView - CommonRenderer", ({ beforeEach }) => {
|
|||
QUnit.test("Day: check date", async (assert) => {
|
||||
await start({ model: { scale: "day" } });
|
||||
assert.containsOnce(target, ".fc-day-header");
|
||||
assert.strictEqual(target.querySelector(".fc-day-header").textContent, "July 16, 2021");
|
||||
const dayHeader = target.querySelector(".fc-day-header");
|
||||
assert.strictEqual(dayHeader.querySelector(".o_cw_day_name").textContent, "Friday");
|
||||
assert.strictEqual(dayHeader.querySelector(".o_cw_day_number").textContent, "16");
|
||||
});
|
||||
|
||||
QUnit.test("Day: click all day slot", async (assert) => {
|
||||
|
|
@ -152,11 +154,35 @@ QUnit.module("CalendarView - CommonRenderer", ({ beforeEach }) => {
|
|||
await start({ model: { scale: "week" } });
|
||||
assert.containsN(target, ".fc-day-header", 7);
|
||||
|
||||
const dates = ["Sun 11", "Mon 12", "Tue 13", "Wed 14", "Thu 15", "Fri 16", "Sat 17"];
|
||||
const dateNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
const dates = ["11", "12", "13", "14", "15", "16", "17"];
|
||||
|
||||
const els = target.querySelectorAll(".fc-day-header");
|
||||
for (let i = 0; i < els.length; i++) {
|
||||
assert.strictEqual(els[i].textContent, dates[i]);
|
||||
assert.strictEqual(els[i].querySelector(".o_cw_day_name").textContent, dateNames[i]);
|
||||
assert.strictEqual(els[i].querySelector(".o_cw_day_number").textContent, dates[i]);
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test("Day: automatically scroll to 6am", async (assert) => {
|
||||
// Make calendar scrollable
|
||||
target.style.height = "500px";
|
||||
await start({ model: { scale: "day" } });
|
||||
const containerDimensions = target.querySelector(".fc-scroller").getBoundingClientRect();
|
||||
const dayStartDimensions = target
|
||||
.querySelector('tr[data-time="06:00:00"')
|
||||
.getBoundingClientRect();
|
||||
assert.ok(Math.abs(dayStartDimensions.y - containerDimensions.y) <= 2);
|
||||
});
|
||||
|
||||
QUnit.test("Week: automatically scroll to 6am", async (assert) => {
|
||||
// Make calendar scrollable
|
||||
target.style.height = "500px";
|
||||
await start({ model: { scale: "week" } });
|
||||
const containerDimensions = target.querySelector(".fc-scroller").getBoundingClientRect();
|
||||
const dayStartDimensions = target
|
||||
.querySelector('tr[data-time="06:00:00"')
|
||||
.getBoundingClientRect();
|
||||
assert.ok(Math.abs(dayStartDimensions.y - containerDimensions.y) <= 2);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,124 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { CalendarDatePicker } from "@web/views/calendar/date_picker/calendar_date_picker";
|
||||
import { click, getFixture, patchDate } from "../../helpers/utils";
|
||||
import { makeEnv, makeFakeModel, mountComponent } from "./helpers";
|
||||
|
||||
let target;
|
||||
|
||||
async function start(params = {}) {
|
||||
const { services, props, model: modelParams } = params;
|
||||
const env = await makeEnv(services);
|
||||
const model = makeFakeModel(modelParams);
|
||||
return await mountComponent(CalendarDatePicker, env, {
|
||||
model,
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
QUnit.module("CalendarView - DatePicker", ({ beforeEach }) => {
|
||||
beforeEach(() => {
|
||||
target = getFixture();
|
||||
patchDate(2021, 7, 14, 8, 0, 0);
|
||||
});
|
||||
|
||||
QUnit.test("Mount a CalendarDatePicker", async (assert) => {
|
||||
await start({ model: { scale: "day" } });
|
||||
assert.containsOnce(target, ".o_calendar_mini.hasDatepicker");
|
||||
assert.strictEqual(target.querySelector(".o_selected_range").textContent, "16");
|
||||
assert.containsOnce(target, `[data-month="6"][data-year="2021"] .o_selected_range`);
|
||||
assert.strictEqual(target.querySelector(".ui-datepicker-month").textContent, "Jul");
|
||||
assert.strictEqual(target.querySelector(".ui-datepicker-year").textContent, "2021");
|
||||
assert.strictEqual(target.querySelector("thead").textContent, "SMTWTFS");
|
||||
});
|
||||
|
||||
QUnit.test("Scale: init with day", async (assert) => {
|
||||
await start({ model: { scale: "day" } });
|
||||
assert.containsOnce(target, ".o_selected_range");
|
||||
assert.containsOnce(target, "a.o_selected_range");
|
||||
assert.strictEqual(target.querySelector(".o_selected_range").textContent, "16");
|
||||
});
|
||||
|
||||
QUnit.test("Scale: init with week", async (assert) => {
|
||||
await start({ model: { scale: "week" } });
|
||||
assert.containsOnce(target, ".o_selected_range");
|
||||
assert.containsOnce(target, "tr.o_selected_range");
|
||||
assert.hasClass(target.querySelector("tr.o_selected_range"), "o_color");
|
||||
assert.strictEqual(target.querySelector(".o_selected_range").textContent, "11121314151617");
|
||||
});
|
||||
|
||||
QUnit.test("Scale: init with month", async (assert) => {
|
||||
await start({ model: { scale: "month" } });
|
||||
assert.containsN(target, "td.o_selected_range", 35);
|
||||
});
|
||||
|
||||
QUnit.test("Scale: init with year", async (assert) => {
|
||||
await start({ model: { scale: "year" } });
|
||||
assert.containsN(target, "td.o_selected_range", 35);
|
||||
});
|
||||
|
||||
QUnit.test("First day: 0 = Sunday", async (assert) => {
|
||||
await start({ model: { scale: "day", firstDayOfWeek: 0 } });
|
||||
assert.strictEqual(target.querySelector("thead").textContent, "SMTWTFS");
|
||||
});
|
||||
|
||||
QUnit.test("First day: 1 = Monday", async (assert) => {
|
||||
await start({ model: { scale: "day", firstDayOfWeek: 1 } });
|
||||
assert.strictEqual(target.querySelector("thead").textContent, "MTWTFSS");
|
||||
});
|
||||
|
||||
QUnit.test("Click on active day should change scale : day -> month", async (assert) => {
|
||||
assert.expect(2);
|
||||
|
||||
await start({
|
||||
model: {
|
||||
scale: "day",
|
||||
load(params) {
|
||||
assert.strictEqual(params.scale, "month");
|
||||
assert.ok(params.date.equals(luxon.DateTime.local(2021, 7, 16)));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await click(target, ".ui-state-active");
|
||||
});
|
||||
|
||||
QUnit.test("Click on active day should change scale : month -> week", async (assert) => {
|
||||
assert.expect(2);
|
||||
|
||||
await start({
|
||||
model: {
|
||||
scale: "month",
|
||||
load(params) {
|
||||
assert.strictEqual(params.scale, "week");
|
||||
assert.ok(params.date.equals(luxon.DateTime.local(2021, 7, 16)));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await click(target, ".ui-state-active");
|
||||
});
|
||||
|
||||
QUnit.test("Click on active day should change scale : week -> day", async (assert) => {
|
||||
assert.expect(2);
|
||||
|
||||
await start({
|
||||
model: {
|
||||
scale: "week",
|
||||
load(params) {
|
||||
assert.strictEqual(params.scale, "day");
|
||||
assert.ok(params.date.equals(luxon.DateTime.local(2021, 7, 16)));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await click(target, ".ui-state-active");
|
||||
});
|
||||
|
||||
QUnit.test("Scale: today is correctly highlighted", async (assert) => {
|
||||
patchDate(2021, 6, 4, 8, 0, 0);
|
||||
await start({ model: { scale: "month" } });
|
||||
assert.containsOnce(target, ".ui-datepicker-today");
|
||||
assert.strictEqual(target.querySelector(".ui-datepicker-today").textContent, "4");
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { CalendarFilterPanel } from "@web/views/calendar/filter_panel/calendar_filter_panel";
|
||||
import { click, getFixture, triggerEvent } from "../../helpers/utils";
|
||||
import { click, getFixture } from "../../helpers/utils";
|
||||
import { makeEnv, makeFakeModel, mountComponent } from "./helpers";
|
||||
|
||||
let target;
|
||||
|
|
@ -47,13 +47,13 @@ QUnit.module("CalendarView - FilterPanel", ({ beforeEach }) => {
|
|||
assert.containsN(sections[0], ".o_calendar_filter_item", 4);
|
||||
assert.strictEqual(
|
||||
sections[0].textContent.trim(),
|
||||
"AttendeesMitchell AdminMarc DemoBrandon FreemanEverybody's calendar"
|
||||
"AttendeesMitchell AdminBrandon FreemanMarc DemoEverybody's calendar"
|
||||
);
|
||||
|
||||
header = sections[1].querySelector(".o_cw_filter_label");
|
||||
assert.strictEqual(header.textContent, "Users");
|
||||
assert.containsN(sections[1], ".o_calendar_filter_item", 2);
|
||||
assert.strictEqual(sections[1].textContent.trim(), "UsersMarc DemoBrandon Freeman");
|
||||
assert.strictEqual(sections[1].textContent.trim(), "UsersBrandon FreemanMarc Demo");
|
||||
});
|
||||
|
||||
QUnit.test("section can collapse", async (assert) => {
|
||||
|
|
@ -101,12 +101,12 @@ QUnit.module("CalendarView - FilterPanel", ({ beforeEach }) => {
|
|||
assert.hasAttrValue(
|
||||
filters[1].querySelector(".o_cw_filter_avatar"),
|
||||
"data-src",
|
||||
"/web/image/res.partner/6/avatar_128"
|
||||
"/web/image/res.partner/4/avatar_128"
|
||||
);
|
||||
assert.hasAttrValue(
|
||||
filters[2].querySelector(".o_cw_filter_avatar"),
|
||||
"data-src",
|
||||
"/web/image/res.partner/4/avatar_128"
|
||||
"/web/image/res.partner/6/avatar_128"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ QUnit.module("CalendarView - FilterPanel", ({ beforeEach }) => {
|
|||
|
||||
await click(filters[1], ".o_calendar_filter_item .o_remove");
|
||||
await click(filters[2], ".o_calendar_filter_item .o_remove");
|
||||
assert.verifySteps(["partner_ids 2", "partner_ids 1"]);
|
||||
assert.verifySteps(["partner_ids 1", "partner_ids 2"]);
|
||||
});
|
||||
|
||||
QUnit.test("click on filter", async (assert) => {
|
||||
|
|
@ -172,42 +172,10 @@ QUnit.module("CalendarView - FilterPanel", ({ beforeEach }) => {
|
|||
await click(filters[3], "input");
|
||||
assert.verifySteps([
|
||||
"partner_ids 3 false",
|
||||
"partner_ids 6 true",
|
||||
"partner_ids 4 false",
|
||||
"partner_ids 6 true",
|
||||
"partner_ids all true",
|
||||
"partner_ids all false",
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("hover filter opens tooltip", async (assert) => {
|
||||
await start({
|
||||
services: {
|
||||
popover: {
|
||||
start: () => ({
|
||||
add: (target, _, props) => {
|
||||
assert.step(props.filter.label);
|
||||
assert.step("" + props.filter.hasAvatar);
|
||||
assert.step("" + props.filter.value);
|
||||
return () => {
|
||||
assert.step("popOver Closed");
|
||||
};
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const section = target.querySelectorAll(".o_calendar_filter")[0];
|
||||
const filters = section.querySelectorAll(".o_calendar_filter_item");
|
||||
|
||||
await triggerEvent(filters[0], null, "mouseenter");
|
||||
assert.verifySteps(["Mitchell Admin", "true", "3"]);
|
||||
await triggerEvent(filters[0], null, "mouseleave");
|
||||
assert.verifySteps(["popOver Closed"]);
|
||||
|
||||
await triggerEvent(filters[3], null, "mouseenter");
|
||||
assert.verifySteps([]);
|
||||
await triggerEvent(filters[3], null, "mouseleave");
|
||||
assert.verifySteps([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -90,18 +90,18 @@ QUnit.module("CalendarView - YearPopover", ({ beforeEach }) => {
|
|||
QUnit.test("group records", async (assert) => {
|
||||
await start({});
|
||||
|
||||
assert.containsN(target, ".o_cw_body > div", 5);
|
||||
assert.containsN(target, ".o_cw_body > a", 5);
|
||||
assert.containsN(target, ".o_cw_body > div", 4);
|
||||
assert.containsN(target, ".o_cw_body > a", 1);
|
||||
|
||||
const sectionTitles = target.querySelectorAll(".o_cw_body > div");
|
||||
assert.strictEqual(sectionTitles[0].textContent.trim(), "July 16, 2021");
|
||||
assert.strictEqual(sectionTitles[1].textContent.trim(), "July 13-17, 2021");
|
||||
assert.strictEqual(sectionTitles[2].textContent.trim(), "July 15-17, 2021");
|
||||
assert.strictEqual(sectionTitles[3].textContent.trim(), "July 15-19, 2021");
|
||||
assert.strictEqual(sectionTitles[0].textContent.trim(), "July 16, 2021R114:00R2");
|
||||
assert.strictEqual(sectionTitles[1].textContent.trim(), "July 13-17, 2021R4");
|
||||
assert.strictEqual(sectionTitles[2].textContent.trim(), "July 15-17, 2021R3");
|
||||
assert.strictEqual(sectionTitles[3].textContent.trim(), "July 15-19, 2021R5");
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_cw_body").textContent.trim(),
|
||||
"July 16, 2021R114:00 R2July 13-17, 2021R4July 15-17, 2021R3July 15-19, 2021R5 Create"
|
||||
"July 16, 2021R114:00R2July 13-17, 2021R4July 15-17, 2021R3July 15-19, 2021R5 Create"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -113,8 +113,8 @@ QUnit.module("CalendarView - YearPopover", ({ beforeEach }) => {
|
|||
editRecord: () => assert.step("edit"),
|
||||
},
|
||||
});
|
||||
assert.containsOnce(target, ".o_cw_body > a");
|
||||
await click(target, ".o_cw_body > a");
|
||||
assert.containsOnce(target, ".o_cw_body a.o_cw_popover_link");
|
||||
await click(target, ".o_cw_body a.o_cw_popover_link");
|
||||
assert.verifySteps(["edit"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,18 +34,18 @@ QUnit.module("CalendarView - YearRenderer", ({ beforeEach }) => {
|
|||
// check "title format"
|
||||
assert.strictEqual(monthHeaders.length, 12);
|
||||
const monthTitles = [
|
||||
"Jan 2021",
|
||||
"Feb 2021",
|
||||
"Mar 2021",
|
||||
"Apr 2021",
|
||||
"January 2021",
|
||||
"February 2021",
|
||||
"March 2021",
|
||||
"April 2021",
|
||||
"May 2021",
|
||||
"Jun 2021",
|
||||
"Jul 2021",
|
||||
"Aug 2021",
|
||||
"Sep 2021",
|
||||
"Oct 2021",
|
||||
"Nov 2021",
|
||||
"Dec 2021",
|
||||
"June 2021",
|
||||
"July 2021",
|
||||
"August 2021",
|
||||
"September 2021",
|
||||
"October 2021",
|
||||
"November 2021",
|
||||
"December 2021",
|
||||
];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
assert.strictEqual(monthHeaders[i].textContent, monthTitles[i]);
|
||||
|
|
@ -137,18 +137,24 @@ QUnit.module("CalendarView - YearRenderer", ({ beforeEach }) => {
|
|||
await selectDateRange(target, "2021-07-02", "2021-07-05");
|
||||
});
|
||||
|
||||
QUnit.test("display correct column header for days, independent of the timezone", async (assert) => {
|
||||
// Regression test: when the system tz is somewhere in a negative GMT (in our example Alaska)
|
||||
// the day headers of a months were incorrectly set. (S S M T W T F) instead of (S M T W T F S)
|
||||
// if the first day of the week is Sunday.
|
||||
patchTimeZone(-540); // UTC-9 = Alaska
|
||||
QUnit.test(
|
||||
"display correct column header for days, independent of the timezone",
|
||||
async (assert) => {
|
||||
// Regression test: when the system tz is somewhere in a negative GMT (in our example Alaska)
|
||||
// the day headers of a months were incorrectly set. (S S M T W T F) instead of (S M T W T F S)
|
||||
// if the first day of the week is Sunday.
|
||||
patchTimeZone(-540); // UTC-9 = Alaska
|
||||
|
||||
await start({});
|
||||
await start({});
|
||||
|
||||
const dayHeaders = target
|
||||
.querySelector(".fc-month-container")
|
||||
.querySelectorAll(".fc-day-header");
|
||||
const dayHeaders = target
|
||||
.querySelector(".fc-month-container")
|
||||
.querySelectorAll(".fc-day-header");
|
||||
|
||||
assert.deepEqual([...dayHeaders].map((el) => el.textContent), ["S", "M", "T", "W", "T", "F", "S"]);
|
||||
});
|
||||
assert.deepEqual(
|
||||
[...dayHeaders].map((el) => el.textContent),
|
||||
["S", "M", "T", "W", "T", "F", "S"]
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { uiService } from "@web/core/ui/ui_service";
|
||||
import { createElement } from "@web/core/utils/xml";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env";
|
||||
import { click, getFixture, mount, nextTick, triggerEvent } from "../../helpers/utils";
|
||||
import { setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
|
@ -206,6 +208,8 @@ export const FAKE_FIELDS = {
|
|||
};
|
||||
|
||||
function makeFakeModelState() {
|
||||
const fakeFieldNode = createElement("field", { name: "name" });
|
||||
const fakeModels = { event: FAKE_FIELDS };
|
||||
return {
|
||||
canCreate: true,
|
||||
canDelete: true,
|
||||
|
|
@ -226,9 +230,18 @@ function makeFakeModelState() {
|
|||
isTimeHidden: false,
|
||||
hasAllDaySlot: true,
|
||||
hasEditDialog: false,
|
||||
hasQuickCreate: false,
|
||||
popoverFields: {
|
||||
name: { rawAttrs: {}, options: {} },
|
||||
quickCreate: false,
|
||||
popoverFieldNodes: {
|
||||
name: Field.parseFieldNode(fakeFieldNode, fakeModels, "event", "calendar"),
|
||||
},
|
||||
activeFields: {
|
||||
name: {
|
||||
context: "{}",
|
||||
invisible: false,
|
||||
readonly: false,
|
||||
required: false,
|
||||
onChange: false,
|
||||
},
|
||||
},
|
||||
rangeEnd: makeFakeDate().endOf("month"),
|
||||
rangeStart: makeFakeDate().startOf("month"),
|
||||
|
|
@ -263,16 +276,15 @@ async function scrollTo(el, scrollParam) {
|
|||
}
|
||||
|
||||
export function findPickedDate(target) {
|
||||
return target.querySelector(".ui-datepicker-current-day");
|
||||
return target.querySelector(".o_datetime_picker .o_selected");
|
||||
}
|
||||
|
||||
export async function pickDate(target, date) {
|
||||
const [year, month, day] = date.split("-");
|
||||
const iMonth = parseInt(month, 10) - 1;
|
||||
const day = date.split("-")[2];
|
||||
const iDay = parseInt(day, 10) - 1;
|
||||
const el = target.querySelectorAll(
|
||||
`.ui-datepicker-calendar td[data-year="${year}"][data-month="${iMonth}"]`
|
||||
)[iDay];
|
||||
const el = target.querySelectorAll(`.o_datetime_picker .o_date_item_cell:not(.o_out_of_range)`)[
|
||||
iDay
|
||||
];
|
||||
el.scrollIntoView();
|
||||
await click(el);
|
||||
}
|
||||
|
|
@ -497,9 +509,40 @@ export async function resizeEventToTime(target, eventId, dateTime) {
|
|||
await nextTick();
|
||||
}
|
||||
|
||||
export async function resizeEventToDate(target, eventId, date) {
|
||||
const event = findEvent(target, eventId);
|
||||
const slot = findAllDaySlot(target, date);
|
||||
|
||||
await scrollTo(event);
|
||||
await triggerEventForCalendar(event, "mouseenter");
|
||||
|
||||
// Find event resizer
|
||||
const resizer = event.querySelector(".fc-end-resizer");
|
||||
resizer.style.display = "block";
|
||||
resizer.style.width = "100%";
|
||||
resizer.style.height = "1em";
|
||||
resizer.style.bottom = "0";
|
||||
const resizerRect = resizer.getBoundingClientRect();
|
||||
const resizerPos = {
|
||||
x: resizerRect.x + resizerRect.width,
|
||||
y: resizerRect.y + resizerRect.height / 2,
|
||||
};
|
||||
await triggerEventForCalendar(resizer, "mousedown", resizerPos);
|
||||
// Find slot position
|
||||
await scrollTo(slot, false);
|
||||
const slotRect = slot.getBoundingClientRect();
|
||||
const toPos = {
|
||||
x: slotRect.x + slotRect.width / 2,
|
||||
y: slotRect.y + slotRect.height / 2,
|
||||
};
|
||||
await triggerEventForCalendar(slot, "mousemove", toPos);
|
||||
await triggerEventForCalendar(slot, "mouseup", toPos);
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
export async function changeScale(target, scale) {
|
||||
await click(target, `.o_calendar_scale_buttons .scale_button_selection`);
|
||||
await click(target, `.o_calendar_scale_buttons .o_calendar_button_${scale}`);
|
||||
await click(target, `.o_view_scale_selector .scale_button_selection`);
|
||||
await click(target, `.o_view_scale_selector .o_scale_button_${scale}`);
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
/** @odoo-module **/
|
||||
/* global ace */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { getFixture, triggerEvents } from "@web/../tests/helpers/utils";
|
||||
import {
|
||||
click,
|
||||
clickSave,
|
||||
editInput,
|
||||
getFixture,
|
||||
nextTick,
|
||||
triggerEvent,
|
||||
triggerEvents,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { pagerNext } from "@web/../tests/search/helpers";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { fakeCookieService } from "@web/../tests/helpers/mock_services";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
|
@ -33,7 +40,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
};
|
||||
|
||||
setupViewRegistries();
|
||||
registry.category("services").add("cookie", fakeCookieService);
|
||||
});
|
||||
|
||||
QUnit.module("AceEditorField");
|
||||
|
|
@ -46,7 +52,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="foo" widget="ace" />
|
||||
<field name="foo" widget="code" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
|
@ -57,7 +63,96 @@ QUnit.module("Fields", (hooks) => {
|
|||
"should have rendered something with ace editor"
|
||||
);
|
||||
|
||||
assert.ok(target.querySelector(".o_field_ace").textContent.includes("yop"));
|
||||
assert.ok(target.querySelector(".o_field_code").textContent.includes("yop"));
|
||||
});
|
||||
|
||||
QUnit.test("AceEditorField mark as dirty as soon at onchange", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="foo" widget="code" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.ok("ace" in window, "the ace library should be loaded");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"div.ace_content",
|
||||
"should have rendered something with ace editor"
|
||||
);
|
||||
|
||||
assert.ok(target.querySelector(".o_field_code").textContent.includes("yop"));
|
||||
// edit the foo field
|
||||
const aceEditor = target.querySelector(".ace_editor");
|
||||
ace.edit(aceEditor).setValue("blip");
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_form_status_indicator_buttons");
|
||||
assert.doesNotHaveClass(
|
||||
target.querySelector(".o_form_status_indicator_buttons"),
|
||||
"invisible"
|
||||
);
|
||||
|
||||
// revert edition
|
||||
ace.edit(aceEditor).setValue("yop");
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_form_status_indicator_buttons");
|
||||
assert.hasClass(target.querySelector(".o_form_status_indicator_buttons"), "invisible");
|
||||
});
|
||||
|
||||
QUnit.test("AceEditorField on html fields works", async function (assert) {
|
||||
assert.expect(8);
|
||||
serverData.models.partner.fields.htmlField = {
|
||||
string: "HTML Field",
|
||||
type: "html",
|
||||
};
|
||||
serverData.models.partner.records.push({
|
||||
id: 3,
|
||||
htmlField: "<p>My little HTML Test</p>",
|
||||
});
|
||||
serverData.models.partner.onchanges = { htmlField: function () {} };
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 3,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="foo"/>
|
||||
<field name="htmlField" widget="code" />
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method) {
|
||||
assert.step(args.method);
|
||||
if (args.method === "web_save") {
|
||||
assert.deepEqual(args.args[1], { foo: "DEF" });
|
||||
}
|
||||
if (args.method === "onchange") {
|
||||
throw new Error("Should not call onchange, htmlField wasn't changed");
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.ok("ace" in window, "the ace library should be loaded");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"div.ace_content",
|
||||
"should have rendered something with ace editor"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
target.querySelector(".o_field_code").textContent.includes("My little HTML Test")
|
||||
);
|
||||
|
||||
// Modify foo and save
|
||||
await editInput(target, ".o_field_widget[name=foo] textarea", "DEF");
|
||||
await clickSave(target);
|
||||
|
||||
assert.verifySteps(["get_views", "web_read", "web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test("AceEditorField doesn't crash when editing", async (assert) => {
|
||||
|
|
@ -69,7 +164,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<form>
|
||||
<field name="display_name" />
|
||||
<field name="foo" widget="ace" />
|
||||
<field name="foo" widget="code" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
|
@ -86,15 +181,17 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="foo" widget="ace" />
|
||||
<field name="foo" widget="code" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.ok(target.querySelector(".o_field_ace").textContent.includes("yop"));
|
||||
assert.ok(target.querySelector(".o_field_code").textContent.includes("yop"));
|
||||
|
||||
await pagerNext(target);
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
|
||||
assert.ok(target.querySelector(".o_field_ace").textContent.includes("blip"));
|
||||
assert.ok(target.querySelector(".o_field_code").textContent.includes("blip"));
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
|
|
@ -120,9 +217,74 @@ QUnit.module("Fields", (hooks) => {
|
|||
},
|
||||
});
|
||||
|
||||
assert.verifySteps(["get_views: []", 'read: [[1],["foo","display_name"]]']);
|
||||
assert.verifySteps(["get_views: []", "web_read: [[1]]"]);
|
||||
await pagerNext(target);
|
||||
assert.verifySteps(['read: [[2],["foo","display_name"]]']);
|
||||
assert.verifySteps(["web_read: [[2]]"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("AceEditorField only trigger onchanges when blurred", async (assert) => {
|
||||
serverData.models.partner.onchanges = {
|
||||
foo: (obj) => {},
|
||||
};
|
||||
|
||||
serverData.models.partner.records.forEach((rec) => {
|
||||
rec.foo = false;
|
||||
});
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
resIds: [1, 2],
|
||||
serverData,
|
||||
arch: `<form>
|
||||
<field name="display_name" />
|
||||
<field name="foo" widget="code" />
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method) {
|
||||
assert.step(`${args.method}: ${JSON.stringify(args.args)}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.verifySteps(["get_views: []", "web_read: [[1]]"]);
|
||||
const textArea = target.querySelector(".ace_editor textarea");
|
||||
await click(textArea);
|
||||
textArea.focus();
|
||||
textArea.value = "a";
|
||||
await triggerEvent(textArea, null, "input", {});
|
||||
await triggerEvents(textArea, null, ["blur"]);
|
||||
assert.verifySteps(['onchange: [[1],{"foo":"a"},["foo"],{"display_name":{},"foo":{}}]']);
|
||||
await click(target, ".o_form_button_save");
|
||||
assert.verifySteps(['web_save: [[1],{"foo":"a"}]']);
|
||||
});
|
||||
|
||||
QUnit.test("Save and Discard buttons will become invisible after saving", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="display_name" />
|
||||
<field name="foo" widget="code" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
const textArea = target.querySelector(".ace_editor textarea");
|
||||
await click(textArea);
|
||||
textArea.focus();
|
||||
textArea.value = "a";
|
||||
await triggerEvent(textArea, null, "input", {});
|
||||
assert.containsOnce(target, ".o_form_status_indicator_buttons");
|
||||
assert.doesNotHaveClass(
|
||||
target.querySelector(".o_form_status_indicator_buttons"),
|
||||
"invisible"
|
||||
);
|
||||
await click(target, ".o_form_button_save");
|
||||
assert.containsOnce(target, ".o_form_status_indicator_buttons");
|
||||
assert.hasClass(target.querySelector(".o_form_status_indicator_buttons"), "invisible");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
await click(target, ".o_form_button_save");
|
||||
|
||||
var newRecord = _.last(serverData.models.partner.records);
|
||||
var newRecord = serverData.models.partner.records.at(-1);
|
||||
assert.strictEqual(newRecord.product_id, 37, "should have saved record with correct value");
|
||||
});
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
await click(target.querySelector(".o_form_button_save"));
|
||||
|
||||
var newRecord = _.last(serverData.models.partner.records);
|
||||
var newRecord = serverData.models.partner.records.at(-1);
|
||||
assert.strictEqual(
|
||||
newRecord.color,
|
||||
"black",
|
||||
|
|
@ -151,12 +151,18 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.test(
|
||||
"BadgeSelectionField widget on a selection unchecking selected value",
|
||||
async function (assert) {
|
||||
async (assert) => {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: '<form><field name="color" widget="selection_badge"/></form>',
|
||||
mockRPC(_, { method, model, args }) {
|
||||
if (method === "web_save" && model === "partner") {
|
||||
assert.step("web_save");
|
||||
assert.deepEqual(args[1], { color: false });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
|
|
@ -165,18 +171,20 @@ QUnit.module("Fields", (hooks) => {
|
|||
"should have rendered outer div"
|
||||
);
|
||||
assert.containsN(target, "span.o_selection_badge", 2, "should have 2 possible choices");
|
||||
assert.containsN(target, "span.o_selection_badge.active", 1, "one is active");
|
||||
assert.strictEqual(
|
||||
target.querySelector("span.o_selection_badge").textContent,
|
||||
target.querySelector("span.o_selection_badge.active").textContent,
|
||||
"Red",
|
||||
"one of them should be Red"
|
||||
"the active one should be Red"
|
||||
);
|
||||
|
||||
// click again on red option
|
||||
await click(target.querySelector("span.o_selection_badge.active"));
|
||||
// click again on red option and save to update the server data
|
||||
await click(target, "span.o_selection_badge.active");
|
||||
assert.verifySteps([]);
|
||||
await click(target, ".o_form_button_save");
|
||||
assert.verifySteps(["web_save"], "should have created a new record");
|
||||
|
||||
await click(target.querySelector(".o_form_button_save"));
|
||||
|
||||
var newRecord = _.last(serverData.models.partner.records);
|
||||
const newRecord = serverData.models.partner.records.at(-1);
|
||||
assert.strictEqual(
|
||||
newRecord.color,
|
||||
false,
|
||||
|
|
@ -184,4 +192,49 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"BadgeSelectionField widget on a selection unchecking selected value (required field)",
|
||||
async (assert) => {
|
||||
serverData.models.partner.fields.color.required = true;
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: '<form><field name="color" widget="selection_badge"/></form>',
|
||||
mockRPC(_, { method, model, args }) {
|
||||
if (method === "web_save" && model === "partner") {
|
||||
assert.step("web_save");
|
||||
assert.deepEqual(args[1], { color: "red" });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"div.o_field_selection_badge",
|
||||
"should have rendered outer div"
|
||||
);
|
||||
assert.containsN(target, "span.o_selection_badge", 2, "should have 2 possible choices");
|
||||
assert.containsN(target, "span.o_selection_badge.active", 1, "one is active");
|
||||
assert.strictEqual(
|
||||
target.querySelector("span.o_selection_badge.active").textContent,
|
||||
"Red",
|
||||
"the active one should be Red"
|
||||
);
|
||||
|
||||
// click again on red option and save to update the server data
|
||||
await click(target, "span.o_selection_badge.active");
|
||||
assert.verifySteps([]);
|
||||
await click(target, ".o_form_button_save");
|
||||
assert.verifySteps(["web_save"], "should have created a new record");
|
||||
|
||||
const newRecord = serverData.models.partner.records.at(-1);
|
||||
assert.strictEqual(
|
||||
newRecord.color,
|
||||
"red",
|
||||
"the new value should be red"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerCleanup } from "@web/../tests/helpers/cleanup";
|
||||
import { makeServerError } from "@web/../tests/helpers/mock_server";
|
||||
import { makeMockXHR } from "@web/../tests/helpers/mock_services";
|
||||
import {
|
||||
click,
|
||||
|
|
@ -12,13 +13,16 @@ import {
|
|||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { RPCError } from "@web/core/network/rpc_service";
|
||||
import { errorService } from "@web/core/errors/error_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { MAX_FILENAME_SIZE_BYTES } from "@web/views/fields/binary/binary_field";
|
||||
import { toBase64Length } from "@web/core/utils/binary";
|
||||
|
||||
const BINARY_FILE =
|
||||
"R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
|
|
@ -94,13 +98,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
const MockXHR = makeMockXHR("", send);
|
||||
|
||||
patchWithCleanup(
|
||||
browser,
|
||||
{
|
||||
XMLHttpRequest: MockXHR,
|
||||
},
|
||||
{ pure: true }
|
||||
);
|
||||
patchWithCleanup(browser, { XMLHttpRequest: MockXHR });
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
|
|
@ -167,13 +165,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
const MockXHR = makeMockXHR("", send);
|
||||
|
||||
patchWithCleanup(
|
||||
browser,
|
||||
{
|
||||
XMLHttpRequest: MockXHR,
|
||||
},
|
||||
{ pure: true }
|
||||
);
|
||||
patchWithCleanup(browser, { XMLHttpRequest: MockXHR });
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
|
|
@ -308,6 +300,36 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("icons are displayed exactly once", async (assert) => {
|
||||
assert.expect(3);
|
||||
patchWithCleanup(odoo, { debug: true });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="document" filename="foo"/>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_binary .o_select_file_button",
|
||||
"only one select file icon should be visible"
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_binary .o_download_file_button",
|
||||
"only one download file icon should be visible"
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_binary .o_clear_file_button",
|
||||
"only one clear file icon should be visible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"binary fields input value is empty when clearing after uploading",
|
||||
async function (assert) {
|
||||
|
|
@ -380,13 +402,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
const MockXHR = makeMockXHR("", download);
|
||||
|
||||
patchWithCleanup(
|
||||
browser,
|
||||
{
|
||||
XMLHttpRequest: MockXHR,
|
||||
},
|
||||
{ pure: true }
|
||||
);
|
||||
patchWithCleanup(browser, { XMLHttpRequest: MockXHR });
|
||||
|
||||
serverData.models.partner.onchanges = {
|
||||
product_id: function (obj) {
|
||||
|
|
@ -408,7 +424,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
resId: 1,
|
||||
});
|
||||
|
||||
await click(target, ".o_form_button_create");
|
||||
await click(
|
||||
target,
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_form_button_create"
|
||||
);
|
||||
await click(target, ".o_field_many2one[name='product_id'] input");
|
||||
await click(
|
||||
target.querySelector(".o_field_many2one[name='product_id'] .dropdown-item")
|
||||
|
|
@ -429,7 +448,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
);
|
||||
|
||||
QUnit.test("Binary field in list view", async function (assert) {
|
||||
QUnit.test("BinaryField in list view (formatter)", async function (assert) {
|
||||
serverData.models.partner.records[0].document = BINARY_FILE;
|
||||
|
||||
await makeView({
|
||||
|
|
@ -438,7 +457,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="document" filename="yooo"/>
|
||||
<field name="document"/>
|
||||
</tree>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
|
@ -449,7 +468,28 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("Binary field for new record has no download button", async function (assert) {
|
||||
QUnit.test("BinaryField in list view with filename", async function (assert) {
|
||||
serverData.models.partner.records[0].document = BINARY_FILE;
|
||||
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="document" filename="foo" widget="binary"/>
|
||||
<field name="foo"/>
|
||||
</tree>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_data_row .o_data_cell").textContent,
|
||||
"coucou.txt"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("BinaryField for new record has no download button", async function (assert) {
|
||||
serverData.models.partner.fields.document.default = BINARY_FILE;
|
||||
await makeView({
|
||||
serverData,
|
||||
|
|
@ -466,8 +506,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.test("Binary filename doesn't exceed 255 bytes", async function (assert) {
|
||||
const LARGE_BINARY_FILE = BINARY_FILE.repeat(5);
|
||||
assert.ok((LARGE_BINARY_FILE.length / 4 * 3) > MAX_FILENAME_SIZE_BYTES,
|
||||
"The initial binary file should be larger than max bytes that can represent the filename");
|
||||
assert.ok(
|
||||
(LARGE_BINARY_FILE.length / 4) * 3 > MAX_FILENAME_SIZE_BYTES,
|
||||
"The initial binary file should be larger than max bytes that can represent the filename"
|
||||
);
|
||||
serverData.models.partner.fields.document.default = LARGE_BINARY_FILE;
|
||||
await makeView({
|
||||
serverData,
|
||||
|
|
@ -528,14 +570,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test('isUploading state should be set to false after upload', async function(assert) {
|
||||
assert.expect(1);
|
||||
QUnit.test("isUploading state should be set to false after upload", async function (assert) {
|
||||
serviceRegistry.add("error", errorService);
|
||||
|
||||
serverData.models.partner.onchanges = {
|
||||
document: function (obj) {
|
||||
if (obj.document) {
|
||||
const error = new RPCError();
|
||||
error.exceptionName = "odoo.exceptions.ValidationError";
|
||||
throw error;
|
||||
throw makeServerError({ type: "ValidationError" });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -552,16 +593,19 @@ QUnit.module("Fields", (hooks) => {
|
|||
await editInput(target, ".o_field_binary .o_input_file", file);
|
||||
assert.equal(
|
||||
target.querySelector(".o_select_file_button").innerText,
|
||||
"UPLOAD YOUR FILE",
|
||||
"Upload your file",
|
||||
"displayed value should be upload your file"
|
||||
);
|
||||
assert.containsOnce(target, ".o_error_dialog");
|
||||
});
|
||||
|
||||
QUnit.test("doesn't crash if value is not a string", async (assert) => {
|
||||
serverData.models.partner.records = [{
|
||||
id: 1,
|
||||
document: {},
|
||||
}]
|
||||
serverData.models.partner.records = [
|
||||
{
|
||||
id: 1,
|
||||
document: {},
|
||||
},
|
||||
];
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -573,9 +617,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="document"/>
|
||||
</form>`,
|
||||
});
|
||||
assert.equal(
|
||||
target.querySelector(".o_field_binary input").value,
|
||||
""
|
||||
);
|
||||
})
|
||||
assert.equal(target.querySelector(".o_field_binary input").value, "");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_kanban_record .o_field_widget .o_favorite > a").textContent,
|
||||
" Remove from Favorites",
|
||||
"Remove from Favorites",
|
||||
'the label should say "Remove from Favorites"'
|
||||
);
|
||||
|
||||
|
|
@ -69,11 +69,94 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_kanban_record .o_field_widget .o_favorite > a").textContent,
|
||||
" Add to Favorites",
|
||||
"Add to Favorites",
|
||||
'the label should say "Add to Favorites"'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("FavoriteField saves changes by default", async function (assert) {
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="bar" widget="boolean_favorite" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "web_save" && args.model === "partner") {
|
||||
assert.step("save");
|
||||
assert.deepEqual(args.args, [[1], { bar: false }]);
|
||||
}
|
||||
},
|
||||
domain: [["id", "=", 1]],
|
||||
});
|
||||
|
||||
// click on favorite
|
||||
await click(target, ".o_field_widget .o_favorite");
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_kanban_record .o_field_widget .o_favorite > a i.fa.fa-star",
|
||||
"should not be favorite"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_kanban_record .o_field_widget .o_favorite > a").textContent,
|
||||
"Add to Favorites",
|
||||
'the label should say "Add to Favorites"'
|
||||
);
|
||||
|
||||
assert.verifySteps(["save"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"FavoriteField does not save if autosave option is set to false",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="bar" widget="boolean_favorite" options="{'autosave': False}"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "web_save" && args.model === "partner") {
|
||||
assert.step("save");
|
||||
}
|
||||
},
|
||||
domain: [["id", "=", 1]],
|
||||
});
|
||||
|
||||
// click on favorite
|
||||
await click(target, ".o_field_widget .o_favorite");
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_kanban_record .o_field_widget .o_favorite > a i.fa.fa-star",
|
||||
"should not be favorite"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_kanban_record .o_field_widget .o_favorite > a")
|
||||
.textContent,
|
||||
"Add to Favorites",
|
||||
'the label should say "Add to Favorites"'
|
||||
);
|
||||
|
||||
assert.verifySteps([]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("FavoriteField in form view", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -97,7 +180,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget .o_favorite > a").textContent,
|
||||
" Remove from Favorites",
|
||||
"Remove from Favorites",
|
||||
'the label should say "Remove from Favorites"'
|
||||
);
|
||||
|
||||
|
|
@ -110,7 +193,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget .o_favorite > a").textContent,
|
||||
" Add to Favorites",
|
||||
"Add to Favorites",
|
||||
'the label should say "Add to Favorites"'
|
||||
);
|
||||
|
||||
|
|
@ -121,7 +204,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget .o_favorite > a").textContent,
|
||||
" Add to Favorites",
|
||||
"Add to Favorites",
|
||||
'the label should say "Add to Favorites"'
|
||||
);
|
||||
|
||||
|
|
@ -134,7 +217,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget .o_favorite > a").textContent,
|
||||
" Remove from Favorites",
|
||||
"Remove from Favorites",
|
||||
'the label should say "Remove from Favorites"'
|
||||
);
|
||||
|
||||
|
|
@ -147,7 +230,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget .o_favorite > a").textContent,
|
||||
" Remove from Favorites",
|
||||
"Remove from Favorites",
|
||||
'the label should say "Remove from Favorites"'
|
||||
);
|
||||
});
|
||||
|
|
@ -159,7 +242,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="bar" widget="boolean_favorite" nolabel="1" />
|
||||
<field name="bar" widget="boolean_favorite" nolabel="1" options="{'autosave': False}"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -190,9 +190,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
"the row is now selected, in edition"
|
||||
);
|
||||
assert.ok(
|
||||
!cell.querySelector(".o-checkbox input:checked").disabled,
|
||||
"input should now be enabled"
|
||||
!cell.querySelector(".o-checkbox input:not(:checked)").disabled,
|
||||
"input should now be enabled and unchecked"
|
||||
);
|
||||
await click(cell, ".o-checkbox");
|
||||
await click(cell);
|
||||
assert.notOk(
|
||||
cell.querySelector(".o-checkbox input:checked").disabled,
|
||||
|
|
@ -220,10 +221,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
"should now have only 3 checked input"
|
||||
);
|
||||
|
||||
// Re-Edit the line and fake-check the checkbox
|
||||
// Fake-check the checkbox
|
||||
await click(cell);
|
||||
await click(cell, ".o-checkbox");
|
||||
await click(cell, ".o-checkbox");
|
||||
|
||||
// Save
|
||||
await clickSave(target);
|
||||
|
|
@ -273,4 +273,34 @@ QUnit.module("Fields", (hooks) => {
|
|||
"checkbox should still be disabled"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("onchange return value before toggle checkbox", async function (assert) {
|
||||
serverData.models.partner.onchanges = {
|
||||
bar(obj) {
|
||||
obj.bar = true;
|
||||
},
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `<form><field name="bar"/></form>`,
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_boolean input:checked",
|
||||
"checkbox should still be checked"
|
||||
);
|
||||
|
||||
await click(target, ".o_field_boolean .o-checkbox");
|
||||
await nextTick();
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_boolean input:checked",
|
||||
"checkbox should still be checked"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { click } from "../../helpers/utils";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
QUnit.module("Fields", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
bar: { string: "Bar", type: "boolean", default: true, searchable: true },
|
||||
barOff: {
|
||||
string: "Bar Off",
|
||||
type: "boolean",
|
||||
default: true,
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
records: [{ id: 1, bar: true, barOff: false }],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("BooleanIconField");
|
||||
|
||||
QUnit.test("boolean_icon field in form view", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<label for="bar" string="Bar" />
|
||||
<field name="bar" widget="boolean_icon" options="{'icon': 'fa-recycle'}" />
|
||||
<field name="barOff" widget="boolean_icon" options="{'icon': 'fa-trash'}" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsN(target, ".o_field_boolean_icon button", 2, "icon buttons are visible");
|
||||
assert.strictEqual(
|
||||
target.querySelector("[name='bar'] button").dataset.tooltip,
|
||||
"Bar",
|
||||
"first button has the label as tooltip"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelector("[name='bar'] button"),
|
||||
"btn-primary",
|
||||
"active boolean button has the right class"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelector("[name='bar'] button"),
|
||||
"fa-recycle",
|
||||
"first button has the right icon"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelector("[name='barOff'] button"),
|
||||
"btn-outline-secondary",
|
||||
"inactive boolean button has the right class"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelector("[name='barOff'] button"),
|
||||
"fa-trash",
|
||||
"second button has the right icon"
|
||||
);
|
||||
|
||||
await click(target.querySelector("[name='bar'] button"));
|
||||
assert.hasClass(
|
||||
target.querySelector("[name='bar'] button"),
|
||||
"btn-outline-secondary",
|
||||
"boolean button is now inactive"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -249,13 +249,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(_route, { method }) {
|
||||
if (method === "write") {
|
||||
assert.step("write");
|
||||
if (method === "web_save") {
|
||||
assert.step("web_save");
|
||||
}
|
||||
},
|
||||
});
|
||||
await click(target, ".o_field_widget[name='bar'] input");
|
||||
assert.verifySteps(["write"]);
|
||||
assert.verifySteps(["web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test("BooleanToggleField - autosave option set to false", async function (assert) {
|
||||
|
|
@ -269,8 +269,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(_route, { method }) {
|
||||
if (method === "write") {
|
||||
assert.step("write");
|
||||
if (method === "web_save") {
|
||||
assert.step("web_save");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(args[1].foo, false, "the foo value should be false");
|
||||
}
|
||||
},
|
||||
|
|
@ -407,60 +407,63 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("translation dialog should close if field is not there anymore", async function (assert) {
|
||||
// In this test, we simulate the case where the field is removed from the view
|
||||
// this can happend for example if the user click the back button of the browser.
|
||||
serverData.models.partner.fields.foo.translate = true;
|
||||
serviceRegistry.add("localization", makeFakeLocalizationService({ multiLang: true }), {
|
||||
force: true,
|
||||
});
|
||||
patchWithCleanup(session.user_context, {
|
||||
lang: "en_US",
|
||||
});
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
QUnit.test(
|
||||
"translation dialog should close if field is not there anymore",
|
||||
async function (assert) {
|
||||
// In this test, we simulate the case where the field is removed from the view
|
||||
// this can happend for example if the user click the back button of the browser.
|
||||
serverData.models.partner.fields.foo.translate = true;
|
||||
serviceRegistry.add("localization", makeFakeLocalizationService({ multiLang: true }), {
|
||||
force: true,
|
||||
});
|
||||
patchWithCleanup(session.user_context, {
|
||||
lang: "en_US",
|
||||
});
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="int_field" />
|
||||
<field name="foo" attrs="{'invisible': [('int_field', '==', 9)]}"/>
|
||||
<field name="foo" invisible="int_field == 9"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC(route, { args, method, model }) {
|
||||
if (route === "/web/dataset/call_kw/res.lang/get_installed") {
|
||||
return Promise.resolve([
|
||||
["en_US", "English"],
|
||||
["fr_BE", "French (Belgium)"],
|
||||
["es_ES", "Spanish"],
|
||||
]);
|
||||
}
|
||||
if (route === "/web/dataset/call_kw/partner/get_field_translations") {
|
||||
return Promise.resolve([
|
||||
[
|
||||
{ lang: "en_US", source: "yop", value: "yop" },
|
||||
{ lang: "fr_BE", source: "yop", value: "valeur français" },
|
||||
{ lang: "es_ES", source: "yop", value: "yop español" },
|
||||
],
|
||||
{ translation_type: "char", translation_show_source: false },
|
||||
]);
|
||||
}
|
||||
},
|
||||
});
|
||||
mockRPC(route, { args, method, model }) {
|
||||
if (route === "/web/dataset/call_kw/res.lang/get_installed") {
|
||||
return Promise.resolve([
|
||||
["en_US", "English"],
|
||||
["fr_BE", "French (Belgium)"],
|
||||
["es_ES", "Spanish"],
|
||||
]);
|
||||
}
|
||||
if (route === "/web/dataset/call_kw/partner/get_field_translations") {
|
||||
return Promise.resolve([
|
||||
[
|
||||
{ lang: "en_US", source: "yop", value: "yop" },
|
||||
{ lang: "fr_BE", source: "yop", value: "valeur français" },
|
||||
{ lang: "es_ES", source: "yop", value: "yop español" },
|
||||
],
|
||||
{ translation_type: "char", translation_show_source: false },
|
||||
]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.hasClass(target.querySelector("[name=foo] input"), "o_field_translate");
|
||||
assert.hasClass(target.querySelector("[name=foo] input"), "o_field_translate");
|
||||
|
||||
await click(target, ".o_field_char .btn.o_field_translate");
|
||||
assert.containsOnce(target, ".modal", "a translate modal should be visible");
|
||||
await editInput(target, ".o_field_widget[name=int_field] input", "9");
|
||||
await nextTick();
|
||||
assert.containsNone(target, "[name=foo] input", "the field foo should be invisible");
|
||||
assert.containsNone(target, ".modal", "a translate modal should not be visible");
|
||||
});
|
||||
await click(target, ".o_field_char .btn.o_field_translate");
|
||||
assert.containsOnce(target, ".modal", "a translate modal should be visible");
|
||||
await editInput(target, ".o_field_widget[name=int_field] input", "9");
|
||||
await nextTick();
|
||||
assert.containsNone(target, "[name=foo] input", "the field foo should be invisible");
|
||||
assert.containsNone(target, ".modal", "a translate modal should not be visible");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("html field translatable", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
|
@ -718,6 +721,77 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"input field: change value before pending onchange returns (2)",
|
||||
async function (assert) {
|
||||
serverData.models.partner.onchanges = {
|
||||
int_field(obj) {
|
||||
if (obj.int_field === 7) {
|
||||
obj.foo = "blabla";
|
||||
} else {
|
||||
obj.foo = "tralala";
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const def = makeDeferred();
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="int_field" />
|
||||
<field name="foo" />
|
||||
</sheet>
|
||||
</form>`,
|
||||
async mockRPC(route, { method }) {
|
||||
if (method === "onchange") {
|
||||
await def;
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='foo'] input").value,
|
||||
"yop",
|
||||
"should contain the correct value"
|
||||
);
|
||||
|
||||
// trigger a deferred onchange
|
||||
await editInput(target, ".o_field_widget[name='int_field'] input", "7");
|
||||
|
||||
// insert a value in input foo
|
||||
target.querySelector(".o_field_widget[name=foo] input").value = "test";
|
||||
await triggerEvent(target, ".o_field_widget[name=foo] input", "input");
|
||||
|
||||
// complete the onchange
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='foo'] input").value,
|
||||
"test",
|
||||
"The onchange value should not be applied because the input is in edition"
|
||||
);
|
||||
|
||||
// apply the value of the input foo
|
||||
await triggerEvent(target, ".o_field_widget[name=foo] input", "change");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='foo'] input").value,
|
||||
"test"
|
||||
);
|
||||
|
||||
// trigger another onchange (not deferred)
|
||||
await editInput(target, ".o_field_widget[name='int_field'] input", "10");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='foo'] input").value,
|
||||
"tralala",
|
||||
"the onchange value should be applied because the input is not in edition"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"input field: change value before pending onchange returns (with fieldDebounce)",
|
||||
async function (assert) {
|
||||
|
|
@ -792,6 +866,30 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
);
|
||||
|
||||
QUnit.test("onchange return value before editing input", async function (assert) {
|
||||
serverData.models.partner.onchanges = {
|
||||
foo(obj) {
|
||||
obj.foo = "yop";
|
||||
},
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="foo" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.strictEqual(target.querySelector("[name='foo'] input").value, "yop");
|
||||
|
||||
await editInput(target, "[name='foo'] input", "tralala");
|
||||
assert.strictEqual(target.querySelector("[name='foo'] input").value, "yop");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"input field: change value before pending onchange renaming",
|
||||
async function (assert) {
|
||||
|
|
@ -1047,4 +1145,94 @@ QUnit.module("Fields", (hooks) => {
|
|||
"Placeholder"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"char field: correct value is used to evaluate the modifiers",
|
||||
async function (assert) {
|
||||
serverData.models.partner.onchanges = {
|
||||
foo: (obj) => {
|
||||
if (obj.foo === "a") {
|
||||
obj.display_name = false;
|
||||
} else if (obj.foo === "b") {
|
||||
obj.display_name = "";
|
||||
}
|
||||
},
|
||||
};
|
||||
serverData.models.partner.records[0].foo = false;
|
||||
serverData.models.partner.records[0].display_name = false;
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="foo" />
|
||||
<field name="display_name" invisible="'' == display_name"/>
|
||||
</form>`,
|
||||
});
|
||||
assert.containsOnce(target, "[name='display_name'] input");
|
||||
|
||||
await editInput(target, "[name='foo'] input", "a");
|
||||
assert.containsOnce(target, "[name='display_name'] input");
|
||||
|
||||
await editInput(target, "[name='foo'] input", "b");
|
||||
assert.containsNone(target, "[name='display_name'] input");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"edit a char field should display the status indicator buttons without flickering",
|
||||
async function (assert) {
|
||||
serverData.models.partner.records[0].p = [2];
|
||||
serverData.models.partner.onchanges = {
|
||||
foo() {},
|
||||
};
|
||||
|
||||
const def = makeDeferred();
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="p">
|
||||
<tree editable="bottom">
|
||||
<field name="foo"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
async mockRPC(route, { method }) {
|
||||
if (method === "onchange") {
|
||||
assert.step("onchange");
|
||||
await def;
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_form_status_indicator_buttons.invisible",
|
||||
"form view is not dirty"
|
||||
);
|
||||
|
||||
await click(target, ".o_data_cell");
|
||||
await editInput(target, "[name='foo'] input", "a");
|
||||
assert.verifySteps(["onchange"]);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_form_status_indicator_buttons:not(.invisible)",
|
||||
"form view is dirty"
|
||||
);
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_form_status_indicator_buttons:not(.invisible)",
|
||||
"form view is dirty"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(target.querySelector(".o_field_color input").value, "#000000");
|
||||
await editInput(target, ".o_field_color input", "#fefefe");
|
||||
assert.verifySteps([
|
||||
'onchange [[1],{"id":1,"hex_color":"#fefefe"},"hex_color",{"hex_color":"1"}]',
|
||||
'onchange [[1],{"hex_color":"#fefefe"},["hex_color"],{"hex_color":{},"display_name":{}}]',
|
||||
]);
|
||||
assert.strictEqual(target.querySelector(".o_field_color input").value, "#fefefe");
|
||||
assert.strictEqual(
|
||||
|
|
@ -113,31 +113,34 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.containsN(
|
||||
target,
|
||||
'.o_field_color input:disabled',
|
||||
".o_field_color input:disabled",
|
||||
2,
|
||||
"the field should not be editable"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("color field read-only in model definition, in non-editable list", async function (assert) {
|
||||
serverData.models.partner.fields.hex_color.readonly = true;
|
||||
await makeView({
|
||||
type: "list",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
QUnit.test(
|
||||
"color field read-only in model definition, in non-editable list",
|
||||
async function (assert) {
|
||||
serverData.models.partner.fields.hex_color.readonly = true;
|
||||
await makeView({
|
||||
type: "list",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="hex_color" widget="color" />
|
||||
</tree>`,
|
||||
});
|
||||
});
|
||||
|
||||
assert.containsN(
|
||||
target,
|
||||
'.o_field_color input:disabled',
|
||||
2,
|
||||
"the field should not be editable"
|
||||
);
|
||||
});
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_field_color input:disabled",
|
||||
2,
|
||||
"the field should not be editable"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("color field change via another field's onchange", async (assert) => {
|
||||
serverData.models.partner.onchanges = {
|
||||
|
|
@ -170,7 +173,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(target.querySelector(".o_field_color input").value, "#000000");
|
||||
await editInput(target, ".o_field_char[name='foo'] input", "someValue");
|
||||
assert.verifySteps([
|
||||
'onchange [[1],{"id":1,"foo":"someValue","hex_color":false},"foo",{"foo":"1","hex_color":""}]',
|
||||
'onchange [[1],{"foo":"someValue"},["foo"],{"foo":{},"hex_color":{},"display_name":{}}]',
|
||||
]);
|
||||
assert.strictEqual(target.querySelector(".o_field_color input").value, "#fefefe");
|
||||
assert.strictEqual(
|
||||
|
|
|
|||
|
|
@ -224,7 +224,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
</tree>`,
|
||||
domain: [["id", "<", 0]],
|
||||
});
|
||||
await click(target.querySelector(".o_list_button_add"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_add"
|
||||
)
|
||||
);
|
||||
const date_column_width = target
|
||||
.querySelector('.o_list_table thead th[data-name="date_field"]')
|
||||
.style.width.replace("px", "");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@
|
|||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { click, getFixture, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import {
|
||||
click,
|
||||
getFixture,
|
||||
editInput,
|
||||
nextTick,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
|
@ -73,8 +79,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("CopyClipboardField on unset field", async function (assert) {
|
||||
QUnit.test("CopyClipboardField: show copy button even on empty field", async function (assert) {
|
||||
serverData.models.partner.records[0].char_field = false;
|
||||
serverData.models.partner.records[0].text_field = false;
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
|
|
@ -85,26 +92,25 @@ QUnit.module("Fields", (hooks) => {
|
|||
<sheet>
|
||||
<group>
|
||||
<field name="char_field" widget="CopyClipboardChar" />
|
||||
<field name="text_field" widget="CopyClipboardText" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.containsNone(
|
||||
assert.containsOnce(
|
||||
target,
|
||||
'.o_field_copy[name="char_field"] .o_clipboard_button',
|
||||
"char_field (unset) should not contain a button"
|
||||
'.o_field_CopyClipboardChar[name="char_field"] .o_clipboard_button'
|
||||
);
|
||||
assert.containsOnce(
|
||||
target.querySelector(".o_field_widget[name=char_field]"),
|
||||
"input",
|
||||
"char_field (unset) should contain an input field"
|
||||
target,
|
||||
'.o_field_CopyClipboardText[name="text_field"] .o_clipboard_button'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"CopyClipboardField on readonly unset fields in create mode",
|
||||
"CopyClipboardField: show copy button even on readonly empty field",
|
||||
async function (assert) {
|
||||
serverData.models.partner.fields.display_name.readonly = true;
|
||||
|
||||
|
|
@ -122,10 +128,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsNone(
|
||||
assert.containsOnce(
|
||||
target,
|
||||
'.o_field_copy[name="display_name"] .o_clipboard_button',
|
||||
"the readonly unset field should not contain a button"
|
||||
'.o_field_CopyClipboardChar[name="display_name"] .o_clipboard_button'
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
@ -190,42 +195,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.verifySteps(["copied tooltip"]);
|
||||
});
|
||||
|
||||
QUnit.test("CopyClipboard fields with clipboard not available", async function (assert) {
|
||||
patchWithCleanup(browser, {
|
||||
console: {
|
||||
warn: (msg) => assert.step(msg),
|
||||
},
|
||||
navigator: {
|
||||
clipboard: undefined,
|
||||
},
|
||||
});
|
||||
QUnit.module("CopyClipboardButtonField");
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<div>
|
||||
<field name="text_field" widget="CopyClipboardText"/>
|
||||
</div>
|
||||
</sheet>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
await click(target, ".o_clipboard_button");
|
||||
await nextTick();
|
||||
assert.verifySteps(
|
||||
["This browser doesn't allow to copy to clipboard"],
|
||||
"console simply displays a warning on failure"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.module("CopyToClipboardButtonField");
|
||||
|
||||
QUnit.test("CopyToClipboardButtonField in form view", async function (assert) {
|
||||
QUnit.test("CopyClipboardButtonField in form view", async function (assert) {
|
||||
patchWithCleanup(browser, {
|
||||
navigator: {
|
||||
clipboard: {
|
||||
|
|
@ -267,4 +239,52 @@ Ho-ho-hoooo Merry Christmas`,
|
|||
"yop",
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("CopyClipboardButtonField can be disabled", async function (assert) {
|
||||
patchWithCleanup(browser, {
|
||||
navigator: {
|
||||
clipboard: {
|
||||
writeText: (text) => {
|
||||
assert.step(text);
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<div>
|
||||
<field name="text_field" disabled="1" widget="CopyClipboardButton"/>
|
||||
<field name="char_field" disabled="char_field == 'yop'" widget="CopyClipboardButton"/>
|
||||
<field name="char_field" widget="char"/>
|
||||
</div>
|
||||
</sheet>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_clipboard_button.o_btn_text_copy[disabled]",
|
||||
"The inner button should be disabled."
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_clipboard_button.o_btn_char_copy[disabled]",
|
||||
"The inner button should be disabled."
|
||||
);
|
||||
|
||||
await editInput(target, ".o_input", "yip");
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_clipboard_button.o_btn_char_copy[disabled]",
|
||||
"The inner button should not be disabled."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
||||
import { getPickerCell, zoomOut } from "@web/../tests/core/datetime/datetime_test_helpers";
|
||||
import {
|
||||
click,
|
||||
clickCreate,
|
||||
|
|
@ -16,8 +16,7 @@ import {
|
|||
triggerScroll,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { strftimeToLuxonFormat } from "@web/core/l10n/dates";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
|
@ -70,7 +69,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.module("DateField");
|
||||
|
||||
QUnit.test("DateField: toggle datepicker", async function (assert) {
|
||||
QUnit.test("DateField: toggle datepicker", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -81,29 +80,21 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="date" />
|
||||
</form>`,
|
||||
});
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be closed initially"
|
||||
);
|
||||
assert.containsNone(target, ".o_datetime_picker", "datepicker should be closed initially");
|
||||
|
||||
await click(target, ".o_datepicker .o_datepicker_input");
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be opened"
|
||||
);
|
||||
await click(target, ".o_field_date input");
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should be opened");
|
||||
|
||||
// focus another field
|
||||
await click(target, ".o_field_widget[name='foo'] input");
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
target,
|
||||
".o_datetime_picker",
|
||||
"datepicker should close itself when the user clicks outside"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("DateField: toggle datepicker far in the future", async function (assert) {
|
||||
QUnit.test("DateField: toggle datepicker far in the future", async (assert) => {
|
||||
serverData.models.partner.records = [
|
||||
{
|
||||
id: 1,
|
||||
|
|
@ -124,29 +115,21 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be closed initially"
|
||||
);
|
||||
assert.containsNone(target, ".o_datetime_picker", "datepicker should be closed initially");
|
||||
|
||||
await click(target, ".o_datepicker .o_datepicker_input");
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be opened"
|
||||
);
|
||||
await click(target, ".o_field_date input");
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should be opened");
|
||||
|
||||
// focus another field
|
||||
await click(target, ".o_field_widget[name='foo'] input");
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
target,
|
||||
".o_datetime_picker",
|
||||
"datepicker should close itself when the user clicks outside"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("date field is empty if no date is set", async function (assert) {
|
||||
QUnit.test("date field is empty if no date is set", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -157,7 +140,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget .o_datepicker_input",
|
||||
".o_field_widget input",
|
||||
"should have one input in the form view"
|
||||
);
|
||||
assert.strictEqual(
|
||||
|
|
@ -167,47 +150,24 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"DateField: set an invalid date when the field is already set",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: '<form><field name="date"/></form>',
|
||||
});
|
||||
QUnit.test("DateField: set an invalid date when the field is already set", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: '<form><field name="date"/></form>',
|
||||
});
|
||||
|
||||
const input = target.querySelector(".o_field_widget[name='date'] input");
|
||||
assert.strictEqual(input.value, "02/03/2017");
|
||||
const input = target.querySelector(".o_field_widget[name='date'] input");
|
||||
assert.strictEqual(input.value, "02/03/2017");
|
||||
|
||||
input.value = "mmmh";
|
||||
await triggerEvent(input, null, "change");
|
||||
assert.strictEqual(input.value, "02/03/2017", "should have reset the original value");
|
||||
}
|
||||
);
|
||||
input.value = "mmmh";
|
||||
await triggerEvent(input, null, "change");
|
||||
assert.strictEqual(input.value, "02/03/2017", "should have reset the original value");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"DateField: set an invalid date when the field is not set yet",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 4,
|
||||
serverData,
|
||||
arch: '<form><field name="date"/></form>',
|
||||
});
|
||||
|
||||
const input = target.querySelector(".o_field_widget[name='date'] input");
|
||||
assert.strictEqual(input.value, "");
|
||||
|
||||
input.value = "mmmh";
|
||||
await triggerEvent(input, null, "change");
|
||||
assert.strictEqual(input.value, "", "The date field should be empty");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("DateField value should not set on first click", async function (assert) {
|
||||
QUnit.test("DateField: set an invalid date when the field is not set yet", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -216,25 +176,42 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: '<form><field name="date"/></form>',
|
||||
});
|
||||
|
||||
await click(target, ".o_datepicker .o_datepicker_input");
|
||||
const input = target.querySelector(".o_field_widget[name='date'] input");
|
||||
assert.strictEqual(input.value, "");
|
||||
|
||||
input.value = "mmmh";
|
||||
await triggerEvent(input, null, "change");
|
||||
assert.strictEqual(input.value, "", "The date field should be empty");
|
||||
});
|
||||
|
||||
QUnit.test("DateField value should not set on first click", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 4,
|
||||
serverData,
|
||||
arch: '<form><field name="date"/></form>',
|
||||
});
|
||||
|
||||
await click(target, ".o_field_date input");
|
||||
// open datepicker and select a date
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='date'] input").value,
|
||||
"",
|
||||
"date field's input should be empty on first click"
|
||||
);
|
||||
await click(document.body, ".day[data-day*='/22/']");
|
||||
await click(getPickerCell("22"));
|
||||
|
||||
// re-open datepicker
|
||||
await click(target, ".o_datepicker .o_datepicker_input");
|
||||
await click(target, ".o_field_date input");
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".day.active").textContent,
|
||||
target.querySelector(".o_date_item_cell.o_selected").textContent,
|
||||
"22",
|
||||
"datepicker should be highlight with 22nd day of month"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("DateField in form view (with positive time zone offset)", async function (assert) {
|
||||
QUnit.test("DateField in form view (with positive time zone offset)", async (assert) => {
|
||||
assert.expect(7);
|
||||
|
||||
patchTimeZone(120); // Should be ignored by date fields
|
||||
|
|
@ -246,7 +223,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: '<form><field name="date"/></form>',
|
||||
mockRPC(route, { args }) {
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
assert.strictEqual(
|
||||
args[1].date,
|
||||
"2017-02-22",
|
||||
|
|
@ -257,39 +234,28 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_date input").value,
|
||||
"02/03/2017",
|
||||
"the date should be correct in edit mode"
|
||||
);
|
||||
|
||||
// open datepicker and select another value
|
||||
await click(target, ".o_datepicker_input");
|
||||
await click(target, ".o_field_date input");
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should be opened");
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be opened"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".day.active[data-day='02/03/2017']",
|
||||
"datepicker should be highlight February 3"
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[0]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[1]
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .year")[8]);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .month")[1]);
|
||||
await click(document.body.querySelector(".day[data-day*='/22/']"));
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be closed"
|
||||
target,
|
||||
".o_date_item_cell.o_selected",
|
||||
"datepicker should have a selected day"
|
||||
);
|
||||
// select 22 Feb 2017
|
||||
await zoomOut();
|
||||
await zoomOut();
|
||||
await click(getPickerCell("2017"));
|
||||
await click(getPickerCell("Feb"));
|
||||
await click(getPickerCell("22"));
|
||||
assert.containsNone(target, ".o_datetime_picker", "datepicker should be closed");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_date input").value,
|
||||
"02/22/2017",
|
||||
"the selected date should be displayed in the input"
|
||||
);
|
||||
|
|
@ -303,7 +269,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("DateField in form view (with negative time zone offset)", async function (assert) {
|
||||
QUnit.test("DateField in form view (with negative time zone offset)", async (assert) => {
|
||||
patchTimeZone(-120); // Should be ignored by date fields
|
||||
|
||||
await makeView({
|
||||
|
|
@ -315,13 +281,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_date input").value,
|
||||
"02/03/2017",
|
||||
"the date should be correct in edit mode"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("DateField dropdown disappears on scroll", async function (assert) {
|
||||
QUnit.test("DateField dropdown doesn't disappear on scroll", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -335,22 +301,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
});
|
||||
|
||||
await click(target, ".o_datepicker .o_datepicker_input");
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be opened"
|
||||
);
|
||||
await click(target, ".o_field_date input");
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should be opened");
|
||||
|
||||
await triggerScroll(target, { top: 50 });
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be closed"
|
||||
);
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should still be opened");
|
||||
});
|
||||
|
||||
QUnit.test("DateField with label opens datepicker on click", async function (assert) {
|
||||
QUnit.test("DateField with label opens datepicker on click", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -364,14 +322,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
await click(target.querySelector("label.o_form_label"));
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be opened"
|
||||
);
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should be opened");
|
||||
});
|
||||
|
||||
QUnit.test("DateField with warn_future option", async function (assert) {
|
||||
QUnit.test("DateField with warn_future option", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -379,42 +333,36 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="date" options="{ 'datepicker': { 'warn_future': true } }" />
|
||||
<field name="date" options="{'warn_future': true}" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
// open datepicker and select another value
|
||||
await click(target, ".o_datepicker .o_datepicker_input");
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[0]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[1]
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .year")[11]);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .month")[11]);
|
||||
await click(document.body, ".day[data-day*='/31/']");
|
||||
await click(target, ".o_field_date input");
|
||||
await zoomOut();
|
||||
await zoomOut();
|
||||
await click(getPickerCell("2030"));
|
||||
await click(getPickerCell("Dec"));
|
||||
await click(getPickerCell("31"));
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_datepicker_warning",
|
||||
".fa-exclamation-triangle",
|
||||
"should have a warning in the form view"
|
||||
);
|
||||
|
||||
const input = target.querySelector(".o_field_widget[name='date'] input");
|
||||
input.value = "";
|
||||
await triggerEvent(input, null, "change"); // remove the value
|
||||
await editInput(target, ".o_field_widget[name='date'] input", "");
|
||||
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_datepicker_warning",
|
||||
".fa-exclamation-triangle",
|
||||
"the warning in the form view should be hidden"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"DateField with warn_future option: do not overwrite datepicker option",
|
||||
async function (assert) {
|
||||
async (assert) => {
|
||||
// Making sure we don't have a legit default value
|
||||
// or any onchange that would set the value
|
||||
serverData.models.partner.fields.date.default = undefined;
|
||||
|
|
@ -428,7 +376,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<form>
|
||||
<field name="foo" /> <!-- Do not let the date field get the focus in the first place -->
|
||||
<field name="date" options="{ 'datepicker': { 'warn_future': true } }" />
|
||||
<field name="date" options="{'warn_future': true}" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
|
@ -447,7 +395,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
);
|
||||
|
||||
QUnit.test("DateField in editable list view", async function (assert) {
|
||||
QUnit.test("DateField in editable list view", async (assert) => {
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
|
|
@ -465,45 +413,33 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"input.o_datepicker_input",
|
||||
".o_field_date input",
|
||||
"the view should have a date input for editable mode"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector("input.o_datepicker_input"),
|
||||
target.querySelector(".o_field_date input"),
|
||||
document.activeElement,
|
||||
"date input should have the focus"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector("input.o_datepicker_input").value,
|
||||
target.querySelector(".o_field_date input").value,
|
||||
"02/03/2017",
|
||||
"the date should be correct in edit mode"
|
||||
);
|
||||
|
||||
// open datepicker and select another value
|
||||
await click(target, ".o_datepicker_input");
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be opened"
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[0]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[1]
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .year")[8]);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .month")[1]);
|
||||
await click(document.body.querySelector(".day[data-day*='/22/']"));
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be closed"
|
||||
);
|
||||
await click(target, ".o_field_date input");
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should be opened");
|
||||
await zoomOut();
|
||||
await zoomOut();
|
||||
await click(getPickerCell("2017"));
|
||||
await click(getPickerCell("Feb"));
|
||||
await click(getPickerCell("22"));
|
||||
assert.containsNone(target, ".o_datetime_picker", "datepicker should be closed");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_date input").value,
|
||||
"02/22/2017",
|
||||
"the selected date should be displayed in the input"
|
||||
);
|
||||
|
|
@ -517,44 +453,41 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"multi edition of DateField in list view: clear date in input",
|
||||
async function (assert) {
|
||||
serverData.models.partner.records[1].date = "2017-02-03";
|
||||
QUnit.test("multi edition of DateField in list view: clear date in input", async (assert) => {
|
||||
serverData.models.partner.records[1].date = "2017-02-03";
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
arch: '<tree multi_edit="1"><field name="date"/></tree>',
|
||||
});
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
arch: '<tree multi_edit="1"><field name="date"/></tree>',
|
||||
});
|
||||
|
||||
const rows = target.querySelectorAll(".o_data_row");
|
||||
const rows = target.querySelectorAll(".o_data_row");
|
||||
|
||||
// select two records and edit them
|
||||
await click(rows[0], ".o_list_record_selector input");
|
||||
await click(rows[1], ".o_list_record_selector input");
|
||||
// select two records and edit them
|
||||
await click(rows[0], ".o_list_record_selector input");
|
||||
await click(rows[1], ".o_list_record_selector input");
|
||||
|
||||
await click(rows[0], ".o_data_cell");
|
||||
await click(rows[0], ".o_data_cell");
|
||||
|
||||
assert.containsOnce(target, "input.o_datepicker_input");
|
||||
await editInput(target, ".o_datepicker_input", "");
|
||||
assert.containsOnce(target, ".o_field_date input");
|
||||
await editInput(target, ".o_field_date input", "");
|
||||
|
||||
assert.containsOnce(document.body, ".modal");
|
||||
await click(target, ".modal .modal-footer .btn-primary");
|
||||
assert.containsOnce(target, ".modal");
|
||||
await click(target, ".modal .modal-footer .btn-primary");
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_data_row:first-child .o_data_cell").textContent,
|
||||
""
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_data_row:nth-child(2) .o_data_cell").textContent,
|
||||
""
|
||||
);
|
||||
}
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_data_row:first-child .o_data_cell").textContent,
|
||||
""
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_data_row:nth-child(2) .o_data_cell").textContent,
|
||||
""
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("DateField remove value", async function (assert) {
|
||||
QUnit.test("DateField remove value", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -569,16 +502,16 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_date input").value,
|
||||
"02/03/2017",
|
||||
"the date should be correct in edit mode"
|
||||
);
|
||||
|
||||
const input = target.querySelector(".o_datepicker_input");
|
||||
const input = target.querySelector(".o_field_date input");
|
||||
input.value = "";
|
||||
await triggerEvents(input, null, ["input", "change", "focusout"]);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_date input").value,
|
||||
"",
|
||||
"should have correctly removed the value"
|
||||
);
|
||||
|
|
@ -592,93 +525,32 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"do not trigger a field_changed for datetime field with date widget",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: '<form><field name="datetime" widget="date"/></form>',
|
||||
mockRPC(route, { method }) {
|
||||
assert.step(method);
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
"02/08/2017",
|
||||
"the date should be correct"
|
||||
);
|
||||
|
||||
const input = target.querySelector(".o_field_widget[name='datetime'] input");
|
||||
input.value = "02/08/2017";
|
||||
await triggerEvents(input, null, ["input", "change", "focusout"]);
|
||||
|
||||
assert.containsOnce(target, ".o_form_saved");
|
||||
assert.verifySteps(["get_views", "read"]); // should not have save as nothing changed
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"field date should select its content onclick when there is one",
|
||||
async function (assert) {
|
||||
assert.expect(3);
|
||||
const done = assert.async();
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: '<form><field name="date"/></form>',
|
||||
});
|
||||
|
||||
$(target).on("show.datetimepicker", () => {
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"bootstrap-datetimepicker is visible"
|
||||
);
|
||||
const active = document.activeElement;
|
||||
assert.strictEqual(
|
||||
active.tagName,
|
||||
"INPUT",
|
||||
"The datepicker input should be focused"
|
||||
);
|
||||
assert.strictEqual(
|
||||
active.value.slice(active.selectionStart, active.selectionEnd),
|
||||
"02/03/2017",
|
||||
"The whole input of the date field should have been selected"
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
await click(target, ".o_datepicker .o_datepicker_input");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("DateField support internationalization", async function (assert) {
|
||||
// The DatePicker component needs the locale to be available since it
|
||||
// is still using Moment.js for the bootstrap datepicker
|
||||
const originalLocale = moment.locale();
|
||||
moment.defineLocale("no", {
|
||||
monthsShort: "jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.".split("_"),
|
||||
monthsParseExact: true,
|
||||
dayOfMonthOrdinalParse: /\d{1,2}\./,
|
||||
ordinal: "%d.",
|
||||
QUnit.test("field date should select its content onclick when there is one", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: '<form><field name="date"/></form>',
|
||||
});
|
||||
|
||||
registry.category("services").remove("localization");
|
||||
registry
|
||||
.category("services")
|
||||
.add(
|
||||
"localization",
|
||||
makeFakeLocalizationService({ dateFormat: strftimeToLuxonFormat("%d-%m/%Y") })
|
||||
);
|
||||
patchWithCleanup(luxon.Settings, {
|
||||
defaultLocale: "no",
|
||||
const input = target.querySelector(".o_field_date input");
|
||||
await click(input);
|
||||
input.focus();
|
||||
|
||||
assert.containsOnce(target, ".o_datetime_picker");
|
||||
const active = document.activeElement;
|
||||
assert.strictEqual(active.tagName, "INPUT", "The datepicker input should be focused");
|
||||
assert.strictEqual(
|
||||
active.value.slice(active.selectionStart, active.selectionEnd),
|
||||
"02/03/2017",
|
||||
"The whole input of the date field should have been selected"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("DateField supports custom format", async (assert) => {
|
||||
patchWithCleanup(localization, {
|
||||
dateFormat: "dd-MM-yyyy",
|
||||
});
|
||||
|
||||
await makeView({
|
||||
|
|
@ -690,27 +562,60 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
const dateViewForm = target.querySelector(".o_field_date input").value;
|
||||
await click(target, ".o_datepicker .o_datepicker_input");
|
||||
await click(target, ".o_field_date input");
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_date input").value,
|
||||
dateViewForm,
|
||||
"input date field should be the same as it was in the view form"
|
||||
);
|
||||
await click(document.body.querySelector(".day[data-day*='/22/']"));
|
||||
const dateEditForm = target.querySelector(".o_datepicker_input").value;
|
||||
await click(getPickerCell("22"));
|
||||
const dateEditForm = target.querySelector(".o_field_date input").value;
|
||||
await clickSave(target);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_date input").value,
|
||||
dateEditForm,
|
||||
"date field should be the same as the one selected in the view form"
|
||||
);
|
||||
|
||||
moment.locale(originalLocale);
|
||||
moment.updateLocale("no", null);
|
||||
});
|
||||
|
||||
QUnit.test("DateField: hit enter should update value", async function (assert) {
|
||||
QUnit.test("DateField supports internationalization", async (assert) => {
|
||||
patchWithCleanup(luxon.Settings, {
|
||||
defaultLocale: "nb-NO",
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: '<form><field name="date"/></form>',
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
const dateViewForm = target.querySelector(".o_field_date input").value;
|
||||
await click(target, ".o_field_date input");
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_date input").value,
|
||||
dateViewForm,
|
||||
"input date field should be the same as it was in the view form"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_zoom_out strong").textContent,
|
||||
"februar 2017",
|
||||
"Norwegian locale should be correctly applied"
|
||||
);
|
||||
await click(getPickerCell("22"));
|
||||
const dateEditForm = target.querySelector(".o_field_date input").value;
|
||||
await clickSave(target);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_date input").value,
|
||||
dateEditForm,
|
||||
"date field should be the same as the one selected in the view form"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("DateField: hit enter should update value", async (assert) => {
|
||||
patchTimeZone(120);
|
||||
|
||||
await makeView({
|
||||
|
|
@ -725,23 +630,23 @@ QUnit.module("Fields", (hooks) => {
|
|||
const input = target.querySelector(".o_field_widget[name='date'] input");
|
||||
|
||||
input.value = "01/08";
|
||||
await triggerEvent(input, null, "keydown", { key: "Enter" });
|
||||
await triggerEvent(input, null, "change");
|
||||
await triggerEvent(input, null, "keydown", { key: "Enter" });
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='date'] input").value,
|
||||
`01/08/${year}`
|
||||
);
|
||||
|
||||
input.value = "08/01";
|
||||
await triggerEvent(input, null, "keydown", { key: "Enter" });
|
||||
await triggerEvent(input, null, "change");
|
||||
await triggerEvent(input, null, "keydown", { key: "Enter" });
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='date'] input").value,
|
||||
`08/01/${year}`
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("DateField: allow to use compute dates (+5d for instance)", async function (assert) {
|
||||
QUnit.test("DateField: allow to use compute dates (+5d for instance)", async (assert) => {
|
||||
patchDate(2021, 1, 15, 10, 0, 0); // current date : 15 Feb 2021 10:00:00
|
||||
serverData.models.partner.fields.date.default = "2019-09-15";
|
||||
|
||||
|
|
@ -755,20 +660,20 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "09/15/2019"); // default date
|
||||
|
||||
// Calculate a new date from current date + 5 days
|
||||
await editInput(target, ".o_field_widget[name=date] .o_datepicker_input", "+5d");
|
||||
await editInput(target, ".o_field_widget[name=date] input", "+5d");
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "02/20/2021");
|
||||
|
||||
// Discard and do it again
|
||||
await clickDiscard(target);
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "09/15/2019"); // default date
|
||||
await editInput(target, ".o_field_widget[name=date] .o_datepicker_input", "+5d");
|
||||
await editInput(target, ".o_field_widget[name=date] input", "+5d");
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "02/20/2021");
|
||||
|
||||
// Save and do it again
|
||||
await clickSave(target);
|
||||
// new computed date (current date + 5 days) is saved
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "02/20/2021");
|
||||
await editInput(target, ".o_field_widget[name=date] .o_datepicker_input", "+5d");
|
||||
await editInput(target, ".o_field_widget[name=date] input", "+5d");
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "02/20/2021");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +1,25 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import {
|
||||
getPickerApplyButton,
|
||||
getPickerCell,
|
||||
getTimePickers,
|
||||
zoomOut,
|
||||
} from "@web/../tests/core/datetime/datetime_test_helpers";
|
||||
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
||||
import {
|
||||
click,
|
||||
clickSave,
|
||||
editInput,
|
||||
editSelect,
|
||||
getFixture,
|
||||
patchTimeZone,
|
||||
patchWithCleanup,
|
||||
triggerEvent,
|
||||
triggerEvents,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
|
@ -55,7 +63,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.module("DatetimeField");
|
||||
|
||||
QUnit.test("DatetimeField in form view", async function (assert) {
|
||||
QUnit.test("DatetimeField in form view", async (assert) => {
|
||||
patchTimeZone(120);
|
||||
|
||||
await makeView({
|
||||
|
|
@ -70,56 +78,32 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(
|
||||
target.querySelector(".o_field_datetime input").value,
|
||||
expectedDateString,
|
||||
"the datetime should be correctly displayed in readonly"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
expectedDateString,
|
||||
"the datetime should be correct in edit mode"
|
||||
"the datetime should be correctly displayed"
|
||||
);
|
||||
|
||||
// datepicker should not open on focus
|
||||
assert.containsNone(document.body, ".bootstrap-datetimepicker-widget");
|
||||
assert.containsNone(target, ".o_datetime_picker");
|
||||
|
||||
await click(target, ".o_datepicker_input");
|
||||
assert.containsOnce(document.body, ".bootstrap-datetimepicker-widget");
|
||||
await click(target, ".o_field_datetime input");
|
||||
assert.containsOnce(target, ".o_datetime_picker");
|
||||
|
||||
// select 22 February at 8:25:35
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[0]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[1]
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .year")[8]);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .month")[3]);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .day[data-day*='/22/']")
|
||||
);
|
||||
await click(document.body.querySelector(".bootstrap-datetimepicker-widget .fa-clock-o"));
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .timepicker-hour")
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .hour")[8]);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .timepicker-minute")
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .minute")[5]);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .timepicker-second")
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .second")[7]);
|
||||
// select 22 April 2018 at 8:25
|
||||
await zoomOut();
|
||||
await zoomOut();
|
||||
await click(getPickerCell("2018"));
|
||||
await click(getPickerCell("Apr"));
|
||||
await click(getPickerCell("22"));
|
||||
const [hourSelect, minuteSelect] = getTimePickers().at(0);
|
||||
await editSelect(hourSelect, null, "8");
|
||||
await editSelect(minuteSelect, null, "25");
|
||||
// Close the datepicker
|
||||
await click(target, ".o_form_view_container");
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be closed"
|
||||
);
|
||||
assert.containsNone(target, ".o_datetime_picker", "datepicker should be closed");
|
||||
|
||||
const newExpectedDateString = "04/22/2017 08:25:35";
|
||||
const newExpectedDateString = "04/22/2018 08:25:00";
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_datetime input").value,
|
||||
newExpectedDateString,
|
||||
"the selected date should be displayed in the input"
|
||||
);
|
||||
|
|
@ -134,8 +118,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test(
|
||||
"DatetimeField does not trigger fieldChange before datetime completly picked",
|
||||
async function (assert) {
|
||||
"DatetimeField only triggers fieldChange when a day is picked and when an hour/minute is selected",
|
||||
async (assert) => {
|
||||
patchTimeZone(120);
|
||||
|
||||
serverData.models.partner.onchanges = {
|
||||
|
|
@ -154,60 +138,38 @@ QUnit.module("Fields", (hooks) => {
|
|||
},
|
||||
});
|
||||
|
||||
await click(target, ".o_datepicker_input");
|
||||
assert.containsOnce(document.body, ".bootstrap-datetimepicker-widget");
|
||||
await click(target, ".o_field_datetime input");
|
||||
|
||||
// select a date and time
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[0]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[1]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .year")[8]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .month")[3]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelector(
|
||||
".bootstrap-datetimepicker-widget .day[data-day*='/22/']"
|
||||
)
|
||||
);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .fa-clock-o")
|
||||
);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .timepicker-hour")
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .hour")[8]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .timepicker-minute")
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .minute")[5]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .timepicker-second")
|
||||
);
|
||||
assert.verifySteps([], "should not have done any onchange yet");
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .second")[7]
|
||||
);
|
||||
assert.containsOnce(target, ".o_datetime_picker");
|
||||
|
||||
assert.containsNone(document.body, ".bootstrap-datetimepicker-widget");
|
||||
// select 22 April 2018 at 8:25
|
||||
await zoomOut();
|
||||
await zoomOut();
|
||||
await click(getPickerCell("2018"));
|
||||
await click(getPickerCell("Apr"));
|
||||
await click(getPickerCell("22"));
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
||||
const [hourSelect, minuteSelect] = getTimePickers().at(0);
|
||||
await editSelect(hourSelect, null, "8");
|
||||
await editSelect(minuteSelect, null, "25");
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
||||
// Close the datepicker
|
||||
await click(target);
|
||||
|
||||
assert.containsNone(target, ".o_datetime_picker");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
"04/22/2017 08:25:35"
|
||||
target.querySelector(".o_field_datetime input").value,
|
||||
"04/22/2018 08:25:00"
|
||||
);
|
||||
assert.verifySteps(["onchange"], "should have done only one onchange");
|
||||
assert.verifySteps(["onchange"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("DatetimeField with datetime formatted without second", async function (assert) {
|
||||
QUnit.test("DatetimeField with datetime formatted without second", async (assert) => {
|
||||
patchTimeZone(0);
|
||||
|
||||
serverData.models.partner.fields.datetime.default = "2017-08-02 12:00:05";
|
||||
|
|
@ -234,14 +196,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(
|
||||
target.querySelector(".o_field_datetime input").value,
|
||||
expectedDateString,
|
||||
"the datetime should be correctly displayed in readonly"
|
||||
"the datetime should be correctly displayed"
|
||||
);
|
||||
|
||||
await click(target, ".o_form_button_cancel");
|
||||
assert.containsNone(document.body, ".modal", "there should not be a Warning dialog");
|
||||
assert.containsNone(target, ".modal", "there should not be a Warning dialog");
|
||||
});
|
||||
|
||||
QUnit.test("DatetimeField in editable list view", async function (assert) {
|
||||
QUnit.test("DatetimeField in editable list view", async (assert) => {
|
||||
patchTimeZone(120);
|
||||
|
||||
await makeView({
|
||||
|
|
@ -256,69 +217,54 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(
|
||||
cell.textContent,
|
||||
expectedDateString,
|
||||
"the datetime should be correctly displayed in readonly"
|
||||
"the datetime should be correctly displayed"
|
||||
);
|
||||
|
||||
// switch to edit mode
|
||||
await click(target.querySelector(".o_data_row .o_data_cell"));
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"input.o_datepicker_input",
|
||||
".o_field_datetime input",
|
||||
"the view should have a date input for editable mode"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector("input.o_datepicker_input"),
|
||||
target.querySelector(".o_field_datetime input"),
|
||||
document.activeElement,
|
||||
"date input should have the focus"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector("input.o_datepicker_input").value,
|
||||
target.querySelector(".o_field_datetime input").value,
|
||||
expectedDateString,
|
||||
"the date should be correct in edit mode"
|
||||
);
|
||||
|
||||
assert.containsNone(document.body, ".bootstrap-datetimepicker-widget");
|
||||
await click(target, ".o_datepicker_input");
|
||||
assert.containsOnce(document.body, ".bootstrap-datetimepicker-widget");
|
||||
assert.containsNone(target, ".o_datetime_picker");
|
||||
|
||||
// select 22 February at 8:25:35
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[0]
|
||||
);
|
||||
await click(
|
||||
document.body.querySelectorAll(".bootstrap-datetimepicker-widget .picker-switch")[1]
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .year")[8]);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .month")[3]);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .day[data-day*='/22/']")
|
||||
);
|
||||
await click(document.body.querySelector(".bootstrap-datetimepicker-widget .fa-clock-o"));
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .timepicker-hour")
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .hour")[8]);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .timepicker-minute")
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .minute")[5]);
|
||||
await click(
|
||||
document.body.querySelector(".bootstrap-datetimepicker-widget .timepicker-second")
|
||||
);
|
||||
await click(document.body.querySelectorAll(".bootstrap-datetimepicker-widget .second")[7]);
|
||||
await click(target, ".o_field_datetime input");
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be closed"
|
||||
);
|
||||
assert.containsOnce(target, ".o_datetime_picker");
|
||||
|
||||
const newExpectedDateString = "04/22/2017 08:25:35";
|
||||
// select 22 April 2018 at 8:25
|
||||
await zoomOut();
|
||||
await zoomOut();
|
||||
await click(getPickerCell("2018"));
|
||||
await click(getPickerCell("Apr"));
|
||||
await click(getPickerCell("22"));
|
||||
const [hourSelect, minuteSelect] = getTimePickers().at(0);
|
||||
await editSelect(hourSelect, null, "8");
|
||||
await editSelect(minuteSelect, null, "25");
|
||||
// Apply changes
|
||||
await click(getPickerApplyButton());
|
||||
|
||||
assert.containsNone(target, ".o_datetime_picker", "datepicker should be closed");
|
||||
|
||||
const newExpectedDateString = "04/22/2018 08:25:00";
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_datetime input").value,
|
||||
newExpectedDateString,
|
||||
"the selected datetime should be displayed in the input"
|
||||
"the date should be updated in the input"
|
||||
);
|
||||
|
||||
// save
|
||||
|
|
@ -332,7 +278,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.test(
|
||||
"multi edition of DatetimeField in list view: edit date in input",
|
||||
async function (assert) {
|
||||
async (assert) => {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "list",
|
||||
|
|
@ -348,10 +294,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
await click(rows[0], ".o_data_cell");
|
||||
|
||||
assert.containsOnce(target, "input.o_datepicker_input");
|
||||
await editInput(target, ".o_datepicker_input", "10/02/2019 09:00:00");
|
||||
assert.containsOnce(target, ".o_field_datetime input");
|
||||
|
||||
await editInput(target, ".o_field_datetime input", "10/02/2019 09:00:00");
|
||||
|
||||
assert.containsOnce(target, ".modal");
|
||||
|
||||
assert.containsOnce(document.body, ".modal");
|
||||
await click(target.querySelector(".modal .modal-footer .btn-primary"));
|
||||
|
||||
assert.strictEqual(
|
||||
|
|
@ -367,7 +315,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.test(
|
||||
"multi edition of DatetimeField in list view: clear date in input",
|
||||
async function (assert) {
|
||||
async (assert) => {
|
||||
serverData.models.partner.records[1].datetime = "2017-02-08 10:00:00";
|
||||
|
||||
await makeView({
|
||||
|
|
@ -385,10 +333,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
await click(rows[0], ".o_data_cell");
|
||||
|
||||
assert.containsOnce(target, "input.o_datepicker_input");
|
||||
await editInput(target, ".o_datepicker_input", "");
|
||||
assert.containsOnce(target, ".o_field_datetime input");
|
||||
|
||||
await editInput(target, ".o_field_datetime input", "");
|
||||
|
||||
assert.containsOnce(target, ".modal");
|
||||
|
||||
assert.containsOnce(document.body, ".modal");
|
||||
await click(target, ".modal .modal-footer .btn-primary");
|
||||
|
||||
assert.strictEqual(
|
||||
|
|
@ -402,7 +352,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
);
|
||||
|
||||
QUnit.test("DatetimeField remove value", async function (assert) {
|
||||
QUnit.test("DatetimeField remove value", async (assert) => {
|
||||
assert.expect(4);
|
||||
|
||||
patchTimeZone(120);
|
||||
|
|
@ -414,7 +364,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: '<form><field name="datetime"/></form>',
|
||||
mockRPC(route, { args }) {
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
assert.strictEqual(
|
||||
args[1].datetime,
|
||||
false,
|
||||
|
|
@ -425,16 +375,16 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_datetime input").value,
|
||||
"02/08/2017 12:00:00",
|
||||
"the date time should be correct in edit mode"
|
||||
);
|
||||
|
||||
const input = target.querySelector(".o_datepicker_input");
|
||||
const input = target.querySelector(".o_field_datetime input");
|
||||
input.value = "";
|
||||
await triggerEvents(input, null, ["input", "change", "focusout"]);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_datepicker_input").value,
|
||||
target.querySelector(".o_field_datetime input").value,
|
||||
"",
|
||||
"should have an empty input"
|
||||
);
|
||||
|
|
@ -449,8 +399,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test(
|
||||
"DatetimeField with date/datetime widget (with day change)",
|
||||
async function (assert) {
|
||||
"DatetimeField with date/datetime widget (with day change) does not care about widget",
|
||||
async (assert) => {
|
||||
patchTimeZone(-240);
|
||||
|
||||
serverData.models.partner.records[0].p = [2];
|
||||
|
|
@ -484,16 +434,16 @@ QUnit.module("Fields", (hooks) => {
|
|||
// switch to form view
|
||||
await click(target, ".o_field_widget[name='p'] .o_data_cell");
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".modal .o_field_date[name='datetime'] input").value,
|
||||
"02/07/2017",
|
||||
target.querySelector(".modal .o_field_date[name='datetime'] input").value,
|
||||
"02/07/2017 22:00:00",
|
||||
"the datetime (date widget) should be correctly displayed in form view"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"DatetimeField with date/datetime widget (without day change)",
|
||||
async function (assert) {
|
||||
"DatetimeField with date/datetime widget (without day change) does not care about widget",
|
||||
async (assert) => {
|
||||
patchTimeZone(-240);
|
||||
|
||||
serverData.models.partner.records[0].p = [2];
|
||||
|
|
@ -527,44 +477,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
// switch to form view
|
||||
await click(target, ".o_field_widget[name='p'] .o_data_cell");
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".modal .o_field_date[name='datetime'] input").value,
|
||||
"02/08/2017",
|
||||
target.querySelector(".modal .o_field_date[name='datetime'] input").value,
|
||||
"02/08/2017 06:00:00",
|
||||
"the datetime (date widget) should be correctly displayed in form view"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("datepicker option: daysOfWeekDisabled", async function (assert) {
|
||||
serverData.models.partner.fields.datetime.default = "2017-08-02 12:00:05";
|
||||
serverData.models.partner.fields.datetime.required = true;
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="datetime" options="{'datepicker': { 'daysOfWeekDisabled': [0, 6] }}" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await click(target, ".o_datepicker_input");
|
||||
|
||||
for (const el of document.body.querySelectorAll(".day:nth-child(2), .day:last-child")) {
|
||||
assert.hasClass(el, "disabled", "first and last days must be disabled");
|
||||
}
|
||||
|
||||
// the assertions below could be replaced by a single hasClass classic on the jQuery set using the idea
|
||||
// All not <=> not Exists. But we want to be sure that the set is non empty. We don't have an helper
|
||||
// function for that.
|
||||
for (const el of document.body.querySelectorAll(
|
||||
".day:not(:nth-child(2)):not(:last-child)"
|
||||
)) {
|
||||
assert.doesNotHaveClass(el, "disabled", "other days must stay clickable");
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test("datetime field: hit enter should update value", async function (assert) {
|
||||
QUnit.test("datetime field: hit enter should update value", async (assert) => {
|
||||
// This test verifies that the field datetime is correctly computed when:
|
||||
// - we press enter to validate our entry
|
||||
// - we click outside the field to validate our entry
|
||||
|
|
@ -609,36 +529,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(value, datetimeValue);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"datetime field with date widget: hit enter should update value",
|
||||
async function (assert) {
|
||||
/**
|
||||
* Don't think this test is usefull in the new system.
|
||||
*/
|
||||
patchTimeZone(120);
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: '<form><field name="datetime" widget="date"/></form>',
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
await editInput(target, ".o_field_widget .o_datepicker_input", "01/08/22");
|
||||
await triggerEvent(target, ".o_field_widget .o_datepicker_input", "keydown", {
|
||||
key: "Enter",
|
||||
});
|
||||
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "01/08/2022");
|
||||
|
||||
// Click outside the field to check that the field is not changed
|
||||
await clickSave(target);
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "01/08/2022");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("DateTimeField with label opens datepicker on click", async function (assert) {
|
||||
QUnit.test("DateTimeField with label opens datepicker on click", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -652,10 +543,66 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
await click(target.querySelector("label.o_form_label"));
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be opened"
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should be opened");
|
||||
});
|
||||
|
||||
QUnit.test("datetime field: use picker with arabic numbering system", async (assert) => {
|
||||
patchWithCleanup(luxon.Settings, {
|
||||
defaultLocale: "ar-001",
|
||||
defaultNumberingSystem: "arab",
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: /* xml */ `
|
||||
<form string="Partners">
|
||||
<field name="datetime" />
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
const getInput = () => target.querySelector("[name=datetime] input");
|
||||
|
||||
assert.strictEqual(getInput().value, "٠٢/٠٨/٢٠١٧ ١١:٠٠:٠٠");
|
||||
|
||||
await click(getInput());
|
||||
await editSelect(getTimePickers()[0][1], null, 45);
|
||||
|
||||
assert.strictEqual(getInput().value, "٠٢/٠٨/٢٠١٧ ١١:٤٥:٠٠");
|
||||
});
|
||||
|
||||
QUnit.test("list datetime with date widget test", async (assert) => {
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<tree editable="bottom">
|
||||
<field name="datetime" widget="datetime" options="{'show_time': false}"/>
|
||||
<field name="datetime" widget="datetime"/>
|
||||
</tree>`,
|
||||
serverData,
|
||||
});
|
||||
|
||||
const dates = target.querySelectorAll(".o_field_cell");
|
||||
|
||||
assert.strictEqual(
|
||||
dates[0].textContent,
|
||||
"02/08/2017",
|
||||
"for datetime field only date should be visible with show_time as false and readonly"
|
||||
);
|
||||
assert.strictEqual(
|
||||
dates[1].textContent,
|
||||
"02/08/2017 11:00:00",
|
||||
"for datetime field both date and time should be visible with show_time by default true"
|
||||
);
|
||||
await click(dates[0]);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_datetime input").value,
|
||||
"02/08/2017 11:00:00",
|
||||
"for datetime field both date and time should be visible with show_time as false and edit"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,14 +9,34 @@ import {
|
|||
getFixture,
|
||||
makeDeferred,
|
||||
nextTick,
|
||||
patchDate,
|
||||
patchWithCleanup,
|
||||
triggerEvent,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
|
||||
import { getPickerCell } from "@web/../tests/core/datetime/datetime_test_helpers";
|
||||
import * as dsHelpers from "@web/../tests/core/domain_selector_tests";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
function replaceNotificationService(assert) {
|
||||
registry.category("services").add(
|
||||
"notification",
|
||||
{
|
||||
start() {
|
||||
return {
|
||||
add(message) {
|
||||
assert.step(message);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{ force: true }
|
||||
);
|
||||
}
|
||||
|
||||
QUnit.module("Fields", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
|
|
@ -89,19 +109,20 @@ QUnit.module("Fields", (hooks) => {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("DomainField");
|
||||
|
||||
QUnit.test(
|
||||
"The domain editor should not crash the view when given a dynamic filter",
|
||||
"The domain editor should not crash the view when given a dynamic filter (allow_expressions=False)",
|
||||
async function (assert) {
|
||||
// dynamic filters (containing variables, such as uid, parent or today)
|
||||
// are not handled by the domain editor, but it shouldn't crash the view
|
||||
// are handled by the domain editor
|
||||
serverData.models.partner.records[0].foo = `[("int_field", "=", uid)]`;
|
||||
|
||||
replaceNotificationService(assert);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -115,20 +136,51 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_edit_mode").textContent,
|
||||
" This domain is not supported. Reset domain",
|
||||
"The widget should not crash the view, but gracefully admit its failure."
|
||||
dsHelpers.getCurrentValue(target),
|
||||
"uid",
|
||||
"The widget should show the dynamic filter."
|
||||
);
|
||||
assert.doesNotHaveClass(target.querySelector(".o_field_domain"), "o_field_invalid");
|
||||
assert.verifySteps(["The domain should not involve non-literals"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"The domain editor should not crash the view when given a dynamic filter (allow_expressions=True)",
|
||||
async function (assert) {
|
||||
// dynamic filters (containing variables, such as uid, parent or today)
|
||||
// are handled by the domain editor
|
||||
serverData.models.partner.records[0].foo = `[("int_field", "=", uid)]`;
|
||||
|
||||
replaceNotificationService(assert);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="foo" widget="domain" options="{'model': 'partner', 'allow_expressions':True}" />
|
||||
<field name="int_field" invisible="1" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
dsHelpers.getCurrentValue(target),
|
||||
"uid",
|
||||
"The widget should show the dynamic filter."
|
||||
);
|
||||
assert.doesNotHaveClass(target.querySelector(".o_field_domain"), "o_field_invalid");
|
||||
assert.verifySteps(["The domain involves non-literals. Their evaluation might fail."]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"The domain editor should not crash the view when given a dynamic filter ( datetime )",
|
||||
async function (assert) {
|
||||
// dynamic filters (containing variables, such as uid, parent or today)
|
||||
// are not handled by the domain editor, but it shouldn't crash the view
|
||||
serverData.models.partner.records[0].foo = `[("datetime", "=", context_today())]`;
|
||||
serverData.models.partner.fields.datetime = { string: "A date", type: "datetime" };
|
||||
serverData.models.partner.records[0].foo = `[("datetime", "=", context_today())]`;
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -141,27 +193,20 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
});
|
||||
|
||||
// The input field should display that the date is invalid
|
||||
assert.equal(target.querySelector(".o_datepicker_input").value, "Invalid DateTime");
|
||||
assert.equal(dsHelpers.getCurrentValue(target), "context_today()");
|
||||
|
||||
await dsHelpers.clearNotSupported(target);
|
||||
|
||||
// Change the date in the datepicker
|
||||
await click(target, ".o_datepicker_input");
|
||||
await click(target, ".o_datetime_input");
|
||||
// Select a date in the datepicker
|
||||
await click(
|
||||
document.body.querySelector(
|
||||
`.bootstrap-datetimepicker-widget :not(.today)[data-action="selectDay"]`
|
||||
)
|
||||
);
|
||||
await click(getPickerCell("15"));
|
||||
// Close the datepicker
|
||||
await click(
|
||||
document.body.querySelector(
|
||||
`.bootstrap-datetimepicker-widget a[data-action="close"]`
|
||||
)
|
||||
);
|
||||
await click(target);
|
||||
await clickDiscard(target);
|
||||
|
||||
// Open the datepicker again
|
||||
await click(target, ".o_datepicker_input");
|
||||
assert.equal(dsHelpers.getCurrentValue(target), "context_today()");
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -183,48 +228,32 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
});
|
||||
|
||||
// As the domain is empty, there should be a button to add the first
|
||||
// domain part
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_domain_add_first_node_button",
|
||||
"there should be a button to create first domain element"
|
||||
);
|
||||
// As the domain is empty, there should be a button to add a new rule
|
||||
assert.containsOnce(target, dsHelpers.SELECTORS.addNewRule);
|
||||
|
||||
// Clicking on the button should add the [["id", "=", "1"]] domain, so
|
||||
// there should be a field selector in the DOM
|
||||
await click(target, ".o_domain_add_first_node_button");
|
||||
assert.containsOnce(target, ".o_field_selector", "there should be a field selector");
|
||||
await dsHelpers.addNewRule(target);
|
||||
assert.containsOnce(target, ".o_model_field_selector", "there should be a field selector");
|
||||
|
||||
// Focusing the field selector input should open the field selector
|
||||
// popover
|
||||
await click(target, ".o_field_selector");
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".o_field_selector_popover",
|
||||
"field selector popover should be visible"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".o_field_selector_search input",
|
||||
"field selector popover should contain a search input"
|
||||
);
|
||||
await click(target, ".o_model_field_selector");
|
||||
assert.containsOnce(document.body, ".o_model_field_selector_popover");
|
||||
assert.containsOnce(document.body, ".o_model_field_selector_popover_search input");
|
||||
|
||||
// The popover should contain the list of partner_type fields and so
|
||||
// there should be the "Color index" field
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".o_field_selector_item").textContent,
|
||||
"Color index",
|
||||
"field selector popover should contain 'Color index' field"
|
||||
document.body.querySelector(".o_model_field_selector_popover_item_name").textContent,
|
||||
"Color index"
|
||||
);
|
||||
|
||||
// Clicking on this field should close the popover, then changing the
|
||||
// associated value should reveal one matched record
|
||||
await click(document.body.querySelector(".o_field_selector_item"));
|
||||
await click(document.body.querySelector(".o_model_field_selector_popover_item_name"));
|
||||
|
||||
const input = target.querySelector(".o_domain_leaf_value_input");
|
||||
input.value = 2;
|
||||
await triggerEvent(input, null, "change");
|
||||
await dsHelpers.editValue(target, 2);
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_domain_show_selection_button").textContent.trim().substr(0, 2),
|
||||
|
|
@ -235,10 +264,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
// Saving the form view should show a readonly domain containing the
|
||||
// "color" field
|
||||
await clickSave(target);
|
||||
assert.ok(
|
||||
target.querySelector(".o_field_domain").textContent.includes("Color index"),
|
||||
"field selector readonly value should now contain 'Color index'"
|
||||
);
|
||||
assert.ok(target.querySelector(".o_field_domain").textContent.includes("Color index"));
|
||||
});
|
||||
|
||||
QUnit.test("using binary field in domain widget", async function (assert) {
|
||||
|
|
@ -260,9 +286,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
});
|
||||
|
||||
await click(target, ".o_domain_add_first_node_button");
|
||||
await click(target, ".o_field_selector");
|
||||
await click(document.body.querySelector(".o_field_selector_item[data-name='image']"));
|
||||
await dsHelpers.addNewRule(target);
|
||||
await click(target, ".o_model_field_selector");
|
||||
await click(
|
||||
document.body.querySelector(
|
||||
".o_model_field_selector_popover_item[data-name='image'] button"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("domain field is correctly reset on every view change", async function (assert) {
|
||||
|
|
@ -290,15 +320,15 @@ QUnit.module("Fields", (hooks) => {
|
|||
// selector to change this
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_domain .o_field_selector",
|
||||
".o_field_domain .o_model_field_selector",
|
||||
"there should be a field selector"
|
||||
);
|
||||
|
||||
// Focusing its input should open the field selector popover
|
||||
await click(target.querySelector(".o_field_selector"));
|
||||
await click(target.querySelector(".o_model_field_selector"));
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".o_field_selector_popover",
|
||||
".o_model_field_selector_popover",
|
||||
"field selector popover should be visible"
|
||||
);
|
||||
|
||||
|
|
@ -306,11 +336,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
// popover should contain the list of "product" fields
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".o_field_selector_item",
|
||||
".o_model_field_selector_popover_item",
|
||||
"field selector popover should contain only one field"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".o_field_selector_item").textContent,
|
||||
document.body.querySelector(".o_model_field_selector_popover_item").textContent,
|
||||
"Product Name",
|
||||
"field selector popover should contain 'Product Name' field"
|
||||
);
|
||||
|
|
@ -319,22 +349,22 @@ QUnit.module("Fields", (hooks) => {
|
|||
await editInput(target, ".o_field_widget[name='bar'] input", "partner_type");
|
||||
|
||||
// Refocusing the field selector input should open the popover again
|
||||
await click(target.querySelector(".o_field_selector"));
|
||||
await click(target.querySelector(".o_model_field_selector"));
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".o_field_selector_popover",
|
||||
".o_model_field_selector_popover",
|
||||
"field selector popover should be visible"
|
||||
);
|
||||
|
||||
// Now the list of fields should be the ones of the "partner_type" model
|
||||
assert.containsN(
|
||||
document.body,
|
||||
".o_field_selector_item",
|
||||
".o_model_field_selector_popover_item",
|
||||
2,
|
||||
"field selector popover should contain two fields"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".o_field_selector_item").textContent,
|
||||
document.body.querySelector(".o_model_field_selector_popover_item").textContent,
|
||||
"Color index",
|
||||
"field selector popover should contain 'Color index' field"
|
||||
);
|
||||
|
|
@ -405,16 +435,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='foo']:not(.o_field_empty)",
|
||||
"there should be a domain field, not considered empty"
|
||||
);
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_field_widget[name='foo'] .text-warning",
|
||||
"should not display that the domain is invalid"
|
||||
);
|
||||
assert.containsOnce(target, ".o_field_widget[name='foo']:not(.o_field_empty)");
|
||||
assert.containsNone(target, ".o_field_widget[name='foo'] .text-warning");
|
||||
});
|
||||
|
||||
QUnit.test("basic domain field: show the selection", async function (assert) {
|
||||
|
|
@ -533,7 +555,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"2 record(s)"
|
||||
);
|
||||
|
||||
await editInput(target, ".o_domain_debug_input", "[['id', '<', 40]]");
|
||||
await editInput(target, dsHelpers.SELECTORS.debugArea, "[['id', '<', 40]]");
|
||||
// the count should not be re-computed when editing with the textarea
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
|
||||
|
|
@ -588,7 +610,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
throw new Error("should not save");
|
||||
}
|
||||
if (route === "/web/domain/validate") {
|
||||
return JSON.stringify(domain) === "[[\"abc\",\"=\",1]]";
|
||||
return JSON.stringify(domain) === '[["abc","=",1]]';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -601,7 +623,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"2 record(s)"
|
||||
);
|
||||
|
||||
await editInput(target, ".o_domain_debug_input", "[['abc']]");
|
||||
await editInput(target, dsHelpers.SELECTORS.debugArea, "[['abc', '=', 1]]");
|
||||
// the count should not be re-computed when editing with the textarea
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
|
||||
|
|
@ -609,6 +631,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.verifySteps([]);
|
||||
|
||||
await editInput(target, dsHelpers.SELECTORS.debugArea, "[['abc']]");
|
||||
assert.verifySteps([]);
|
||||
|
||||
await clickSave(target);
|
||||
assert.hasClass(
|
||||
target.querySelector(".o_field_domain"),
|
||||
|
|
@ -673,7 +698,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"2 record(s)"
|
||||
);
|
||||
|
||||
await editInput(target, ".o_domain_debug_input", "[['id', '<', 40]]");
|
||||
await editInput(target, dsHelpers.SELECTORS.debugArea, "[['id', '<', 40]]");
|
||||
// the count should not be re-computed when editing with the textarea
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
|
||||
|
|
@ -732,11 +757,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
patchWithCleanup(odoo, { debug: true });
|
||||
|
||||
let rawDomain = `
|
||||
[
|
||||
["date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -365), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S")]
|
||||
]
|
||||
`;
|
||||
let rawDomain = `[("date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -365), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S"))]`;
|
||||
serverData.models.partner.records[0].foo = rawDomain;
|
||||
serverData.models.partner.fields.bar.type = "char";
|
||||
serverData.models.partner.records[0].bar = "partner";
|
||||
|
|
@ -745,7 +766,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"partner,false,form": `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
|
||||
<field name="foo" widget="domain" options="{'model': 'bar', 'allow_expressions':True}"/>
|
||||
</form>`,
|
||||
"partner,false,search": `<search />`,
|
||||
};
|
||||
|
|
@ -764,7 +785,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
const webClient = await createWebClient({
|
||||
serverData,
|
||||
mockRPC(route, { method, args }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(args[1].foo, rawDomain);
|
||||
}
|
||||
if (route === "/web/domain/validate") {
|
||||
|
|
@ -774,21 +795,18 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
await doAction(webClient, 1);
|
||||
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
|
||||
assert.strictEqual(target.querySelector(dsHelpers.SELECTORS.debugArea).value, rawDomain);
|
||||
|
||||
rawDomain = `
|
||||
[
|
||||
["date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -1), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S")]
|
||||
]
|
||||
`;
|
||||
await editInput(target, ".o_domain_debug_input", rawDomain);
|
||||
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
|
||||
rawDomain = `[("date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -1), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S"))]`;
|
||||
await editInput(target, dsHelpers.SELECTORS.debugArea, rawDomain);
|
||||
assert.strictEqual(target.querySelector(dsHelpers.SELECTORS.debugArea).value, rawDomain);
|
||||
|
||||
await clickSave(target);
|
||||
});
|
||||
|
||||
QUnit.test("domain field: edit through selector (dynamic content)", async function (assert) {
|
||||
patchWithCleanup(odoo, { debug: true });
|
||||
patchDate(2020, 8, 5, 0, 0, 0);
|
||||
|
||||
let rawDomain = `[("date", ">=", context_today())]`;
|
||||
serverData.models.partner.records[0].foo = rawDomain;
|
||||
|
|
@ -799,7 +817,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"partner,false,form": `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
|
||||
<field name="foo" widget="domain" options="{'model': 'bar', 'allow_expressions':True}"/>
|
||||
</form>`,
|
||||
"partner,false,search": `<search />`,
|
||||
};
|
||||
|
|
@ -824,29 +842,39 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.verifySteps(["/web/webclient/load_menus"]);
|
||||
|
||||
await doAction(webClient, 1);
|
||||
assert.verifySteps(["/web/action/load", "get_views", "read", "search_count", "fields_get"]);
|
||||
assert.verifySteps([
|
||||
"/web/action/load",
|
||||
"get_views",
|
||||
"web_read",
|
||||
"search_count",
|
||||
"fields_get",
|
||||
]);
|
||||
|
||||
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
|
||||
assert.containsOnce(target, ".o_datepicker", "there should be a datepicker");
|
||||
assert.strictEqual(target.querySelector(dsHelpers.SELECTORS.debugArea).value, rawDomain);
|
||||
|
||||
await dsHelpers.clearNotSupported(target);
|
||||
rawDomain = `[("date", ">=", "2020-09-05")]`;
|
||||
assert.containsOnce(target, ".o_datetime_input", "there should be a datepicker");
|
||||
assert.verifySteps(["search_count"]);
|
||||
|
||||
// Open and close the datepicker
|
||||
await click(target, ".o_datepicker_input");
|
||||
assert.containsOnce(document.body, ".bootstrap-datetimepicker-widget");
|
||||
await click(target, ".o_datetime_input");
|
||||
assert.containsOnce(target, ".o_datetime_picker");
|
||||
await triggerEvent(window, null, "scroll");
|
||||
assert.containsNone(document.body, ".bootstrap-datetimepicker-widget");
|
||||
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
|
||||
assert.containsOnce(target, ".o_datetime_picker");
|
||||
assert.strictEqual(target.querySelector(dsHelpers.SELECTORS.debugArea).value, rawDomain);
|
||||
assert.verifySteps([]);
|
||||
|
||||
// Manually input a date
|
||||
rawDomain = `[("date", ">=", "2020-09-09")]`;
|
||||
await editInput(target, ".o_datepicker_input", "09/09/2020");
|
||||
await editInput(target, ".o_datetime_input", "09/09/2020");
|
||||
assert.verifySteps(["search_count"]);
|
||||
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
|
||||
assert.strictEqual(target.querySelector(dsHelpers.SELECTORS.debugArea).value, rawDomain);
|
||||
|
||||
// Save
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["write", "read", "search_count"]);
|
||||
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
|
||||
assert.verifySteps(["web_save", "search_count"]);
|
||||
assert.strictEqual(target.querySelector(dsHelpers.SELECTORS.debugArea).value, rawDomain);
|
||||
});
|
||||
|
||||
QUnit.test("domain field without model", async function (assert) {
|
||||
|
|
@ -874,13 +902,15 @@ QUnit.module("Fields", (hooks) => {
|
|||
"should contain an error message saying the model is missing"
|
||||
);
|
||||
assert.verifySteps([]);
|
||||
await editInput(target, ".o_field_widget[name=model_name] input", "test");
|
||||
assert.notStrictEqual(
|
||||
target.querySelector('.o_field_widget[name="display_name"]').innerText,
|
||||
"Select a model to add a filter.",
|
||||
"should not contain an error message anymore"
|
||||
|
||||
await editInput(target, ".o_field_widget[name=model_name] input", "partner");
|
||||
assert.strictEqual(
|
||||
target
|
||||
.querySelector('.o_field_widget[name="display_name"] .o_field_domain_panel')
|
||||
.innerText.toLowerCase(),
|
||||
"5 record(s)"
|
||||
);
|
||||
assert.verifySteps(["test"]);
|
||||
assert.verifySteps(["partner"]);
|
||||
});
|
||||
|
||||
QUnit.test("domain field in kanban view", async function (assert) {
|
||||
|
|
@ -936,20 +966,27 @@ QUnit.module("Fields", (hooks) => {
|
|||
<form>
|
||||
<field name="display_name" widget="domain" options="{'model': 'partner', 'in_dialog': True}"/>
|
||||
</form>`,
|
||||
mockRPC: (route) => {
|
||||
if (route === "/web/domain/validate") {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.containsNone(target, ".o_domain_leaf");
|
||||
assert.containsNone(target, dsHelpers.SELECTORS.condition);
|
||||
assert.containsNone(target, ".modal");
|
||||
await click(target, ".o_field_domain_dialog_button");
|
||||
assert.containsOnce(target, ".modal");
|
||||
await click(target, ".modal .o_domain_add_first_node_button");
|
||||
await click(target, `.modal ${dsHelpers.SELECTORS.addNewRule}`);
|
||||
await click(target, ".modal-footer .btn-primary");
|
||||
assert.containsOnce(target, ".o_domain_leaf");
|
||||
assert.strictEqual(target.querySelector(".o_domain_leaf").textContent, "ID = 1");
|
||||
assert.containsOnce(target, dsHelpers.SELECTORS.condition);
|
||||
assert.strictEqual(dsHelpers.getConditionText(target), "ID = 1");
|
||||
});
|
||||
|
||||
QUnit.test("invalid value in domain field with 'inDialog' options", async function (assert) {
|
||||
serverData.models.partner.fields.display_name.default = "[]";
|
||||
|
||||
patchWithCleanup(odoo, {
|
||||
debug: true,
|
||||
});
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -958,33 +995,59 @@ QUnit.module("Fields", (hooks) => {
|
|||
<form>
|
||||
<field name="display_name" widget="domain" options="{'model': 'partner', 'in_dialog': True}"/>
|
||||
</form>`,
|
||||
mockRPC: (route, args) => {
|
||||
if (args.method === "search_count") {
|
||||
const domain = args.args[0];
|
||||
if (domain.length && domain[0][0] === "id" && domain[0][2] === "01/01/2002") {
|
||||
throw new Error("Invalid Domain");
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.containsNone(target, ".o_domain_leaf");
|
||||
assert.containsNone(target, dsHelpers.SELECTORS.condition);
|
||||
assert.containsNone(target, ".modal");
|
||||
assert.containsNone(target, ".o_field_domain .text-warning");
|
||||
|
||||
await click(target, ".o_field_domain_dialog_button");
|
||||
assert.containsOnce(target, ".modal");
|
||||
|
||||
await click(target, ".modal .o_domain_add_first_node_button");
|
||||
await editInput(target, ".o_domain_leaf_value_input", "01/01/2002");
|
||||
await click(target, `.modal ${dsHelpers.SELECTORS.addNewRule}`);
|
||||
await editInput(target, dsHelpers.SELECTORS.debugArea, "[(0, '=', expr)]");
|
||||
await click(target, ".modal-footer .btn-primary");
|
||||
assert.containsOnce(target, ".o_domain_leaf");
|
||||
assert.strictEqual(target.querySelector(".o_domain_leaf").textContent, 'ID = "01/01/2002"');
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_domain .text-warning").textContent.trim(),
|
||||
"Invalid domain"
|
||||
);
|
||||
assert.containsOnce(target, ".modal", "the domain is invalid: the dialog is not closed");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"edit domain button is available even while loading records count",
|
||||
async function (assert) {
|
||||
serverData.models.partner.fields.display_name.default = "[]";
|
||||
patchWithCleanup(odoo, {
|
||||
debug: true,
|
||||
});
|
||||
const searchCountDeffered = makeDeferred();
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="display_name" widget="domain" options="{'model': 'partner', 'in_dialog': True}"/>
|
||||
</form>`,
|
||||
mockRPC: async (route) => {
|
||||
if (route === "/web/dataset/call_kw/partner/search_count") {
|
||||
await searchCountDeffered;
|
||||
}
|
||||
if (route === "/web/domain/validate") {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.containsNone(target, ".modal");
|
||||
assert.containsOnce(target, ".o_field_domain_dialog_button");
|
||||
await click(target, ".o_field_domain_dialog_button");
|
||||
searchCountDeffered.resolve();
|
||||
assert.containsOnce(target, ".modal");
|
||||
await click(target, ".modal-footer .btn-primary");
|
||||
assert.containsNone(target, ".modal");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_domain_show_selection_button").textContent,
|
||||
"5 record(s) "
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"quick check on save if domain has been edited via the debug input",
|
||||
async function (assert) {
|
||||
|
|
@ -1013,7 +1076,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
|
||||
"0 record(s)"
|
||||
);
|
||||
await editInput(target, ".o_domain_debug_input", "[['id', '!=', False]]");
|
||||
await editInput(target, dsHelpers.SELECTORS.debugArea, "[['id', '!=', False]]");
|
||||
await click(target, "button.o_form_button_save");
|
||||
assert.verifySteps(["/web/domain/validate"]);
|
||||
assert.strictEqual(
|
||||
|
|
@ -1022,4 +1085,226 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
}
|
||||
);
|
||||
QUnit.test("domain field can be foldable", async function (assert) {
|
||||
serverData.models.partner.records[0].foo = "[]";
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" widget="domain" options="{'model': 'partner_type', 'foldable': true}" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
// As the domain is empty, the "Match all records" span should be visible
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_domain span").textContent,
|
||||
"Match all records"
|
||||
);
|
||||
|
||||
// Unfold the domain
|
||||
await click(target, ".o_field_domain > div > div");
|
||||
|
||||
// There should be a button to add a new rule
|
||||
assert.containsOnce(target, dsHelpers.SELECTORS.addNewRule);
|
||||
|
||||
// Clicking on the button should add the [["id", "=", "1"]] domain, so
|
||||
// there should be a field selector in the DOM
|
||||
await dsHelpers.addNewRule(target);
|
||||
assert.containsOnce(target, ".o_model_field_selector");
|
||||
|
||||
// Focusing the field selector input should open the field selector
|
||||
// popover
|
||||
await click(target, ".o_model_field_selector");
|
||||
assert.containsOnce(document.body, ".o_model_field_selector_popover");
|
||||
assert.containsOnce(document.body, ".o_model_field_selector_popover_search input");
|
||||
|
||||
// The popover should contain the list of partner_type fields and so
|
||||
// there should be the "Color index" field
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".o_model_field_selector_popover_item_name").textContent,
|
||||
"Color index"
|
||||
);
|
||||
|
||||
// Clicking on this field should close the popover, then changing the
|
||||
// associated value should reveal one matched record
|
||||
await click(document.body.querySelector(".o_model_field_selector_popover_item_name"));
|
||||
|
||||
await dsHelpers.editValue(target, 2);
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_domain_show_selection_button").textContent.trim().substr(0, 2),
|
||||
"1 ",
|
||||
"changing color value to 2 should reveal only one record"
|
||||
);
|
||||
|
||||
// Saving the form view should show a readonly domain containing the
|
||||
// "color" field
|
||||
await clickSave(target);
|
||||
assert.ok(target.querySelector(".o_field_domain").textContent.includes("Color index"));
|
||||
|
||||
// Fold domain selector
|
||||
await click(target, ".o_field_domain a i");
|
||||
|
||||
assert.containsOnce(target, ".o_field_domain .o_facet_values:contains('Color index = 2')");
|
||||
});
|
||||
|
||||
QUnit.test("add condition in empty foldable domain", async function (assert) {
|
||||
patchWithCleanup(odoo, { debug: true });
|
||||
serverData.models.partner.records[0].foo = '[("id", "=", 1)]';
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" widget="domain" options="{'model': 'partner_type', 'foldable': true}" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
// As the domain is not empty, the "Add condition" button should not be available
|
||||
assert.containsNone(target, ".o_domain_add_first_node_button");
|
||||
|
||||
// Unfold the domain and delete the condition
|
||||
await click(target, ".o_field_domain > div > div");
|
||||
await dsHelpers.clickOnButtonDeleteNode(target);
|
||||
|
||||
// Fold domain selector
|
||||
await click(target, ".o_field_domain a i");
|
||||
|
||||
// As the domain is empty, the "Add condition" button should now be available
|
||||
assert.containsOnce(target, ".o_domain_add_first_node_button");
|
||||
|
||||
// Click on "Add condition"
|
||||
await click(target, ".o_domain_add_first_node_button");
|
||||
// Domain is now unfolded with the default condition
|
||||
assert.containsOnce(target, ".o_model_field_selector");
|
||||
assert.strictEqual(
|
||||
target.querySelector(dsHelpers.SELECTORS.debugArea).value,
|
||||
'[("id", "=", 1)]'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"foldable domain field unfolds and hides caret when domain is invalid",
|
||||
async function (assert) {
|
||||
serverData.models.partner.records[0].foo = "[";
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" widget="domain" options="{'model': 'partner_type', 'foldable': true}" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_domain span").textContent,
|
||||
" Invalid domain "
|
||||
);
|
||||
assert.containsNone(target, ".fa-caret-down");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_domain_selector_row").textContent,
|
||||
" This domain is not supported. Reset domain"
|
||||
);
|
||||
await click(target, ".o_domain_selector_row button");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_domain span").textContent,
|
||||
"Match all records"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("allow_expressions = true", async function (assert) {
|
||||
serverData.models.partner.records[0].foo = "[]";
|
||||
|
||||
patchWithCleanup(odoo, { debug: true });
|
||||
replaceNotificationService(assert);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" widget="domain" options="{'model': 'partner_type', 'allow_expressions':True}" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC(route) {
|
||||
if (route === "/web/domain/validate") {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
context: { path: "name", name: "name" },
|
||||
});
|
||||
|
||||
await editInput(target, dsHelpers.SELECTORS.debugArea, `[("name", "=", [name])]`);
|
||||
assert.doesNotHaveClass(target.querySelector(".o_field_domain"), "o_field_invalid");
|
||||
assert.verifySteps(["The domain involves non-literals. Their evaluation might fail."]);
|
||||
|
||||
await editInput(
|
||||
target,
|
||||
dsHelpers.SELECTORS.debugArea,
|
||||
`["&", ("name", "=", "name"), (path, "=", "other name")]`
|
||||
);
|
||||
assert.doesNotHaveClass(target.querySelector(".o_field_domain"), "o_field_invalid");
|
||||
assert.verifySteps(["The domain involves non-literals. Their evaluation might fail."]);
|
||||
});
|
||||
|
||||
QUnit.test("allow_expressions = false (default)", async function (assert) {
|
||||
serverData.models.partner.records[0].foo = "[]";
|
||||
|
||||
patchWithCleanup(odoo, { debug: true });
|
||||
replaceNotificationService(assert);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" widget="domain" options="{'model': 'partner_type' }" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
context: { path: "name", name: "name" },
|
||||
});
|
||||
|
||||
await editInput(target, dsHelpers.SELECTORS.debugArea, `[("name", "=", name)]`);
|
||||
assert.hasClass(target.querySelector(".o_field_domain"), "o_field_invalid");
|
||||
assert.verifySteps(["The domain should not involve non-literals"]);
|
||||
|
||||
await editInput(
|
||||
target,
|
||||
dsHelpers.SELECTORS.debugArea,
|
||||
`["&", ("name", "=", "name"), (path, "=", 1)]`
|
||||
);
|
||||
assert.hasClass(target.querySelector(".o_field_domain"), "o_field_invalid");
|
||||
assert.verifySteps(["The domain should not involve non-literals"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { click, getFixture, triggerEvent, triggerHotkey } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
QUnit.module("Dynamic placeholder", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
char: {
|
||||
string: "Char",
|
||||
type: "char",
|
||||
},
|
||||
placeholder: {
|
||||
string: "Placeholder",
|
||||
type: "char",
|
||||
default: "partner",
|
||||
},
|
||||
product_id: {
|
||||
string: "Product",
|
||||
type: "many2one",
|
||||
relation: "product",
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
char: "yop",
|
||||
product_id: 37,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
char: "blip",
|
||||
product_id: 41,
|
||||
},
|
||||
],
|
||||
},
|
||||
product: {
|
||||
fields: {
|
||||
name: { string: "Product Name", type: "char", searchable: true },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 37,
|
||||
name: "xphone",
|
||||
},
|
||||
{
|
||||
id: 41,
|
||||
name: "xpad",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.test("dynamic placeholder close on click out", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="placeholder" invisible="1"/>
|
||||
<sheet>
|
||||
<group>
|
||||
<field
|
||||
name="char"
|
||||
options="{
|
||||
'dynamic_placeholder': true,
|
||||
'dynamic_placeholder_model_reference_field': 'placeholder'
|
||||
}"
|
||||
/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
await triggerEvent(target, ".o_field_char input", "keydown", { key: "#" });
|
||||
assert.containsOnce(target, ".o_model_field_selector_popover");
|
||||
await click(target, ".o_content");
|
||||
assert.containsNone(target, ".o_model_field_selector_popover");
|
||||
await triggerEvent(target, ".o_field_char input", "keydown", { key: "#" });
|
||||
await click(target, ".o_model_field_selector_popover_item_relation");
|
||||
await click(target, ".o_content");
|
||||
assert.containsNone(target, ".o_model_field_selector_popover");
|
||||
});
|
||||
|
||||
QUnit.test("dynamic placeholder close with escape", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="placeholder" invisible="1"/>
|
||||
<sheet>
|
||||
<group>
|
||||
<field
|
||||
name="char"
|
||||
options="{
|
||||
'dynamic_placeholder': true,
|
||||
'dynamic_placeholder_model_reference_field': 'placeholder'
|
||||
}"
|
||||
/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
await triggerEvent(target, ".o_field_char input", "keydown", { key: "#" });
|
||||
assert.containsOnce(target, ".o_model_field_selector_popover");
|
||||
await triggerHotkey("Escape");
|
||||
assert.containsNone(target, ".o_model_field_selector_popover");
|
||||
await triggerEvent(target, ".o_field_char input", "keydown", { key: "#" });
|
||||
await click(target, ".o_model_field_selector_popover_item_relation");
|
||||
await triggerHotkey("Escape");
|
||||
assert.containsNone(target, ".o_model_field_selector_popover");
|
||||
});
|
||||
|
||||
QUnit.test("dynamic placeholder close when clicking on the cross", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="placeholder" invisible="1"/>
|
||||
<sheet>
|
||||
<group>
|
||||
<field
|
||||
name="char"
|
||||
options="{
|
||||
'dynamic_placeholder': true,
|
||||
'dynamic_placeholder_model_reference_field': 'placeholder'
|
||||
}"
|
||||
/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
await triggerEvent(target, ".o_field_char input", "keydown", { key: "#" });
|
||||
assert.containsOnce(target, ".o_model_field_selector_popover");
|
||||
await click(target, ".o_model_field_selector_popover_close");
|
||||
assert.containsNone(target, ".o_model_field_selector_popover");
|
||||
await triggerEvent(target, ".o_field_char input", "keydown", { key: "#" });
|
||||
await click(target, ".o_model_field_selector_popover_item_relation");
|
||||
await click(target, ".o_model_field_selector_popover_close");
|
||||
assert.containsNone(target, ".o_model_field_selector_popover");
|
||||
});
|
||||
});
|
||||
|
|
@ -75,6 +75,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
"should have rendered the email button as a link with correct classes"
|
||||
);
|
||||
assert.hasAttrValue(emailBtn, "href", "mailto:yop", "should have proper mailto prefix");
|
||||
assert.hasAttrValue(
|
||||
emailBtn,
|
||||
"target",
|
||||
"_blank",
|
||||
"should have target attribute set to _blank"
|
||||
);
|
||||
|
||||
// change value in edit mode
|
||||
await editInput(target, ".o_field_email input[type='email']", "new");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { editSelect, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
// Note: the containsN always check for one more as there will be an invisible empty option every time.
|
||||
QUnit.module("Fields", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
program: {
|
||||
fields: {
|
||||
program_type: {
|
||||
type: "selection",
|
||||
selection: [
|
||||
["coupon", "Coupons"],
|
||||
["promotion", "Promotion"],
|
||||
["gift_card", "gift_card"],
|
||||
],
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
records: [
|
||||
{ id: 1, program_type: "coupon" },
|
||||
{ id: 2, program_type: "gift_card" },
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("utils");
|
||||
|
||||
QUnit.test("FilterableSelectionField test whitelist", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "program",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="program_type" widget="filterable_selection" options="{'whitelisted_values': ['coupons', 'promotion']}"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsN(target, "select option", 3);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='program_type'] select option[value='\"coupon\"']",
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='program_type'] select option[value='\"promotion\"']",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("FilterableSelectionField test blacklist", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "program",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="program_type" widget="filterable_selection" options="{'blacklisted_values': ['gift_card']}"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsN(target, "select option", 3);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='program_type'] select option[value='\"coupon\"']",
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='program_type'] select option[value='\"promotion\"']",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("FilterableSelectionField test with invalid value", async (assert) => {
|
||||
// The field should still display the current value in the list
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "program",
|
||||
resId: 2,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="program_type" widget="filterable_selection" options="{'blacklisted_values': ['gift_card']}"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsN(target, "select option", 4);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='program_type'] select option[value='\"gift_card\"']",
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='program_type'] select option[value='\"coupon\"']",
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='program_type'] select option[value='\"promotion\"']",
|
||||
);
|
||||
|
||||
await editSelect(target, ".o_field_widget[name='program_type'] select", '"coupon"');
|
||||
assert.containsN(target, "select option", 3);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='program_type'] select option[value='\"coupon\"']",
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='program_type'] select option[value='\"promotion\"']",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { clickSave, editInput, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
|
@ -39,7 +41,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</sheet>
|
||||
</form>`,
|
||||
mockRPC(route, { args }) {
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
// 2.3 / 0.5 = 4.6
|
||||
assert.strictEqual(args[1].qux, 4.6, "the correct float value should be saved");
|
||||
}
|
||||
|
|
@ -60,4 +62,41 @@ QUnit.module("Fields", (hooks) => {
|
|||
"The new value should be saved and displayed properly."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("FloatFactorField comma as decimal point", async function (assert) {
|
||||
assert.expect(3);
|
||||
registry.category("services").remove("localization");
|
||||
registry.category("services").add(
|
||||
"localization",
|
||||
makeFakeLocalizationService({
|
||||
decimalPoint: ",",
|
||||
thousandsSep: "",
|
||||
})
|
||||
);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="qux" widget="float_factor" options="{'factor': 0.5}" digits="[16,2]" />
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC(route, { args }) {
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
// 2.3 / 0.5 = 4.6
|
||||
assert.strictEqual(args[1].qux, 4.6, "the correct float value should be saved");
|
||||
assert.step("save");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await editInput(target, ".o_field_widget[name='qux'] input", "2,3");
|
||||
await clickSave(target);
|
||||
|
||||
assert.verifySteps(["save"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
||||
import { click, clickSave, editInput, getFixture, triggerEvent } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
|
@ -24,6 +25,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
{ id: 3, float_field: -3.89859 },
|
||||
{ id: 4, float_field: false },
|
||||
{ id: 5, float_field: 9.1 },
|
||||
{ id: 100, float_field: 2.034567e3 },
|
||||
{ id: 101, float_field: 3.75675456e6 },
|
||||
{ id: 102, float_field: 6.67543577586e12 },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
@ -34,6 +38,66 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.module("FloatField");
|
||||
|
||||
QUnit.test("human readable format 1", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 101,
|
||||
arch: `<form><field name="float_field" options="{'human_readable': 'true'}"/></form>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"4M",
|
||||
"The value should be rendered in human readable format (k, M, G, T)."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("human readable format 2", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 100,
|
||||
arch: `<form><field name="float_field" options="{'human_readable': 'true', 'decimals': 1}"/></form>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"2.0k",
|
||||
"The value should be rendered in human readable format (k, M, G, T)."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("human readable format 3", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 102,
|
||||
arch: `<form><field name="float_field" options="{'human_readable': 'true', 'decimals': 4}"/></form>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"6.6754T",
|
||||
"The value should be rendered in human readable format (k, M, G, T)."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("still human readable when readonly", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 102,
|
||||
arch: `<form><field readonly="true" name="float_field" options="{'human_readable': 'true', 'decimals': 4}"/></form>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget span").textContent,
|
||||
"6.6754T",
|
||||
"The value should be rendered in human readable format when input is readonly."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("unset field should be set to 0", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -251,8 +315,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
// switch to edit mode
|
||||
var cell = target.querySelector("tr.o_data_row td:not(.o_list_record_selector)");
|
||||
await click(cell);
|
||||
await click(target.querySelector("tr.o_data_row td:not(.o_list_record_selector)"));
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
|
|
@ -268,7 +331,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
await editInput(target, 'div[name="float_field"] input', "18.8958938598598");
|
||||
await click(target.querySelector(".o_list_button_save"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
)
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget").textContent,
|
||||
"18.896",
|
||||
|
|
@ -389,6 +456,81 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("field with enable_formatting option as false", async function (assert) {
|
||||
registry.category("services").remove("localization");
|
||||
registry
|
||||
.category("services")
|
||||
.add(
|
||||
"localization",
|
||||
makeFakeLocalizationService({ thousandsSep: ",", grouping: [3, 0] })
|
||||
);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: `<form><field name="float_field" options="{'enable_formatting': false}"/></form>`,
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"0.36",
|
||||
"Integer value must not be formatted"
|
||||
);
|
||||
|
||||
await editInput(target, ".o_field_widget[name=float_field] input", "123456.789");
|
||||
await clickSave(target);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"123456.789",
|
||||
"Integer value must be not formatted if input type is number."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"field with enable_formatting option as false in editable list view",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="float_field" widget="float" digits="[5,3]" options="{'enable_formatting': false}" />
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
// switch to edit mode
|
||||
await click(target.querySelector("tr.o_data_row td:not(.o_list_record_selector)"));
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
'div[name="float_field"] input',
|
||||
"The view should have 1 input for editable float."
|
||||
);
|
||||
|
||||
await editInput(target, 'div[name="float_field"] input', "108.2458938598598");
|
||||
assert.strictEqual(
|
||||
target.querySelector('div[name="float_field"] input').value,
|
||||
"108.2458938598598",
|
||||
"The value should not be formatted on blur."
|
||||
);
|
||||
|
||||
await editInput(target, 'div[name="float_field"] input', "18.8958938598598");
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
)
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget").textContent,
|
||||
"18.8958938598598",
|
||||
"The new value should not be rounded as well."
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("float_field field with placeholder", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -407,14 +549,17 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test("float field can be updated by another field/widget", async function (assert) {
|
||||
class MyWidget extends owl.Component {
|
||||
class MyWidget extends Component {
|
||||
static template = xml`<button t-on-click="onClick">do it</button>`;
|
||||
onClick() {
|
||||
const val = this.props.record.data.float_field;
|
||||
this.props.record.update({ float_field: val + 1 });
|
||||
}
|
||||
}
|
||||
MyWidget.template = owl.xml`<button t-on-click="onClick">do it</button>`;
|
||||
registry.category("view_widgets").add("wi", MyWidget);
|
||||
const myWidget = {
|
||||
component: MyWidget,
|
||||
};
|
||||
registry.category("view_widgets").add("wi", myWidget);
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</sheet>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
// 48 / 60 = 0.8
|
||||
assert.strictEqual(
|
||||
args.args[1].qux,
|
||||
|
|
@ -89,7 +89,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="qux" widget="float_time"/>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
assert.strictEqual(
|
||||
args.args[1].qux,
|
||||
9.5,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="float_field" widget="float_toggle" options="{'factor': 0.125, 'range': [0, 1, 0.75, 0.5, 0.25]}" digits="[5,3]"/>
|
||||
</form>`,
|
||||
mockRPC(route, { args }) {
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
// 1.000 / 0.125 = 8
|
||||
assert.step(args[1].float_field.toString());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { markup } from "@odoo/owl";
|
||||
import { defaultLocalization } from "@web/../tests/helpers/mock_services";
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { currencies } from "@web/core/currency";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { session } from "@web/session";
|
||||
import {
|
||||
formatFloat,
|
||||
formatFloatFactor,
|
||||
|
|
@ -14,6 +15,7 @@ import {
|
|||
formatMonetary,
|
||||
formatPercentage,
|
||||
formatReference,
|
||||
formatText,
|
||||
formatX2many,
|
||||
} from "@web/views/fields/formatters";
|
||||
|
||||
|
|
@ -26,135 +28,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.test("formatFloat", function (assert) {
|
||||
assert.strictEqual(formatFloat(false), "");
|
||||
assert.strictEqual(formatFloat(null), "0.00");
|
||||
assert.strictEqual(formatFloat(1000000), "1,000,000.00");
|
||||
|
||||
const options = { grouping: [3, 2, -1], decimalPoint: "?", thousandsSep: "€" };
|
||||
assert.strictEqual(formatFloat(106500, options), "1€06€500?00");
|
||||
|
||||
assert.strictEqual(formatFloat(1500, { thousandsSep: "" }), "1500.00");
|
||||
assert.strictEqual(formatFloat(-1.01), "-1.01");
|
||||
assert.strictEqual(formatFloat(-0.01), "-0.01");
|
||||
|
||||
assert.strictEqual(formatFloat(38.0001, { noTrailingZeros: true }), "38");
|
||||
assert.strictEqual(formatFloat(38.1, { noTrailingZeros: true }), "38.1");
|
||||
|
||||
patchWithCleanup(localization, { grouping: [3, 3, 3, 3] });
|
||||
assert.strictEqual(formatFloat(1000000), "1,000,000.00");
|
||||
|
||||
patchWithCleanup(localization, { grouping: [3, 2, -1] });
|
||||
assert.strictEqual(formatFloat(106500), "1,06,500.00");
|
||||
|
||||
patchWithCleanup(localization, { grouping: [1, 2, -1] });
|
||||
assert.strictEqual(formatFloat(106500), "106,50,0.00");
|
||||
|
||||
patchWithCleanup(localization, {
|
||||
grouping: [2, 0],
|
||||
decimalPoint: "!",
|
||||
thousandsSep: "@",
|
||||
});
|
||||
assert.strictEqual(formatFloat(6000), "60@00!00");
|
||||
});
|
||||
|
||||
QUnit.test("formatFloat (humanReadable=true)", async (assert) => {
|
||||
assert.strictEqual(
|
||||
formatFloat(1020, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"1.02k"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(1020000, { humanReadable: true, decimals: 2, minDigits: 2 }),
|
||||
"1,020k"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(10200000, { humanReadable: true, decimals: 2, minDigits: 2 }),
|
||||
"10.20M"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(1020, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"1.02k"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(1002, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"1.00k"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(101, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"101.00"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(64.2, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"64.20"
|
||||
);
|
||||
assert.strictEqual(formatFloat(1e18, { humanReadable: true }), "1E");
|
||||
assert.strictEqual(
|
||||
formatFloat(1e21, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"1e+21"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(1.0045e22, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"1e+22"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(1.0045e22, { humanReadable: true, decimals: 3, minDigits: 1 }),
|
||||
"1.005e+22"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(1.012e43, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"1.01e+43"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(1.012e43, { humanReadable: true, decimals: 2, minDigits: 2 }),
|
||||
"1.01e+43"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-1020, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"-1.02k"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-1020000, { humanReadable: true, decimals: 2, minDigits: 2 }),
|
||||
"-1,020k"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-10200000, { humanReadable: true, decimals: 2, minDigits: 2 }),
|
||||
"-10.20M"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-1020, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"-1.02k"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-1002, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"-1.00k"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-101, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"-101.00"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-64.2, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"-64.20"
|
||||
);
|
||||
assert.strictEqual(formatFloat(-1e18, { humanReadable: true }), "-1E");
|
||||
assert.strictEqual(
|
||||
formatFloat(-1e21, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"-1e+21"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-1.0045e22, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"-1e+22"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-1.0045e22, { humanReadable: true, decimals: 3, minDigits: 1 }),
|
||||
"-1.004e+22"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-1.012e43, { humanReadable: true, decimals: 2, minDigits: 1 }),
|
||||
"-1.01e+43"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatFloat(-1.012e43, { humanReadable: true, decimals: 2, minDigits: 2 }),
|
||||
"-1.01e+43"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("formatFloatFactor", function (assert) {
|
||||
|
|
@ -234,10 +107,21 @@ QUnit.module("Fields", (hooks) => {
|
|||
QUnit.test("formatMany2one", function (assert) {
|
||||
assert.strictEqual(formatMany2one(false), "");
|
||||
assert.strictEqual(formatMany2one([false, "M2O value"]), "M2O value");
|
||||
assert.strictEqual(formatMany2one([1, false]), "Unnamed");
|
||||
assert.strictEqual(formatMany2one([1, "M2O value"]), "M2O value");
|
||||
assert.strictEqual(formatMany2one([1, "M2O value"], { escape: true }), "M2O%20value");
|
||||
});
|
||||
|
||||
QUnit.test("formatText", function (assert) {
|
||||
assert.strictEqual(formatText(false), "");
|
||||
assert.strictEqual(formatText("value"), "value");
|
||||
assert.strictEqual(formatText(1), "1");
|
||||
assert.strictEqual(formatText(1.5), "1.5");
|
||||
assert.strictEqual(formatText(markup("<p>This is a Test</p>")), "<p>This is a Test</p>");
|
||||
assert.strictEqual(formatText([1, 2, 3, 4, 5]), "1,2,3,4,5");
|
||||
assert.strictEqual(formatText({ a: 1, b: 2 }), "[object Object]");
|
||||
});
|
||||
|
||||
QUnit.test("formatX2many", function (assert) {
|
||||
// Results are cast as strings since they're lazy translated.
|
||||
assert.strictEqual(String(formatX2many({ currentIds: [] })), "No records");
|
||||
|
|
@ -246,7 +130,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test("formatMonetary", function (assert) {
|
||||
patchWithCleanup(session.currencies, {
|
||||
patchWithCleanup(currencies, {
|
||||
10: {
|
||||
digits: [69, 2],
|
||||
position: "after",
|
||||
|
|
@ -265,67 +149,24 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.strictEqual(formatMonetary(false), "");
|
||||
assert.strictEqual(formatMonetary(200), "200.00");
|
||||
|
||||
assert.deepEqual(formatMonetary(1234567.654, { currencyId: 10 }), "1,234,567.65\u00a0€");
|
||||
assert.deepEqual(formatMonetary(1234567.654, { currencyId: 11 }), "$\u00a01,234,567.65");
|
||||
assert.deepEqual(formatMonetary(1234567.654, { currencyId: 44 }), "1,234,567.65");
|
||||
assert.deepEqual(
|
||||
formatMonetary(1234567.654, { currencyId: 10, noSymbol: true }),
|
||||
"1,234,567.65"
|
||||
);
|
||||
assert.deepEqual(
|
||||
formatMonetary(8.0, { currencyId: 10, humanReadable: true }),
|
||||
"8.00\u00a0€"
|
||||
);
|
||||
assert.deepEqual(
|
||||
formatMonetary(1234567.654, { currencyId: 10, humanReadable: true }),
|
||||
"1.23M\u00a0€"
|
||||
);
|
||||
assert.deepEqual(
|
||||
formatMonetary(1990000.001, { currencyId: 10, humanReadable: true }),
|
||||
"1.99M\u00a0€"
|
||||
);
|
||||
assert.deepEqual(
|
||||
formatMonetary(1234567.654, { currencyId: 44, digits: [69, 1] }),
|
||||
"1,234,567.7"
|
||||
);
|
||||
assert.deepEqual(
|
||||
formatMonetary(1234567.654, { currencyId: 11, digits: [69, 1] }),
|
||||
"$\u00a01,234,567.7",
|
||||
"options digits should take over currency digits when both are defined"
|
||||
);
|
||||
const field = {
|
||||
type: "monetary",
|
||||
currency_field: "c_x",
|
||||
};
|
||||
let data = {
|
||||
c_x: [11],
|
||||
c_y: 12,
|
||||
};
|
||||
assert.deepEqual(formatMonetary(200, { field, currencyId: 10, data }), "200.00\u00a0€");
|
||||
assert.deepEqual(formatMonetary(200, { field, data }), "$\u00a0200.00");
|
||||
assert.deepEqual(formatMonetary(200, { field, currencyField: "c_y", data }), "200.00\u00a0&");
|
||||
|
||||
// GES TODO do we keep below behavior ?
|
||||
// with field and data
|
||||
// const field = {
|
||||
// type: "monetary",
|
||||
// currency_field: "c_x",
|
||||
// };
|
||||
// let data = {
|
||||
// c_x: { res_id: 11 },
|
||||
// c_y: { res_id: 12 },
|
||||
// };
|
||||
// assert.strictEqual(formatMonetary(200, { field, currencyId: 10, data }), "200.00 €");
|
||||
// assert.strictEqual(formatMonetary(200, { field, data }), "$ 200.00");
|
||||
// assert.strictEqual(formatMonetary(200, { field, currencyField: "c_y", data }), "200.00 &");
|
||||
//
|
||||
// const floatField = { type: "float" };
|
||||
// data = {
|
||||
// currency_id: { res_id: 11 },
|
||||
// };
|
||||
// assert.strictEqual(formatMonetary(200, { field: floatField, data }), "$ 200.00");
|
||||
});
|
||||
|
||||
QUnit.test("formatMonetary without currency", function (assert) {
|
||||
patchWithCleanup(session, {
|
||||
currencies: {},
|
||||
});
|
||||
assert.deepEqual(
|
||||
formatMonetary(1234567.654, { currencyId: 10, humanReadable: true }),
|
||||
"1.23M"
|
||||
);
|
||||
assert.deepEqual(formatMonetary(1234567.654, { currencyId: 10 }), "1,234,567.65");
|
||||
const floatField = { type: "float" };
|
||||
data = {
|
||||
currency_id: [11],
|
||||
};
|
||||
assert.deepEqual(formatMonetary(200, { field: floatField, data }), "$\u00a0200.00");
|
||||
});
|
||||
|
||||
QUnit.test("formatPercentage", function (assert) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { onMounted } from "@odoo/owl";
|
||||
import { getFixture, getNodesTextContent, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { GaugeField } from "@web/views/fields/gauge/gauge_field";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
QUnit.module("Fields", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
int_field: {
|
||||
string: "int_field",
|
||||
type: "integer",
|
||||
},
|
||||
another_int_field: {
|
||||
string: "another_int_field",
|
||||
type: "integer",
|
||||
},
|
||||
},
|
||||
records: [
|
||||
{ id: 1, int_field: 10, another_int_field: 45 },
|
||||
{ id: 2, int_field: 4, another_int_field: 10 },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("GaugeField");
|
||||
|
||||
QUnit.test("GaugeField in kanban view", async function (assert) {
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<field name="another_int_field"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="int_field" widget="gauge" options="{'max_field': 'another_int_field'}"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
|
||||
assert.containsN(target, ".o_kanban_record:not(.o_kanban_ghost)", 2);
|
||||
assert.containsN(target, ".o_field_widget[name=int_field] .oe_gauge canvas", 2);
|
||||
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_gauge_value")), [
|
||||
"10",
|
||||
"4",
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("GaugeValue supports max_value option", async function (assert) {
|
||||
patchWithCleanup(GaugeField.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
onMounted(() => {
|
||||
assert.step("gauge mounted");
|
||||
assert.strictEqual(this.chart.config.options.plugins.tooltip.callbacks.label({}), "Max: 120");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
serverData.models.partner.records = serverData.models.partner.records.slice(0,1);
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="int_field" widget="gauge" options="{'max_value': 120}"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
|
||||
assert.verifySteps(["gauge mounted"]);
|
||||
assert.containsN(target, ".o_field_widget[name=int_field] .oe_gauge canvas", 1);
|
||||
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_gauge_value")), [
|
||||
"10",
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { HtmlField } from "@web/views/fields/html/html_field";
|
||||
import { htmlField } from "@web/views/fields/html/html_field";
|
||||
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
||||
import { session } from "@web/session";
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ QUnit.module("Fields", ({ beforeEach }) => {
|
|||
setupViewRegistries();
|
||||
|
||||
// Explicitly removed by web_editor, we need to add it back
|
||||
registry.category("fields").add("html", HtmlField, { force: true });
|
||||
registry.category("fields").add("html", htmlField, { force: true });
|
||||
});
|
||||
|
||||
QUnit.module("HtmlField");
|
||||
|
|
@ -295,4 +295,99 @@ QUnit.module("Fields", ({ beforeEach }) => {
|
|||
|
||||
await click(target, ".modal button.btn-primary"); // save
|
||||
});
|
||||
|
||||
QUnit.test("html fields: spellcheck is disabled on blur", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: /* xml */ `<form><field name="txt" /></form>`,
|
||||
});
|
||||
|
||||
const textarea = target.querySelector(".o_field_html textarea");
|
||||
assert.strictEqual(textarea.spellcheck, true, "by default, spellcheck is enabled");
|
||||
textarea.focus();
|
||||
|
||||
await editInput(textarea, null, "nev walue");
|
||||
textarea.blur();
|
||||
assert.strictEqual(
|
||||
textarea.spellcheck,
|
||||
false,
|
||||
"spellcheck is disabled once the field has lost its focus"
|
||||
);
|
||||
textarea.focus();
|
||||
assert.strictEqual(
|
||||
textarea.spellcheck,
|
||||
true,
|
||||
"spellcheck is re-enabled once the field is focused"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Setting an html field to empty string is saved as a false value",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="txt" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(args[1].txt, false, "the txt value should be false");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await editInput(target, ".o_field_widget[name=txt] textarea", "");
|
||||
await clickSave(target);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"html field: correct value is used to evaluate the modifiers",
|
||||
async function (assert) {
|
||||
serverData.models.partner.fields.foo = { string: "foo", type: "char" };
|
||||
serverData.models.partner.onchanges = {
|
||||
foo: (obj) => {
|
||||
if (obj.foo === "a") {
|
||||
obj.txt = false;
|
||||
} else if (obj.foo === "b") {
|
||||
obj.txt = "";
|
||||
}
|
||||
},
|
||||
};
|
||||
serverData.models.partner.records[0].foo = false;
|
||||
serverData.models.partner.records[0].txt = false;
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="foo" />
|
||||
<field name="txt" invisible="'' == txt"/>
|
||||
</form>`,
|
||||
});
|
||||
assert.containsOnce(target, "[name='txt'] textarea");
|
||||
|
||||
await editInput(target, "[name='foo'] input", "a");
|
||||
assert.containsOnce(target, "[name='txt'] textarea");
|
||||
|
||||
await editInput(target, "[name='foo'] input", "b");
|
||||
assert.containsNone(target, "[name='txt'] textarea");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ let target;
|
|||
|
||||
function getUnique(target) {
|
||||
const src = target.dataset.src;
|
||||
return new URL(src).searchParams.get("unique");
|
||||
return new URL(src, window.location).searchParams.get("unique");
|
||||
}
|
||||
|
||||
QUnit.module("Fields", (hooks) => {
|
||||
|
|
@ -87,7 +87,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
QUnit.test("ImageField is correctly rendered", async function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
serverData.models.partner.records[0].__last_update = "2017-02-08 10:00:00";
|
||||
serverData.models.partner.records[0].write_date = "2017-02-08 10:00:00";
|
||||
serverData.models.partner.records[0].document = MY_IMAGE;
|
||||
|
||||
await makeView({
|
||||
|
|
@ -99,12 +99,16 @@ QUnit.module("Fields", (hooks) => {
|
|||
<form>
|
||||
<field name="document" widget="image" options="{'size': [90, 90]}" />
|
||||
</form>`,
|
||||
mockRPC(route, { args }) {
|
||||
if (route === "/web/dataset/call_kw/partner/read") {
|
||||
mockRPC(route, { args, kwargs }) {
|
||||
if (route === "/web/dataset/call_kw/partner/web_read") {
|
||||
assert.deepEqual(
|
||||
args[1],
|
||||
["__last_update", "document", "display_name"],
|
||||
"The fields document, display_name and __last_update should be present when reading an image"
|
||||
kwargs.specification,
|
||||
{
|
||||
display_name: {},
|
||||
document: {},
|
||||
write_date: {},
|
||||
},
|
||||
"The fields document, display_name and write_date should be present when reading an image"
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -295,7 +299,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
|
||||
rec.document = "3 kb";
|
||||
rec.__last_update = "2022-08-05 08:37:00"; // 1659688620000
|
||||
rec.write_date = "2022-08-05 08:37:00"; // 1659688620000
|
||||
|
||||
// 1659692220000, 1659695820000
|
||||
const lastUpdates = ["2022-08-05 09:37:00", "2022-08-05 10:37:00"];
|
||||
|
|
@ -312,8 +316,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="document" widget="image" />
|
||||
</form>`,
|
||||
mockRPC(_route, { method, args }) {
|
||||
if (method === "write") {
|
||||
args[1].__last_update = lastUpdates[index];
|
||||
if (method === "web_save") {
|
||||
args[1].write_date = lastUpdates[index];
|
||||
args[1].document = "4 kb";
|
||||
index++;
|
||||
}
|
||||
|
|
@ -381,7 +385,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
};
|
||||
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
|
||||
rec.document = "3 kb";
|
||||
rec.__last_update = "2022-08-05 08:37:00"; // 1659688620000
|
||||
rec.write_date = "2022-08-05 08:37:00"; // 1659688620000
|
||||
|
||||
// 1659692220000
|
||||
const lastUpdates = ["2022-08-05 09:37:00"];
|
||||
|
|
@ -398,8 +402,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="document" widget="image" />
|
||||
</form>`,
|
||||
mockRPC(_route, { method, args }) {
|
||||
if (method === "write") {
|
||||
args[1].__last_update = lastUpdates[index];
|
||||
if (method === "web_save") {
|
||||
args[1].write_date = lastUpdates[index];
|
||||
args[1].document = "3 kb";
|
||||
index++;
|
||||
}
|
||||
|
|
@ -517,7 +521,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.test("ImageField: zoom and zoom_delay options (edit)", async function (assert) {
|
||||
serverData.models.partner.records[0].document = "3 kb";
|
||||
serverData.models.partner.records[0].__last_update = "2022-08-05 08:37:00";
|
||||
serverData.models.partner.records[0].write_date = "2022-08-05 08:37:00";
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -547,7 +551,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"ImageField displays the right images with zoom and preview_image options (readonly)",
|
||||
async function (assert) {
|
||||
serverData.models.partner.records[0].document = "3 kb";
|
||||
serverData.models.partner.records[0].__last_update = "2022-08-05 08:37:00";
|
||||
serverData.models.partner.records[0].write_date = "2022-08-05 08:37:00";
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -583,7 +587,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
QUnit.test("ImageField in subviews is loaded correctly", async function (assert) {
|
||||
serverData.models.partner.records[0].__last_update = "2017-02-08 10:00:00";
|
||||
serverData.models.partner.records[0].write_date = "2017-02-08 10:00:00";
|
||||
serverData.models.partner.records[0].document = MY_IMAGE;
|
||||
serverData.models.partner_type.fields.image = {
|
||||
name: "image",
|
||||
|
|
@ -708,7 +712,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
fileInput.files = list.files;
|
||||
fileInput.dispatchEvent(new Event("change"));
|
||||
// It can take some time to encode the data as a base64 url
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
// Wait for a render
|
||||
await nextTick();
|
||||
}
|
||||
|
|
@ -728,7 +732,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
await clickSave(target);
|
||||
await click(target, ".o_form_button_create");
|
||||
await click(target, ".o_control_panel_main_buttons .d-none .o_form_button_create");
|
||||
assert.strictEqual(
|
||||
target.querySelector("img[data-alt='Binary file']").dataset.src,
|
||||
"/web/static/img/placeholder.png",
|
||||
|
|
@ -751,7 +755,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
|
||||
rec.document = "3 kb";
|
||||
rec.__last_update = "2022-08-05 08:37:00";
|
||||
rec.write_date = "2022-08-05 08:37:00";
|
||||
|
||||
await makeView({
|
||||
resId: 1,
|
||||
|
|
@ -765,14 +769,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
mockRPC(route, { method, args }) {
|
||||
assert.step(method);
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
// 1659692220000
|
||||
args[1].__last_update = "2022-08-05 09:37:00";
|
||||
args[1].write_date = "2022-08-05 09:37:00";
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.verifySteps(["get_views", "read"]);
|
||||
assert.verifySteps(["get_views", "web_read"]);
|
||||
assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659688620000");
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
|
@ -785,7 +789,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659688620000");
|
||||
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["write", "read"]);
|
||||
assert.verifySteps(["web_save"]);
|
||||
|
||||
assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659692220000");
|
||||
});
|
||||
|
|
@ -793,11 +797,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
QUnit.test("unique in url change on record change", async (assert) => {
|
||||
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
|
||||
rec.document = "3 kb";
|
||||
rec.__last_update = "2022-08-05 08:37:00";
|
||||
rec.write_date = "2022-08-05 08:37:00";
|
||||
|
||||
const rec2 = serverData.models.partner.records.find((rec) => rec.id === 2);
|
||||
rec2.document = "3 kb";
|
||||
rec2.__last_update = "2022-08-05 09:37:00";
|
||||
rec2.write_date = "2022-08-05 09:37:00";
|
||||
|
||||
await makeView({
|
||||
resIds: [1, 2],
|
||||
|
|
@ -822,11 +826,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test(
|
||||
"unique in url does not change on record change if no_reload option is set",
|
||||
"unique in url does not change on record change if reload option is set to false",
|
||||
async (assert) => {
|
||||
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
|
||||
rec.document = "3 kb";
|
||||
rec.__last_update = "2022-08-05 08:37:00";
|
||||
rec.write_date = "2022-08-05 08:37:00";
|
||||
|
||||
await makeView({
|
||||
resIds: [1, 2],
|
||||
|
|
@ -836,8 +840,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="document" widget="image" required="1" options="{'no_reload': true}" />
|
||||
<field name="__last_update" />
|
||||
<field name="document" widget="image" required="1" options="{'reload': false}" />
|
||||
<field name="write_date" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
|
@ -850,12 +854,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
getUnique(target.querySelector(".o_field_image img")),
|
||||
"1659688620000"
|
||||
);
|
||||
await editInput(
|
||||
target.querySelector(
|
||||
"div[name='__last_update'] > div > input",
|
||||
"2022-08-05 08:39:00"
|
||||
)
|
||||
);
|
||||
await editInput(target, "div[name='write_date'] > div > input", "2022-08-05 08:39:00");
|
||||
await click(target, ".o_form_button_save");
|
||||
assert.strictEqual(
|
||||
getUnique(target.querySelector(".o_field_image img")),
|
||||
|
|
@ -895,7 +894,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
],
|
||||
};
|
||||
|
||||
serverData.models.partner.records[0].__last_update = "2017-02-08 10:00:00";
|
||||
serverData.models.partner.records[0].write_date = "2017-02-08 10:00:00";
|
||||
|
||||
patchDate(2017, 1, 6, 11, 0, 0);
|
||||
|
||||
|
|
@ -906,16 +905,15 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" />
|
||||
<field name="user"/>
|
||||
<field name="related" widget="image"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<field name="foo" />
|
||||
<field name="user"/>
|
||||
<field name="related" widget="image"/>
|
||||
</form>`,
|
||||
async mockRPC(route, { args }, performRpc) {
|
||||
if (route === "/web/dataset/call_kw/partner/read") {
|
||||
if (
|
||||
route === "/web/dataset/call_kw/partner/web_read" ||
|
||||
route === "/web/dataset/call_kw/partner/web_save"
|
||||
) {
|
||||
const res = await performRpc(...arguments);
|
||||
// The mockRPC doesn't implement related fields
|
||||
res[0].related = "3 kb";
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
{ id: 1, int_field: 10 },
|
||||
{ id: 2, int_field: false },
|
||||
{ id: 3, int_field: 8069 },
|
||||
{ id: 100, int_field: 2.034567e3 },
|
||||
{ id: 101, int_field: 3.75675456e6 },
|
||||
{ id: 102, int_field: 6.67543577586e12 },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
@ -43,6 +46,66 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.module("IntegerField");
|
||||
|
||||
QUnit.test("human readable format 1", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 101,
|
||||
arch: `<form><field name="int_field" options="{'human_readable': 'true'}"/></form>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"4M",
|
||||
"The value should be rendered in human readable format (k, M, G, T)."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("human readable format 2", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 100,
|
||||
arch: `<form><field name="int_field" options="{'human_readable': 'true', 'decimals': 1}"/></form>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"2.0k",
|
||||
"The value should be rendered in human readable format (k, M, G, T)."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("human readable format 3", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 102,
|
||||
arch: `<form><field name="int_field" options="{'human_readable': 'true', 'decimals': 4}"/></form>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"6.6754T",
|
||||
"The value should be rendered in human readable format (k, M, G, T)."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("still human readable when readonly", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 102,
|
||||
arch: `<form><field readonly="true" name="int_field" options="{'human_readable': 'true', 'decimals': 4}"/></form>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget span").textContent,
|
||||
"6.6754T",
|
||||
"The value should be rendered in human readable format when input is readonly."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("should be 0 when unset", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -264,7 +327,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
"The value should be displayed properly in the input."
|
||||
);
|
||||
|
||||
await click(target.querySelector(".o_list_button_save"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
)
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector("td:not(.o_list_record_selector)").textContent,
|
||||
"-28",
|
||||
|
|
@ -289,6 +356,32 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("IntegerField with enable_formatting option as false", async function (assert) {
|
||||
patchWithCleanup(localization, { ...defaultLocalization, grouping: [3, 0] });
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 3,
|
||||
arch: `<form><field name="int_field" options="{'enable_formatting': false}"/></form>`,
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"8069",
|
||||
"Integer value must not be formatted"
|
||||
);
|
||||
|
||||
await editInput(target, ".o_field_widget[name=int_field] input", "1234567890");
|
||||
await clickSave(target);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
"1234567890",
|
||||
"Integer value must be not formatted if input type is number."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"no need to focus out of the input to save the record after correcting an invalid input",
|
||||
async function (assert) {
|
||||
|
|
@ -329,16 +422,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
resId: 1,
|
||||
arch: '<form><field name="int_field"/></form>',
|
||||
});
|
||||
|
||||
const fieldSelector = ".o_field_widget[name=int_field]";
|
||||
const inputSelector = fieldSelector + " input";
|
||||
|
||||
assert.strictEqual(target.querySelector(inputSelector).value, "10");
|
||||
|
||||
await editInput(target.querySelector(inputSelector), null, "a");
|
||||
assert.strictEqual(target.querySelector(inputSelector).value, "a");
|
||||
assert.hasClass(target.querySelector(fieldSelector), "o_field_invalid");
|
||||
|
||||
await editInput(target.querySelector(inputSelector), null, "10");
|
||||
assert.strictEqual(target.querySelector(inputSelector).value, "10");
|
||||
assert.doesNotHaveClass(target.querySelector(fieldSelector), "o_field_invalid");
|
||||
|
|
@ -383,4 +472,25 @@ QUnit.module("Fields", (hooks) => {
|
|||
await triggerEvent(target, ".o_field_widget input", "keydown", { key: "Enter" });
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "8,069");
|
||||
});
|
||||
|
||||
QUnit.test("value is formatted on click out (even if same value)", async function (assert) {
|
||||
patchWithCleanup(localization, { ...defaultLocalization, grouping: [3, 0] });
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 3,
|
||||
arch: '<form><field name="int_field"/></form>',
|
||||
});
|
||||
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "8,069");
|
||||
|
||||
target.querySelector(".o_field_widget input").value = 8069;
|
||||
await triggerEvent(target, ".o_field_widget input", "input");
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "8069");
|
||||
|
||||
await triggerEvent(target, ".o_field_widget input", "change"); // triggered when clicking out
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "8,069");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { fakeCookieService } from "@web/../tests/helpers/mock_services";
|
||||
import { click, getFixture, nextTick, triggerEvent } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
|
|
@ -85,7 +83,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
};
|
||||
|
||||
setupViewRegistries();
|
||||
registry.category("services").add("cookie", fakeCookieService);
|
||||
});
|
||||
|
||||
async function reloadKanbanView(target) {
|
||||
|
|
|
|||
|
|
@ -181,7 +181,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
// save and check the result
|
||||
await click(target.querySelector(".o_list_button_save"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
)
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelectorAll(".o_field_widget .badge:not(:empty)").length,
|
||||
3,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
QUnit.module("Many2ManyBinaryField");
|
||||
|
||||
QUnit.test("widget many2many_binary", async function (assert) {
|
||||
assert.expect(24);
|
||||
assert.expect(21);
|
||||
|
||||
const fakeHTTPService = {
|
||||
start() {
|
||||
|
|
@ -95,8 +95,33 @@ QUnit.module("Fields", (hooks) => {
|
|||
if (args.method !== "get_views") {
|
||||
assert.step(route);
|
||||
}
|
||||
if (route === "/web/dataset/call_kw/ir.attachment/read") {
|
||||
assert.deepEqual(args.args[1], ["name", "mimetype"]);
|
||||
if (args.method === "web_read" && args.model === "turtle") {
|
||||
assert.deepEqual(args.kwargs.specification, {
|
||||
display_name: {},
|
||||
picture_ids: {
|
||||
fields: {
|
||||
mimetype: {},
|
||||
name: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (args.method === "web_save" && args.model === "turtle") {
|
||||
assert.deepEqual(args.kwargs.specification, {
|
||||
display_name: {},
|
||||
picture_ids: {
|
||||
fields: {
|
||||
mimetype: {},
|
||||
name: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (args.method === "web_read" && args.model === "ir.attachment") {
|
||||
assert.deepEqual(args.kwargs.specification, {
|
||||
mimetype: {},
|
||||
name: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -138,10 +163,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"image/*",
|
||||
'there should be an attribute "accept" on the input'
|
||||
);
|
||||
assert.verifySteps([
|
||||
"/web/dataset/call_kw/turtle/read",
|
||||
"/web/dataset/call_kw/ir.attachment/read",
|
||||
]);
|
||||
assert.verifySteps(["/web/dataset/call_kw/turtle/web_read"]);
|
||||
|
||||
// Set and trigger the change of a file for the input
|
||||
const fileInput = target.querySelector('input[type="file"]');
|
||||
|
|
@ -181,10 +203,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
"there should be only one attachment left"
|
||||
);
|
||||
assert.verifySteps([
|
||||
"/web/dataset/call_kw/ir.attachment/read",
|
||||
"/web/dataset/call_kw/turtle/write",
|
||||
"/web/dataset/call_kw/turtle/read",
|
||||
"/web/dataset/call_kw/ir.attachment/read",
|
||||
"/web/dataset/call_kw/ir.attachment/web_read",
|
||||
"/web/dataset/call_kw/turtle/web_save",
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { click, clickSave, editInput, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import {
|
||||
click,
|
||||
clickSave,
|
||||
editInput,
|
||||
getFixture,
|
||||
getNodesTextContent,
|
||||
nextTick,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
|
|
@ -41,7 +50,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.test("Many2ManyCheckBoxesField", async function (assert) {
|
||||
serverData.models.partner.records[0].timmy = [12];
|
||||
const commands = [[[6, false, [12, 14]]], [[6, false, [14]]]];
|
||||
const commands = [[[4, 14]], [[3, 12]]];
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
|
|
@ -54,8 +63,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
</group>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "write") {
|
||||
assert.step("write");
|
||||
if (args.method === "web_save") {
|
||||
assert.step("web_save");
|
||||
assert.deepEqual(args.args[1].timmy, commands.shift());
|
||||
}
|
||||
},
|
||||
|
|
@ -82,7 +91,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.notOk(checkboxes[0].checked);
|
||||
assert.ok(checkboxes[1].checked);
|
||||
|
||||
assert.verifySteps(["write", "write"]);
|
||||
assert.verifySteps(["web_save", "web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyCheckBoxesField (readonly)", async function (assert) {
|
||||
|
|
@ -95,7 +104,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<form>
|
||||
<group>
|
||||
<field name="timmy" widget="many2many_checkboxes" attrs="{'readonly': true}" />
|
||||
<field name="timmy" widget="many2many_checkboxes" readonly="True" />
|
||||
</group>
|
||||
</form>`,
|
||||
});
|
||||
|
|
@ -125,6 +134,50 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyCheckBoxesField does not read added record", async function (assert) {
|
||||
serverData.models.partner.records[0].timmy = [];
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<group>
|
||||
<field name="timmy" widget="many2many_checkboxes" />
|
||||
</group>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsN(target, "div.o_field_widget div.form-check", 2);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_field_widget .form-check-label")),
|
||||
["gold", "silver"]
|
||||
);
|
||||
assert.containsNone(target, "div.o_field_widget div.form-check input:checked");
|
||||
|
||||
await click(target.querySelector("div.o_field_widget div.form-check input"));
|
||||
assert.containsN(target, "div.o_field_widget div.form-check", 2);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_field_widget .form-check-label")),
|
||||
["gold", "silver"]
|
||||
);
|
||||
assert.containsOnce(target, "div.o_field_widget div.form-check input:checked");
|
||||
|
||||
await clickSave(target);
|
||||
assert.containsN(target, "div.o_field_widget div.form-check", 2);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_field_widget .form-check-label")),
|
||||
["gold", "silver"]
|
||||
);
|
||||
assert.containsOnce(target, "div.o_field_widget div.form-check input:checked");
|
||||
|
||||
assert.verifySteps(["get_views", "web_read", "name_search", "web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Many2ManyCheckBoxesField: start non empty, then remove twice",
|
||||
async function (assert) {
|
||||
|
|
@ -190,6 +243,32 @@ QUnit.module("Fields", (hooks) => {
|
|||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"Many2ManyCheckBoxesField: many2many read, field context is properly sent",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="timmy" widget="many2many_checkboxes" context="{ 'hello': 'world' }" />
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "web_read" && args.model === "partner") {
|
||||
assert.step(`${args.method} ${args.model}`);
|
||||
assert.strictEqual(args.kwargs.specification.timmy.context.hello, "world");
|
||||
} else if (args.method === "name_search" && args.model === "partner_type") {
|
||||
assert.step(`${args.method} ${args.model}`);
|
||||
assert.strictEqual(args.kwargs.context.hello, "world");
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.verifySteps(["web_read partner", "name_search partner_type"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("Many2ManyCheckBoxesField with 40+ values", async function (assert) {
|
||||
// 40 is the default limit for x2many fields. However, the many2many_checkboxes is a
|
||||
// special field that fetches its data through the fetchSpecialData mechanism, and it
|
||||
|
|
@ -219,10 +298,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="timmy" widget="many2many_checkboxes" />
|
||||
</form>`,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "write") {
|
||||
const expectedIds = records.map((r) => r.id);
|
||||
expectedIds.pop();
|
||||
assert.deepEqual(args[1].timmy, [[6, false, expectedIds]]);
|
||||
if (method === "web_save") {
|
||||
assert.deepEqual(args[1].timmy, [[3, records[records.length - 1].id]]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -274,11 +351,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="timmy" widget="many2many_checkboxes" />
|
||||
</form>`,
|
||||
async mockRPC(route, { args, method }) {
|
||||
if (method === "write") {
|
||||
const expectedIds = records.map((r) => r.id);
|
||||
expectedIds.shift();
|
||||
assert.deepEqual(args[1].timmy, [[6, false, expectedIds]]);
|
||||
assert.step("write");
|
||||
if (method === "web_save") {
|
||||
assert.deepEqual(args[1].timmy, [[3, records[0].id]]);
|
||||
assert.step("web_save");
|
||||
}
|
||||
if (method === "name_search") {
|
||||
assert.step("name_search");
|
||||
|
|
@ -303,7 +378,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.notOk(
|
||||
target.querySelector(".o_field_widget[name='timmy'] input[type='checkbox']").checked
|
||||
);
|
||||
assert.verifySteps(["name_search", "write"]);
|
||||
assert.verifySteps(["name_search", "web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyCheckBoxesField in a one2many", async function (assert) {
|
||||
|
|
@ -326,9 +401,20 @@ QUnit.module("Fields", (hooks) => {
|
|||
</field>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "write") {
|
||||
if (args.method === "web_save") {
|
||||
assert.deepEqual(args.args[1], {
|
||||
p: [[1, 1, { timmy: [[6, false, [15, 12]]] }]],
|
||||
p: [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
{
|
||||
timmy: [
|
||||
[4, 12],
|
||||
[3, 14],
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -352,7 +438,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
QUnit.test("Many2ManyCheckBoxesField with default values", async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
serverData.models.partner.fields.timmy.default = [3];
|
||||
serverData.models.partner.fields.timmy.default = [[4, 3]];
|
||||
serverData.models.partner.fields.timmy.type = "many2many";
|
||||
serverData.models.partner_type.records.push({ id: 3, display_name: "bronze" });
|
||||
|
||||
|
|
@ -365,10 +451,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="timmy" widget="many2many_checkboxes"/>
|
||||
</form>`,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === "create") {
|
||||
if (args.method === "web_save") {
|
||||
assert.deepEqual(
|
||||
args.args[0].timmy,
|
||||
[[6, false, [12]]],
|
||||
args.args[1].timmy,
|
||||
[[4, 12]],
|
||||
"correct values should have been sent to create"
|
||||
);
|
||||
}
|
||||
|
|
@ -408,4 +494,131 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
await clickSave(target);
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyCheckBoxesField batches successive changes", async function (assert) {
|
||||
serverData.models.partner.records[0].timmy = [];
|
||||
serverData.models.partner.onchanges = {
|
||||
timmy: () => {},
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<group>
|
||||
<field name="timmy" widget="many2many_checkboxes" />
|
||||
</group>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsN(target, "div.o_field_widget div.form-check", 2);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_field_widget .form-check-label")),
|
||||
["gold", "silver"]
|
||||
);
|
||||
assert.containsNone(target, "div.o_field_widget div.form-check input:checked");
|
||||
|
||||
let mockSetTimeout;
|
||||
patchWithCleanup(browser, { setTimeout: (fn) => (mockSetTimeout = fn) });
|
||||
await click(target.querySelectorAll("div.o_field_widget div.form-check input")[0]);
|
||||
await click(target.querySelectorAll("div.o_field_widget div.form-check input")[1]);
|
||||
// checkboxes are updated directly
|
||||
assert.containsN(target, "div.o_field_widget div.form-check input:checked", 2);
|
||||
// but no onchanges has been fired yet
|
||||
assert.verifySteps(["get_views", "web_read", "name_search"]);
|
||||
// execute the setTimeout callback
|
||||
mockSetTimeout();
|
||||
await nextTick();
|
||||
assert.verifySteps(["onchange"]);
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyCheckBoxesField sends batched changes on save", async function (assert) {
|
||||
serverData.models.partner.records[0].timmy = [];
|
||||
serverData.models.partner.onchanges = {
|
||||
timmy: () => {},
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<group>
|
||||
<field name="timmy" widget="many2many_checkboxes" />
|
||||
</group>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsN(target, "div.o_field_widget div.form-check", 2);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_field_widget .form-check-label")),
|
||||
["gold", "silver"]
|
||||
);
|
||||
assert.containsNone(target, "div.o_field_widget div.form-check input:checked");
|
||||
|
||||
patchWithCleanup(browser, { setTimeout: () => {} }); // never call it
|
||||
await click(target.querySelectorAll("div.o_field_widget div.form-check input")[0]);
|
||||
await click(target.querySelectorAll("div.o_field_widget div.form-check input")[1]);
|
||||
// checkboxes are updated directly
|
||||
assert.containsN(target, "div.o_field_widget div.form-check input:checked", 2);
|
||||
// but no onchanges has been fired yet
|
||||
assert.verifySteps(["get_views", "web_read", "name_search"]);
|
||||
// save
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["onchange", "web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyCheckBoxesField in a notebook tab", async function (assert) {
|
||||
serverData.models.partner.records[0].timmy = [];
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<notebook>
|
||||
<page string="Page 1">
|
||||
<field name="timmy" widget="many2many_checkboxes" />
|
||||
</page>
|
||||
<page string="Page 2">
|
||||
<field name="int_field" />
|
||||
</page>
|
||||
</notebook>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsOnce(target, "div.o_field_widget[name=timmy]");
|
||||
assert.containsN(target, "div.o_field_widget[name=timmy] div.form-check", 2);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_field_widget .form-check-label")),
|
||||
["gold", "silver"]
|
||||
);
|
||||
assert.containsNone(target, "div.o_field_widget[name=timmy] div.form-check input:checked");
|
||||
|
||||
patchWithCleanup(browser, { setTimeout: () => {} }); // never call it
|
||||
await click(target.querySelectorAll("div.o_field_widget div.form-check input")[0]);
|
||||
await click(target.querySelectorAll("div.o_field_widget div.form-check input")[1]);
|
||||
// checkboxes are updated directly
|
||||
assert.containsN(target, "div.o_field_widget div.form-check input:checked", 2);
|
||||
// go to the other tab
|
||||
await click(target.querySelectorAll(".o_notebook .nav-link")[1]);
|
||||
assert.containsNone(target, "div.o_field_widget[name=timmy]");
|
||||
assert.containsOnce(target, "div.o_field_widget[name=int_field]");
|
||||
// save
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["get_views", "web_read", "name_search", "web_save"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import {
|
||||
addRow,
|
||||
click,
|
||||
|
|
@ -17,10 +16,11 @@ import { editSearch, validateSearch } from "@web/../tests/search/helpers";
|
|||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Deferred } from "@web/core/utils/concurrency";
|
||||
import { session } from "@web/session";
|
||||
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
|
||||
import { X2ManyField } from "@web/views/fields/x2many/x2many_field";
|
||||
import { companyService } from "@web/webclient/company_service";
|
||||
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
|
@ -222,7 +222,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
QUnit.module("Many2ManyField");
|
||||
|
||||
QUnit.test("many2many kanban: edition", async function (assert) {
|
||||
assert.expect(31);
|
||||
assert.expect(29);
|
||||
|
||||
serverData.views = {
|
||||
"partner_type,false,form": '<form><field name="display_name"/></form>',
|
||||
|
|
@ -260,37 +260,41 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(route, args) {
|
||||
if (route === "/web/dataset/call_kw/partner_type/write") {
|
||||
if (
|
||||
route === "/web/dataset/call_kw/partner_type/web_save" &&
|
||||
args.args[0].length !== 0
|
||||
) {
|
||||
assert.strictEqual(
|
||||
args.args[1].display_name,
|
||||
"new name",
|
||||
"should write 'new_name'"
|
||||
);
|
||||
}
|
||||
if (route === "/web/dataset/call_kw/partner_type/create") {
|
||||
if (
|
||||
route === "/web/dataset/call_kw/partner_type/web_save" &&
|
||||
args.args[0].length === 0
|
||||
) {
|
||||
assert.strictEqual(
|
||||
args.args[0].display_name,
|
||||
args.args[1].display_name,
|
||||
"A new type",
|
||||
"should create 'A new type'"
|
||||
);
|
||||
}
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
var commands = args.args[1].timmy;
|
||||
assert.strictEqual(commands.length, 1, "should have generated one command");
|
||||
assert.strictEqual(
|
||||
commands[0][0],
|
||||
6,
|
||||
"generated command should be REPLACE WITH"
|
||||
);
|
||||
if (
|
||||
route === "/web/dataset/call_kw/partner/web_save" &&
|
||||
args.args[0].length !== 0
|
||||
) {
|
||||
const commands = args.args[1].timmy;
|
||||
// get the created type's id
|
||||
var createdType = _.findWhere(serverData.models.partner_type.records, {
|
||||
display_name: "A new type",
|
||||
const createdType = serverData.models.partner_type.records.find((record) => {
|
||||
return record.display_name === "A new type";
|
||||
});
|
||||
var ids = _.sortBy([12, 15, 18].concat(createdType.id), _.identity.bind(_));
|
||||
assert.ok(
|
||||
_.isEqual(_.sortBy(commands[0][2], _.identity.bind(_)), ids),
|
||||
"new value should be " + ids
|
||||
);
|
||||
assert.deepEqual(commands, [
|
||||
[4, 15],
|
||||
[4, 18],
|
||||
[4, createdType.id],
|
||||
[3, 14],
|
||||
]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -632,7 +636,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test(
|
||||
"many2many list (non editable): create a new record and click on action button",
|
||||
"many2many list (non editable): create a new record and click on action button 1",
|
||||
async function (assert) {
|
||||
serverData.views = {
|
||||
"partner_type,false,list": '<tree><field name="display_name"/></tree>',
|
||||
|
|
@ -659,8 +663,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
resId: 1,
|
||||
mockRPC: async (route, args) => {
|
||||
assert.step(args.method);
|
||||
if (args.method === "create") {
|
||||
assert.deepEqual(args.args[0], { display_name: "Hello" });
|
||||
if (args.method === "web_save") {
|
||||
assert.deepEqual(args.args[1], { display_name: "Hello" });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -673,20 +677,25 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
let modal = target.querySelector(".modal");
|
||||
await click(modal, ".o_create_button");
|
||||
assert.verifySteps(["get_views", "read", "get_views", "web_search_read", "onchange"]);
|
||||
|
||||
assert.verifySteps([
|
||||
"get_views",
|
||||
"web_read",
|
||||
"get_views",
|
||||
"web_search_read",
|
||||
"onchange",
|
||||
]);
|
||||
modal = target.querySelector(".modal");
|
||||
await editInput(modal, "[name='display_name'] input", "Hello");
|
||||
assert.strictEqual(modal.querySelector("[name='display_name'] input").value, "Hello");
|
||||
|
||||
await click(modal, ".o_statusbar_buttons [name='myaction']");
|
||||
assert.strictEqual(modal.querySelector("[name='display_name'] input").value, "Hello");
|
||||
assert.verifySteps(["create", "read", "action: myaction"]);
|
||||
assert.verifySteps(["web_save", "action: myaction"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"many2many list (non editable): create a new record and click on action button",
|
||||
"many2many list (non editable): create a new record and click on action button 2",
|
||||
async function (assert) {
|
||||
serverData.views = {
|
||||
"partner_type,false,list": '<tree><field name="display_name"/></tree>',
|
||||
|
|
@ -713,8 +722,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
resId: 1,
|
||||
mockRPC: async (route, args) => {
|
||||
assert.step(args.method);
|
||||
if (args.method === "create") {
|
||||
assert.deepEqual(args.args[0], { display_name: "Hello" });
|
||||
if (args.method === "web_save" && args.args[0].length === 0) {
|
||||
assert.deepEqual(args.args[1], { display_name: "Hello" });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -727,7 +736,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
let modal = target.querySelector(".modal");
|
||||
await click(modal, ".o_create_button");
|
||||
assert.verifySteps(["get_views", "read", "get_views", "web_search_read", "onchange"]);
|
||||
assert.verifySteps([
|
||||
"get_views",
|
||||
"web_read",
|
||||
"get_views",
|
||||
"web_search_read",
|
||||
"onchange",
|
||||
]);
|
||||
|
||||
modal = target.querySelector(".modal");
|
||||
await editInput(modal, "[name='display_name'] input", "Hello");
|
||||
|
|
@ -753,10 +768,57 @@ QUnit.module("Fields", (hooks) => {
|
|||
["Hello (edited)"]
|
||||
);
|
||||
|
||||
assert.verifySteps(["create", "read", "action: myaction", "write", "read", "read"]);
|
||||
assert.verifySteps(["web_save", "action: myaction", "web_save", "web_read"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("add a new record in a many2many non editable list", async function (assert) {
|
||||
serverData.views = {
|
||||
"partner_type,false,list": '<tree><field name="display_name"/></tree>',
|
||||
"partner_type,false,form": '<form><field name="display_name"/></form>',
|
||||
"partner_type,false,search": '<search><field name="display_name"/></search>',
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="timmy">
|
||||
<tree>
|
||||
<field name="display_name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
if (args.method === "web_save") {
|
||||
// should not read the record as we're closing the dialog
|
||||
assert.deepEqual(args.kwargs.specification, {});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await click(target.querySelector(".o_field_x2many_list_row_add a"));
|
||||
await click(target.querySelector(".o_dialog .o_create_button"));
|
||||
await editInput(
|
||||
target.querySelector(".o_dialog"),
|
||||
".o_field_widget[name=display_name] input",
|
||||
"a name"
|
||||
);
|
||||
await click(target.querySelector(".o_dialog .o_form_button_save"));
|
||||
assert.verifySteps([
|
||||
"get_views",
|
||||
"onchange",
|
||||
"get_views",
|
||||
"web_search_read",
|
||||
"get_views",
|
||||
"onchange",
|
||||
"web_save",
|
||||
"web_read",
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("add record in a many2many non editable list with context", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
|
|
@ -794,10 +856,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
await editInput(target, ".o_field_widget[name=int_field] input", "2");
|
||||
await click(target.querySelector(".o_field_x2many_list_row_add a"));
|
||||
});
|
||||
|
||||
QUnit.test("many2many list (editable): edition", async function (assert) {
|
||||
assert.expect(29);
|
||||
|
||||
QUnit.test("many2many list (editable): edition concurrence", async function (assert) {
|
||||
assert.expect(5);
|
||||
serverData.models.partner.records[0].timmy = [12, 14];
|
||||
serverData.models.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 });
|
||||
serverData.models.partner_type.fields.float_field = { string: "Float", type: "float" };
|
||||
|
|
@ -820,9 +880,49 @@ QUnit.module("Fields", (hooks) => {
|
|||
</field>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method !== "get_views") {
|
||||
assert.step(_.last(route.split("/")));
|
||||
assert.step(args.method);
|
||||
if (args.method === "web_save") {
|
||||
//check that delete command is not duplicate
|
||||
assert.deepEqual(args.args, [
|
||||
[1],
|
||||
{
|
||||
timmy: [[3, 12]],
|
||||
},
|
||||
]);
|
||||
}
|
||||
},
|
||||
resId: 1,
|
||||
});
|
||||
const t = target.querySelector(".o_list_record_remove");
|
||||
click(t);
|
||||
click(t);
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["get_views", "web_read", "web_save"]);
|
||||
});
|
||||
QUnit.test("many2many list (editable): edition", async function (assert) {
|
||||
serverData.models.partner.records[0].timmy = [12, 14];
|
||||
serverData.models.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 });
|
||||
serverData.models.partner_type.fields.float_field = { string: "Float", type: "float" };
|
||||
|
||||
serverData.views = {
|
||||
"partner_type,false,list": '<tree><field name="display_name"/></tree>',
|
||||
"partner_type,false,search": '<search><field name="display_name"/></search>',
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="timmy">
|
||||
<tree editable="top">
|
||||
<field name="display_name"/>
|
||||
<field name="float_field"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
if (args.method === "write") {
|
||||
assert.deepEqual(args.args[1].timmy, [
|
||||
[6, false, [12, 15]],
|
||||
|
|
@ -895,7 +995,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"new name",
|
||||
"value of subrecord should have been updated"
|
||||
);
|
||||
assert.verifySteps(["read", "read"]);
|
||||
assert.verifySteps(["get_views", "web_read"]);
|
||||
|
||||
// add new subrecords
|
||||
await click(target.querySelector(".o_field_x2many_list_row_add a"));
|
||||
|
|
@ -943,11 +1043,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
assert.verifySteps([
|
||||
"get_views", // list view in dialog
|
||||
"web_search_read", // list view in dialog
|
||||
"read", // relational field (updated)
|
||||
"write", // save main record
|
||||
"read", // main record
|
||||
"read", // relational field
|
||||
"web_read", // relational field (updated)
|
||||
"web_save", // save main record
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -1050,14 +1149,20 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="timmy" widget="many2many" can_create="false" can_write="false"/>
|
||||
<field name="timmy" widget="many2many" can_create="False" can_write="False"/>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (route === "/web/dataset/call_kw/partner/create") {
|
||||
assert.deepEqual(args.args[0], { timmy: [[6, false, [12]]] });
|
||||
if (
|
||||
route === "/web/dataset/call_kw/partner/web_save" &&
|
||||
args.args[0].length === 0
|
||||
) {
|
||||
assert.deepEqual(args.args[1], { timmy: [[4, 12]] });
|
||||
}
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
assert.deepEqual(args.args[1], { timmy: [[6, false, []]] });
|
||||
if (
|
||||
route === "/web/dataset/call_kw/partner/web_save" &&
|
||||
args.args[0].length !== 0
|
||||
) {
|
||||
assert.deepEqual(args.args[1], { timmy: [[3, 12]] });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -1377,7 +1482,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test("many2many list: list of id as default value", async function (assert) {
|
||||
serverData.models.partner.fields.turtles.default = [2, 3];
|
||||
serverData.models.partner.fields.turtles.default = [
|
||||
[4, 2],
|
||||
[4, 3],
|
||||
];
|
||||
serverData.models.partner.fields.turtles.type = "many2many";
|
||||
|
||||
await makeView({
|
||||
|
|
@ -1401,6 +1509,49 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"context and domain dependent on an x2m must contain the list of current ids for the x2m",
|
||||
async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
serverData.models.partner.fields.turtles.default = [
|
||||
[4, 2],
|
||||
[4, 3],
|
||||
];
|
||||
serverData.models.partner.fields.turtles.type = "many2many";
|
||||
serverData.views = {
|
||||
"turtle,false,list": '<tree><field name="display_name"/></tree>',
|
||||
"turtle,false,search": '<search><field name="display_name"/></search>',
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="turtles" context="{'test': turtles}" domain="[('id', 'in', turtles)]">
|
||||
<tree>
|
||||
<field name="turtle_foo"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "web_search_read") {
|
||||
assert.deepEqual(args.kwargs.domain, [
|
||||
"&",
|
||||
["id", "in", [2, 3]],
|
||||
"!",
|
||||
["id", "in", [2, 3]],
|
||||
]);
|
||||
assert.deepEqual(args.kwargs.context.test, [2, 3]);
|
||||
}
|
||||
},
|
||||
});
|
||||
await addRow(target);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("many2many list with x2many: add a record", async function (assert) {
|
||||
serverData.models.partner_type.fields.m2m = {
|
||||
string: "M2M",
|
||||
|
|
@ -1428,10 +1579,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
resId: 1,
|
||||
mockRPC(route, args) {
|
||||
if (args.method !== "get_views") {
|
||||
assert.step(_.last(route.split("/")) + " on " + args.model);
|
||||
}
|
||||
if (args.model === "turtle") {
|
||||
assert.step(JSON.stringify(args.args[0])); // the read ids
|
||||
assert.step(route.split("/").at(-1) + " on " + args.model);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -1466,19 +1614,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
assert.verifySteps([
|
||||
"read on partner",
|
||||
"web_read on partner",
|
||||
"web_search_read on partner_type",
|
||||
"read on turtle",
|
||||
"[1,2,3]",
|
||||
"read on partner_type",
|
||||
"read on turtle",
|
||||
"[1,2]",
|
||||
"web_read on partner_type",
|
||||
"web_search_read on partner_type",
|
||||
"read on turtle",
|
||||
"[2,3]",
|
||||
"read on partner_type",
|
||||
"read on turtle",
|
||||
"[2,3]",
|
||||
"web_read on partner_type",
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -1537,20 +1677,71 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.step(args.method);
|
||||
},
|
||||
});
|
||||
assert.verifySteps(["get_views", "read", "read"]);
|
||||
assert.verifySteps(["get_views", "web_read"]);
|
||||
|
||||
await click($(target).find("td.o_data_cell:first")[0]);
|
||||
assert.verifySteps(["get_views", "read"]);
|
||||
await click(target.querySelector("td.o_data_cell"));
|
||||
assert.verifySteps(["get_views", "web_read"]);
|
||||
|
||||
await click($('.modal-body input[type="checkbox"]')[0]);
|
||||
await click($(".modal .modal-footer .btn-primary").first()[0]);
|
||||
assert.verifySteps(["write", "onchange", "read"]);
|
||||
await click(target.querySelector(".modal-body input[type=checkbox]"));
|
||||
await click(target.querySelector(".modal .modal-footer .btn-primary"));
|
||||
assert.verifySteps(["web_save"]);
|
||||
|
||||
// there is nothing left to save -> should not do a 'write' RPC
|
||||
await clickSave(target);
|
||||
assert.verifySteps([]);
|
||||
});
|
||||
|
||||
QUnit.test("many2many concurrency edition", async function (assert) {
|
||||
serverData.models.partner.fields.turtles.type = "many2many";
|
||||
serverData.models.partner.onchanges.turtles = function () {};
|
||||
serverData.models.turtle.records.push({
|
||||
id: 4,
|
||||
display_name: "Bloop",
|
||||
turtle_bar: true,
|
||||
turtle_foo: "Bloop",
|
||||
partner_ids: [],
|
||||
});
|
||||
serverData.models.partner.records[0].turtles = [1, 2, 3, 4];
|
||||
serverData.views = {
|
||||
"turtle,false,list": '<tree><field name="display_name"/></tree>',
|
||||
"turtle,false,search": '<search><field name="display_name" string="Name"/></search>',
|
||||
};
|
||||
|
||||
const def = new Deferred();
|
||||
let firstOnChange = false;
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="turtles">
|
||||
<tree>
|
||||
<field name="turtle_foo"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC: async (route, args) => {
|
||||
if (args.method === "onchange") {
|
||||
if (!firstOnChange) {
|
||||
firstOnChange = true;
|
||||
await def;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.containsN(target, ".o_data_row", 4);
|
||||
await click(target.querySelector(".o_data_row .o_list_record_remove"));
|
||||
await click(target.querySelector(".o_data_row .o_list_record_remove"));
|
||||
await click(target, ".o_field_x2many_list_row_add a");
|
||||
await click(target.querySelectorAll(".modal .o_data_row td.o_data_cell")[0]);
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
assert.containsN(target, ".o_data_row", 3);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"many2many widget: creates a new record with a context containing the parentID",
|
||||
async function (assert) {
|
||||
|
|
@ -1584,13 +1775,18 @@ QUnit.module("Fields", (hooks) => {
|
|||
{},
|
||||
[],
|
||||
{
|
||||
turtle_trululu: "",
|
||||
turtle_foo: {},
|
||||
turtle_trululu: {
|
||||
fields: {
|
||||
display_name: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.verifySteps(["get_views", "read", "read"]);
|
||||
assert.verifySteps(["get_views", "web_read"]);
|
||||
|
||||
await addRow(target);
|
||||
assert.verifySteps(["get_views", "web_search_read"]);
|
||||
|
|
@ -1607,10 +1803,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
QUnit.test("onchange with 40+ commands for a many2many", async function (assert) {
|
||||
// this test ensures that the basic_model correctly handles more LINK_TO
|
||||
// commands than the limit of the dataPoint (40 for x2many kanban)
|
||||
assert.expect(25);
|
||||
assert.expect(20);
|
||||
|
||||
// create a lot of partner_types that will be linked by the onchange
|
||||
var commands = [[5]];
|
||||
const commands = [];
|
||||
for (var i = 0; i < 45; i++) {
|
||||
var id = 100 + i;
|
||||
serverData.models.partner_type.records.push({ id: id, display_name: "type " + id });
|
||||
|
|
@ -1643,22 +1839,20 @@ QUnit.module("Fields", (hooks) => {
|
|||
resId: 1,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
if (args.method === "write") {
|
||||
assert.strictEqual(args.args[1].timmy[0][0], 6, "should send a command 6");
|
||||
assert.strictEqual(
|
||||
args.args[1].timmy[0][2].length,
|
||||
45,
|
||||
"should replace with 45 ids"
|
||||
if (args.method === "web_save") {
|
||||
assert.deepEqual(
|
||||
args.args[1].timmy,
|
||||
commands.map((c) => [c[0], c[1]]),
|
||||
"should send all commands"
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.verifySteps(["get_views", "read"]);
|
||||
assert.verifySteps(["get_views", "web_read"]);
|
||||
|
||||
await editInput(target, ".o_field_widget[name=foo] input", "trigger onchange");
|
||||
|
||||
assert.verifySteps(["onchange", "read"]);
|
||||
assert.verifySteps(["onchange"]);
|
||||
assert.strictEqual(
|
||||
$(target).find(".o_x2m_control_panel .o_pager_counter").text().trim(),
|
||||
"1-40 / 45",
|
||||
|
|
@ -1669,9 +1863,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
40,
|
||||
"there should be 40 records displayed on page 1"
|
||||
);
|
||||
|
||||
await click($(target).find(".o_field_widget[name=timmy] .o_pager_next")[0]);
|
||||
assert.verifySteps(["read"]);
|
||||
assert.verifySteps([]);
|
||||
assert.strictEqual(
|
||||
$(target).find(".o_x2m_control_panel .o_pager_counter").text().trim(),
|
||||
"41-45 / 45",
|
||||
|
|
@ -1720,21 +1913,15 @@ QUnit.module("Fields", (hooks) => {
|
|||
"there should be 40 records displayed on page 1"
|
||||
);
|
||||
|
||||
assert.verifySteps(["write", "read", "read", "read"]);
|
||||
assert.verifySteps(["web_save", "web_read"]);
|
||||
});
|
||||
|
||||
QUnit.test("default_get, onchange, onchange on m2m", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
serverData.models.partner.onchanges.int_field = function (obj) {
|
||||
if (obj.int_field === 2) {
|
||||
assert.deepEqual(obj.timmy, [
|
||||
[6, false, [12]],
|
||||
[1, 12, { display_name: "gold" }],
|
||||
]);
|
||||
}
|
||||
obj.timmy = [[5], [1, 12, { display_name: "gold" }]];
|
||||
};
|
||||
serverData.models.partner.onchanges.int_field = function () {};
|
||||
|
||||
let firstOnChange = true;
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -1751,14 +1938,30 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="int_field"/>
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange") {
|
||||
if (firstOnChange) {
|
||||
firstOnChange = false;
|
||||
return {
|
||||
value: {
|
||||
timmy: [[1, 12, { display_name: "gold" }]],
|
||||
},
|
||||
};
|
||||
} else {
|
||||
assert.deepEqual(args.args[1], {
|
||||
display_name: false,
|
||||
int_field: 2,
|
||||
timmy: [[1, 12, { display_name: "gold" }]],
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await editInput(target, ".o_field_widget[name=int_field] input", 2);
|
||||
});
|
||||
|
||||
QUnit.test("many2many list add *many* records, remove, re-add", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
serverData.models.partner.fields.timmy.domain = [["color", "=", 2]];
|
||||
serverData.models.partner.fields.timmy.onChange = true;
|
||||
serverData.models.partner_type.fields.product_ids = {
|
||||
|
|
@ -1767,8 +1970,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
relation: "product",
|
||||
};
|
||||
|
||||
for (var i = 0; i < 50; i++) {
|
||||
var new_record_partner_type = { id: 100 + i, display_name: "batch" + i, color: 2 };
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const new_record_partner_type = { id: 100 + i, display_name: "batch" + i, color: 2 };
|
||||
serverData.models.partner_type.records.push(new_record_partner_type);
|
||||
}
|
||||
|
||||
|
|
@ -1806,7 +2009,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
// First round: add 51 records in batch
|
||||
await click(target.querySelector(".o_field_x2many_list_row_add a"));
|
||||
|
||||
var $modal = $(".modal-lg");
|
||||
let $modal = $(".modal-lg");
|
||||
|
||||
assert.equal($modal.length, 1, "There should be one modal");
|
||||
|
||||
|
|
@ -1821,26 +2024,43 @@ QUnit.module("Fields", (hooks) => {
|
|||
"We should have added all the records present in the search view to the m2m field"
|
||||
); // the 50 in batch + 'gold'
|
||||
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_field_many2many.o_field_widget .o_field_x2many.o_field_x2many_list .o_cp_pager",
|
||||
"pager should not be displayed"
|
||||
);
|
||||
|
||||
await clickSave(target);
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_many2many.o_field_widget .o_field_x2many.o_field_x2many_list .o_cp_pager",
|
||||
"pager should not be displayed"
|
||||
);
|
||||
|
||||
const pagerValue = target.querySelector(
|
||||
".o_field_many2many.o_field_widget .o_field_x2many.o_field_x2many_list .o_pager_value"
|
||||
);
|
||||
assert.strictEqual(pagerValue.textContent, "1-40", "The pager should be updated.");
|
||||
|
||||
// Secound round: remove one record
|
||||
var trash_buttons = $(target).find(
|
||||
const trash_buttons = $(target).find(
|
||||
".o_field_many2many.o_field_widget .o_field_x2many.o_field_x2many_list .o_list_record_remove"
|
||||
);
|
||||
|
||||
await click(trash_buttons.first()[0]);
|
||||
|
||||
var pager_limit = $(target).find(
|
||||
const pager_limit = $(target).find(
|
||||
".o_field_many2many.o_field_widget .o_field_x2many.o_field_x2many_list .o_pager_limit"
|
||||
);
|
||||
assert.equal(pager_limit.text(), "50", "We should have 50 records in the m2m field");
|
||||
assert.strictEqual(pager_limit.text(), "50", "We should have 50 records in the m2m field");
|
||||
|
||||
// Third round: re-add 1 records
|
||||
await click($(target).find(".o_field_x2many_list_row_add a")[0]);
|
||||
|
||||
$modal = $(".modal-lg");
|
||||
|
||||
assert.equal($modal.length, 1, "There should be one modal");
|
||||
assert.strictEqual($modal.length, 1, "There should be one modal");
|
||||
|
||||
await click($modal.find("thead input[type=checkbox]")[0]);
|
||||
await nextTick();
|
||||
|
|
@ -1849,8 +2069,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.strictEqual(
|
||||
$(target).find(".o_data_row").length,
|
||||
51,
|
||||
"We should have 51 records in the m2m field"
|
||||
41,
|
||||
"We should have 41 records in the m2m field"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -1931,12 +2151,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test("many2many basic keys in field evalcontext -- in list", async (assert) => {
|
||||
assert.expect(6);
|
||||
assert.expect(5);
|
||||
serverData.models.partner_type.fields.partner_id = {
|
||||
string: "Partners",
|
||||
type: "many2one",
|
||||
relation: "partner",
|
||||
};
|
||||
serverData.models.partner.records.push({ id: 7, display_name: "default partner" });
|
||||
serverData.views = {
|
||||
"partner_type,false,form": `<form><field name="partner_id" /></form>`,
|
||||
};
|
||||
|
|
@ -1964,13 +2185,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<field name="timmy" widget="many2many_tags" context="{ 'default_partner_id': active_id, 'ids': active_ids, 'model': active_model, 'company_id': current_company_id}"/>
|
||||
<field name="timmy" widget="many2many_tags" context="{ 'default_partner_id': uid, 'allowed_company_ids': allowed_company_ids, 'company_id': current_company_id}"/>
|
||||
</tree>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange") {
|
||||
assert.strictEqual(args.kwargs.context.default_partner_id, 1);
|
||||
assert.strictEqual(args.kwargs.context.model, "partner");
|
||||
assert.deepEqual(args.kwargs.context.ids, [1]);
|
||||
assert.strictEqual(args.kwargs.context.uid, 7);
|
||||
assert.deepEqual(args.kwargs.context.allowed_company_ids, [3]);
|
||||
assert.strictEqual(args.kwargs.context.company_id, 3);
|
||||
}
|
||||
},
|
||||
|
|
@ -1978,22 +2198,22 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
await click(target.querySelector(".o_data_cell"));
|
||||
await editInput(target, ".o_field_many2many_selection input", "indianapolis");
|
||||
await nextTick();
|
||||
await clickOpenedDropdownItem(target, "timmy", "Create and edit...");
|
||||
assert.containsOnce(target, ".modal .o_field_many2one");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".modal .o_field_many2one input").value,
|
||||
"first record"
|
||||
"default partner"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("many2many basic keys in field evalcontext -- in form", async (assert) => {
|
||||
assert.expect(6);
|
||||
assert.expect(5);
|
||||
serverData.models.partner_type.fields.partner_id = {
|
||||
string: "Partners",
|
||||
type: "many2one",
|
||||
relation: "partner",
|
||||
};
|
||||
serverData.models.partner.records.push({ id: 7, display_name: "default partner" });
|
||||
serverData.views = {
|
||||
"partner_type,false,form": `<form><field name="partner_id" /></form>`,
|
||||
};
|
||||
|
|
@ -2022,13 +2242,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="timmy" widget="many2many_tags" context="{ 'default_partner_id': active_id, 'ids': active_ids, 'model': active_model, 'company_id': current_company_id}"/>
|
||||
<field name="timmy" widget="many2many_tags" context="{ 'default_partner_id': uid, 'allowed_company_ids': allowed_company_ids, 'company_id': current_company_id}"/>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange") {
|
||||
assert.strictEqual(args.kwargs.context.default_partner_id, 1);
|
||||
assert.strictEqual(args.kwargs.context.model, "partner");
|
||||
assert.deepEqual(args.kwargs.context.ids, [1]);
|
||||
assert.strictEqual(args.kwargs.context.default_partner_id, 7);
|
||||
assert.deepEqual(args.kwargs.context.allowed_company_ids, [3]);
|
||||
assert.strictEqual(args.kwargs.context.company_id, 3);
|
||||
}
|
||||
},
|
||||
|
|
@ -2040,19 +2259,20 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.containsOnce(target, ".modal .o_field_many2one");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".modal .o_field_many2one input").value,
|
||||
"first record"
|
||||
"default partner"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"many2many basic keys in field evalcontext -- in a x2many in form",
|
||||
async (assert) => {
|
||||
assert.expect(6);
|
||||
assert.expect(5);
|
||||
serverData.models.partner_type.fields.partner_id = {
|
||||
string: "Partners",
|
||||
type: "many2one",
|
||||
relation: "partner",
|
||||
};
|
||||
serverData.models.partner.records.push({ id: 7, display_name: "default partner" });
|
||||
serverData.views = {
|
||||
"partner_type,false,form": `<form><field name="partner_id" /></form>`,
|
||||
};
|
||||
|
|
@ -2085,15 +2305,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
<form>
|
||||
<field name="p">
|
||||
<tree editable="top">
|
||||
<field name="timmy" widget="many2many_tags" context="{ 'default_partner_id': active_id, 'ids': active_ids, 'model': active_model, 'company_id': current_company_id}"/>
|
||||
<field name="timmy" widget="many2many_tags" context="{ 'default_partner_id': uid, 'allowed_company_ids': allowed_company_ids, 'company_id': current_company_id}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange") {
|
||||
assert.strictEqual(args.kwargs.context.default_partner_id, 1);
|
||||
assert.strictEqual(args.kwargs.context.model, "partner");
|
||||
assert.deepEqual(args.kwargs.context.ids, [1]);
|
||||
assert.strictEqual(args.kwargs.context.default_partner_id, 7);
|
||||
assert.deepEqual(args.kwargs.context.allowed_company_ids, [3]);
|
||||
assert.strictEqual(args.kwargs.context.company_id, 3);
|
||||
}
|
||||
},
|
||||
|
|
@ -2105,73 +2324,44 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.containsOnce(target, ".modal .o_field_many2one");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".modal .o_field_many2one input").value,
|
||||
"first record"
|
||||
"default partner"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("many2many field calling replaceWith (add + remove)", async function (assert) {
|
||||
serverData.models.partner.records[0].p = [1];
|
||||
QUnit.test(
|
||||
"`this` inside rendererProps should reference the component",
|
||||
async function (assert) {
|
||||
class CustomX2manyField extends X2ManyField {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.selectCreate = (params) => {
|
||||
assert.step("selectCreate");
|
||||
assert.strictEqual(this.num, 2);
|
||||
};
|
||||
this.num = 1;
|
||||
}
|
||||
|
||||
class MyX2Many extends Component {
|
||||
onClick() {
|
||||
this.props.value.replaceWith([2, 3]);
|
||||
async onAdd({ context, editable } = {}) {
|
||||
this.num = 2;
|
||||
assert.step("onAdd");
|
||||
super.onAdd(...arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
MyX2Many.template = xml`
|
||||
<span class="ids" t-esc="this.props.value.resIds"/>
|
||||
<button class="my_btn" t-on-click="onClick">To id</button>`;
|
||||
|
||||
registry.category("fields").add("my_x2many", MyX2Many);
|
||||
const customX2ManyField = {
|
||||
...x2ManyField,
|
||||
component: CustomX2manyField,
|
||||
};
|
||||
registry.category("fields").add("custom", customX2ManyField);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "turtle",
|
||||
serverData,
|
||||
arch: `
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="partner_ids" widget="my_x2many"/>
|
||||
</form>`,
|
||||
resId: 2,
|
||||
});
|
||||
|
||||
assert.strictEqual(target.querySelector(".ids").innerText, "2,4");
|
||||
await click(target.querySelector(".my_btn"));
|
||||
assert.strictEqual(target.querySelector(".ids").innerText, "2,3");
|
||||
});
|
||||
|
||||
QUnit.test("`this` inside rendererProps should reference the component", async function (assert) {
|
||||
class CustomX2manyField extends X2ManyField {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.selectCreate = (params) => {
|
||||
assert.step("selectCreate");
|
||||
assert.strictEqual(this.num, 2);
|
||||
};
|
||||
this.num = 1;
|
||||
}
|
||||
|
||||
async onAdd({ context, editable } = {}) {
|
||||
this.num = 2;
|
||||
assert.step("onAdd");
|
||||
super.onAdd(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("fields").add("custom_x2many", CustomX2manyField);
|
||||
|
||||
serverData.views = {
|
||||
"partner_type,false,list": `<tree><field name="display_name"/></tree>`,
|
||||
"partner_type,false,search": `<search></search>`,
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="timmy" widget="custom_x2many">
|
||||
<field name="timmy" widget="custom">
|
||||
<tree editable="top">
|
||||
<field name="display_name"/>
|
||||
</tree>
|
||||
|
|
@ -2180,9 +2370,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>
|
||||
</field>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
await click(target.querySelector(".o_field_x2many_list_row_add a"));
|
||||
assert.verifySteps(["onAdd", "selectCreate"]);
|
||||
});
|
||||
resId: 1,
|
||||
});
|
||||
await click(target.querySelector(".o_field_x2many_list_row_add a"));
|
||||
assert.verifySteps(["onAdd", "selectCreate"]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { click, clickSave, getFixture, selectDropdownItem } from "@web/../tests/helpers/utils";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import {
|
||||
click,
|
||||
clickSave,
|
||||
getFixture,
|
||||
patchWithCleanup,
|
||||
selectDropdownItem,
|
||||
triggerEvent,
|
||||
editInput,
|
||||
clickOpenedDropdownItem,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { triggerHotkey } from "../../helpers/utils";
|
||||
|
||||
|
|
@ -59,13 +69,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_field_many2many_tags_avatar.o_field_widget .badge",
|
||||
".o_field_many2many_tags_avatar.o_field_widget .o_avatar img",
|
||||
2,
|
||||
"should have 2 records"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_many2many_tags_avatar.o_field_widget .badge img").dataset
|
||||
.src,
|
||||
target.querySelector(".o_field_many2many_tags_avatar.o_field_widget .o_avatar img")
|
||||
.dataset.src,
|
||||
"/web/image/partner/2/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
|
|
@ -116,13 +126,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_data_row:nth-child(2) .o_field_many2many_tags_avatar .o_tag:not(.o_m2m_avatar_empty)",
|
||||
".o_data_row:nth-child(2) .o_field_many2many_tags_avatar .o_avatar:not(.o_m2m_avatar_empty) img",
|
||||
4,
|
||||
"should have 4 records"
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_data_row:nth-child(3) .o_field_many2many_tags_avatar .o_tag:not(.o_m2m_avatar_empty)",
|
||||
".o_data_row:nth-child(3) .o_field_many2many_tags_avatar .o_avatar:not(.o_m2m_avatar_empty) img",
|
||||
5,
|
||||
"should have 5 records"
|
||||
);
|
||||
|
|
@ -149,21 +159,21 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(
|
||||
".o_data_row:nth-child(2) .o_field_many2many_tags_avatar .o_tag:nth-child(2) img.o_m2m_avatar"
|
||||
".o_data_row:nth-child(2) .o_field_many2many_tags_avatar .o_avatar:nth-child(2) img.o_m2m_avatar"
|
||||
).dataset.src,
|
||||
"/web/image/partner/2/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(
|
||||
".o_data_row:nth-child(2) .o_field_many2many_tags_avatar .o_tag:nth-child(3) img.o_m2m_avatar"
|
||||
".o_data_row:nth-child(2) .o_field_many2many_tags_avatar .o_avatar:nth-child(3) img.o_m2m_avatar"
|
||||
).dataset.src,
|
||||
"/web/image/partner/4/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(
|
||||
".o_data_row:nth-child(2) .o_field_many2many_tags_avatar .o_tag:nth-child(4) img.o_m2m_avatar"
|
||||
".o_data_row:nth-child(2) .o_field_many2many_tags_avatar .o_avatar:nth-child(4) img.o_m2m_avatar"
|
||||
).dataset.src,
|
||||
"/web/image/partner/5/avatar_128",
|
||||
"should have correct avatar image"
|
||||
|
|
@ -175,7 +185,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_data_row:nth-child(4) .o_field_many2many_tags_avatar .o_tag:not(.o_m2m_avatar_empty)",
|
||||
".o_data_row:nth-child(4) .o_field_many2many_tags_avatar .o_avatar:not(.o_m2m_avatar_empty) img",
|
||||
4,
|
||||
"should have 4 records"
|
||||
);
|
||||
|
|
@ -213,16 +223,20 @@ QUnit.module("Fields", (hooks) => {
|
|||
await click(target.querySelector(".o_data_row .o_many2many_tags_avatar_cell"));
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_data_row.o_selected_row .o_many2many_tags_avatar_cell .badge",
|
||||
".o_data_row.o_selected_row .o_many2many_tags_avatar_cell .o_avatar img",
|
||||
1,
|
||||
"should have 1 many2many badges in edit mode"
|
||||
);
|
||||
|
||||
await selectDropdownItem(target, "partner_ids", "second record");
|
||||
await click(target.querySelector(".o_list_button_save"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
)
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_data_row:first-child .o_field_many2many_tags_avatar .o_tag",
|
||||
".o_data_row:first-child .o_field_many2many_tags_avatar .o_avatar img",
|
||||
2,
|
||||
"should have 2 records"
|
||||
);
|
||||
|
|
@ -273,16 +287,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
QUnit.test("widget many2many_tags_avatar in kanban view", async function (assert) {
|
||||
assert.expect(13);
|
||||
assert.expect(21);
|
||||
|
||||
const records = [];
|
||||
for (let id = 5; id <= 15; id++) {
|
||||
records.push({
|
||||
serverData.models.partner.records.push({
|
||||
id,
|
||||
display_name: `record ${id}`,
|
||||
});
|
||||
}
|
||||
serverData.models.partner.records = serverData.models.partner.records.concat(records);
|
||||
|
||||
serverData.models.turtle.records.push({
|
||||
id: 4,
|
||||
|
|
@ -294,6 +306,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData.models.turtle.records[2].partner_ids = [1, 2, 4, 5];
|
||||
serverData.views = {
|
||||
"turtle,false,form": '<form><field name="display_name"/></form>',
|
||||
"partner,false,list": '<tree><field name="display_name"/></tree>',
|
||||
"partner,false,search": "<search/>",
|
||||
};
|
||||
|
||||
await makeView({
|
||||
|
|
@ -325,23 +339,21 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
},
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(
|
||||
".o_kanban_record:first-child .o_field_many2many_tags_avatar img.o_m2m_avatar"
|
||||
).dataset.src,
|
||||
"/web/image/partner/1/avatar_128",
|
||||
"should have correct avatar image"
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_kanban_record:first-child .o_field_many2many_tags_avatar .o_quick_assign",
|
||||
"should have the assign icon"
|
||||
);
|
||||
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_kanban_record:nth-child(2) .o_field_many2many_tags_avatar .o_tag",
|
||||
3,
|
||||
"should have 3 records"
|
||||
".o_kanban_record:nth-child(2) .o_field_many2many_tags_avatar .o_avatar img",
|
||||
2,
|
||||
"should have 2 records"
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_kanban_record:nth-child(3) .o_field_many2many_tags_avatar .o_tag",
|
||||
".o_kanban_record:nth-child(3) .o_field_many2many_tags_avatar .o_avatar img",
|
||||
2,
|
||||
"should have 2 records"
|
||||
);
|
||||
|
|
@ -349,14 +361,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
target.querySelector(
|
||||
".o_kanban_record:nth-child(3) .o_field_many2many_tags_avatar img.o_m2m_avatar"
|
||||
).dataset.src,
|
||||
"/web/image/partner/1/avatar_128",
|
||||
"/web/image/partner/5/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelectorAll(
|
||||
".o_kanban_record:nth-child(3) .o_field_many2many_tags_avatar img.o_m2m_avatar"
|
||||
)[1].dataset.src,
|
||||
"/web/image/partner/2/avatar_128",
|
||||
"/web/image/partner/4/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
assert.containsOnce(
|
||||
|
|
@ -376,7 +388,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_kanban_record:nth-child(4) .o_field_many2many_tags_avatar .o_tag",
|
||||
".o_kanban_record:nth-child(4) .o_field_many2many_tags_avatar .o_avatar img",
|
||||
2,
|
||||
"should have 2 records"
|
||||
);
|
||||
|
|
@ -394,28 +406,94 @@ QUnit.module("Fields", (hooks) => {
|
|||
"9+",
|
||||
"should have 9+ in o_m2m_avatar_empty"
|
||||
);
|
||||
assert.containsNone(target, ".o_field_many2many_tags_avatar .o_field_many2many_selection");
|
||||
|
||||
// check data-tooltip attribute (used by the tooltip service)
|
||||
const tag = target.querySelector(
|
||||
".o_kanban_record:nth-child(3) .o_field_many2many_tags_avatar .o_m2m_avatar_empty"
|
||||
const o_kanban_record = target.querySelector(".o_kanban_record:nth-child(2)");
|
||||
await click(o_kanban_record, ".o_field_tags > .o_m2m_avatar_empty");
|
||||
const popover = document.querySelector(".o-overlay-container");
|
||||
assert.strictEqual(
|
||||
document.activeElement,
|
||||
popover.querySelector("input"),
|
||||
"the input inside the popover should have the focus"
|
||||
);
|
||||
assert.strictEqual(popover.querySelectorAll(".o_tag").length, 3, "Should have 3 tags");
|
||||
// delete inside the popover
|
||||
await click(popover.querySelector(".o_tag .o_delete"));
|
||||
assert.strictEqual(popover.querySelectorAll(".o_tag").length, 2, "Should have 2 tag");
|
||||
assert.strictEqual(
|
||||
o_kanban_record.querySelectorAll(".o_tag").length,
|
||||
2,
|
||||
"Should have 2 tags"
|
||||
);
|
||||
// select first input
|
||||
await click(popover.querySelector(".o-autocomplete--dropdown-item"));
|
||||
assert.strictEqual(popover.querySelectorAll(".o_tag").length, 3, "Should have 3 tags");
|
||||
assert.strictEqual(
|
||||
o_kanban_record.querySelectorAll(".o_tag").length,
|
||||
2,
|
||||
"Should have 2 tags"
|
||||
);
|
||||
// load more
|
||||
await click(popover.querySelector(".o_m2o_dropdown_option_search_more"));
|
||||
// first item
|
||||
await click(document.querySelector(".o_dialog .o_list_table .o_data_row .o_data_cell"));
|
||||
assert.strictEqual(popover.querySelectorAll(".o_tag").length, 4, "Should have 4 tags");
|
||||
assert.strictEqual(
|
||||
o_kanban_record.querySelectorAll(".o_tag").length,
|
||||
2,
|
||||
"Should have 2 tags"
|
||||
);
|
||||
assert.strictEqual(
|
||||
tag.dataset["tooltipTemplate"],
|
||||
"web.TagsList.Tooltip",
|
||||
"uses the proper tooltip template"
|
||||
o_kanban_record.querySelector("img.o_m2m_avatar").dataset.src,
|
||||
"/web/image/partner/5/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
const tooltipInfo = JSON.parse(tag.dataset["tooltipInfo"]);
|
||||
assert.strictEqual(
|
||||
tooltipInfo.tags.map((tag) => tag.text).join(" "),
|
||||
"aaa record 5",
|
||||
"shows a tooltip on hover"
|
||||
);
|
||||
|
||||
await click(
|
||||
target.querySelector(".o_kanban_record .o_field_many2many_tags_avatar img.o_m2m_avatar")
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"widget many2many_tags_avatar add/remove tags in kanban view",
|
||||
async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "turtle",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<field name="display_name"/>
|
||||
<div class="oe_kanban_footer">
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="partner_ids" widget="many2many_tags_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
async mockRPC(route, { method, args }) {
|
||||
if (method === "web_save") {
|
||||
const command = args[1].partner_ids[0];
|
||||
assert.step(`web_save: ${command[0]}-${command[1]}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
await click(target, ".o_kanban_record:first-child .o_quick_assign");
|
||||
// add and directly remove an item
|
||||
await click(target, ".o_popover .o-autocomplete--dropdown-item:first-child");
|
||||
await click(target, ".o_popover .o_tag .o_delete");
|
||||
assert.verifySteps(["web_save: 4-1", "web_save: 3-1"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("widget many2many_tags_avatar delete tag", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -432,25 +510,192 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_field_many2many_tags_avatar.o_field_widget .badge",
|
||||
".o_field_many2many_tags_avatar.o_field_widget .o_tag",
|
||||
2,
|
||||
"should have 2 records"
|
||||
);
|
||||
|
||||
await click(
|
||||
target.querySelector(".o_field_many2many_tags_avatar.o_field_widget .badge .o_delete")
|
||||
target.querySelector(".o_field_many2many_tags_avatar.o_field_widget .o_tag .o_delete")
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_many2many_tags_avatar.o_field_widget .badge",
|
||||
".o_field_many2many_tags_avatar.o_field_widget .o_tag",
|
||||
"should have 1 record"
|
||||
);
|
||||
|
||||
await clickSave(target);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_many2many_tags_avatar.o_field_widget .badge",
|
||||
".o_field_many2many_tags_avatar.o_field_widget .o_tag",
|
||||
"should have 1 record"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"widget many2many_tags_avatar quick add tags and close in kanban view with keyboard navigation",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "turtle",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<field name="display_name"/>
|
||||
<div class="oe_kanban_footer">
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="partner_ids" widget="many2many_tags_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
await click(target, ".o_kanban_record:first-child .o_quick_assign");
|
||||
// add and directly close the dropdown
|
||||
await triggerEvent(target, null, "keydown", { key: "Tab" });
|
||||
await triggerEvent(document.activeElement, null, "keydown", { key: "Enter" });
|
||||
await triggerEvent(target, null, "keydown", { key: "Escape" });
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_kanban_record:first-child .o_field_many2many_tags_avatar .o_tag",
|
||||
"should assign the user"
|
||||
);
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_kanban_record:first-child .o_field_many2many_tags_avatar .o_popover",
|
||||
"should have close the popover"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"widget many2many_tags_avatar in kanban view missing access rights",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "turtle",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban edit="0" create="0">
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<field name="display_name"/>
|
||||
<div class="oe_kanban_footer">
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="partner_ids" widget="many2many_tags_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_kanban_record:first-child .o_field_many2many_tags_avatar .o_quick_assign",
|
||||
"should not have the assign icon"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("widget many2many_tags_avatar", async function (assert) {
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
});
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "turtle",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="partner_ids" widget="many2many_tags_avatar"/>
|
||||
</sheet>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll("[name='partner_ids'] .o_tag")].map((el) => el.textContent),
|
||||
[]
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector("[name='partner_ids'] .o_input_dropdown input").value,
|
||||
""
|
||||
);
|
||||
|
||||
await editInput(target, "[name='partner_ids'] .o_input_dropdown input", "first record");
|
||||
await triggerEvent(target, "[name='partner_ids'] .o_input_dropdown input", "keydown", {
|
||||
key: "Enter",
|
||||
});
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll("[name='partner_ids'] .o_tag")].map((el) => el.textContent),
|
||||
["first record"]
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector("[name='partner_ids'] .o_input_dropdown input").value,
|
||||
""
|
||||
);
|
||||
|
||||
await editInput(target, "[name='partner_ids'] .o_input_dropdown input", "abc");
|
||||
await triggerEvent(target, "[name='partner_ids'] .o_input_dropdown input", "keydown", {
|
||||
key: "Enter",
|
||||
});
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll("[name='partner_ids'] .o_tag")].map((el) => el.textContent),
|
||||
["first record", "abc"]
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector("[name='partner_ids'] .o_input_dropdown input").value,
|
||||
""
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Many2ManyTagsAvatarField: make sure that the arch context is passed to the form view call",
|
||||
async function (assert) {
|
||||
serverData.views = {
|
||||
"partner,false,form": `<form><field name="display_name"/></form>`,
|
||||
};
|
||||
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "turtle",
|
||||
serverData,
|
||||
arch: `<list editable="top">
|
||||
<field name="partner_ids" widget="many2many_tags_avatar" context="{ 'append_coucou': 'test_value' }"/>
|
||||
</list>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange" && args.model === "partner") {
|
||||
if (args.kwargs.context.append_coucou === "test_value") {
|
||||
assert.step("onchange with context given");
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await click(target.querySelector("div[name=partner_ids]"));
|
||||
await editInput(target, `div[name="partner_ids"] input`, "A new partner");
|
||||
await clickOpenedDropdownItem(target, "partner_ids", "Create and edit...");
|
||||
|
||||
assert.containsOnce(target, ".modal .o_form_view", "Here we should have opened the modal form view");
|
||||
assert.verifySteps(["onchange with context given"]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { makeServerError } from "@web/../tests/helpers/mock_server";
|
||||
import { AutoComplete } from "@web/core/autocomplete/autocomplete";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { Many2ManyTagsField } from "@web/views/fields/many2many_tags/many2many_tags_field";
|
||||
import {
|
||||
addRow,
|
||||
click,
|
||||
clickDiscard,
|
||||
clickDropdown,
|
||||
|
|
@ -19,7 +20,6 @@ import {
|
|||
triggerEvent,
|
||||
triggerHotkey,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { RPCError } from "@web/core/network/rpc_service";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
|
|
@ -38,7 +38,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
string: "one2many turtle field",
|
||||
type: "one2many",
|
||||
relation: "turtle",
|
||||
relation_field: "turtle_trululu",
|
||||
},
|
||||
timmy: { string: "pokemon", type: "many2many", relation: "partner_type" },
|
||||
},
|
||||
|
|
@ -113,13 +112,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
QUnit.module("Many2ManyTagsField");
|
||||
|
||||
QUnit.test("Many2ManyTagsField with and without color", async function (assert) {
|
||||
assert.expect(12);
|
||||
assert.expect(14);
|
||||
|
||||
serverData.models.partner.fields.partner_ids = {
|
||||
string: "Partner",
|
||||
type: "many2many",
|
||||
relation: "partner",
|
||||
};
|
||||
serverData.models.partner.fields.color = { string: "Color index", type: "integer" };
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -130,17 +130,19 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="partner_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
<field name="timmy" widget="many2many_tags"/>
|
||||
</form>`,
|
||||
mockRPC: (route, { args, method, model }) => {
|
||||
if (method === "read" && model === "partner_type") {
|
||||
mockRPC: (route, { args, method, model, kwargs }) => {
|
||||
if (method === "web_read" && model === "partner_type") {
|
||||
assert.deepEqual(args, [[12]]);
|
||||
assert.deepEqual(
|
||||
args,
|
||||
[[12], ["display_name"]],
|
||||
kwargs.specification,
|
||||
{ display_name: {} },
|
||||
"should not read any color field"
|
||||
);
|
||||
} else if (method === "read" && model === "partner") {
|
||||
} else if (method === "web_read" && model === "partner") {
|
||||
assert.deepEqual(args, [[1]]);
|
||||
assert.deepEqual(
|
||||
args,
|
||||
[[1], ["display_name", "color"]],
|
||||
kwargs.specification,
|
||||
{ display_name: {}, color: {} },
|
||||
"should read color field"
|
||||
);
|
||||
}
|
||||
|
|
@ -163,8 +165,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
const autocomplete = target.querySelector("[name='timmy'] .o-autocomplete.dropdown");
|
||||
assert.strictEqual(
|
||||
autocomplete.querySelectorAll("li").length,
|
||||
3,
|
||||
"autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')"
|
||||
4,
|
||||
"autocomplete dropdown should have 4 entries (2 values + 'Search More...' + 'Search and Edit...')"
|
||||
);
|
||||
await clickOpenedDropdownItem(target, "timmy", "gold");
|
||||
assert.containsOnce(target, "[name=timmy] .o_tag");
|
||||
|
|
@ -182,7 +184,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test("Many2ManyTagsField with color: rendering and edition", async function (assert) {
|
||||
assert.expect(26);
|
||||
assert.expect(24);
|
||||
|
||||
serverData.models.partner.records[0].timmy = [12, 14];
|
||||
serverData.models.partner_type.records.push({ id: 13, display_name: "red", color: 8 });
|
||||
|
|
@ -195,22 +197,22 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="timmy" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True }"/>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC: (route, { args, method, model }) => {
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
mockRPC: (route, { args, method, model, kwargs }) => {
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
var commands = args[1].timmy;
|
||||
assert.strictEqual(commands.length, 1, "should have generated one command");
|
||||
assert.strictEqual(
|
||||
commands[0][0],
|
||||
6,
|
||||
"generated command should be REPLACE WITH"
|
||||
);
|
||||
assert.deepEqual(commands[0][2], [12, 13], "new value should be [12, 13]");
|
||||
}
|
||||
if (method === "read" && model === "partner_type") {
|
||||
assert.strictEqual(commands.length, 2, "should have generated two commands");
|
||||
assert.strictEqual(commands.map((cmd) => cmd[0]).join("-"), "4-3");
|
||||
assert.deepEqual(
|
||||
args[1],
|
||||
["display_name", "color"],
|
||||
"should read the color field"
|
||||
commands.map((cmd) => cmd[1]),
|
||||
[13, 14],
|
||||
"Should add 13, remove 14"
|
||||
);
|
||||
}
|
||||
if ((method === "web_read" || method === "web_save") && model === "partner_type") {
|
||||
assert.deepEqual(
|
||||
kwargs.specification,
|
||||
{ display_name: {}, color: {} },
|
||||
"should read color field"
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -245,8 +247,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.strictEqual(
|
||||
autocompleteDropdown.querySelectorAll("li").length,
|
||||
2,
|
||||
"autocomplete dropdown should have 2 entry"
|
||||
3,
|
||||
"autocomplete dropdown should have 3 entry"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
|
|
@ -349,14 +351,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.containsNone(target, ".badge.dropdown-toggle", "the tags should not be dropdowns");
|
||||
|
||||
// click on the tag: should do nothing and open the form view
|
||||
click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
|
||||
await click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
|
||||
assert.verifySteps(["selectRecord"]);
|
||||
await nextTick();
|
||||
|
||||
assert.containsNone(target, ".o_colorlist");
|
||||
|
||||
await click(target.querySelectorAll(".o_list_record_selector")[1]);
|
||||
click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
|
||||
await click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
|
||||
assert.verifySteps(["selectRecord"]);
|
||||
await nextTick();
|
||||
|
||||
|
|
@ -384,14 +386,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.containsNone(target, ".badge.dropdown-toggle", "the tags should not be dropdowns");
|
||||
|
||||
// click on the tag: should do nothing and open the form view
|
||||
click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
|
||||
await click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
|
||||
assert.verifySteps(["selectRecord"]);
|
||||
await nextTick();
|
||||
|
||||
assert.containsNone(target, ".o_colorlist");
|
||||
|
||||
await click(target.querySelectorAll(".o_list_record_selector")[1]);
|
||||
click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
|
||||
await click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
|
||||
assert.verifySteps([]);
|
||||
await nextTick();
|
||||
|
||||
|
|
@ -439,8 +441,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.strictEqual(
|
||||
autocompleteDropdown.querySelectorAll("li").length,
|
||||
2,
|
||||
"autocomplete dropdown should have 2 entry"
|
||||
3,
|
||||
"autocomplete dropdown should have 3 entries"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
|
|
@ -464,6 +466,116 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("use binary field as the domain", async (assert) => {
|
||||
serverData.models.partner.fields.domain = { string: "Domain", type: "binary" };
|
||||
serverData.models.partner.records[0].domain = [["id", "<", 50]];
|
||||
serverData.models.partner.records[0].timmy = [12];
|
||||
serverData.models.partner_type.records.push({ id: 99, display_name: "red", color: 8 });
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="timmy" widget="many2many_tags" domain="domain"/>
|
||||
<field name="domain" invisible="1"/>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_field_many2many_tags .badge", "should contain 1 tag");
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".badge")),
|
||||
["gold"],
|
||||
"should have fetched and rendered gold partner tag"
|
||||
);
|
||||
|
||||
await clickDropdown(target, "timmy");
|
||||
|
||||
const autocompleteDropdown = target.querySelector(".o-autocomplete--dropdown-menu");
|
||||
|
||||
assert.strictEqual(
|
||||
autocompleteDropdown.querySelectorAll("li").length,
|
||||
3,
|
||||
"autocomplete dropdown should have 3 entries"
|
||||
);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(autocompleteDropdown.querySelectorAll("li")),
|
||||
["silver", "Search More...", "Start typing..."],
|
||||
"should contain newly added tag 'silver'"
|
||||
);
|
||||
assert.strictEqual(
|
||||
autocompleteDropdown.querySelector("li a").textContent,
|
||||
"silver",
|
||||
"autocomplete dropdown should contain 'silver'"
|
||||
);
|
||||
|
||||
await clickOpenedDropdownItem(target, "timmy", "silver");
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelectorAll(".o_field_many2many_tags .badge").length,
|
||||
2,
|
||||
"should contain 2 tags"
|
||||
);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".badge")),
|
||||
["gold", "silver"],
|
||||
"should contain newly added tag 'silver'"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Domain: allow python code domain in fieldInfo", async function (assert) {
|
||||
assert.expect(4);
|
||||
serverData.models.partner.fields.timmy.domain =
|
||||
"foo and [('color', '>', 3)] or [('color', '<', 3)]";
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="foo"/>
|
||||
<field name="timmy" widget="many2many_tags"></field>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
// foo set => only silver (id=5) selectable
|
||||
await clickDropdown(target, "timmy");
|
||||
let autocompleteDropdown = target.querySelector(".o-autocomplete--dropdown-menu");
|
||||
assert.containsN(
|
||||
autocompleteDropdown,
|
||||
"li",
|
||||
3,
|
||||
"autocomplete should contain 'silver'm 'Search More...' and 'Start typing...' options"
|
||||
);
|
||||
assert.strictEqual(
|
||||
autocompleteDropdown.querySelector("li a").textContent,
|
||||
"silver",
|
||||
"autocomplete dropdown should contain 'silver'"
|
||||
);
|
||||
await clickOpenedDropdownItem(target, "timmy", "Start typing...");
|
||||
|
||||
// set foo = "" => only gold (id=2) selectable
|
||||
const textInput = target.querySelector("[name=foo] input");
|
||||
textInput.focus();
|
||||
await editInput(textInput, null, "");
|
||||
await clickDropdown(target, "timmy");
|
||||
autocompleteDropdown = target.querySelector(".o-autocomplete--dropdown-menu");
|
||||
assert.containsN(
|
||||
autocompleteDropdown,
|
||||
"li",
|
||||
3,
|
||||
"autocomplete should contain 'gold'm 'Search More...' and 'Start typing...' options"
|
||||
);
|
||||
assert.strictEqual(
|
||||
autocompleteDropdown.querySelector("li a").textContent,
|
||||
"gold",
|
||||
"autocomplete dropdown should contain 'gold'"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyTagsField in a new record", async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
|
|
@ -473,15 +585,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: '<form><field name="timmy" widget="many2many_tags"/></form>',
|
||||
mockRPC: (route, { args }) => {
|
||||
if (route === "/web/dataset/call_kw/partner/create") {
|
||||
var commands = args[0].timmy;
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
const commands = args[1].timmy;
|
||||
assert.strictEqual(commands.length, 1, "should have generated one command");
|
||||
assert.strictEqual(
|
||||
commands[0][0],
|
||||
6,
|
||||
"generated command should be REPLACE WITH"
|
||||
);
|
||||
assert.ok(_.isEqual(commands[0][2], [12]), "new value should be [12]");
|
||||
assert.strictEqual(commands[0][0], 4, "generated command should be LINK TO");
|
||||
assert.strictEqual(commands[0][1], 12, "new value should be 12");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -495,8 +603,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
const autocomplete = target.querySelector("[name='timmy'] .o-autocomplete.dropdown");
|
||||
assert.strictEqual(
|
||||
autocomplete.querySelectorAll("li").length,
|
||||
3,
|
||||
"autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')"
|
||||
4,
|
||||
"autocomplete dropdown should have 4 entries (2 values + 'Search More...' + 'Search and Edit...')"
|
||||
);
|
||||
await clickOpenedDropdownItem(target, "timmy", "gold");
|
||||
|
||||
|
|
@ -524,7 +632,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="timmy" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
</form>`,
|
||||
mockRPC: (route, { args, method }) => {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.step(JSON.stringify(args[1]));
|
||||
}
|
||||
},
|
||||
|
|
@ -610,7 +718,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test("Many2ManyTagsField in editable list", async function (assert) {
|
||||
assert.expect(7);
|
||||
assert.expect(5);
|
||||
|
||||
serverData.models.partner.records[0].timmy = [12];
|
||||
|
||||
|
|
@ -624,7 +732,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="timmy" widget="many2many_tags"/>
|
||||
</tree>`,
|
||||
mockRPC: (route, { kwargs, method, model }) => {
|
||||
if (method === "read" && model === "partner_type") {
|
||||
if (method === "web_read" && model === "partner_type") {
|
||||
assert.strictEqual(
|
||||
kwargs.context.take,
|
||||
"five",
|
||||
|
|
@ -680,43 +788,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Many2ManyTagsField loads records according to limit defined on widget prototype",
|
||||
async function (assert) {
|
||||
patchWithCleanup(Many2ManyTagsField, {
|
||||
limit: 30,
|
||||
});
|
||||
|
||||
serverData.models.partner.fields.partner_ids = {
|
||||
string: "Partner",
|
||||
type: "many2many",
|
||||
relation: "partner",
|
||||
};
|
||||
serverData.models.partner.records[0].partner_ids = [];
|
||||
for (var i = 15; i < 50; i++) {
|
||||
serverData.models.partner.records.push({ id: i, display_name: "walter" + i });
|
||||
serverData.models.partner.records[0].partner_ids.push(i);
|
||||
}
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: '<form><field name="partner_ids" widget="many2many_tags"/></form>',
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelectorAll('.o_field_widget[name="partner_ids"] .badge').length,
|
||||
30,
|
||||
"should have rendered 30 tags even though 35 records linked"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("Many2ManyTagsField keeps focus when being edited", async function (assert) {
|
||||
serverData.models.partner.records[0].timmy = [12];
|
||||
serverData.models.partner.onchanges.foo = function (obj) {
|
||||
obj.timmy = [[5]]; // DELETE command
|
||||
obj.timmy = [[3, 12]];
|
||||
};
|
||||
|
||||
await makeView({
|
||||
|
|
@ -1069,15 +1144,21 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: '<form><field name="timmy" widget="many2many_tags"/></form>',
|
||||
resId: 1,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "read" && args.model === "partner_type") {
|
||||
assert.step(args.kwargs.context.hello);
|
||||
if (args.method === "web_read" && args.model === "partner") {
|
||||
assert.step(`${args.method} ${args.model}`);
|
||||
assert.strictEqual(args.kwargs.specification.timmy.context.hello, "world");
|
||||
}
|
||||
|
||||
if (args.method === "web_read" && args.model === "partner_type") {
|
||||
assert.step(`${args.method} ${args.model}`);
|
||||
assert.strictEqual(args.kwargs.context.hello, "world");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.verifySteps(["world"]);
|
||||
assert.verifySteps(["web_read partner"]);
|
||||
await selectDropdownItem(target, "timmy", "silver");
|
||||
assert.verifySteps(["world"]);
|
||||
assert.verifySteps(["web_read partner_type"]);
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyTagsField: select multiple records", async function (assert) {
|
||||
|
|
@ -1482,12 +1563,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: '<form><field name="timmy" widget="many2many_tags"/></form>',
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "name_create") {
|
||||
const error = new RPCError("Something went wrong");
|
||||
error.exceptionName = "odoo.exceptions.ValidationError";
|
||||
throw error;
|
||||
throw makeServerError({ type: "ValidationError" });
|
||||
}
|
||||
if (args.method === "create") {
|
||||
assert.deepEqual(args.args[0], {
|
||||
if (args.method === "web_save") {
|
||||
assert.deepEqual(args.args[1], {
|
||||
color: 8,
|
||||
name: "new partner",
|
||||
});
|
||||
|
|
@ -1553,6 +1632,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="timmy" widget="many2many_tags"/>
|
||||
<field name="name"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
|
|
@ -1620,8 +1700,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch:
|
||||
'<form><field name="timmy" widget="many2many_tags" placeholder="Placeholder"/></form>',
|
||||
arch: '<form><field name="timmy" widget="many2many_tags" placeholder="Placeholder"/></form>',
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
|
|
@ -1649,7 +1728,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(
|
||||
target.querySelector(".o_field_many2many_tags .o-autocomplete--dropdown-menu")
|
||||
.textContent,
|
||||
"goldsilver"
|
||||
"goldsilverSearch More..."
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -1674,6 +1753,52 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.containsOnce(target, "[name='timmy'].o_field_invalid");
|
||||
});
|
||||
|
||||
QUnit.test("set a required many2many_tags and save directly", async function (assert) {
|
||||
let def;
|
||||
const form = await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: '<form><field name="timmy" widget="many2many_tags" required="1"/></form>',
|
||||
async mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
if (args.method === "web_read") {
|
||||
await def;
|
||||
}
|
||||
},
|
||||
});
|
||||
patchWithCleanup(form.env.services.notification, {
|
||||
add: () => assert.step("notification"),
|
||||
});
|
||||
|
||||
assert.verifySteps(["get_views", "onchange"]);
|
||||
|
||||
assert.containsNone(target, ".o_tag");
|
||||
|
||||
def = makeDeferred();
|
||||
await clickDropdown(target, "timmy");
|
||||
await clickOpenedDropdownItem(target, "timmy", "gold");
|
||||
assert.containsOnce(target, ".o_tag");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_tag").textContent,
|
||||
"",
|
||||
"The tag is displayed, but the web read is not finished yet"
|
||||
);
|
||||
|
||||
assert.verifySteps(["name_search", "web_read"]);
|
||||
|
||||
await clickSave(target);
|
||||
assert.doesNotHaveClass(target, "[name='timmy']", "o_field_invalid");
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
assert.strictEqual(target.querySelector(".o_tag").textContent, "gold");
|
||||
|
||||
assert.verifySteps(["web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyTagsField with option 'no_quick_create' set to true", async (assert) => {
|
||||
serverData.views = {
|
||||
"partner_type,false,form": `<form><field name="name"/><field name="color"/></form>`,
|
||||
|
|
@ -1767,7 +1892,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `<form><field name="timmy" widget="many2many_tags" context="{ 'append_coucou': True }"/></form>`,
|
||||
async mockRPC(route, args, performRPC) {
|
||||
const result = await performRPC(route, args);
|
||||
if (args.method === "read") {
|
||||
if (args.method === "web_read") {
|
||||
if (args.kwargs.context.append_coucou) {
|
||||
assert.step("read with context given");
|
||||
result[0].display_name += " coucou";
|
||||
|
|
@ -1799,7 +1924,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `<list editable="top"><field name="timmy" widget="many2many_tags" context="{ 'append_coucou': True }"/></list>`,
|
||||
async mockRPC(route, args, performRPC) {
|
||||
const result = await performRPC(route, args);
|
||||
if (args.method === "read") {
|
||||
if (args.method === "web_read") {
|
||||
if (args.kwargs.context.append_coucou) {
|
||||
assert.step("read with context given");
|
||||
result[0].display_name += " coucou";
|
||||
|
|
@ -1823,4 +1948,89 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.verifySteps(["name search with context given", "read with context given"]);
|
||||
assert.strictEqual(target.querySelector(".o_field_tags").innerText, "gold coucou");
|
||||
});
|
||||
|
||||
QUnit.test("Many2ManyTagsField doesn't use virtualId for 'name_search'", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
resId: 1,
|
||||
arch: `<form>
|
||||
<field name="turtles" widget="many2many_tags"/>
|
||||
<field name="turtles">
|
||||
<tree>
|
||||
<field name="display_name"/>
|
||||
</tree>
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
</form>
|
||||
</field>
|
||||
</form>`,
|
||||
async mockRPC(route, { method, kwargs }) {
|
||||
if (method === "name_search") {
|
||||
assert.step("name_search");
|
||||
// no virtualId in domain
|
||||
assert.deepEqual(kwargs.args, ["!", ["id", "in", [2]]]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await addRow(target);
|
||||
assert.containsOnce(target, ".modal");
|
||||
|
||||
await editInput(target, ".modal [name='display_name'] input", "yop");
|
||||
await click(target.querySelector(".modal .o_form_button_save"));
|
||||
assert.containsNone(target, ".modal");
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll("[name='turtles'] .o_tag_badge_text")].map(
|
||||
(el) => el.textContent
|
||||
),
|
||||
["donatello", "yop"]
|
||||
);
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll("[name='turtles'] .o_data_row")].map(
|
||||
(el) => el.textContent
|
||||
),
|
||||
["donatello", "yop"]
|
||||
);
|
||||
|
||||
await click(target.querySelector("[name='turtles'] input"));
|
||||
assert.verifySteps(["name_search"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Many2ManyTagsField: quickly remove several tags with backspace",
|
||||
async function (assert) {
|
||||
serverData.models.partner.records[0].timmy = [12, 14];
|
||||
serverData.models.partner.onchanges.timmy = () => {};
|
||||
|
||||
const def = makeDeferred();
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="timmy" widget="many2many_tags"/>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange") {
|
||||
assert.step(`onchange ${JSON.stringify(args.args[1].timmy)}`);
|
||||
return def;
|
||||
}
|
||||
},
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.containsN(target, ".o_field_many2many_tags .badge", 2);
|
||||
|
||||
target.querySelectorAll(".o_field_many2many_tags .badge")[1].focus();
|
||||
triggerHotkey("BackSpace");
|
||||
triggerHotkey("BackSpace");
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
assert.containsN(target, ".o_field_many2many_tags .badge", 1);
|
||||
assert.verifySteps(["onchange [[3,14]]"]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
selectDropdownItem,
|
||||
triggerEvent,
|
||||
clickDiscard,
|
||||
clickOpenedDropdownItem,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
|
|
@ -87,7 +88,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
target,
|
||||
'.o_m2o_avatar > img[data-src="/web/image/user/17/avatar_128"]'
|
||||
);
|
||||
assert.containsOnce(target, '.o_field_many2one_avatar > div[data-tooltip="Aline"]');
|
||||
assert.containsOnce(target, ".o_field_many2one_avatar > div");
|
||||
|
||||
assert.containsOnce(target, ".o_input_dropdown");
|
||||
assert.strictEqual(target.querySelector(".o_input_dropdown input").value, "Aline");
|
||||
|
|
@ -186,7 +187,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_data_cell[name='user_id'] span span")),
|
||||
getNodesTextContent(target.querySelectorAll(".o_data_cell[name='user_id']")),
|
||||
["Aline", "Christine", "Aline", ""]
|
||||
);
|
||||
const imgs = target.querySelectorAll(".o_m2o_avatar > img");
|
||||
|
|
@ -204,7 +205,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_data_cell[name='user_id'] span span")),
|
||||
getNodesTextContent(target.querySelectorAll(".o_data_cell[name='user_id']")),
|
||||
["Aline", "Christine", "Aline", ""]
|
||||
);
|
||||
|
||||
|
|
@ -226,8 +227,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch:
|
||||
'<form><field name="user_id" widget="many2one_avatar" placeholder="Placeholder"/></form>',
|
||||
arch: '<form><field name="user_id" widget="many2one_avatar" placeholder="Placeholder"/></form>',
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
|
|
@ -255,7 +255,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
await click(target.querySelectorAll(".o_data_row")[0], ".o_list_record_selector input");
|
||||
await click(target.querySelector(".o_data_row .o_data_cell [name='user_id'] span span"));
|
||||
await click(target.querySelector(".o_data_row .o_data_cell [name='user_id']"));
|
||||
assert.hasClass(target.querySelector(".o_data_row"), "o_selected_row");
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
|
@ -280,7 +280,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
await click(target.querySelectorAll(".o_data_row")[0], ".o_list_record_selector input");
|
||||
await click(target.querySelector(".o_data_row .o_data_cell [name='user_id'] span span"));
|
||||
await click(target.querySelector(".o_data_row .o_data_cell [name='user_id']"));
|
||||
assert.hasClass(target.querySelector(".o_data_row"), "o_selected_row");
|
||||
|
||||
assert.verifySteps([]);
|
||||
|
|
@ -304,12 +304,41 @@ QUnit.module("Fields", (hooks) => {
|
|||
</tree>`,
|
||||
});
|
||||
|
||||
await click(target.querySelector(".o_data_row .o_data_cell [name='user_id'] span span"));
|
||||
await click(target.querySelector(".o_data_row .o_data_cell [name='user_id']"));
|
||||
assert.containsNone(target, ".o_selected_row");
|
||||
|
||||
assert.verifySteps(["openRecord"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"readonly many2one_avatar in form view should contain a link",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: `<form><field name="user_id" widget="many2one_avatar" readonly="1"/></form>`,
|
||||
});
|
||||
|
||||
assert.containsOnce(target, "[name='user_id'] a");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"readonly many2one_avatar in list view should not contain a link",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "list",
|
||||
serverData,
|
||||
resModel: "partner",
|
||||
arch: `<tree><field name="user_id" widget="many2one_avatar"/></tree>`,
|
||||
});
|
||||
|
||||
assert.containsNone(target, "[name='user_id'] a");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("cancelling create dialog should clear value in the field", async function (assert) {
|
||||
serverData.views = {
|
||||
"user,false,form": `
|
||||
|
|
@ -332,11 +361,171 @@ QUnit.module("Fields", (hooks) => {
|
|||
const input = target.querySelector(".o_field_widget[name=user_id] input");
|
||||
input.value = "yy";
|
||||
await triggerEvent(input, null, "input");
|
||||
await click(target, ".o_field_widget[name=user_id] input");
|
||||
await selectDropdownItem(target, "user_id", "Create and edit...");
|
||||
await clickOpenedDropdownItem(target, "user_id", "Create and edit...");
|
||||
|
||||
await clickDiscard(target.querySelector(".modal"));
|
||||
assert.strictEqual(target.querySelector(".o_field_widget[name=user_id] input").value, "");
|
||||
assert.containsOnce(target, ".o_field_widget[name=user_id] span.o_m2o_avatar_empty");
|
||||
});
|
||||
|
||||
QUnit.test("widget many2one_avatar in kanban view (load more dialog)", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
for (let id = 1; id <= 10; id++) {
|
||||
serverData.models.user.records.push({
|
||||
id,
|
||||
display_name: `record ${id}`,
|
||||
});
|
||||
}
|
||||
|
||||
serverData.views = {
|
||||
"user,false,list": '<tree><field name="display_name"/></tree>',
|
||||
"user,false,search": "<search/>",
|
||||
};
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="oe_kanban_footer">
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="user_id" widget="many2one_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
|
||||
// open popover
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_kanban_record:nth-child(4) .o_field_many2one_avatar .o_m2o_avatar > a.o_quick_assign"
|
||||
)
|
||||
);
|
||||
|
||||
// load more
|
||||
await click(
|
||||
document.querySelector(".o-overlay-container .o_m2o_dropdown_option_search_more")
|
||||
);
|
||||
await click(document.querySelector(".o_dialog .o_list_table .o_data_row .o_data_cell"));
|
||||
assert.strictEqual(
|
||||
target.querySelector(
|
||||
".o_kanban_record:nth-child(4) .o_field_many2one_avatar .o_m2o_avatar > img"
|
||||
).dataset.src,
|
||||
"/web/image/user/1/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("widget many2one_avatar in kanban view", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="oe_kanban_footer">
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="user_id" widget="many2one_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(
|
||||
".o_kanban_record:nth-child(1) .o_field_many2one_avatar .o_m2o_avatar > img"
|
||||
).dataset.src,
|
||||
"/web/image/user/17/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_kanban_record:nth-child(4) .o_field_many2one_avatar .o_m2o_avatar > .o_quick_assign",
|
||||
"should have the quick assign icon"
|
||||
);
|
||||
// open popover
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_kanban_record:nth-child(4) .o_field_many2one_avatar .o_m2o_avatar > .o_quick_assign"
|
||||
)
|
||||
);
|
||||
const popover = document.querySelector(".o-overlay-container");
|
||||
assert.strictEqual(
|
||||
document.activeElement,
|
||||
popover.querySelector("input"),
|
||||
"the input inside the popover should have the focus"
|
||||
);
|
||||
// select first input
|
||||
await click(popover.querySelector(".o-autocomplete--dropdown-item"));
|
||||
assert.strictEqual(
|
||||
target.querySelector(
|
||||
".o_kanban_record:nth-child(4) .o_field_many2one_avatar .o_m2o_avatar > img"
|
||||
).dataset.src,
|
||||
"/web/image/user/17/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_kanban_record:nth-child(4) .o_field_many2one_avatar .o_m2o_avatar > .o_quick_assign",
|
||||
"should not have the quick assign icon"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"widget many2one_avatar in kanban view without access rights",
|
||||
async function (assert) {
|
||||
assert.expect(2);
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban edit="0" create="0">
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="oe_kanban_footer">
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="user_id" widget="many2one_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(
|
||||
".o_kanban_record:nth-child(1) .o_field_many2one_avatar .o_m2o_avatar > img"
|
||||
).dataset.src,
|
||||
"/web/image/user/17/avatar_128",
|
||||
"should have correct avatar image"
|
||||
);
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_kanban_record:nth-child(4) .o_field_many2one_avatar .o_m2o_avatar > .o_quick_assign",
|
||||
"should not have the quick assign icon"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import * as BarcodeScanner from "@web/webclient/barcode/barcode_scanner";
|
|||
let serverData;
|
||||
let target;
|
||||
|
||||
const CREATE = "create";
|
||||
const NAME_SEARCH = "name_search";
|
||||
const PRODUCT_PRODUCT = "product.product";
|
||||
const SALE_ORDER_LINE = "sale_order_line";
|
||||
|
|
@ -133,8 +132,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>
|
||||
`,
|
||||
async mockRPC(route, args, performRPC) {
|
||||
if (args.method === CREATE && args.model === SALE_ORDER_LINE) {
|
||||
const selectedId = args.args[0][PRODUCT_FIELD_NAME];
|
||||
if (args.method === "web_save" && args.model === SALE_ORDER_LINE) {
|
||||
const selectedId = args.args[1][PRODUCT_FIELD_NAME];
|
||||
assert.equal(
|
||||
selectedId,
|
||||
selectedRecordTest.id,
|
||||
|
|
@ -172,8 +171,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="${PRODUCT_FIELD_NAME}" options="{'can_scan_barcode': True}"/>
|
||||
</form>`,
|
||||
async mockRPC(route, args, performRPC) {
|
||||
if (args.method === CREATE && args.model === SALE_ORDER_LINE) {
|
||||
const selectedId = args.args[0][PRODUCT_FIELD_NAME];
|
||||
if (args.method === "web_save" && args.model === SALE_ORDER_LINE) {
|
||||
const selectedId = args.args[1][PRODUCT_FIELD_NAME];
|
||||
assert.equal(
|
||||
selectedId,
|
||||
selectedRecordTest.id,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -10,7 +10,7 @@ import {
|
|||
patchWithCleanup,
|
||||
triggerEvent,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { session } from "@web/session";
|
||||
import { currencies } from "@web/core/currency";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
|
@ -252,15 +252,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.test("with currency digits != 2 - float field", async function (assert) {
|
||||
// need to also add it to the session (as currencies are loaded there)
|
||||
patchWithCleanup(session, {
|
||||
currencies: {
|
||||
...session.currencies,
|
||||
3: {
|
||||
name: "VEF",
|
||||
symbol: "Bs.F",
|
||||
position: "after",
|
||||
digits: [0, 4],
|
||||
},
|
||||
patchWithCleanup(currencies, {
|
||||
3: {
|
||||
name: "VEF",
|
||||
symbol: "Bs.F",
|
||||
position: "after",
|
||||
digits: [0, 4],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -313,15 +310,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.test("with currency digits != 2 - monetary field", async function (assert) {
|
||||
// need to also add it to the session (as currencies are loaded there)
|
||||
patchWithCleanup(session, {
|
||||
currencies: {
|
||||
...session.currencies,
|
||||
3: {
|
||||
name: "VEF",
|
||||
symbol: "Bs.F",
|
||||
position: "after",
|
||||
digits: [0, 4],
|
||||
},
|
||||
patchWithCleanup(currencies, {
|
||||
3: {
|
||||
name: "VEF",
|
||||
symbol: "Bs.F",
|
||||
position: "after",
|
||||
digits: [0, 4],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -406,7 +400,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="float_field" widget="monetary"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
|
|
@ -501,7 +495,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="monetary_field"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
|
|
@ -685,7 +679,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
string: "m2m",
|
||||
type: "many2many",
|
||||
relation: "partner",
|
||||
default: [[6, false, [2]]],
|
||||
default: [[4, 2]],
|
||||
};
|
||||
serverData.views = {
|
||||
"partner,false,list": `
|
||||
|
|
@ -777,7 +771,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
</tree>`,
|
||||
});
|
||||
|
||||
await click(target.querySelector(".o_list_button_add"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_add"
|
||||
)
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_selected_row .o_field_widget[name=float_field] input",
|
||||
|
|
@ -865,15 +863,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
},
|
||||
];
|
||||
|
||||
patchWithCleanup(session, {
|
||||
currencies: {
|
||||
...session.currencies,
|
||||
1: {
|
||||
name: "USD",
|
||||
symbol: "$",
|
||||
position: "before",
|
||||
digits: [0, 4],
|
||||
},
|
||||
patchWithCleanup(currencies, {
|
||||
1: {
|
||||
name: "USD",
|
||||
symbol: "$",
|
||||
position: "before",
|
||||
digits: [0, 4],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { getFixture, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { getFixture, nextTick, patchWithCleanup, triggerEvent } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { useNumpadDecimal } from "@web/views/fields/numpad_decimal_hook";
|
||||
import { makeTestEnv } from "../../helpers/mock_env";
|
||||
|
||||
const { Component, mount, useState, xml } = owl;
|
||||
import { Component, mount, useState, xml } from "@odoo/owl";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
|
@ -358,4 +357,18 @@ QUnit.module("Fields", (hooks) => {
|
|||
await testInputElements(target.querySelectorAll("main > input"));
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("select all content on focus", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `<form><field name="monetary"/></form>`,
|
||||
});
|
||||
|
||||
const input = target.querySelector(".o_field_widget[name='monetary'] input");
|
||||
await triggerEvent(input, null, "focus");
|
||||
assert.strictEqual(input.selectionStart, 0);
|
||||
assert.strictEqual(input.selectionEnd, 4);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -83,8 +83,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: '<form><field name="document" widget="pdf_viewer"/></form>',
|
||||
async mockRPC(_route, { method, args }) {
|
||||
if (method === "create") {
|
||||
assert.deepEqual(args[0], { document: btoa("test") });
|
||||
if (method === "web_save") {
|
||||
assert.deepEqual(args[1], { document: btoa("test") });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,15 +27,16 @@ QUnit.module("Fields", (hooks) => {
|
|||
searchable: true,
|
||||
},
|
||||
float_field: {
|
||||
string: "Float_field",
|
||||
string: "float_field",
|
||||
type: "float",
|
||||
digits: [0, 1],
|
||||
},
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
records: [
|
||||
{ id: 1, foo: "yop", int_field: 10 },
|
||||
{ id: 2, foo: "gnap", int_field: 80 },
|
||||
{ id: 3, foo: "dop", float_field: 65.6},
|
||||
{ id: 3, foo: "blip", float_field: 33.3333 },
|
||||
],
|
||||
onchanges: {},
|
||||
},
|
||||
|
|
@ -69,74 +70,17 @@ QUnit.module("Fields", (hooks) => {
|
|||
"should have a pie chart"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_pie_value")
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie_info .o_pie_value")
|
||||
.textContent,
|
||||
"10%",
|
||||
"should have 10% as pie value since int_field=10"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_mask").style
|
||||
.transform,
|
||||
"rotate(180deg)",
|
||||
"left mask should be covering the whole left side of the pie"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelectorAll(".o_field_percent_pie.o_field_widget .o_pie .o_mask")[1].style
|
||||
.transform,
|
||||
"rotate(36deg)",
|
||||
"right mask should be rotated from 360*(10/100) = 36 degrees"
|
||||
);
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_percent_pie.o_field_widget .o_pie",
|
||||
"should have a pie chart"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_pie_value")
|
||||
.textContent,
|
||||
"10%",
|
||||
"should have 10% as pie value since int_field=10"
|
||||
);
|
||||
assert.ok(
|
||||
_.str.include(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_mask").style
|
||||
.transform,
|
||||
"rotate(180deg)"
|
||||
),
|
||||
"left mask should be covering the whole left side of the pie"
|
||||
);
|
||||
assert.ok(
|
||||
_.str.include(
|
||||
target.querySelectorAll(".o_field_percent_pie.o_field_widget .o_pie .o_mask")[1]
|
||||
.style.transform,
|
||||
"rotate(36deg)"
|
||||
),
|
||||
"right mask should be rotated from 360*(10/100) = 36 degrees"
|
||||
);
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_percent_pie.o_field_widget .o_pie",
|
||||
"should have a pie chart"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_pie_value")
|
||||
.textContent,
|
||||
"10%",
|
||||
"should have 10% as pie value since int_field=10"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_mask").style
|
||||
.transform,
|
||||
"rotate(180deg)",
|
||||
"left mask should be covering the whole left side of the pie"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelectorAll(".o_field_percent_pie.o_field_widget .o_pie .o_mask")[1].style
|
||||
.transform,
|
||||
"rotate(36deg)",
|
||||
"right mask should be rotated from 360*(10/100) = 36 degrees"
|
||||
target
|
||||
.querySelector(".o_field_percent_pie.o_field_widget .o_pie")
|
||||
.style.background.replaceAll(/\s+/g, " "),
|
||||
"conic-gradient( var(--PercentPieField-color-active) 0% 10%, var(--PercentPieField-color-static) 0% 100% )",
|
||||
"pie should have a background computed for its value of 10%"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -162,69 +106,17 @@ QUnit.module("Fields", (hooks) => {
|
|||
"should have a pie chart"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_pie_value")
|
||||
.textContent,
|
||||
"80%",
|
||||
"should have 80% as pie value since int_field=80"
|
||||
);
|
||||
assert.ok(
|
||||
_.str.include(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_mask").style
|
||||
.transform,
|
||||
"rotate(288deg)"
|
||||
),
|
||||
"left mask should be rotated from 360*(80/100) = 288 degrees"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelectorAll(".o_field_percent_pie.o_field_widget .o_pie .o_mask")[1],
|
||||
"o_full",
|
||||
"right mask should be hidden since the value > 50%"
|
||||
);
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_percent_pie.o_field_widget .o_pie",
|
||||
"should have a pie chart"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_pie_value")
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie_info .o_pie_value")
|
||||
.textContent,
|
||||
"80%",
|
||||
"should have 80% as pie value since int_field=80"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_mask").style
|
||||
.transform,
|
||||
"rotate(288deg)",
|
||||
"left mask should be rotated from 360*(80/100) = 288 degrees"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelectorAll(".o_field_percent_pie.o_field_widget .o_pie .o_mask")[1],
|
||||
"o_full",
|
||||
"right mask should be hidden since the value > 50%"
|
||||
);
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_percent_pie.o_field_widget .o_pie",
|
||||
"should have a pie chart"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_pie_value")
|
||||
.textContent,
|
||||
"80%",
|
||||
"should have 80% as pie value since int_field=80"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_mask").style
|
||||
.transform,
|
||||
"rotate(288deg)",
|
||||
"left mask should be rotated from 360*(80/100) = 288 degrees"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelectorAll(".o_field_percent_pie.o_field_widget .o_pie .o_mask")[1],
|
||||
"o_full",
|
||||
"right mask should be hidden since the value > 50%"
|
||||
target
|
||||
.querySelector(".o_field_percent_pie.o_field_widget .o_pie")
|
||||
.style.background.replaceAll(/\s+/g, " "),
|
||||
"conic-gradient( var(--PercentPieField-color-active) 0% 80%, var(--PercentPieField-color-static) 0% 100% )",
|
||||
"pie should have a background computed for its value of 80%"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -243,60 +135,72 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 3,
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_percent_pie.o_field_widget .o_pie",
|
||||
"should have a pie chart"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_pie_value")
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie_info .o_pie_value")
|
||||
.textContent,
|
||||
"66%",
|
||||
"should have 66% as pie value since float_field=65.6"
|
||||
"33.33%",
|
||||
"should have 33.33% as pie value since float_field=33.3333 and its value is rounded to 2 decimals"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target
|
||||
.querySelector(".o_field_percent_pie.o_field_widget .o_pie")
|
||||
.style.background.replaceAll(/\s+/g, " "),
|
||||
"conic-gradient( var(--PercentPieField-color-active) 0% 33.3333%, var(--PercentPieField-color-static) 0% 100% )",
|
||||
"pie should have a background computed for its value of 33.3333%"
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: This test would pass without any issue since all the classes and
|
||||
// custom style attributes are correctly set on the widget in list
|
||||
// view, but since the scss itself for this widget currently only
|
||||
// applies inside the form view, the widget is unusable. This test can
|
||||
// be uncommented when we refactor the scss files so that this widget
|
||||
// stylesheet applies in both form and list view.
|
||||
// QUnit.test('percentpie widget in editable list view', async function(assert) {
|
||||
// assert.expect(10);
|
||||
//
|
||||
// var list = await createView({
|
||||
// View: ListView,
|
||||
// model: 'partner',
|
||||
// data: this.data,
|
||||
// arch: '<tree editable="bottom">' +
|
||||
// '<field name="foo"/>' +
|
||||
// '<field name="int_field" widget="percentpie"/>' +
|
||||
// '</tree>',
|
||||
// });
|
||||
//
|
||||
// assert.containsN(list, '.o_field_percent_pie .o_pie', 5,
|
||||
// "should have five pie charts");
|
||||
// assert.strictEqual(target.querySelector('.o_field_percent_pie:first .o_pie .o_pie_value').textContent,
|
||||
// '10%', "should have 10% as pie value since int_field=10");
|
||||
// assert.strictEqual(target.querySelector('.o_field_percent_pie:first .o_pie .o_mask').attr('style'),
|
||||
// 'rotate(180deg)', "left mask should be covering the whole left side of the pie");
|
||||
// assert.strictEqual(target.querySelector('.o_field_percent_pie:first .o_pie .o_mask').last().attr('style'),
|
||||
// 'rotate(36deg)', "right mask should be rotated from 360*(10/100) = 36 degrees");
|
||||
//
|
||||
// // switch to edit mode and check the result
|
||||
// testUtils.dom.click( target.querySelector('tbody td:not(.o_list_record_selector)'));
|
||||
// assert.strictEqual(target.querySelector('.o_field_percent_pie:first .o_pie .o_pie_value').textContent,
|
||||
// '10%', "should have 10% as pie value since int_field=10");
|
||||
// assert.strictEqual(target.querySelector('.o_field_percent_pie:first .o_pie .o_mask').attr('style'),
|
||||
// 'rotate(180deg)', "left mask should be covering the whole right side of the pie");
|
||||
// assert.strictEqual(target.querySelector('.o_field_percent_pie:first .o_pie .o_mask').last().attr('style'),
|
||||
// 'rotate(36deg)', "right mask should be rotated from 360*(10/100) = 36 degrees");
|
||||
//
|
||||
// // save
|
||||
// testUtils.dom.click( list.$buttons.find('.o_list_button_save'));
|
||||
// assert.strictEqual(target.querySelector('.o_field_percent_pie:first .o_pie .o_pie_value').textContent,
|
||||
// '10%', "should have 10% as pie value since int_field=10");
|
||||
// assert.strictEqual(target.querySelector('.o_field_percent_pie:first .o_pie .o_mask').attr('style'),
|
||||
// 'rotate(180deg)', "left mask should be covering the whole right side of the pie");
|
||||
// assert.strictEqual(target.querySelector('.o_field_percent_pie:first .o_pie .o_mask').last().attr('style'),
|
||||
// 'rotate(36deg)', "right mask should be rotated from 360*(10/100) = 36 degrees");
|
||||
//
|
||||
// list.destroy();
|
||||
// });
|
||||
QUnit.test(
|
||||
"hide the string when the PercentPieField widget is used in the view",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="int_field" widget="percentpie"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
resId: 2,
|
||||
});
|
||||
assert.containsOnce(target, ".o_field_percent_pie.o_field_widget .o_pie");
|
||||
assert.isNotVisible(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie_info .o_pie_text")
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"show the string when the PercentPieField widget is used in a button with the class oe_stat_button",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<form>
|
||||
<div name="button_box" class="oe_button_box">
|
||||
<button type="object" class="oe_stat_button">
|
||||
<field name="int_field" widget="percentpie"/>
|
||||
</button>
|
||||
</div>
|
||||
</form>`,
|
||||
resId: 2,
|
||||
});
|
||||
assert.containsOnce(target, ".o_field_percent_pie.o_field_widget .o_pie");
|
||||
assert.isVisible(
|
||||
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie_info .o_pie_text")
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="float_field" widget="percentage"/>
|
||||
</form>`,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(
|
||||
args[1].float_field,
|
||||
0.24,
|
||||
|
|
|
|||
|
|
@ -140,7 +140,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
await editInput(cell, "input", "new");
|
||||
|
||||
// save
|
||||
await click(target.querySelector(".o_list_button_save"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
)
|
||||
);
|
||||
cell = target.querySelector("tbody td:not(.o_list_record_selector)");
|
||||
assert.doesNotHaveClass(
|
||||
cell.parentElement,
|
||||
|
|
@ -260,4 +264,49 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
assert.hasAttrValue(phone, "href", "tel:+12345678900", "href should not contain any space");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"New record, fill in phone field, then click on call icon and save",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="display_name" required="1"/>
|
||||
<field name="foo" widget="phone"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await editInput(target, "div[name='display_name'] input[type='text']", "TEST");
|
||||
await editInput(target, "div[name='foo'] input[type='tel']", "+12345678900");
|
||||
target.querySelector(".o_field_widget[name=foo] input").focus();
|
||||
await click(target.querySelector(".o_phone_form_link"));
|
||||
assert.doesNotHaveClass(
|
||||
target.querySelector(".o_form_status_indicator_buttons"),
|
||||
"invisible",
|
||||
"save button should be visible"
|
||||
);
|
||||
await clickSave(target);
|
||||
|
||||
assert.deepEqual(
|
||||
target.querySelector(".o_field_widget[name=display_name] input").value,
|
||||
"TEST"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name=foo] input").value,
|
||||
"+12345678900"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelector(".o_form_status_indicator_buttons"),
|
||||
"invisible",
|
||||
"save button should be invisible"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -333,20 +333,24 @@ QUnit.module("Fields", (hooks) => {
|
|||
</templates>
|
||||
</kanban>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "write") {
|
||||
assert.step(`write ${JSON.stringify(args.args)}`);
|
||||
if (args.method === "web_save") {
|
||||
assert.step(`web_save ${JSON.stringify(args.args)}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.containsNone(target, ".o_kanban_record .fa-star");
|
||||
await click(target.querySelector(".o_priority a.o_priority_star.fa-star-o"), null, true);
|
||||
assert.verifySteps(['write [[1],{"selection":"1"}]']);
|
||||
await click(target.querySelector(".o_priority a.o_priority_star.fa-star-o"));
|
||||
assert.verifySteps(['web_save [[1],{"selection":"1"}]']);
|
||||
assert.containsOnce(target, ".o_kanban_record .fa-star");
|
||||
|
||||
await click(target, ".o-kanban-button-new");
|
||||
await click(
|
||||
target,
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o-kanban-button-new"
|
||||
);
|
||||
await nextTick();
|
||||
await click(target, ".o_kanban_quick_create .o_kanban_add");
|
||||
await click(target.querySelector(".o_priority a.o_priority_star.fa-star-o"), null, true);
|
||||
assert.verifySteps(['write [[6],{"selection":"1"}]']);
|
||||
await click(target.querySelector(".o_priority a.o_priority_star.fa-star-o"));
|
||||
assert.verifySteps(['web_save [[6],{"selection":"1"}]']);
|
||||
assert.containsN(target, ".o_kanban_record .fa-star", 2);
|
||||
});
|
||||
|
||||
|
|
@ -404,7 +408,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
// save
|
||||
await click(target, ".o_list_button_save");
|
||||
await click(
|
||||
target,
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
);
|
||||
|
||||
assert.containsN(
|
||||
target.querySelectorAll(".o_data_row")[0],
|
||||
|
|
@ -514,7 +521,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
// save
|
||||
await click(target, ".o_list_button_save");
|
||||
await click(
|
||||
target,
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
);
|
||||
rows = target.querySelectorAll(".o_data_row");
|
||||
|
||||
assert.containsN(
|
||||
|
|
@ -627,8 +637,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
</sheet>
|
||||
</form>`,
|
||||
mockRPC(_route, { method }) {
|
||||
if (method === "write") {
|
||||
assert.step("write");
|
||||
if (method === "web_save") {
|
||||
assert.step("web_save");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -637,7 +647,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
".o_field_widget .o_priority a.o_priority_star.fa-star-o"
|
||||
);
|
||||
await click(stars[stars.length - 1]);
|
||||
assert.verifySteps(["write"]);
|
||||
assert.verifySteps(["web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test("PriorityField - prevent auto save with autosave option", async function (assert) {
|
||||
|
|
@ -667,5 +677,4 @@ QUnit.module("Fields", (hooks) => {
|
|||
await click(stars[stars.length - 1]);
|
||||
assert.verifySteps([]);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
makeFakeLocalizationService,
|
||||
makeFakeNotificationService,
|
||||
} from "@web/../tests/helpers/mock_services";
|
||||
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
||||
import {
|
||||
click,
|
||||
clickSave,
|
||||
|
|
@ -87,7 +84,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(route, { method, args }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.deepEqual(
|
||||
args[1],
|
||||
{ int_field: 999, float_field: 5, display_name: "new name" },
|
||||
|
|
@ -129,7 +126,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(route, { method, args }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(
|
||||
args[1].int_field,
|
||||
69,
|
||||
|
|
@ -181,7 +178,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(route, { method, args }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(
|
||||
args[1].int_field,
|
||||
69,
|
||||
|
|
@ -228,7 +225,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(route, { method, args }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(
|
||||
args[1].float_field,
|
||||
69,
|
||||
|
|
@ -256,6 +253,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
await editInput(target, ".o_progressbar_value .o_input", "69");
|
||||
target.querySelector(".o_progressbar_value .o_input").blur(); // because clickSave does not trigger blur
|
||||
await clickSave(target);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_progressbar").textContent +
|
||||
|
|
@ -296,7 +294,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
assert.verifySteps([
|
||||
"/web/dataset/call_kw/partner/get_views",
|
||||
"/web/dataset/call_kw/partner/read",
|
||||
"/web/dataset/call_kw/partner/web_read",
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -322,7 +320,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</kanban>`,
|
||||
resId: 1,
|
||||
mockRPC(route, { method, args }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(args[1].int_field, 69, "New value of progress bar saved");
|
||||
}
|
||||
},
|
||||
|
|
@ -370,18 +368,18 @@ QUnit.module("Fields", (hooks) => {
|
|||
type: "kanban",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="int_field" widget="progressbar" options="{'editable': true, 'max_value': 'float_field', 'readonly': True}" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="int_field" widget="progressbar" options="{'editable': true, 'max_value': 'float_field', 'readonly': True}" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
resId: 1,
|
||||
mockRPC(route, { method, args }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
throw new Error("Not supposed to write");
|
||||
}
|
||||
},
|
||||
|
|
@ -465,7 +463,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC: function (route, { method, args }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(
|
||||
args[1].int_field,
|
||||
1037,
|
||||
|
|
@ -493,6 +491,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.ok(target.querySelector(".o_form_view .o_form_editable"), "Form in edit mode");
|
||||
|
||||
await editInput(target, ".o_field_widget input", "1#037:9");
|
||||
target.querySelector(".o_progressbar_value .o_input").blur(); // because clickSave does not trigger blur
|
||||
await clickSave(target);
|
||||
|
||||
assert.strictEqual(
|
||||
|
|
@ -507,13 +506,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
"ProgressBarField: write gibbrish instead of int throws warning",
|
||||
async function (assert) {
|
||||
serverData.models.partner.records[0].int_field = 99;
|
||||
const mock = () => {
|
||||
assert.step("Show error message");
|
||||
return () => {};
|
||||
};
|
||||
registry.category("services").add("notification", makeFakeNotificationService(mock), {
|
||||
force: true,
|
||||
});
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
|
|
@ -534,8 +526,39 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
await editInput(target, ".o_progressbar_value .o_input", "trente sept virgule neuf");
|
||||
await clickSave(target);
|
||||
assert.containsOnce(target, ".o_form_dirty", "The form has not been saved");
|
||||
assert.verifySteps(["Show error message"], "The error message was shown correctly");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_form_status_indicator span.text-danger",
|
||||
"The form has not been saved"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_form_button_save").disabled,
|
||||
true,
|
||||
"save button is disabled"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"ProgressBarField: color is correctly set when value > max value",
|
||||
async function (assert) {
|
||||
serverData.models.partner.records[0].float_field = 101;
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<form>
|
||||
<field name="float_field" widget="progressbar" options="{'overflow_class': 'bg-warning'}"/>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_progressbar .bg-warning",
|
||||
"As the value has excedded the max value, the color should be set to bg-warning"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { click, clickSave, editInput, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { click, clickSave, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
|
|
@ -86,11 +86,19 @@ QUnit.module("Fields", (hooks) => {
|
|||
target.querySelector(".o_field_radio").textContent.replace(/\s+/g, ""),
|
||||
"xphonexpad"
|
||||
);
|
||||
assert.containsNone(target, "input:checked", "none of the input should be checked");
|
||||
assert.containsNone(
|
||||
target,
|
||||
"input.o_radio_input:checked",
|
||||
"none of the input should be checked"
|
||||
);
|
||||
|
||||
await click(target.querySelectorAll("input.o_radio_input")[0]);
|
||||
|
||||
assert.containsOnce(target, "input:checked", "one of the input should be checked");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"input.o_radio_input:checked",
|
||||
"one of the input should be checked"
|
||||
);
|
||||
|
||||
await clickSave(target);
|
||||
|
||||
|
|
@ -148,7 +156,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
});
|
||||
|
||||
await click(target, "input[type='checkbox']");
|
||||
await click(target, ".o_field_boolean input[type='checkbox']");
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
|
|
@ -161,7 +169,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"the other of the input should be checked"
|
||||
);
|
||||
|
||||
await click(target, "input[type='checkbox']");
|
||||
await click(target, ".o_field_boolean input[type='checkbox']");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"input.o_radio_input[data-value='41']:checked",
|
||||
|
|
@ -205,6 +213,52 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("Two RadioField with same selection", async function (assert) {
|
||||
serverData.models.partner.fields.color_2 = serverData.models.partner.fields.color;
|
||||
serverData.models.partner.records[0].color = "black";
|
||||
serverData.models.partner.records[0].color_2 = "black";
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<group>
|
||||
<field name="color" widget="radio"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="color_2" widget="radio"/>
|
||||
</group>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.hasAttrValue(
|
||||
target.querySelector("[name='color'] input.o_radio_input:checked"),
|
||||
"data-value",
|
||||
"black"
|
||||
);
|
||||
assert.hasAttrValue(
|
||||
target.querySelector("[name='color_2'] input.o_radio_input:checked"),
|
||||
"data-value",
|
||||
"black"
|
||||
);
|
||||
|
||||
// click on Red
|
||||
await click(target.querySelector("[name='color_2'] label"));
|
||||
assert.hasAttrValue(
|
||||
target.querySelector("[name='color'] input.o_radio_input:checked"),
|
||||
"data-value",
|
||||
"black"
|
||||
);
|
||||
assert.hasAttrValue(
|
||||
target.querySelector("[name='color_2'] input.o_radio_input:checked"),
|
||||
"data-value",
|
||||
"red"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("fieldradio widget has o_horizontal or o_vertical class", async function (assert) {
|
||||
serverData.models.partner.fields.color2 = serverData.models.partner.fields.color;
|
||||
|
||||
|
|
@ -261,7 +315,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: '<form><field name="selection" widget="radio"/></form>',
|
||||
mockRPC: function (route, { args, method, model }) {
|
||||
if (model === "partner" && method === "write") {
|
||||
if (model === "partner" && method === "web_save") {
|
||||
assert.strictEqual(args[1].selection, "1", "should write correct value");
|
||||
}
|
||||
},
|
||||
|
|
@ -288,61 +342,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"widget radio on a many2one: domain updated by an onchange",
|
||||
async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
serverData.models.partner.onchanges = {
|
||||
int_field() {},
|
||||
};
|
||||
|
||||
let domain = [];
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="int_field" />
|
||||
<field name="trululu" widget="radio" />
|
||||
</form>`,
|
||||
mockRPC(route, { kwargs, method }) {
|
||||
if (method === "onchange") {
|
||||
domain = [["id", "in", [10]]];
|
||||
return Promise.resolve({
|
||||
value: {
|
||||
trululu: false,
|
||||
},
|
||||
domain: {
|
||||
trululu: domain,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (method === "search_read") {
|
||||
assert.deepEqual(kwargs.domain, domain, "sent domain should be correct");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_field_widget[name='trululu'] .o_radio_item",
|
||||
3,
|
||||
"should be 3 radio buttons"
|
||||
);
|
||||
|
||||
// trigger an onchange that will update the domain
|
||||
await editInput(target, ".o_field_widget[name='int_field'] input", "2");
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_field_widget[name='trululu'] .o_radio_item",
|
||||
"should be no more radio button"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("field is empty", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ import {
|
|||
clickSave,
|
||||
triggerHotkey,
|
||||
nextTick,
|
||||
makeDeferred,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
|
||||
import { makeView, makeViewInDialog, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
|
@ -252,9 +252,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"name_search", // for the select
|
||||
"name_search", // for the spawned many2one
|
||||
"name_create",
|
||||
"create",
|
||||
"read",
|
||||
"name_get",
|
||||
"web_save",
|
||||
],
|
||||
"The name_create method should have been called"
|
||||
);
|
||||
|
|
@ -408,7 +406,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
patchWithCleanup(actionService, {
|
||||
start() {
|
||||
const service = this._super(...arguments);
|
||||
const service = super.start(...arguments);
|
||||
return {
|
||||
...service,
|
||||
doAction(action) {
|
||||
|
|
@ -422,7 +420,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
},
|
||||
});
|
||||
|
||||
await makeView({
|
||||
await makeViewInDialog({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
|
|
@ -431,7 +429,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="reference" string="custom label" open_target="new" />
|
||||
<field name="reference" string="custom label"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
|
|
@ -460,7 +458,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"the name_search should be done on the newly set model"
|
||||
);
|
||||
}
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(model, "partner", "should write on the current model");
|
||||
assert.deepEqual(
|
||||
args,
|
||||
|
|
@ -502,11 +500,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
await click(target, ".o_external_button");
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".modal .modal-title").textContent.trim(),
|
||||
target
|
||||
.querySelector(".o_dialog:not(.o_inactive_modal) .modal-title")
|
||||
.textContent.trim(),
|
||||
"Open: custom label",
|
||||
"dialog title should display the custom string label"
|
||||
);
|
||||
await click(target, ".modal .o_form_button_cancel");
|
||||
await click(target, ".o_dialog:not(.o_inactive_modal) .o_form_button_cancel");
|
||||
|
||||
await editSelect(target, ".o_field_widget select", "partner_type");
|
||||
assert.strictEqual(
|
||||
|
|
@ -526,16 +526,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("Many2One 'Search More...' updates on resModel change", async function (assert) {
|
||||
|
||||
// Patch the Many2XAutocomplete default search limit options
|
||||
patchWithCleanup(Many2XAutocomplete.defaultProps, {
|
||||
searchLimit: -1,
|
||||
});
|
||||
|
||||
QUnit.test("Many2One 'Search more...' updates on resModel change", async function (assert) {
|
||||
serverData.views = {
|
||||
"product,false,list": '<tree><field name="display_name"/></tree>',
|
||||
"product,false,search": '<search/>',
|
||||
"product,false,search": "<search/>",
|
||||
};
|
||||
|
||||
await makeView({
|
||||
|
|
@ -546,14 +540,25 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
// Selecting a relation
|
||||
await editSelect(target.querySelector("div.o_field_reference"), "select.o_input", "partner_type");
|
||||
await editSelect(
|
||||
target.querySelector("div.o_field_reference"),
|
||||
"select.o_input",
|
||||
"partner_type"
|
||||
);
|
||||
|
||||
// Selecting another relation
|
||||
await editSelect(target.querySelector("div.o_field_reference"), "select.o_input", "product");
|
||||
await editSelect(
|
||||
target.querySelector("div.o_field_reference"),
|
||||
"select.o_input",
|
||||
"product"
|
||||
);
|
||||
|
||||
// Opening the Search More... option
|
||||
await click(target.querySelector("div.o_field_reference"), "input.o_input");
|
||||
await click(target.querySelector("div.o_field_reference"), ".o_m2o_dropdown_option_search_more");
|
||||
await click(
|
||||
target.querySelector("div.o_field_reference"),
|
||||
".o_m2o_dropdown_option_search_more"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector("div.modal td.o_data_cell").innerText,
|
||||
|
|
@ -562,82 +567,44 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("computed reference field changed by onchange to 'False,0' value", async function (assert) {
|
||||
assert.expect(1);
|
||||
QUnit.test(
|
||||
"computed reference field changed by onchange to 'False,0' value",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
serverData.models.partner.onchanges = {
|
||||
bar(obj) {
|
||||
if (!obj.bar) {
|
||||
obj.reference_char = "False,0";
|
||||
}
|
||||
},
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
serverData.models.partner.onchanges = {
|
||||
bar(obj) {
|
||||
if (!obj.bar) {
|
||||
obj.reference_char = "False,0";
|
||||
}
|
||||
},
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<field name="reference_char" widget="reference"/>
|
||||
</form>`,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "create") {
|
||||
assert.deepEqual(args[0], {
|
||||
bar: false,
|
||||
reference_char: "False,0",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "web_save") {
|
||||
assert.deepEqual(args[1], {
|
||||
bar: false,
|
||||
reference_char: "False,0",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// trigger the onchange to set a value for the reference field
|
||||
await click(target, ".o_field_boolean input");
|
||||
// trigger the onchange to set a value for the reference field
|
||||
await click(target, ".o_field_boolean input");
|
||||
|
||||
// save
|
||||
await clickSave(target);
|
||||
});
|
||||
|
||||
QUnit.test("ReferenceField with model field", async function (assert) {
|
||||
serverData.models.partner.onchanges = {
|
||||
color(obj) {
|
||||
if (obj.color === "black") {
|
||||
obj.model_id = 20;
|
||||
obj.reference = "product,37";
|
||||
} else {
|
||||
obj.model_id = 17;
|
||||
obj.reference = "partner,1";
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="color" />
|
||||
<field name="model_id" invisible="1"/>
|
||||
<field name="reference" options="{'model_field': 'model_id'}" />
|
||||
</form>`,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "write") {
|
||||
assert.step("write");
|
||||
assert.strictEqual(args[1].reference, "partner,4");
|
||||
}
|
||||
},
|
||||
});
|
||||
await editSelect(target, "select", '"black"');
|
||||
await editSelect(target, "select", '"red"');
|
||||
|
||||
await editInput(target, ".o_field_widget[name=reference] input", "aaa");
|
||||
|
||||
await click(target, ".ui-autocomplete .ui-menu-item:first-child");
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["write"]);
|
||||
});
|
||||
// save
|
||||
await clickSave(target);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("interact with reference field changed by onchange", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
|
@ -659,8 +626,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="reference"/>
|
||||
</form>`,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "create") {
|
||||
assert.deepEqual(args[0], {
|
||||
if (method === "web_save") {
|
||||
assert.deepEqual(args[1], {
|
||||
bar: false,
|
||||
reference: "partner,4",
|
||||
});
|
||||
|
|
@ -707,14 +674,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC(route, { method, model }) {
|
||||
if (method === "name_get") {
|
||||
assert.step(model);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.verifySteps(["product"], "the first name_get should have been done");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='reference'] select").value,
|
||||
"product",
|
||||
|
|
@ -729,7 +690,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
// trigger onchange
|
||||
await editInput(target, ".o_field_widget[name=int_field] input", 12);
|
||||
|
||||
assert.verifySteps(["partner_type"], "the second name_get should have been done");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='reference'] select").value,
|
||||
"partner_type",
|
||||
|
|
@ -784,7 +744,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
obj.foo = "product," + obj.int_field;
|
||||
},
|
||||
};
|
||||
|
||||
let nbNameGet = 0;
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -800,23 +759,26 @@ QUnit.module("Fields", (hooks) => {
|
|||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC(route, { model, method }) {
|
||||
if (model === "product" && method === "name_get") {
|
||||
mockRPC(route, { model, method, args }) {
|
||||
if (
|
||||
model === "product" &&
|
||||
method === "read" &&
|
||||
args[1].length === 1 &&
|
||||
args[1][0] === "display_name"
|
||||
) {
|
||||
nbNameGet++;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(nbNameGet, 1, "the first name_get should have been done");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name=foo]").textContent,
|
||||
"xphone",
|
||||
"foo field should be correctly set"
|
||||
);
|
||||
|
||||
// trigger onchange
|
||||
await editInput(target, ".o_field_widget[name=int_field] input", 41);
|
||||
|
||||
await nextTick();
|
||||
assert.strictEqual(nbNameGet, 2, "the second name_get should have been done");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name=foo]").textContent,
|
||||
|
|
@ -869,7 +831,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="reference" options="{'model_field': 'model_id'}" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsNone(
|
||||
target,
|
||||
"select",
|
||||
|
|
@ -891,6 +852,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
await editInput(target, ".o_field_widget[name='model_id'] input", "Partner");
|
||||
await click(target, ".ui-autocomplete .ui-menu-item:first-child");
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget[name='reference'] input").value,
|
||||
"",
|
||||
|
|
@ -946,7 +908,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
QUnit.test("Reference field with default value in list view", async function (assert) {
|
||||
assert.expect(2);
|
||||
assert.expect(1);
|
||||
|
||||
await makeView({
|
||||
type: "list",
|
||||
|
|
@ -960,19 +922,29 @@ QUnit.module("Fields", (hooks) => {
|
|||
mockRPC: (route, { method, args }) => {
|
||||
if (method === "onchange") {
|
||||
return {
|
||||
value: {reference: "partner,2"},
|
||||
value: {
|
||||
reference: {
|
||||
id: { id: 2, model: "partner" },
|
||||
display_name: "second record",
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (method === "create") {
|
||||
assert.strictEqual(args.length, 1);
|
||||
assert.strictEqual(args[0].reference, "partner,2");
|
||||
} else if (method === "web_save") {
|
||||
assert.strictEqual(args[1].reference, "partner,2");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await click(target, '.o_list_button_add');
|
||||
await click(
|
||||
target,
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_add"
|
||||
);
|
||||
await click(target, '.o_list_char[name="display_name"] input');
|
||||
await editInput(target, '.o_list_char[name="display_name"] input', "Blabla");
|
||||
await click(target, '.o_list_button_save');
|
||||
await click(
|
||||
target,
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
|
|
@ -1003,6 +975,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
// Select the second product without changing the model
|
||||
await click(target, ".o_list_table .reference_field");
|
||||
await nextTick();
|
||||
|
||||
await click(target, ".o_list_table .reference_field input");
|
||||
|
||||
// Enter to select it
|
||||
|
|
@ -1051,6 +1025,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
await click(target.querySelector(".o_list_table .o_data_cell"));
|
||||
await nextTick();
|
||||
await editInput(target, ".o_list_table [name='name'] input", "plop");
|
||||
await click(target, ".o_form_view");
|
||||
assert.strictEqual(
|
||||
|
|
@ -1093,8 +1068,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
await click(target, ".o_list_table td.o_list_many2one");
|
||||
await click(target, ".o_list_table .o_list_many2one input");
|
||||
//Select the "Partner" option, different from original "Product"
|
||||
const dropdownItems = [...target.querySelectorAll(".o_list_table .o_list_many2one .o_input_dropdown .dropdown-item")];
|
||||
await click(dropdownItems.filter(item => item.text === "Partner")[0]);
|
||||
const dropdownItems = [
|
||||
...target.querySelectorAll(
|
||||
".o_list_table .o_list_many2one .o_input_dropdown .dropdown-item"
|
||||
),
|
||||
];
|
||||
await click(dropdownItems.filter((item) => item.text === "Partner")[0]);
|
||||
await nextTick();
|
||||
assert.strictEqual(target.querySelector(".reference_field input").value, "");
|
||||
assert.strictEqual(target.querySelector(".o_list_many2one input").value, "Partner");
|
||||
//Void the associated, required, "reference" field and make sure the form marks the field as required
|
||||
|
|
@ -1158,4 +1138,56 @@ QUnit.module("Fields", (hooks) => {
|
|||
"the selection list of the reference field should exist when hide_model=False and no model_field specified."
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("reference field should await fetch model before render", async function (assert) {
|
||||
serverData.models.partner.records[0].model_id = 20;
|
||||
|
||||
const def = makeDeferred();
|
||||
makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="model_id" invisible="1"/>
|
||||
<field name="reference" options="{'model_field': 'model_id'}" />
|
||||
</form>`,
|
||||
async mockRPC(route, args) {
|
||||
if (args.method === "read" && args.model === "ir.model") {
|
||||
await def;
|
||||
}
|
||||
},
|
||||
});
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
assert.containsNone(target, ".o_form_view");
|
||||
def.resolve();
|
||||
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_form_view");
|
||||
});
|
||||
|
||||
QUnit.test("reference char with list view pager navigation", async function (assert) {
|
||||
assert.expect(2);
|
||||
serverData.models.partner.records[0].reference_char = "product,37";
|
||||
serverData.models.partner.records[1].reference_char = "product,41";
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `<form edit="0"><field name="reference_char" widget="reference" string="Record"/></form>`,
|
||||
resIds: [1, 2],
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_reference .o_form_uri").textContent,
|
||||
"xphone"
|
||||
);
|
||||
await click(target, ".o_pager_next");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_reference .o_form_uri").textContent,
|
||||
"xpad"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
patchTimeZone,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { getPickerCell } from "../../core/datetime/datetime_test_helpers";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
|
@ -116,22 +117,18 @@ QUnit.module("Fields", (hooks) => {
|
|||
await click(rows[1], ".o_list_record_selector input");
|
||||
|
||||
await click(rows[0], ".o_data_cell");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"input.o_datepicker_input",
|
||||
"should have date picker input"
|
||||
);
|
||||
assert.containsOnce(target, ".o_field_remaining_days input");
|
||||
|
||||
await editInput(target, ".o_datepicker_input", "10/10/2017");
|
||||
await editInput(target, ".o_field_remaining_days input", "10/10/2017");
|
||||
await click(target);
|
||||
|
||||
assert.containsOnce(document.body, ".modal");
|
||||
assert.containsOnce(target, ".modal");
|
||||
assert.strictEqual(
|
||||
document.querySelector(".modal .o_field_widget").textContent,
|
||||
"In 2 days",
|
||||
"should have 'In 2 days' value to change"
|
||||
);
|
||||
await click(document.body, ".modal .modal-footer .btn-primary");
|
||||
await click(target, ".modal .modal-footer .btn-primary");
|
||||
|
||||
assert.strictEqual(
|
||||
rows[0].querySelector(".o_data_cell").textContent,
|
||||
|
|
@ -177,15 +174,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
await click(rows[1], ".o_list_record_selector input");
|
||||
|
||||
await click(rows[0], ".o_data_cell");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"input.o_datepicker_input",
|
||||
"should have date picker input"
|
||||
);
|
||||
assert.containsOnce(target, ".o_field_remaining_days input");
|
||||
|
||||
await editInput(target, ".o_datepicker_input", "blabla");
|
||||
await editInput(target, ".o_field_remaining_days input", "blabla");
|
||||
await click(target);
|
||||
assert.containsNone(document.body, ".modal");
|
||||
assert.containsNone(target, ".modal");
|
||||
assert.strictEqual(cells[0].textContent, "Today");
|
||||
assert.strictEqual(cells[1].textContent, "Tomorrow");
|
||||
}
|
||||
|
|
@ -211,16 +204,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "10/08/2017");
|
||||
|
||||
assert.containsOnce(target, ".o_form_editable");
|
||||
assert.containsOnce(target, "div.o_field_widget[name='date'] .o_datepicker");
|
||||
assert.containsOnce(target, "div.o_field_widget[name='date'] input");
|
||||
|
||||
await click(target.querySelector(".o_datepicker .o_datepicker_input"));
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be opened"
|
||||
);
|
||||
await click(target, ".o_field_remaining_days input");
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should be opened");
|
||||
|
||||
await click(document.body, ".bootstrap-datetimepicker-widget .day[data-day='10/09/2017']");
|
||||
await click(getPickerCell("9").at(0));
|
||||
await click(target, ".o_form_button_save");
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "10/09/2017");
|
||||
});
|
||||
|
|
@ -238,12 +227,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_form_editable .o_field_widget[name='date'] .o_datepicker"
|
||||
);
|
||||
await click(target.querySelector(".o_field_widget[name='date'] .o_datepicker input"));
|
||||
assert.containsOnce(document.body, ".bootstrap-datetimepicker-widget");
|
||||
assert.containsOnce(target, ".o_form_editable .o_field_widget[name='date'] input");
|
||||
await click(target, ".o_field_widget[name='date'] input");
|
||||
assert.containsOnce(target, ".o_datetime_picker");
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -303,17 +289,12 @@ QUnit.module("Fields", (hooks) => {
|
|||
target.querySelector(".o_field_widget input").value,
|
||||
"10/08/2017 11:00:00"
|
||||
);
|
||||
assert.containsOnce(target, "div.o_field_widget[name='datetime'] .o_datepicker");
|
||||
assert.containsOnce(target, "div.o_field_widget[name='datetime'] input");
|
||||
|
||||
await click(target.querySelector(".o_datepicker .o_datepicker_input"));
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".bootstrap-datetimepicker-widget",
|
||||
"datepicker should be opened"
|
||||
);
|
||||
await click(target, ".o_field_widget input");
|
||||
assert.containsOnce(target, ".o_datetime_picker", "datepicker should be opened");
|
||||
|
||||
await click(document.body, ".bootstrap-datetimepicker-widget .day[data-day='10/09/2017']");
|
||||
await click(document.body, "a[data-action='close']");
|
||||
await click(getPickerCell("9").at(0));
|
||||
await click(target, ".o_form_button_save");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_widget input").value,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { click, editSelect, editInput, getFixture, clickSave } from "@web/../tests/helpers/utils";
|
||||
import { click, clickSave, editSelect, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
|
|
@ -147,7 +147,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"should have correct value in color field"
|
||||
);
|
||||
|
||||
assert.verifySteps(["get_views", "read", "name_search", "name_search", "onchange"]);
|
||||
assert.verifySteps(["get_views", "web_read", "name_search", "name_search", "onchange"]);
|
||||
});
|
||||
|
||||
QUnit.test("unset selection field with 0 as key", async function (assert) {
|
||||
|
|
@ -225,7 +225,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
serverData,
|
||||
arch: '<form><field name="trululu" widget="selection" /></form>',
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
assert.strictEqual(
|
||||
args[1].trululu,
|
||||
false,
|
||||
|
|
@ -257,59 +257,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"SelectionField on a many2one: domain updated by an onchange",
|
||||
async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
serverData.models.partner.onchanges = {
|
||||
int_field() {},
|
||||
};
|
||||
|
||||
let domain = [];
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="int_field" />
|
||||
<field name="trululu" widget="selection" />
|
||||
</form>`,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "onchange") {
|
||||
domain = [["id", "in", [10]]];
|
||||
return Promise.resolve({
|
||||
domain: {
|
||||
trululu: domain,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (method === "name_search") {
|
||||
assert.deepEqual(args[1], domain, "sent domain should be correct");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_field_widget[name='trululu'] option",
|
||||
4,
|
||||
"should be 4 options in the selection"
|
||||
);
|
||||
|
||||
// trigger an onchange that will update the domain
|
||||
await editInput(target, ".o_field_widget[name='int_field'] input", 2);
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='trululu'] option",
|
||||
"should be 1 option in the selection"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("required selection widget should not have blank option", async function (assert) {
|
||||
serverData.models.partner.fields.feedback_value = {
|
||||
type: "selection",
|
||||
|
|
@ -329,7 +276,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<form>
|
||||
<field name="feedback_value" />
|
||||
<field name="color" attrs="{'required': [('feedback_value', '=', 'bad')]}" />
|
||||
<field name="color" required="feedback_value == 'bad'" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
|
@ -382,7 +329,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<form>
|
||||
<field name="feedback_value" />
|
||||
<field name="color" attrs="{'required': [('feedback_value', '=', 'bad')]}" />
|
||||
<field name="color" required="feedback_value == 'bad'" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
|
@ -421,4 +368,189 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.strictEqual(placeholderOption.textContent, "Placeholder");
|
||||
assert.strictEqual(placeholderOption.value, "false");
|
||||
});
|
||||
|
||||
QUnit.test("SelectionField in kanban view", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="color" widget="selection" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
domain: [["id", "=", 1]],
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='color'] select",
|
||||
"SelectionKanbanField widget applied to selection field"
|
||||
);
|
||||
|
||||
assert.containsN(
|
||||
target.querySelector(".o_field_widget[name='color']"),
|
||||
"option",
|
||||
3,
|
||||
"Three options are displayed (one blank option)"
|
||||
);
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll(".o_field_widget[name='color'] option")].map(
|
||||
(option) => option.value
|
||||
),
|
||||
["false", '"red"', '"black"']
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("SelectionField - auto save record in kanban view", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="color" widget="selection" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
domain: [["id", "=", 1]],
|
||||
mockRPC(_route, { method }) {
|
||||
if (method === "web_save") {
|
||||
assert.step("web_save");
|
||||
}
|
||||
},
|
||||
});
|
||||
await editSelect(target, ".o_field_widget[name='color'] select", '"black"');
|
||||
assert.verifySteps(["web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"SelectionField don't open form view on click in kanban view",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<field name="color" widget="selection" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
domain: [["id", "=", 1]],
|
||||
selectRecord: () => {
|
||||
assert.step("selectRecord");
|
||||
},
|
||||
});
|
||||
|
||||
await click(target, ".o_field_widget[name='color'] select");
|
||||
assert.verifySteps([]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("SelectionField is disabled if field readonly", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
serverData.models.partner.fields.color.readonly = true;
|
||||
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="color" widget="selection" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
`,
|
||||
domain: [["id", "=", 1]],
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='color'] span",
|
||||
"field should be readonly"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("SelectionField is disabled with a readonly attribute", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="color" widget="selection" readonly="1" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
`,
|
||||
domain: [["id", "=", 1]],
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_widget[name='color'] span",
|
||||
"field should be readonly"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("SelectionField in kanban view with handle widget", async function (assert) {
|
||||
// When records are draggable, most pointerdown events are default prevented. This test
|
||||
// comes with a fix that blacklists "select" elements, i.e. pointerdown events on such
|
||||
// elements aren't default prevented, because if they were, the select element can't be
|
||||
// opened. The test is a bit artificial but there's no other way to test the scenario, as
|
||||
// using editSelect simply triggers a "change" event, which obviously always works.
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<field name="int_field" widget="handle"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="color" widget="selection"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
|
||||
const ev = new PointerEvent("pointerdown", { bubbles: true, cancelable: true });
|
||||
const select = target.querySelector(".o_kanban_record .o_field_widget[name=color] select");
|
||||
select.dispatchEvent(ev);
|
||||
assert.notOk(ev.defaultPrevented);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ import {
|
|||
clickSave,
|
||||
editInput,
|
||||
getFixture,
|
||||
makeDeferred,
|
||||
nextTick,
|
||||
patchWithCleanup,
|
||||
triggerEvent,
|
||||
triggerEvents,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { NameAndSignature } from "@web/core/signature/name_and_signature";
|
||||
|
|
@ -60,10 +63,67 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
QUnit.module("Signature Field");
|
||||
|
||||
QUnit.test("signature can be drawn", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `<form>
|
||||
<field name="sign" widget="signature" />
|
||||
</form>`,
|
||||
mockRPC: async (route) => {
|
||||
if (route === "/web/sign/get_fonts/") {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsNone(target, "div[name=sign] img.o_signature");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
"div[name=sign] div.o_signature svg",
|
||||
"should have a valid signature widget"
|
||||
);
|
||||
|
||||
// Click on the widget to open signature modal
|
||||
await click(target, "div[name=sign] div.o_signature");
|
||||
assert.containsOnce(target, ".modal .modal-body .o_web_sign_name_and_signature");
|
||||
assert.containsNone(target, ".modal .btn.btn-primary:not([disabled])");
|
||||
|
||||
// Use a drag&drop simulation to draw a signature
|
||||
const def = makeDeferred();
|
||||
const jSignatureEl = target.querySelector(".modal .o_web_sign_signature");
|
||||
$(jSignatureEl).on("change", def.resolve);
|
||||
const { x, y, width, height } = target
|
||||
.querySelector("canvas.jSignature")
|
||||
.getBoundingClientRect();
|
||||
await triggerEvents(jSignatureEl, "canvas.jSignature", [
|
||||
["mousedown", { clientX: x + 1, clientY: y + 1 }],
|
||||
["mousemove", { clientX: x + width - 1, clientY: height + height - 1 }],
|
||||
["mouseup", { clientX: x + width - 1, clientY: height + height - 1 }],
|
||||
]);
|
||||
await def; // makes sure the signature stroke is taken into account by jSignature
|
||||
await nextTick(); // await owl rendering
|
||||
assert.containsOnce(target, ".modal .btn.btn-primary:not([disabled])");
|
||||
|
||||
// Click on "Adopt and Sign" button
|
||||
await click(target, ".modal .btn.btn-primary:not([disabled])");
|
||||
assert.containsNone(target, ".modal");
|
||||
|
||||
// The signature widget should now display the signature img
|
||||
assert.containsNone(target, "div[name=sign] div.o_signature svg");
|
||||
assert.containsOnce(target, "div[name=sign] img.o_signature");
|
||||
|
||||
const signImgSrc = target.querySelector("div[name=sign] img.o_signature").dataset.src;
|
||||
assert.notOk(signImgSrc.includes("placeholder"));
|
||||
assert.ok(signImgSrc.startsWith("data:image/png;base64,"));
|
||||
});
|
||||
|
||||
QUnit.test("Set simple field in 'full_name' node option", async function (assert) {
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
this._super.apply(this, arguments);
|
||||
super.setup(...arguments);
|
||||
assert.step(this.props.signature.name);
|
||||
},
|
||||
});
|
||||
|
|
@ -96,13 +156,18 @@ QUnit.module("Fields", (hooks) => {
|
|||
".modal .modal-body a.o_web_sign_auto_button",
|
||||
'should open a modal with "Auto" button'
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelector(".o_web_sign_auto_button"),
|
||||
"active",
|
||||
"'Auto' panel is visible by default"
|
||||
);
|
||||
assert.verifySteps(["Pop's Chock'lit"]);
|
||||
});
|
||||
|
||||
QUnit.test("Set m2o field in 'full_name' node option", async function (assert) {
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
this._super.apply(this, arguments);
|
||||
super.setup(...arguments);
|
||||
assert.step(this.props.signature.name);
|
||||
},
|
||||
});
|
||||
|
|
@ -183,7 +248,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
|
||||
rec.sign = "3 kb";
|
||||
rec.__last_update = "2022-08-05 08:37:00"; // 1659688620000
|
||||
rec.write_date = "2022-08-05 08:37:00"; // 1659688620000
|
||||
|
||||
// 1659692220000, 1659695820000
|
||||
const lastUpdates = ["2022-08-05 09:37:00", "2022-08-05 10:37:00"];
|
||||
|
|
@ -203,9 +268,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
if (route === "/web/sign/get_fonts/") {
|
||||
return {};
|
||||
}
|
||||
if (method === "write") {
|
||||
assert.step("write");
|
||||
args[1].__last_update = lastUpdates[index];
|
||||
if (method === "web_save") {
|
||||
assert.step("web_save");
|
||||
args[1].write_date = lastUpdates[index];
|
||||
args[1].sign = "4 kb";
|
||||
index++;
|
||||
}
|
||||
|
|
@ -216,7 +281,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
"1659688620000"
|
||||
);
|
||||
|
||||
await click(target, ".o_field_signature img", true);
|
||||
await click(target, ".o_field_signature img", { skipVisibilityCheck: true });
|
||||
assert.containsOnce(target, ".modal canvas");
|
||||
|
||||
let canvas = target.querySelector(".modal canvas");
|
||||
|
|
@ -244,13 +309,13 @@ QUnit.module("Fields", (hooks) => {
|
|||
);
|
||||
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["write"]);
|
||||
assert.verifySteps(["web_save"]);
|
||||
assert.strictEqual(
|
||||
getUnique(target.querySelector(".o_field_signature img")),
|
||||
"1659692220000"
|
||||
);
|
||||
|
||||
await click(target, ".o_field_signature img", true);
|
||||
await click(target, ".o_field_signature img", { skipVisibilityCheck: true });
|
||||
assert.containsOnce(target, ".modal canvas");
|
||||
|
||||
canvas = target.querySelector(".modal canvas");
|
||||
|
|
@ -272,7 +337,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
`data:image/png;base64,${MYB64_2}`
|
||||
);
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["write"]);
|
||||
assert.verifySteps(["web_save"]);
|
||||
assert.strictEqual(
|
||||
getUnique(target.querySelector(".o_field_signature img")),
|
||||
"1659695820000"
|
||||
|
|
@ -292,7 +357,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
const rec = serverData.models.partner.records.find((rec) => rec.id === 1);
|
||||
rec.sign = "3 kb";
|
||||
rec.__last_update = "2022-08-05 08:37:00"; // 1659688620000
|
||||
rec.write_date = "2022-08-05 08:37:00"; // 1659688620000
|
||||
|
||||
// 1659692220000, 1659695820000
|
||||
const lastUpdates = ["2022-08-05 09:37:00", "2022-08-05 10:37:00"];
|
||||
|
|
@ -309,9 +374,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
<field name="sign" widget="signature" />
|
||||
</form>`,
|
||||
mockRPC(route, { method, args }) {
|
||||
if (method === "write") {
|
||||
assert.step("write");
|
||||
args[1].__last_update = lastUpdates[index];
|
||||
if (method === "web_save") {
|
||||
assert.step("web_save");
|
||||
args[1].write_date = lastUpdates[index];
|
||||
args[1].sign = "4 kb";
|
||||
index++;
|
||||
}
|
||||
|
|
@ -332,6 +397,6 @@ QUnit.module("Fields", (hooks) => {
|
|||
getUnique(target.querySelector(".o_field_signature img")),
|
||||
"1659692220000"
|
||||
);
|
||||
assert.verifySteps(["write"]);
|
||||
assert.verifySteps(["web_save"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -82,26 +82,34 @@ QUnit.module("Fields", (hooks) => {
|
|||
".o_field_widget.o_field_state_selection span.o_status.o_status_green",
|
||||
"should not have one green status since selection is the second, blocked state"
|
||||
);
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_field_state_selection .dropdown-toggle").dataset.tooltip,
|
||||
"Blocked",
|
||||
"tooltip attribute has the right text"
|
||||
);
|
||||
assert.containsNone(target, ".o_content .dropdown-menu", "there should not be a dropdown");
|
||||
|
||||
// Click on the status button to make the dropdown appear
|
||||
await click(target, ".o_field_widget.o_field_state_selection .o_status");
|
||||
assert.containsOnce(document.body, ".dropdown-menu", "there should be a dropdown");
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".o_content .dropdown-menu",
|
||||
"there should be a dropdown"
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".dropdown-menu .dropdown-item",
|
||||
2,
|
||||
"there should be two options in the dropdown"
|
||||
".o_content .dropdown-menu .dropdown-item",
|
||||
3,
|
||||
"there should be three options in the dropdown"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelector(".dropdown-menu .dropdown-item:nth-child(2)"),
|
||||
"active",
|
||||
"current value has a checkmark"
|
||||
);
|
||||
|
||||
// Click on the first option, "Normal"
|
||||
await click(target.querySelector(".dropdown-menu .dropdown-item"));
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown anymore");
|
||||
await click(target.querySelector(".o_content .dropdown-menu .dropdown-item"));
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_content .dropdown-menu",
|
||||
"there should not be a dropdown anymore"
|
||||
);
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_field_widget.o_field_state_selection span.o_status.o_status_red",
|
||||
|
|
@ -118,7 +126,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
"should have one grey status since selection is the first, normal state"
|
||||
);
|
||||
|
||||
assert.containsNone(target, ".dropdown-menu", "there should still not be a dropdown");
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_content .dropdown-menu",
|
||||
"there should still not be a dropdown"
|
||||
);
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_field_widget.o_field_state_selection span.o_status.o_status_red",
|
||||
|
|
@ -137,17 +149,21 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
// Click on the status button to make the dropdown appear
|
||||
await click(target, ".o_field_widget.o_field_state_selection .o_status");
|
||||
assert.containsOnce(target, ".dropdown-menu", "there should be a dropdown");
|
||||
assert.containsOnce(target, ".o_content .dropdown-menu", "there should be a dropdown");
|
||||
assert.containsN(
|
||||
target,
|
||||
".dropdown-menu .dropdown-item",
|
||||
2,
|
||||
"there should be two options in the dropdown"
|
||||
3,
|
||||
"there should be three options in the dropdown"
|
||||
);
|
||||
|
||||
// Click on the last option, "Done"
|
||||
await click(target, ".dropdown-menu .dropdown-item:last-child");
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown anymore");
|
||||
await click(target, ".o_content .dropdown-menu .dropdown-item:last-child");
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_content .dropdown-menu",
|
||||
"there should not be a dropdown anymore"
|
||||
);
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_field_widget.o_field_state_selection span.o_status.o_status_red",
|
||||
|
|
@ -163,7 +179,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
await click(target.querySelector(".o_form_button_save"));
|
||||
assert.containsNone(
|
||||
target,
|
||||
".dropdown-menu",
|
||||
".o_content .dropdown-menu",
|
||||
"there should still not be a dropdown anymore"
|
||||
);
|
||||
assert.containsNone(
|
||||
|
|
@ -193,6 +209,21 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.isNotVisible(target.querySelector(".dropdown-menu"));
|
||||
});
|
||||
|
||||
QUnit.test("StateSelectionField for form view with hide_label option", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="selection" widget="state_selection" options="{'hide_label': False}"/>
|
||||
</form>
|
||||
`,
|
||||
resId: 1,
|
||||
});
|
||||
assert.containsOnce(target, ".o_status_label");
|
||||
});
|
||||
|
||||
QUnit.test("StateSelectionField for list view with hide_label option", async function (assert) {
|
||||
Object.assign(serverData.models.partner.fields, {
|
||||
graph_type: {
|
||||
|
|
@ -214,7 +245,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<tree>
|
||||
<field name="graph_type" widget="state_selection" options="{'hide_label': True}"/>
|
||||
<field name="selection" widget="state_selection"/>
|
||||
<field name="selection" widget="state_selection" options="{'hide_label': False}"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
|
|
@ -281,7 +312,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
".o_state_selection_cell .o_field_state_selection span.o_status.o_status_green",
|
||||
"should have one green status"
|
||||
);
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown");
|
||||
assert.containsNone(target, ".o_content .dropdown-menu", "there should not be a dropdown");
|
||||
|
||||
// Click on the status button to make the dropdown appear
|
||||
let cell = target.querySelector("tbody td.o_state_selection_cell");
|
||||
|
|
@ -293,16 +324,16 @@ QUnit.module("Fields", (hooks) => {
|
|||
"o_selected_row",
|
||||
"should not be in edit mode since we clicked on the state selection widget"
|
||||
);
|
||||
assert.containsOnce(target, ".dropdown-menu", "there should be a dropdown");
|
||||
assert.containsOnce(target, ".o_content .dropdown-menu", "there should be a dropdown");
|
||||
assert.containsN(
|
||||
target,
|
||||
".dropdown-menu .dropdown-item",
|
||||
2,
|
||||
"there should be two options in the dropdown"
|
||||
".o_content .dropdown-menu .dropdown-item",
|
||||
3,
|
||||
"there should be three options in the dropdown"
|
||||
);
|
||||
|
||||
// Click on the first option, "Normal"
|
||||
await click(target.querySelector(".dropdown-menu .dropdown-item"));
|
||||
await click(target.querySelector(".o_content .dropdown-menu .dropdown-item"));
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_state_selection_cell .o_field_state_selection span.o_status",
|
||||
|
|
@ -319,7 +350,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
".o_state_selection_cell .o_field_state_selection span.o_status.o_status_green",
|
||||
"should still have one green status"
|
||||
);
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown");
|
||||
assert.containsNone(target, ".o_content .dropdown-menu", "there should not be a dropdown");
|
||||
assert.containsNone(target, "tr.o_selected_row", "should not be in edit mode");
|
||||
|
||||
// switch to edit mode and check the result
|
||||
|
|
@ -342,24 +373,28 @@ QUnit.module("Fields", (hooks) => {
|
|||
".o_state_selection_cell .o_field_state_selection span.o_status.o_status_green",
|
||||
"should still have one green status"
|
||||
);
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown");
|
||||
assert.containsNone(target, ".o_content .dropdown-menu", "there should not be a dropdown");
|
||||
|
||||
// Click on the status button to make the dropdown appear
|
||||
await click(
|
||||
target.querySelector(".o_state_selection_cell .o_field_state_selection span.o_status")
|
||||
);
|
||||
assert.containsOnce(target, ".dropdown-menu", "there should be a dropdown");
|
||||
assert.containsOnce(target, ".o_content .dropdown-menu", "there should be a dropdown");
|
||||
assert.containsN(
|
||||
target,
|
||||
".dropdown-menu .dropdown-item",
|
||||
2,
|
||||
"there should be two options in the dropdown"
|
||||
".o_content .dropdown-menu .dropdown-item",
|
||||
3,
|
||||
"there should be three options in the dropdown"
|
||||
);
|
||||
|
||||
// Click on another row
|
||||
const lastCell = target.querySelectorAll("tbody td.o_state_selection_cell")[4];
|
||||
await click(lastCell);
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown anymore");
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_content .dropdown-menu",
|
||||
"there should not be a dropdown anymore"
|
||||
);
|
||||
const firstCell = target.querySelector("tbody td.o_state_selection_cell");
|
||||
assert.doesNotHaveClass(
|
||||
firstCell.parentElement,
|
||||
|
|
@ -378,17 +413,21 @@ QUnit.module("Fields", (hooks) => {
|
|||
".o_state_selection_cell .o_field_state_selection span.o_status"
|
||||
)[3]
|
||||
);
|
||||
assert.containsOnce(target, ".dropdown-menu", "there should be a dropdown");
|
||||
assert.containsOnce(target, ".o_content .dropdown-menu", "there should be a dropdown");
|
||||
assert.containsN(
|
||||
target,
|
||||
".dropdown-menu .dropdown-item",
|
||||
2,
|
||||
"there should be two options in the dropdown"
|
||||
".o_content .dropdown-menu .dropdown-item",
|
||||
3,
|
||||
"there should be three options in the dropdown"
|
||||
);
|
||||
|
||||
// Click on the last option, "Done"
|
||||
await click(target, ".dropdown-menu .dropdown-item:last-child");
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown anymore");
|
||||
await click(target, ".o_content .dropdown-menu .dropdown-item:last-child");
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_content .dropdown-menu",
|
||||
"there should not be a dropdown anymore"
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_state_selection_cell .o_field_state_selection span.o_status",
|
||||
|
|
@ -406,10 +445,14 @@ QUnit.module("Fields", (hooks) => {
|
|||
2,
|
||||
"should now have two green status"
|
||||
);
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown");
|
||||
assert.containsNone(target, ".o_content .dropdown-menu", "there should not be a dropdown");
|
||||
|
||||
// save
|
||||
await click(target.querySelector(".o_list_button_save"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
)
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_state_selection_cell .o_field_state_selection span.o_status",
|
||||
|
|
@ -427,11 +470,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
2,
|
||||
"should have two green status"
|
||||
);
|
||||
assert.containsNone(target, ".dropdown-menu", "there should not be a dropdown");
|
||||
assert.containsNone(target, ".o_content .dropdown-menu", "there should not be a dropdown");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
'StateSelectionField edited by the smart action "Set kanban state..."',
|
||||
'StateSelectionField edited by the smart actions "Set kanban state as <state name>"',
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
|
@ -448,20 +491,23 @@ QUnit.module("Fields", (hooks) => {
|
|||
|
||||
triggerHotkey("control+k");
|
||||
await nextTick();
|
||||
const idx = [...target.querySelectorAll(".o_command")]
|
||||
.map((el) => el.textContent)
|
||||
.indexOf("Set kanban state...ALT + SHIFT + R");
|
||||
var commandTexts = [...target.querySelectorAll(".o_command")].map(
|
||||
(el) => el.textContent
|
||||
);
|
||||
assert.ok(commandTexts.includes("Set kanban state as NormalALT + D"));
|
||||
const idx = commandTexts.indexOf("Set kanban state as DoneALT + G");
|
||||
assert.ok(idx >= 0);
|
||||
|
||||
await click([...target.querySelectorAll(".o_command")][idx]);
|
||||
await nextTick();
|
||||
assert.deepEqual(
|
||||
[...target.querySelectorAll(".o_command")].map((el) => el.textContent),
|
||||
["Normal", "Blocked", "Done"]
|
||||
);
|
||||
await click(target, "#o_command_2");
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_status_green");
|
||||
|
||||
triggerHotkey("control+k");
|
||||
await nextTick();
|
||||
commandTexts = [...target.querySelectorAll(".o_command")].map((el) => el.textContent);
|
||||
assert.ok(commandTexts.includes("Set kanban state as NormalALT + D"));
|
||||
assert.ok(commandTexts.includes("Set kanban state as BlockedALT + F"));
|
||||
assert.notOk(commandTexts.includes("Set kanban state as DoneALT + G"));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -492,17 +538,17 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
await click(target, ".o_status");
|
||||
let dropdownItemTexts = [...target.querySelectorAll(".dropdown-item")].map(
|
||||
(el) => el.textContent
|
||||
);
|
||||
assert.deepEqual(dropdownItemTexts, ["Custom normal", "Custom done"]);
|
||||
let dropdownItemTexts = [
|
||||
...target.querySelectorAll(".o_field_state_selection .dropdown-item"),
|
||||
].map((el) => el.textContent);
|
||||
assert.deepEqual(dropdownItemTexts, ["Custom normal", "Custom blocked", "Custom done"]);
|
||||
|
||||
await click(target.querySelector(".dropdown-item .o_status"));
|
||||
await click(target, ".o_status");
|
||||
dropdownItemTexts = [...target.querySelectorAll(".dropdown-item")].map(
|
||||
(el) => el.textContent
|
||||
);
|
||||
assert.deepEqual(dropdownItemTexts, ["Custom blocked", "Custom done"]);
|
||||
dropdownItemTexts = [
|
||||
...target.querySelectorAll(".o_field_state_selection .dropdown-item"),
|
||||
].map((el) => el.textContent);
|
||||
assert.deepEqual(dropdownItemTexts, ["Custom normal", "Custom blocked", "Custom done"]);
|
||||
});
|
||||
|
||||
QUnit.test("works when required in a readonly view ", async function (assert) {
|
||||
|
|
@ -523,18 +569,19 @@ QUnit.module("Fields", (hooks) => {
|
|||
</templates>
|
||||
</kanban>`,
|
||||
mockRPC: (route, args, performRPC) => {
|
||||
if (route === "/web/dataset/call_kw/partner/write") {
|
||||
assert.step("write");
|
||||
if (route === "/web/dataset/call_kw/partner/web_save") {
|
||||
assert.step("web_save");
|
||||
}
|
||||
return performRPC(route, args);
|
||||
},
|
||||
});
|
||||
assert.containsNone(target, ".o_status_label");
|
||||
|
||||
await click(target, ".o_field_state_selection button");
|
||||
const doneItem = target.querySelectorAll(".dropdown-item")[1]; // item "done";
|
||||
const doneItem = target.querySelectorAll(".dropdown-item")[2]; // item "done";
|
||||
await click(doneItem);
|
||||
|
||||
assert.verifySteps(["write"]);
|
||||
assert.verifySteps(["web_save"]);
|
||||
assert.hasClass(target.querySelector(".o_field_state_selection span"), "o_status_green");
|
||||
});
|
||||
|
||||
|
|
@ -555,15 +602,15 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
resId: 1,
|
||||
mockRPC(_route, { method }) {
|
||||
if (method === "write") {
|
||||
assert.step("write");
|
||||
if (method === "web_save") {
|
||||
assert.step("web_save");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await click(target, ".o_field_widget.o_field_state_selection .o_status");
|
||||
await click(target, ".dropdown-menu .dropdown-item:last-child");
|
||||
assert.verifySteps(["write"]);
|
||||
assert.verifySteps(["web_save"]);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -595,4 +642,58 @@ QUnit.module("Fields", (hooks) => {
|
|||
assert.verifySteps([]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"StateSelectionField - hotkey handling when there are more than 3 options available",
|
||||
async function (assert) {
|
||||
serverData.models.partner.fields.selection.selection.push(
|
||||
["martin", "Martin"],
|
||||
["martine", "Martine"]
|
||||
);
|
||||
serverData.models.partner.records[0].selection = null;
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="selection" widget="state_selection" options="{'autosave': False}"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
await click(target, ".o_field_widget.o_field_state_selection .o_status");
|
||||
assert.containsN(
|
||||
target,
|
||||
".dropdown-menu .dropdown-item",
|
||||
5,
|
||||
"Five choices are displayed"
|
||||
);
|
||||
triggerHotkey("control+k");
|
||||
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_command#o_command_2").textContent,
|
||||
"Set kanban state as DoneALT + G",
|
||||
"hotkey and command are present"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_command#o_command_4").textContent,
|
||||
"Set kanban state as Martine",
|
||||
"no hotkey is present, but the command exists"
|
||||
);
|
||||
|
||||
await click(target.querySelector(".o_command#o_command_2"));
|
||||
assert.hasClass(
|
||||
target.querySelector(".o_status"),
|
||||
"o_status_green",
|
||||
"green color and Done state have been set"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { session } from "@web/session";
|
||||
import { makeFakeNotificationService } from "@web/../tests/helpers/mock_services";
|
||||
import {
|
||||
click,
|
||||
clickSave,
|
||||
editInput,
|
||||
getFixture,
|
||||
getNodesTextContent,
|
||||
nextTick,
|
||||
patchWithCleanup,
|
||||
selectDropdownItem,
|
||||
triggerHotkey,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { session } from "@web/session";
|
||||
|
||||
import { EventBus } from "@odoo/owl";
|
||||
|
||||
|
|
@ -247,10 +251,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
target.querySelector(".o_statusbar_status button[data-value='4']"),
|
||||
"o_arrow_button_current"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelector(".o_statusbar_status button[data-value='4']"),
|
||||
"disabled"
|
||||
);
|
||||
assert.ok(target.querySelector(".o_statusbar_status button[data-value='4']").disabled);
|
||||
|
||||
const clickableButtons = target.querySelectorAll(
|
||||
".o_statusbar_status button.btn:not(.dropdown-toggle):not(:disabled):not(.o_arrow_button_current)"
|
||||
|
|
@ -263,10 +264,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
target.querySelector(".o_statusbar_status button[data-value='1']"),
|
||||
"o_arrow_button_current"
|
||||
);
|
||||
assert.hasClass(
|
||||
target.querySelector(".o_statusbar_status button[data-value='1']"),
|
||||
"disabled"
|
||||
);
|
||||
assert.ok(target.querySelector(".o_statusbar_status button[data-value='1']").disabled);
|
||||
});
|
||||
|
||||
QUnit.test("statusbar with no status", async function (assert) {
|
||||
|
|
@ -285,9 +283,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.doesNotHaveClass(target.querySelector(".o_statusbar_status"), "o_field_empty");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_statusbar_status").children.length,
|
||||
0,
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_statusbar_status > :not(.d-none)",
|
||||
"statusbar widget should be empty"
|
||||
);
|
||||
});
|
||||
|
|
@ -308,9 +306,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.doesNotHaveClass(target.querySelector(".o_statusbar_status"), "o_field_empty");
|
||||
const tooltipInfo = target.querySelector(".o_field_statusbar").attributes[
|
||||
"data-tooltip-info"
|
||||
];
|
||||
const tooltipInfo =
|
||||
target.querySelector(".o_field_statusbar").attributes["data-tooltip-info"];
|
||||
assert.strictEqual(
|
||||
JSON.parse(tooltipInfo.value).field.help,
|
||||
"some info about the field",
|
||||
|
|
@ -407,6 +404,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
".o_statusbar_status button:not(.dropdown-toggle)"
|
||||
);
|
||||
await click(buttons[buttons.length - 1]);
|
||||
await nextTick();
|
||||
assert.containsN(target, ".o_statusbar_status button:not(.dropdown-toggle)", 2);
|
||||
}
|
||||
);
|
||||
|
|
@ -434,15 +432,15 @@ QUnit.module("Fields", (hooks) => {
|
|||
</form>`,
|
||||
});
|
||||
|
||||
await click(target, ".o_statusbar_status .dropdown-toggle");
|
||||
await click(target, ".o_statusbar_status .dropdown-toggle:not(.d-none)");
|
||||
|
||||
const status = target.querySelectorAll(".o_statusbar_status");
|
||||
assert.containsOnce(status[0], ".dropdown-item.disabled");
|
||||
assert.containsOnce(status[status.length - 1], "button.disabled");
|
||||
assert.containsOnce(status[status.length - 1], "button:disabled");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("statusbar: choose an item from the 'More' menu", async function (assert) {
|
||||
QUnit.test("statusbar: choose an item from the folded menu", async function (assert) {
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
});
|
||||
|
|
@ -471,11 +469,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
document
|
||||
.querySelector(".o_statusbar_status .dropdown-toggle.o_arrow_button")
|
||||
.textContent.trim(),
|
||||
"More",
|
||||
"...",
|
||||
"button has the correct text"
|
||||
);
|
||||
|
||||
await click(target, ".o_statusbar_status .dropdown-toggle");
|
||||
await click(target, ".o_statusbar_status .dropdown-toggle:not(.d-none)");
|
||||
await click(target, ".o-dropdown .dropdown-item");
|
||||
assert.strictEqual(
|
||||
target.querySelector("[aria-checked='true']").textContent,
|
||||
|
|
@ -509,10 +507,10 @@ QUnit.module("Fields", (hooks) => {
|
|||
},
|
||||
});
|
||||
|
||||
assert.containsN(target, ".o_statusbar_status button.disabled", 3);
|
||||
assert.containsN(target, ".o_statusbar_status button:disabled", 3);
|
||||
assert.strictEqual(rpcCount, 1, "should have done 1 search_read rpc");
|
||||
await editInput(target, ".o_field_widget[name='qux'] input", 9.5);
|
||||
assert.containsN(target, ".o_statusbar_status button.disabled", 2);
|
||||
assert.containsN(target, ".o_statusbar_status button:disabled", 2);
|
||||
assert.strictEqual(rpcCount, 2, "should have done 1 more search_read rpc");
|
||||
await editInput(target, ".o_field_widget[name='qux'] input", "hey");
|
||||
assert.strictEqual(rpcCount, 2, "should not have done 1 more search_read rpc");
|
||||
|
|
@ -551,35 +549,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
await click(target, "#o_command_2");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
'smart action "Move to stage..." is unavailable if readonly',
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<field name="trululu" widget="statusbar" readonly="1"/>
|
||||
</header>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_field_widget");
|
||||
|
||||
triggerHotkey("control+k");
|
||||
await nextTick();
|
||||
const movestage = target.querySelectorAll(".o_command");
|
||||
const idx = [...movestage]
|
||||
.map((el) => el.textContent)
|
||||
.indexOf("Move to Trululu...ALT + SHIFT + X");
|
||||
assert.ok(idx < 0);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("hotkey is unavailable if readonly", async function (assert) {
|
||||
QUnit.test("smart actions are unavailable if readonly", async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
|
|
@ -594,7 +564,34 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
assert.containsOnce(target, ".o_field_widget");
|
||||
triggerHotkey("alt+shift+x");
|
||||
|
||||
triggerHotkey("control+k");
|
||||
await nextTick();
|
||||
const moveStages = [...target.querySelectorAll(".o_command")].map((el) => el.textContent);
|
||||
assert.notOk(moveStages.includes("Move to Trululu...ALT + SHIFT + X"));
|
||||
assert.notOk(moveStages.includes("Move to next...ALT + X"));
|
||||
});
|
||||
|
||||
QUnit.test("hotkeys are unavailable if readonly", async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<field name="trululu" widget="statusbar" readonly="1"/>
|
||||
</header>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_field_widget");
|
||||
triggerHotkey("alt+shift+x"); // Move to stage...
|
||||
await nextTick();
|
||||
assert.containsNone(target, ".modal", "command palette should not open");
|
||||
|
||||
triggerHotkey("alt+x"); // Move to next
|
||||
await nextTick();
|
||||
assert.containsNone(target, ".modal", "command palette should not open");
|
||||
});
|
||||
|
|
@ -612,8 +609,8 @@ QUnit.module("Fields", (hooks) => {
|
|||
</header>
|
||||
</form>`,
|
||||
mockRPC(_route, { method }) {
|
||||
if (method === "write") {
|
||||
assert.step("write");
|
||||
if (method === "web_save") {
|
||||
assert.step("web_save");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -621,9 +618,99 @@ QUnit.module("Fields", (hooks) => {
|
|||
".o_statusbar_status button.btn:not(.dropdown-toggle):not(:disabled):not(.o_arrow_button_current)"
|
||||
);
|
||||
await click(clickableButtons[clickableButtons.length - 1]);
|
||||
assert.verifySteps(["write"]);
|
||||
assert.verifySteps(["web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"For the same record, a single rpc is done to recover the specialData",
|
||||
async function (assert) {
|
||||
serverData.views = {
|
||||
"partner,3,list": '<tree><field name="display_name"/></tree>',
|
||||
"partner,9,search": `<search></search>`,
|
||||
"partner,false,form": `<form>
|
||||
<header>
|
||||
<field name="trululu" widget="statusbar" readonly="1"/>
|
||||
</header>
|
||||
</form>`,
|
||||
};
|
||||
|
||||
serverData.actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
name: "Partners",
|
||||
res_model: "partner",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (args.method === "search_read") {
|
||||
assert.step("search_read");
|
||||
}
|
||||
};
|
||||
|
||||
const webClient = await createWebClient({ serverData, mockRPC });
|
||||
await doAction(webClient, 1);
|
||||
|
||||
await click(target.querySelector(".o_data_row .o_data_cell"));
|
||||
assert.verifySteps(["search_read"]);
|
||||
|
||||
await click(target, ".o_back_button");
|
||||
await click(target.querySelector(".o_data_row .o_data_cell"));
|
||||
assert.verifySteps([]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"open form with statusbar, leave and come back to another one with other domain",
|
||||
async function (assert) {
|
||||
serverData.views = {
|
||||
"partner,3,list": '<tree><field name="display_name"/></tree>',
|
||||
"partner,9,search": `<search></search>`,
|
||||
"partner,false,form": `<form>
|
||||
<header>
|
||||
<field name="trululu" widget="statusbar" domain="[['id', '>', id]]" readonly="1"/>
|
||||
</header>
|
||||
</form>`,
|
||||
};
|
||||
|
||||
serverData.actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
name: "Partners",
|
||||
res_model: "partner",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (args.method === "search_read") {
|
||||
assert.step("search_read");
|
||||
}
|
||||
};
|
||||
|
||||
const webClient = await createWebClient({ serverData, mockRPC });
|
||||
await doAction(webClient, 1);
|
||||
|
||||
// open first record
|
||||
await click(target.querySelector(".o_data_row .o_data_cell"));
|
||||
assert.verifySteps(["search_read"]);
|
||||
|
||||
// go back and open second record
|
||||
await click(target, ".o_back_button");
|
||||
await click(target.querySelectorAll(".o_data_row")[1].querySelector(".o_data_cell"));
|
||||
assert.verifySteps(["search_read"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"clickable statusbar with readonly modifier set to false is editable",
|
||||
async function (assert) {
|
||||
|
|
@ -635,12 +722,15 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<field name="product_id" widget="statusbar" options="{'clickable': true}" attrs="{'readonly': false}"/>
|
||||
<field name="product_id" widget="statusbar" options="{'clickable': true}" readonly="False"/>
|
||||
</header>
|
||||
</form>`,
|
||||
});
|
||||
assert.containsN(target, ".o_statusbar_status button:visible", 2);
|
||||
assert.containsNone(target, ".o_statusbar_status button.disabled[disabled]:visible");
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_statusbar_status button[disabled][aria-checked='false']:visible"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -655,11 +745,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<field name="product_id" widget="statusbar" options="{'clickable': true}" attrs="{'readonly': true}"/>
|
||||
<field name="product_id" widget="statusbar" options="{'clickable': true}" readonly="True"/>
|
||||
</header>
|
||||
</form>`,
|
||||
});
|
||||
assert.containsN(target, ".o_statusbar_status button.disabled[disabled]:visible", 2);
|
||||
assert.containsN(target, ".o_statusbar_status button[disabled]:visible", 2);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -674,11 +764,176 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<field name="product_id" widget="statusbar" options="{'clickable': false}" attrs="{'readonly': false}"/>
|
||||
<field name="product_id" widget="statusbar" options="{'clickable': false}" readonly="False"/>
|
||||
</header>
|
||||
</form>`,
|
||||
});
|
||||
assert.containsN(target, ".o_statusbar_status button.disabled[disabled]:visible", 2);
|
||||
assert.containsN(target, ".o_statusbar_status button[disabled]:visible", 2);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"last status bar button have a border radius (no arrow shape) on the right side when a prior folded stage gets selected",
|
||||
async function (assert) {
|
||||
serverData.models = {
|
||||
stage: {
|
||||
fields: {
|
||||
name: { string: "Name", type: "char" },
|
||||
folded: { string: "Folded", type: "boolean", default: false },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, name: "New" },
|
||||
{ id: 2, name: "In Progress", folded: true },
|
||||
{ id: 3, name: "Done" },
|
||||
],
|
||||
},
|
||||
task: {
|
||||
fields: {
|
||||
status: { string: "Status", type: "many2one", relation: "stage" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, status: 1 },
|
||||
{ id: 2, status: 2 },
|
||||
{ id: 3, status: 3 },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "task",
|
||||
resId: 3,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<field name="status" widget="statusbar" options="{'clickable': true, 'fold_field': 'folded'}" />
|
||||
</header>
|
||||
</form>`,
|
||||
});
|
||||
await click(target, ".o_statusbar_status .dropdown-toggle:not(.d-none)");
|
||||
await click(target, ".o-dropdown .dropdown-item");
|
||||
|
||||
const button = target.querySelector(".o_statusbar_status button[data-value='3']");
|
||||
assert.notEqual(button.style.borderTopRightRadius, "0px");
|
||||
assert.hasClass(button, "o_first");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("correctly load statusbar when dynamic domain changes", async function (assert) {
|
||||
serverData.models = {
|
||||
stage: {
|
||||
fields: {
|
||||
name: { string: "Name", type: "char" },
|
||||
folded: { string: "Folded", type: "boolean", default: false },
|
||||
project_ids: { string: "Project", type: "many2many", relation: "project" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, name: "Stage Project 1", project_ids: [1] },
|
||||
{ id: 2, name: "Stage Project 2", project_ids: [2] },
|
||||
],
|
||||
},
|
||||
project: {
|
||||
fields: {
|
||||
display_name: { string: "Name", type: "char" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, display_name: "Project 1" },
|
||||
{ id: 2, display_name: "Project 2" },
|
||||
],
|
||||
},
|
||||
task: {
|
||||
fields: {
|
||||
status: { string: "Status", type: "many2one", relation: "stage" },
|
||||
project_id: { string: "Project", type: "many2one", relation: "project" },
|
||||
},
|
||||
records: [{ id: 1, project_id: 1, status: 1 }],
|
||||
},
|
||||
};
|
||||
serverData.models.task.onchanges = {
|
||||
project_id: (obj) => {
|
||||
obj.status = obj.project_id === 1 ? 1 : 2;
|
||||
},
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "task",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<field name="status" widget="statusbar" domain="[('project_ids', 'in', project_id)]" />
|
||||
</header>
|
||||
<field name="project_id"/>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "search_read") {
|
||||
assert.step(JSON.stringify(args.kwargs.domain));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_statusbar_status button:not(.d-none)")),
|
||||
["Stage Project 1"]
|
||||
);
|
||||
assert.verifySteps(['["|",["id","=",1],["project_ids","in",1]]']);
|
||||
await selectDropdownItem(target, "project_id", "Project 2");
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_statusbar_status button:not(.d-none)")),
|
||||
["Stage Project 2"]
|
||||
);
|
||||
assert.verifySteps(['["|",["id","=",2],["project_ids","in",2]]']);
|
||||
await clickSave(target);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_statusbar_status button:not(.d-none)")),
|
||||
["Stage Project 2"]
|
||||
);
|
||||
assert.verifySteps([]);
|
||||
});
|
||||
|
||||
QUnit.test('"status" with no stages does not crash command palette', async function (assert) {
|
||||
serverData.models = {
|
||||
stage: {
|
||||
fields: {
|
||||
name: { string: "Stage Name", type: "char" },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
task: {
|
||||
fields: {
|
||||
status: { string: "Stage", type: "many2one", relation: "stage" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, status: false }, // no stage set
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "task",
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<field name="status" widget="statusbar" options="{'withCommand': true, 'clickable': true}"/>
|
||||
</header>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
// Open the command palette (Ctrl+K)
|
||||
triggerHotkey("control+k");
|
||||
await nextTick();
|
||||
|
||||
const commands = [...target.querySelectorAll(".o_command")].map((el) => el.textContent);
|
||||
|
||||
assert.notOk(
|
||||
commands.some((txt) => txt.includes("Move to next Stage")),
|
||||
"No 'Move to next stage' command available when no stages exist"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import {
|
|||
clickSave,
|
||||
editInput,
|
||||
getFixture,
|
||||
nextTick,
|
||||
triggerEvent,
|
||||
triggerHotkey,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
|
|
@ -20,7 +22,6 @@ let target;
|
|||
QUnit.module("Fields", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
|
|
@ -56,6 +57,17 @@ QUnit.module("Fields", (hooks) => {
|
|||
},
|
||||
],
|
||||
},
|
||||
partner_list: {
|
||||
fields: {
|
||||
partner_ids: {
|
||||
string: "Partners",
|
||||
type: "one2many",
|
||||
relation: "partner",
|
||||
relation_field: "id",
|
||||
},
|
||||
},
|
||||
records: [{ id: 1, partner_ids: [1] }],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -159,7 +171,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: `
|
||||
<form>
|
||||
<field name="bar" />
|
||||
<field name="txt" attrs="{'invisible': [('bar', '=', True)]}" />
|
||||
<field name="txt" invisible="bar" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
|
@ -255,11 +267,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
const textarea = target.querySelector("textarea");
|
||||
assert.strictEqual(
|
||||
textarea.rows,
|
||||
4,
|
||||
"rowCount should be the one set on the field",
|
||||
);
|
||||
assert.strictEqual(textarea.rows, 4, "rowCount should be the one set on the field");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
|
|
@ -282,8 +290,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
});
|
||||
|
||||
// ensure that autoresize is correctly done
|
||||
let height = target.querySelector(".o_field_widget[name=text_field] textarea")
|
||||
.offsetHeight;
|
||||
let height = target.querySelector(
|
||||
".o_field_widget[name=text_field] textarea"
|
||||
).offsetHeight;
|
||||
// focus the field to manually trigger autoresize
|
||||
await triggerEvent(target, ".o_field_widget[name=text_field] textarea", "focus");
|
||||
assert.strictEqual(
|
||||
|
|
@ -352,8 +361,9 @@ QUnit.module("Fields", (hooks) => {
|
|||
await click(target.querySelectorAll(".o_notebook .nav .nav-link")[2]);
|
||||
assert.hasClass(target.querySelectorAll(".o_notebook .nav .nav-link")[2], "active");
|
||||
|
||||
height = target.querySelector(".o_field_widget[name=text_field_empty] textarea")
|
||||
.offsetHeight;
|
||||
height = target.querySelector(
|
||||
".o_field_widget[name=text_field_empty] textarea"
|
||||
).offsetHeight;
|
||||
assert.strictEqual(height, 50, "empty textarea should have height of 50px");
|
||||
});
|
||||
|
||||
|
|
@ -584,7 +594,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
arch: '<tree editable="top"><field name="foo"/></tree>',
|
||||
});
|
||||
|
||||
await click(target.querySelector(".o_list_button_add"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_add"
|
||||
)
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector("textarea"),
|
||||
|
|
@ -592,4 +606,96 @@ QUnit.module("Fields", (hooks) => {
|
|||
"text area should have the focus"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("field text with dynamic placeholder", async (assert) => {
|
||||
serverData.models.partner.fields.model_reference_field = {
|
||||
string: "Model Reference Field",
|
||||
type: "char",
|
||||
default: "partner",
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="model_reference_field" invisible="1"/>
|
||||
<sheet>
|
||||
<group>
|
||||
<field
|
||||
name="txt"
|
||||
options="{
|
||||
'dynamic_placeholder': true,
|
||||
'dynamic_placeholder_model_reference_field': 'model_reference_field'
|
||||
}"
|
||||
/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await click(target, "[name=txt] textarea");
|
||||
assert.strictEqual(document.activeElement, target.querySelector("[name=txt] textarea"));
|
||||
|
||||
assert.containsNone(document.body, ".o_popover .o_model_field_selector_popover");
|
||||
triggerHotkey("#");
|
||||
await nextTick();
|
||||
assert.containsOnce(document.body, ".o_popover .o_model_field_selector_popover");
|
||||
});
|
||||
|
||||
QUnit.test("text field should vertical autoresize when saving", async function (assert) {
|
||||
serverData.models.partner.fields.foo.type = "text";
|
||||
serverData.models.partner.records[0].foo = "1";
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner_list",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="partner_ids" widget="one2many">
|
||||
<tree editable="bottom">
|
||||
<field name="foo" widget="text"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await click(target, "[name=foo] div");
|
||||
let textarea = target.querySelector(".o_field_widget[name='foo'] textarea");
|
||||
const initialHeight = textarea.offsetHeight;
|
||||
|
||||
await editInput(textarea, null, "1\n2\n3\n4\n5\n6\n7\n8");
|
||||
await clickSave(target);
|
||||
|
||||
await click(target, "[name=foo] div");
|
||||
textarea = target.querySelector(".o_field_widget[name='foo'] textarea");
|
||||
const afterHeight = textarea.offsetHeight;
|
||||
|
||||
assert.ok(afterHeight > initialHeight, "Should be taller than one character");
|
||||
});
|
||||
|
||||
QUnit.test("text field without line breaks", async function (assert) {
|
||||
serverData.models.partner.fields.foo.type = "text";
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `<form><field name="foo" options="{'line_breaks': False}"/></form>`,
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_field_text textarea", "should have a text area");
|
||||
const textarea = target.querySelector(".o_field_text textarea");
|
||||
assert.strictEqual(textarea.value, "yop");
|
||||
textarea.focus();
|
||||
const keydownEvent = await triggerEvent(textarea, null, "keydown", { key: "Enter" });
|
||||
assert.strictEqual(keydownEvent.defaultPrevented, true);
|
||||
assert.strictEqual(textarea.value, "yop", "no line break should appear");
|
||||
// Simulate a (very artificial) paste event
|
||||
textarea.value = "text\nwith\nline\nbreaks\n";
|
||||
await triggerEvent(textarea, null, "input", { inputType: "insertFromPaste" });
|
||||
assert.strictEqual(textarea.value, "text with line breaks ", "no line break should appear");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
resId: 1,
|
||||
arch: /*xml*/ `
|
||||
<tree string="Colors" editable="top">
|
||||
<field name="tz_offset" invisible="True"/>
|
||||
<field name="tz_offset" column_invisible="True"/>
|
||||
<field name="color" widget="timezone_mismatch" />
|
||||
</tree>
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -226,7 +226,11 @@ QUnit.module("Fields", (hooks) => {
|
|||
await editInput(cell, "input", "brolo");
|
||||
|
||||
// save
|
||||
await click(target.querySelector(".o_list_button_save"));
|
||||
await click(
|
||||
target.querySelector(
|
||||
".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_list_button_save"
|
||||
)
|
||||
);
|
||||
cell = target.querySelector("tbody td:not(.o_list_record_selector)");
|
||||
assert.doesNotHaveClass(
|
||||
cell.parentElement,
|
||||
|
|
@ -272,7 +276,7 @@ QUnit.module("Fields", (hooks) => {
|
|||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" widget="url" attrs="{'readonly': True}" />
|
||||
<field name="foo" widget="url" readonly="True" />
|
||||
<field name="foo2" />
|
||||
</group>
|
||||
</sheet>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { registry } from "@web/core/registry";
|
|||
import { getFixture, patchWithCleanup } from "../../helpers/utils";
|
||||
import { createElement } from "@web/core/utils/xml";
|
||||
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
|
||||
function compileTemplate(arch) {
|
||||
const parser = new DOMParser();
|
||||
|
|
@ -38,7 +39,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
const arch = /*xml*/ `<form><div>lol</div></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t t-translation="off">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ props.record.isDirty ? 'o_form_dirty' : !props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div class="o_form_renderer o_form_nosheet" t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.dirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<div>lol</div>
|
||||
</div>
|
||||
</t>`;
|
||||
|
|
@ -49,12 +50,12 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
QUnit.test(
|
||||
"label with empty string compiles to FormLabel with empty string",
|
||||
async (assert) => {
|
||||
const arch = /*xml*/ `<form><field name="test"/><label for="test" string=""/></form>`;
|
||||
const arch = /*xml*/ `<form><field field_id="test" name="test"/><label for="test" string=""/></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t t-translation="off">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ props.record.isDirty ? 'o_form_dirty' : !props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<Field id="'test'" name="'test'" record="props.record" fieldInfo="props.archInfo.fieldNodes['test']" readonly="props.archInfo.activeActions?.edit === false and !props.record.isNew" setDirty.alike="props.setFieldAsDirty"/>
|
||||
<FormLabel id="'test'" fieldName="'test'" record="props.record" fieldInfo="props.archInfo.fieldNodes['test']" className="""" string="\`\`" />
|
||||
<div class="o_form_renderer o_form_nosheet" t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.dirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<Field id="'test'" name="'test'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['test']" readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew"/>
|
||||
<FormLabel id="'test'" fieldName="'test'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['test']" className="""" string="\`\`" />
|
||||
</div>
|
||||
</t>`;
|
||||
assert.areEquivalent(compileTemplate(arch), expected);
|
||||
|
|
@ -62,13 +63,13 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
);
|
||||
|
||||
QUnit.test("properly compile simple div with field", async (assert) => {
|
||||
const arch = /*xml*/ `<form><div class="someClass">lol<field name="display_name"/></div></form>`;
|
||||
const arch = /*xml*/ `<form><div class="someClass">lol<field field_id="display_name" name="display_name"/></div></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t t-translation="off">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ props.record.isDirty ? 'o_form_dirty' : !props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div class="o_form_renderer o_form_nosheet" t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.dirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<div class="someClass">
|
||||
lol
|
||||
<Field id="'display_name'" name="'display_name'" record="props.record" fieldInfo="props.archInfo.fieldNodes['display_name']" readonly="props.archInfo.activeActions?.edit === false and !props.record.isNew" setDirty.alike="props.setFieldAsDirty"/>
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']" readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>`;
|
||||
|
|
@ -80,23 +81,23 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<group>
|
||||
<group><field name="display_name"/></group>
|
||||
<group><field name="charfield"/></group>
|
||||
<group><field field_id="display_name" name="display_name"/></group>
|
||||
<group><field field_id="charfield" name="charfield"/></group>
|
||||
</group>
|
||||
</form>`;
|
||||
const expected = /*xml*/ `
|
||||
<OuterGroup>
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" isVisible="true" itemSpan="1">
|
||||
<InnerGroup class="scope && scope.className">
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" props="{id:'display_name',fieldName:'display_name',record:props.record,string:props.record.fields.display_name.string,fieldInfo:props.archInfo.fieldNodes['display_name']}" Component="constructor.components.FormLabel" subType="'item_component'" isVisible="true" itemSpan="2">
|
||||
<Field id="'display_name'" name="'display_name'" record="props.record" fieldInfo="props.archInfo.fieldNodes['display_name']" readonly="props.archInfo.activeActions?.edit === false and !props.record.isNew" setDirty.alike="props.setFieldAsDirty" class="scope && scope.className"/>
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" props="{id:'display_name',fieldName:'display_name',record:__comp__.props.record,string:__comp__.props.record.fields.display_name.string,fieldInfo:__comp__.props.archInfo.fieldNodes['display_name']}" Component="__comp__.constructor.components.FormLabel" subType="'item_component'" isVisible="true" itemSpan="2">
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']" readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew" class="scope && scope.className"/>
|
||||
</t>
|
||||
</InnerGroup>
|
||||
</t>
|
||||
<t t-set-slot="item_1" type="'item'" sequence="1" t-slot-scope="scope" isVisible="true" itemSpan="1">
|
||||
<InnerGroup class="scope && scope.className">
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" props="{id:'charfield',fieldName:'charfield',record:props.record,string:props.record.fields.charfield.string,fieldInfo:props.archInfo.fieldNodes['charfield']}" Component="constructor.components.FormLabel" subType="'item_component'" isVisible="true" itemSpan="2">
|
||||
<Field id="'charfield'" name="'charfield'" record="props.record" fieldInfo="props.archInfo.fieldNodes['charfield']" readonly="props.archInfo.activeActions?.edit === false and !props.record.isNew" setDirty.alike="props.setFieldAsDirty" class="scope && scope.className"/>
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" props="{id:'charfield',fieldName:'charfield',record:__comp__.props.record,string:__comp__.props.record.fields.charfield.string,fieldInfo:__comp__.props.archInfo.fieldNodes['charfield']}" Component="__comp__.constructor.components.FormLabel" subType="'item_component'" isVisible="true" itemSpan="2">
|
||||
<Field id="'charfield'" name="'charfield'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['charfield']" readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew" class="scope && scope.className"/>
|
||||
</t>
|
||||
</InnerGroup>
|
||||
</t>
|
||||
|
|
@ -112,7 +113,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
<group>
|
||||
<form>
|
||||
<div>
|
||||
<field name="test"/>
|
||||
<field field_id="test" name="test"/>
|
||||
</div>
|
||||
</form>
|
||||
</group>
|
||||
|
|
@ -120,13 +121,13 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
</form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t t-translation="off">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ props.record.isDirty ? 'o_form_dirty' : !props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div class="o_form_renderer o_form_nosheet" t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.dirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<OuterGroup>
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" isVisible="true" itemSpan="1">
|
||||
<InnerGroup class="scope && scope.className">
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" isVisible="true" itemSpan="1">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ props.record.isDirty ? 'o_form_dirty' : !props.record.isVirtual ? 'o_form_saved' : '' }} {{scope && scope.className || "" }}" class="o_form_nosheet">
|
||||
<div><Field id="'test'" name="'test'" record="props.record" fieldInfo="props.archInfo.fieldNodes['test']" readonly="props.archInfo.activeActions?.edit === false and !props.record.isNew" setDirty.alike="props.setFieldAsDirty"/></div>
|
||||
<div class="o_form_renderer o_form_nosheet" t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.dirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }} {{scope && scope.className || "" }}">
|
||||
<div><Field id="'test'" name="'test'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['test']" readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew"/></div>
|
||||
</div>
|
||||
</t>
|
||||
</InnerGroup>
|
||||
|
|
@ -143,18 +144,18 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<notebook>
|
||||
<page name="p1" string="Page1"><field name="charfield"/></page>
|
||||
<page name="p2" string="Page2"><field name="display_name"/></page>
|
||||
<page name="p1" string="Page1"><field field_id="charfield" name="charfield"/></page>
|
||||
<page name="p2" string="Page2"><field field_id="display_name" name="display_name"/></page>
|
||||
</notebook>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<Notebook defaultPage="props.record.isNew ? undefined : props.activeNotebookPages[0]" onPageUpdate="(page) => this.props.onNotebookPageChange(0, page)">
|
||||
<Notebook defaultPage="__comp__.props.record.isNew ? undefined : __comp__.props.activeNotebookPages[0]" onPageUpdate="(page) => __comp__.props.onNotebookPageChange(0, page)">
|
||||
<t t-set-slot="page_1" title="\`Page1\`" name="\`p1\`" isVisible="true">
|
||||
<Field id="'charfield'" name="'charfield'" record="props.record" fieldInfo="props.archInfo.fieldNodes['charfield']" readonly="props.archInfo.activeActions?.edit === false and !props.record.isNew" setDirty.alike="props.setFieldAsDirty"/>
|
||||
<Field id="'charfield'" name="'charfield'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['charfield']" readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew"/>
|
||||
</t>
|
||||
<t t-set-slot="page_2" title="\`Page2\`" name="\`p2\`" isVisible="true">
|
||||
<Field id="'display_name'" name="'display_name'" record="props.record" fieldInfo="props.archInfo.fieldNodes['display_name']" readonly="props.archInfo.activeActions?.edit === false and !props.record.isNew" setDirty.alike="props.setFieldAsDirty"/>
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']" readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew"/>
|
||||
</t>
|
||||
</Notebook>`;
|
||||
|
||||
|
|
@ -164,11 +165,11 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
QUnit.test("properly compile field without placeholder", async (assert) => {
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name" placeholder="e.g. Contact's Name or //someinfo..."/>
|
||||
<field field_id="display_name" name="display_name" placeholder="e.g. Contact's Name or //someinfo..."/>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<Field id="'display_name'" name="'display_name'" record="props.record" fieldInfo="props.archInfo.fieldNodes['display_name']" readonly="props.archInfo.activeActions?.edit === false and !props.record.isNew" setDirty.alike="props.setFieldAsDirty"/>
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']" readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew"/>
|
||||
`;
|
||||
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
|
|
@ -183,8 +184,8 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
|
||||
const expected = /*xml*/ `
|
||||
<t t-translation="off">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ props.record.isDirty ? 'o_form_dirty' : !props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom"><StatusBarButtons readonly="!props.record.isInEdition"/></div>
|
||||
<div class="o_form_renderer o_form_nosheet" t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.dirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between mb-0 mb-md-2 pb-2 pb-md-0"><StatusBarButtons/></div>
|
||||
<div>someDiv</div>
|
||||
</div>
|
||||
</t>`;
|
||||
|
|
@ -205,11 +206,11 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
|
||||
const expected = /*xml*/ `
|
||||
<t t-translation="off">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }} {{ props.record.isDirty ? 'o_form_dirty' : !props.record.isVirtual ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<div class="o_form_renderer" t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ __comp__.uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }} {{ __comp__.props.record.dirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<div class="o_form_sheet_bg">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom"><StatusBarButtons readonly="!props.record.isInEdition"/></div>
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between mb-0 mb-md-2 pb-2 pb-md-0"><StatusBarButtons/></div>
|
||||
<div>someDiv</div>
|
||||
<div class="o_form_sheet position-relative clearfix">
|
||||
<div class="o_form_sheet position-relative">
|
||||
<div>inside sheet</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -221,6 +222,33 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
assert.areEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
|
||||
QUnit.test("properly compile buttonBox invisible in sheet", async (assert) => {
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box" invisible="'display_name' == 'plop'">
|
||||
<div>Hello</div>
|
||||
</div>
|
||||
</sheet>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<t t-translation="off">
|
||||
<div class="o_form_renderer"
|
||||
t-att-class="__comp__.props.class"
|
||||
t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ __comp__.uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }} {{ __comp__.props.record.dirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}"
|
||||
t-ref="compiled_view_root">
|
||||
<div class="o_form_sheet_bg">
|
||||
<div class="o_form_sheet position-relative">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
`;
|
||||
|
||||
assert.areEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
|
||||
QUnit.test("properly compile invisible", async (assert) => {
|
||||
// cf python side: def transfer_node_to_modifiers
|
||||
// modifiers' string are evaluated to their boolean or array form
|
||||
|
|
@ -228,18 +256,18 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
// ```<form>
|
||||
// <field name="display_name" invisible="1" />
|
||||
// <div class="visible3" invisible="0"/>
|
||||
// <div modifiers="{'invisible': [['display_name', '=', 'take']]}"/>
|
||||
// <div invisible="display_name == 'take'"/>
|
||||
// </form>````
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name" modifiers="{"invisible": true}" />
|
||||
<div class="visible3" modifiers="{"invisible": false}"/>
|
||||
<div modifiers="{"invisible": [["display_name", "=", "take"]]}"/>
|
||||
<field field_id="display_name" name="display_name" invisible="True" />
|
||||
<div class="visible3" invisible="False"/>
|
||||
<div invisible="display_name == "take""/>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="visible3" />
|
||||
<div t-if="!evalDomainFromRecord(props.record,[["display_name","=","take"]])" />
|
||||
<div t-if="!__comp__.evaluateBooleanExpr("display_name == \\"take\\"",__comp__.props.record.evalContextWithVirtualIds)" />
|
||||
`;
|
||||
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
|
|
@ -248,14 +276,14 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
QUnit.test("compile invisible containing string as domain", async (assert) => {
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name" modifiers="{"invisible": true}" />
|
||||
<div class="visible3" modifiers="{"invisible": false}"/>
|
||||
<div modifiers="{"invisible": "[['display_name', '=', 'take']]"}"/>
|
||||
<field name="display_name" invisible="True" />
|
||||
<div class="visible3" invisible="False"/>
|
||||
<div invisible="display_name == 'take'"/>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="visible3" />
|
||||
<div t-if="!evalDomainFromRecord(props.record,"[['display_name','=','take']]")" />
|
||||
<div t-if="!__comp__.evaluateBooleanExpr("display_name == 'take'",__comp__.props.record.evalContextWithVirtualIds)" />
|
||||
`;
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
|
|
@ -267,8 +295,8 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom">
|
||||
<StatusBarButtons readonly="!props.record.isInEdition">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between mb-0 mb-md-2 pb-2 pb-md-0">
|
||||
<StatusBarButtons>
|
||||
<t t-set-slot="button_0" isVisible="true">
|
||||
<div>someDiv</div>
|
||||
</t>
|
||||
|
|
@ -285,8 +313,64 @@ QUnit.module("Form Compiler", (hooks) => {
|
|||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom">
|
||||
<StatusBarButtons readonly="!props.record.isInEdition"/>
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between mb-0 mb-md-2 pb-2 pb-md-0">
|
||||
<StatusBarButtons/>
|
||||
</div>`;
|
||||
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
|
||||
QUnit.test("properly compile settings", (assert) => {
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<setting
|
||||
help="this is bar"
|
||||
documentation="/applications/technical/web/settings/this_is_a_test.html"
|
||||
company_dependent="1">
|
||||
<field field_id="bar" name="bar"/>
|
||||
<label>label with content</label>
|
||||
</setting>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<Setting title="\`\`"
|
||||
help="\`this is bar\`"
|
||||
companyDependent="true"
|
||||
documentation="\`/applications/technical/web/settings/this_is_a_test.html\`"
|
||||
record="__comp__.props.record"
|
||||
fieldInfo="__comp__.props.archInfo.fieldNodes['bar']"
|
||||
fieldName="\`bar\`"
|
||||
fieldId="\`bar\`"
|
||||
string="\`\`"
|
||||
addLabel="true">
|
||||
<t t-set-slot="fieldSlot">
|
||||
<Field id="'bar'"
|
||||
name="'bar'"
|
||||
record="__comp__.props.record"
|
||||
fieldInfo="__comp__.props.archInfo.fieldNodes['bar']"
|
||||
readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew"/>
|
||||
</t>
|
||||
<label>label with content</label>
|
||||
</Setting>`;
|
||||
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
|
||||
QUnit.test("properly compile empty ButtonBox", (assert) => {
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
</div>
|
||||
</sheet>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="o_form_sheet_bg">
|
||||
<div class="o_form_sheet position-relative">
|
||||
<div class="oe_button_box" name="button_box">
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
|
|
@ -314,14 +398,14 @@ QUnit.module("Form Renderer", (hooks) => {
|
|||
};
|
||||
});
|
||||
|
||||
QUnit.test("compile form with modifiers and attrs - string as domain", async (assert) => {
|
||||
QUnit.test("compile form with modifiers", async (assert) => {
|
||||
serverData.views = {
|
||||
"partner,1,form": /*xml*/ `
|
||||
<form>
|
||||
<div modifiers="{"invisible": "[['display_name', '=', uid]]"}">
|
||||
<div invisible="display_name == uid">
|
||||
<field name="charfield"/>
|
||||
</div>
|
||||
<field name="display_name" attrs="{'readonly': "[['display_name', '=', uid]]"}"/>
|
||||
<field name="display_name" readonly="display_name == uid"/>
|
||||
</form>`,
|
||||
};
|
||||
|
||||
|
|
@ -343,7 +427,7 @@ QUnit.module("Form Renderer", (hooks) => {
|
|||
<form>
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page name="p1" attrs="{'invisible': [['display_name', '=', 'lol']]}"><field name="charfield"/></page>
|
||||
<page name="p1" invisible="display_name == 'lol'"><field name="charfield"/></page>
|
||||
<page name="p2"><field name="display_name"/></page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
@ -381,15 +465,19 @@ QUnit.module("Form Renderer", (hooks) => {
|
|||
QUnit.test("render field with placeholder", async (assert) => {
|
||||
assert.expect(1);
|
||||
|
||||
class CharField extends owl.Component {
|
||||
setup() {
|
||||
assert.strictEqual(this.props.placeholder, "e.g. Contact's Name or //someinfo...");
|
||||
}
|
||||
}
|
||||
CharField.template = owl.xml`<div/>`;
|
||||
CharField.extractProps = ({ attrs }) => ({ placeholder: attrs.placeholder });
|
||||
|
||||
registry.category("fields").add("char", CharField, { force: true });
|
||||
const charField = {
|
||||
component: class CharField extends Component {
|
||||
static template = xml`<div/>`;
|
||||
setup() {
|
||||
assert.strictEqual(
|
||||
this.props.placeholder,
|
||||
"e.g. Contact's Name or //someinfo..."
|
||||
);
|
||||
}
|
||||
},
|
||||
extractProps: ({ attrs }) => ({ placeholder: attrs.placeholder }),
|
||||
};
|
||||
registry.category("fields").add("char", charField, { force: true });
|
||||
|
||||
serverData.views = {
|
||||
"partner,1,form": /*xml*/ `
|
||||
|
|
@ -448,7 +536,7 @@ QUnit.module("Form Renderer", (hooks) => {
|
|||
QUnit.test("invisible is correctly computed with another t-if", (assert) => {
|
||||
patchWithCleanup(FormCompiler.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
this.compilers.push({
|
||||
selector: "myNode",
|
||||
fn: () => {
|
||||
|
|
@ -461,9 +549,39 @@ QUnit.module("Form Renderer", (hooks) => {
|
|||
},
|
||||
});
|
||||
|
||||
const arch = `<myNode modifiers="{"invisible": [["field", "=", "value"]]}" />`;
|
||||
const arch = `<myNode invisible="field == 'value'" />`;
|
||||
|
||||
const expected = `<t t-translation="off"><div class="myNode" t-if="( myCondition or myOtherCondition ) and !__comp__.evaluateBooleanExpr("field == 'value'",__comp__.props.record.evalContextWithVirtualIds)" t-ref="compiled_view_root"/></t>`;
|
||||
assert.areEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
|
||||
QUnit.test("keep nosheet style if a sheet is part of a nested form", (assert) => {
|
||||
const arch = `
|
||||
<form>
|
||||
<field name="move_line_ids" field_id="move_line_ids">
|
||||
<form>
|
||||
<sheet/>
|
||||
</form>
|
||||
</field>
|
||||
</form>`;
|
||||
|
||||
const expected = `<t t-translation="off">
|
||||
<div
|
||||
class="o_form_renderer o_form_nosheet"
|
||||
t-att-class="__comp__.props.class"
|
||||
t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.dirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}"
|
||||
t-ref="compiled_view_root"
|
||||
>
|
||||
<Field
|
||||
id="'move_line_ids'"
|
||||
name="'move_line_ids'"
|
||||
record="__comp__.props.record"
|
||||
fieldInfo="__comp__.props.archInfo.fieldNodes['move_line_ids']"
|
||||
readonly="__comp__.props.archInfo.activeActions?.edit === false and !__comp__.props.record.isNew"
|
||||
/>
|
||||
</div>
|
||||
</t>`;
|
||||
|
||||
const expected = `<t t-translation="off"><div class="myNode" t-if="( myCondition or myOtherCondition ) and !evalDomainFromRecord(props.record,[["field","=","value"]])" t-ref="compiled_view_root"/></t>`;
|
||||
assert.areEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,31 +1,25 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import { getFixture, mount, nextTick } from "@web/../tests/helpers/utils";
|
||||
import { getDefaultConfig, View } from "@web/views/view";
|
||||
import { createDebugContext } from "@web/core/debug/debug_context";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { MainComponentsContainer } from "@web/core/main_components_container";
|
||||
import {
|
||||
setupControlPanelFavoriteMenuRegistry,
|
||||
setupControlPanelServiceRegistry,
|
||||
} from "../search/helpers";
|
||||
import { addLegacyMockEnvironment } from "../webclient/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { View, getDefaultConfig } from "@web/views/view";
|
||||
import {
|
||||
fakeCompanyService,
|
||||
makeFakeLocalizationService,
|
||||
makeFakeRouterService,
|
||||
makeFakeUserService,
|
||||
} from "../helpers/mock_services";
|
||||
import { commandService } from "@web/core/commands/command_service";
|
||||
import { popoverService } from "@web/core/popover/popover_service";
|
||||
import { createDebugContext } from "@web/core/debug/debug_context";
|
||||
import {
|
||||
setupControlPanelFavoriteMenuRegistry,
|
||||
setupControlPanelServiceRegistry,
|
||||
} from "../search/helpers";
|
||||
|
||||
import { Component, useSubEnv, xml } from "@odoo/owl";
|
||||
|
||||
import { mapLegacyEnvToWowlEnv } from "@web/legacy/utils";
|
||||
import makeTestEnvironment from "web.test_env";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
const rootDialogTemplate = xml`<Dialog><View t-props="props.viewProps"/></Dialog>`;
|
||||
|
|
@ -41,9 +35,10 @@ const rootDialogTemplate = xml`<Dialog><View t-props="props.viewProps"/></Dialog
|
|||
*/
|
||||
|
||||
/**
|
||||
* @template {Component} T
|
||||
* @param {MakeViewParams} params
|
||||
* @param {boolean} [inDialog=false]
|
||||
* @returns {Component}
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
async function _makeView(params, inDialog = false) {
|
||||
const props = { ...params };
|
||||
|
|
@ -53,11 +48,9 @@ async function _makeView(params, inDialog = false) {
|
|||
...getDefaultConfig(),
|
||||
...props.config,
|
||||
};
|
||||
const legacyParams = props.legacyParams || {};
|
||||
|
||||
delete props.serverData;
|
||||
delete props.mockRPC;
|
||||
delete props.legacyParams;
|
||||
delete props.config;
|
||||
|
||||
if (props.arch) {
|
||||
|
|
@ -74,22 +67,6 @@ async function _makeView(params, inDialog = false) {
|
|||
const env = await makeTestEnv({ serverData, mockRPC });
|
||||
Object.assign(env, createDebugContext(env)); // This is needed if the views are in debug mode
|
||||
|
||||
/** Legacy Environment, for compatibility sakes
|
||||
* Remove this as soon as we drop the legacy support
|
||||
*/
|
||||
const models = params.serverData.models;
|
||||
if (legacyParams && legacyParams.withLegacyMockServer && models) {
|
||||
legacyParams.models = Object.assign({}, 0);
|
||||
// In lagacy, data may not be sole models, but can contain some other variables
|
||||
// So we filter them out for our WOWL mockServer
|
||||
Object.entries(legacyParams.models).forEach(([k, v]) => {
|
||||
if (!(v instanceof Object) || !("fields" in v)) {
|
||||
delete models[k];
|
||||
}
|
||||
});
|
||||
}
|
||||
await addLegacyMockEnvironment(env, legacyParams);
|
||||
|
||||
const target = getFixture();
|
||||
const viewEnv = Object.assign(Object.create(env), { config });
|
||||
|
||||
|
|
@ -124,7 +101,6 @@ async function _makeView(params, inDialog = false) {
|
|||
|
||||
/**
|
||||
* @param {MakeViewParams} params
|
||||
* @returns {Component}
|
||||
*/
|
||||
export function makeView(params) {
|
||||
return _makeView(params);
|
||||
|
|
@ -132,7 +108,6 @@ export function makeView(params) {
|
|||
|
||||
/**
|
||||
* @param {MakeViewParams} params
|
||||
* @returns {Component}
|
||||
*/
|
||||
export function makeViewInDialog(params) {
|
||||
return _makeView(params, true);
|
||||
|
|
@ -147,27 +122,6 @@ export function setupViewRegistries() {
|
|||
{ force: true }
|
||||
);
|
||||
serviceRegistry.add("router", makeFakeRouterService(), { force: true });
|
||||
serviceRegistry.add("localization", makeFakeLocalizationService()), { force: true };
|
||||
serviceRegistry.add("popover", popoverService), { force: true };
|
||||
serviceRegistry.add("localization", makeFakeLocalizationService());
|
||||
serviceRegistry.add("company", fakeCompanyService);
|
||||
serviceRegistry.add("command", commandService);
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper sets the legacy env and mounts a MainComponentsContainer
|
||||
* to allow legacy code to use wowl FormViewDialogs.
|
||||
*
|
||||
* TODO: remove this when there's no legacy code using the wowl FormViewDialog.
|
||||
*
|
||||
* @param {Object} serverData
|
||||
* @param {Function} [mockRPC]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export async function prepareWowlFormViewDialogs(serverData, mockRPC) {
|
||||
setupViewRegistries();
|
||||
const wowlEnv = await makeTestEnv({ serverData, mockRPC });
|
||||
const legacyEnv = makeTestEnvironment();
|
||||
mapLegacyEnvToWowlEnv(legacyEnv, wowlEnv);
|
||||
owl.Component.env = legacyEnv;
|
||||
await mount(MainComponentsContainer, getFixture(), { env: wowlEnv });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { makeFakeDialogService } from "@web/../tests/helpers/mock_services";
|
||||
import { click, editInput, nextTick } from "@web/../tests/helpers/utils";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export function patchDialog(addDialog) {
|
||||
registry.category("services").add("dialog", makeFakeDialogService(addDialog), { force: true });
|
||||
}
|
||||
|
||||
// Kanban
|
||||
// WOWL remove this helper and use the control panel instead
|
||||
export async function reload(kanban, params = {}) {
|
||||
kanban.env.searchModel.reload(params);
|
||||
kanban.env.searchModel.search();
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
export function getCard(target, cardIndex = 0) {
|
||||
return target.querySelectorAll(".o_kanban_record:not(.o_kanban_ghost)")[cardIndex];
|
||||
}
|
||||
|
||||
export function getColumn(target, groupIndex = 0, ignoreFolded = false) {
|
||||
let selector = ".o_kanban_group";
|
||||
if (ignoreFolded) {
|
||||
selector += ":not(.o_column_folded)";
|
||||
}
|
||||
return target.querySelectorAll(selector)[groupIndex];
|
||||
}
|
||||
|
||||
export function getCardTexts(target, groupIndex) {
|
||||
const root = groupIndex >= 0 ? getColumn(target, groupIndex) : target;
|
||||
return [...root.querySelectorAll(".o_kanban_record:not(.o_kanban_ghost)")]
|
||||
.map((card) => card.innerText.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function getCounters(target) {
|
||||
return [...target.querySelectorAll(".o_animated_number")].map((counter) => counter.innerText);
|
||||
}
|
||||
|
||||
export function getProgressBars(target, columnIndex) {
|
||||
const column = getColumn(target, columnIndex);
|
||||
return [...column.querySelectorAll(".o_column_progress .progress-bar")];
|
||||
}
|
||||
|
||||
export function getTooltips(target, groupIndex) {
|
||||
const root = groupIndex >= 0 ? getColumn(target, groupIndex) : target;
|
||||
return [...root.querySelectorAll(".o_column_progress .progress-bar")]
|
||||
.map((card) => card.dataset.tooltip)
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
// Record
|
||||
export async function createRecord(target) {
|
||||
await click(target, ".o_control_panel_main_buttons .d-none button.o-kanban-button-new");
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
export async function quickCreateRecord(target, groupIndex) {
|
||||
await click(getColumn(target, groupIndex), ".o_kanban_quick_add");
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
export async function editQuickCreateInput(target, field, value) {
|
||||
await editInput(target, `.o_kanban_quick_create .o_field_widget[name=${field}] input`, value);
|
||||
}
|
||||
|
||||
export async function validateRecord(target) {
|
||||
await click(target, ".o_kanban_quick_create .o_kanban_add");
|
||||
}
|
||||
|
||||
export async function editRecord(target) {
|
||||
await click(target, ".o_kanban_quick_create .o_kanban_edit");
|
||||
}
|
||||
|
||||
export async function discardRecord(target) {
|
||||
await click(target, ".o_kanban_quick_create .o_kanban_cancel");
|
||||
}
|
||||
|
||||
export async function toggleRecordDropdown(target, recordIndex) {
|
||||
const group = target.querySelectorAll(`.o_kanban_record`)[recordIndex];
|
||||
await click(group, ".o_dropdown_kanban .dropdown-toggle");
|
||||
}
|
||||
|
||||
// Column
|
||||
export async function createColumn(target) {
|
||||
await click(target, ".o_column_quick_create > .o_quick_create_folded");
|
||||
}
|
||||
|
||||
export async function editColumnName(target, value) {
|
||||
await editInput(target, ".o_column_quick_create input", value);
|
||||
}
|
||||
|
||||
export async function validateColumn(target) {
|
||||
await click(target, ".o_column_quick_create .o_kanban_add");
|
||||
}
|
||||
|
||||
export async function toggleColumnActions(target, columnIndex) {
|
||||
const group = getColumn(target, columnIndex);
|
||||
await click(group, ".o_kanban_config .dropdown-toggle");
|
||||
const buttons = group.querySelectorAll(".o_kanban_config .dropdown-menu .dropdown-item");
|
||||
return (buttonText) => {
|
||||
const re = new RegExp(`\\b${buttonText}\\b`, "i");
|
||||
const button = [...buttons].find((b) => re.test(b.innerText));
|
||||
return click(button);
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadMore(target, columnIndex) {
|
||||
await click(getColumn(target, columnIndex), ".o_kanban_load_more button");
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { KanbanArchParser } from "@web/views/kanban/kanban_arch_parser";
|
||||
import { parseXML } from "@web/core/utils/xml";
|
||||
|
||||
function parseArch(arch, options = {}) {
|
||||
const parser = new KanbanArchParser();
|
||||
const xmlDoc = parseXML(arch);
|
||||
return parser.parse(xmlDoc, { fake: {name: { string: "Name", type: "char" },} }, "fake");
|
||||
}
|
||||
QUnit.module("KanbanView - ArchParser");
|
||||
|
||||
QUnit.test("oe_kanban_colorpicker in kanban-menu and kanban-box", (assert) => {
|
||||
const archInfo = parseArch(`
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-menu">
|
||||
<ul class="oe_kanban_colorpicker" data-field="kanban_menu_colorpicker" role="menu"/>
|
||||
</t>
|
||||
<t t-name="kanban-box"/>
|
||||
</templates>
|
||||
</kanban>
|
||||
`);
|
||||
assert.strictEqual(archInfo.colorField, "kanban_menu_colorpicker", "colorField should be 'kanban_menu_colorpicker'");
|
||||
const archInfo_1 = parseArch(`
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-menu"/>
|
||||
<t t-name="kanban-box">
|
||||
<ul class="oe_kanban_colorpicker" data-field="kanban_box_color" role="menu"/>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
`);
|
||||
assert.strictEqual(archInfo_1.colorField, "kanban_box_color", "colorField should be 'kanban_box_color'");
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -5,8 +5,10 @@ import { makeWithSearch, setupControlPanelServiceRegistry } from "@web/../tests/
|
|||
import { Layout } from "@web/search/layout";
|
||||
import { getDefaultConfig } from "@web/views/view";
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import { SearchModel } from "@web/search/search_model";
|
||||
|
||||
import { Component, xml, useChildSubEnv } from "@odoo/owl";
|
||||
import { Component, xml, onWillStart, useChildSubEnv, useSubEnv } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
|
@ -68,7 +70,7 @@ QUnit.module("Views", (hooks) => {
|
|||
class ToyComponent extends Component {}
|
||||
ToyComponent.template = xml`
|
||||
<Layout display="props.display">
|
||||
<t t-set-slot="control-panel-top-right">
|
||||
<t t-set-slot="layout-actions">
|
||||
<div class="toy_search_bar" />
|
||||
</t>
|
||||
<div class="toy_content" />
|
||||
|
|
@ -82,12 +84,49 @@ QUnit.module("Views", (hooks) => {
|
|||
searchViewId: false,
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_control_panel .o_cp_top_right .toy_search_bar");
|
||||
assert.containsOnce(target, ".o_control_panel .o_control_panel_actions .toy_search_bar");
|
||||
assert.containsOnce(target, ".o_component_with_search_panel .o_search_panel");
|
||||
assert.containsNone(target, ".o_cp_searchview");
|
||||
assert.containsOnce(target, ".o_content > .toy_content");
|
||||
});
|
||||
|
||||
QUnit.test("Rendering with default ControlPanel and SearchPanel", async (assert) => {
|
||||
class ToyComponent extends Component {
|
||||
setup() {
|
||||
this.searchModel = new SearchModel(this.env, {
|
||||
user: useService("user"),
|
||||
orm: useService("orm"),
|
||||
view: useService("view"),
|
||||
});
|
||||
useSubEnv({ searchModel: this.searchModel });
|
||||
onWillStart(async () => {
|
||||
await this.searchModel.load({ resModel: "foo" });
|
||||
});
|
||||
}
|
||||
}
|
||||
ToyComponent.template = xml`
|
||||
<Layout className="'o_view_sample_data'" display="{
|
||||
controlPanel: {},
|
||||
searchPanel: true,
|
||||
}">
|
||||
<div class="toy_content" />
|
||||
</Layout>`;
|
||||
ToyComponent.components = { Layout };
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const toyEnv = Object.assign(Object.create(env), {
|
||||
config: { breadcrumbs: getDefaultConfig().breadcrumbs },
|
||||
});
|
||||
|
||||
await mount(ToyComponent, getFixture(), { env: toyEnv });
|
||||
|
||||
assert.containsOnce(target, ".o_search_panel");
|
||||
assert.containsOnce(target, ".o_control_panel");
|
||||
assert.containsOnce(target, ".o_breadcrumb");
|
||||
assert.containsOnce(target, ".o_component_with_search_panel");
|
||||
assert.containsOnce(target, ".o_content > .toy_content");
|
||||
});
|
||||
|
||||
QUnit.test("Nested layouts", async (assert) => {
|
||||
// Component C: bottom (no control panel)
|
||||
class ToyC extends Component {
|
||||
|
|
@ -120,7 +159,7 @@ QUnit.module("Views", (hooks) => {
|
|||
}
|
||||
ToyB.template = xml`
|
||||
<Layout className="'toy_b'" display="props.display">
|
||||
<t t-set-slot="control-panel-top-right">
|
||||
<t t-set-slot="layout-actions">
|
||||
<div class="toy_b_breadcrumbs" />
|
||||
</t>
|
||||
<ToyC/>
|
||||
|
|
@ -131,7 +170,7 @@ QUnit.module("Views", (hooks) => {
|
|||
class ToyA extends Component {}
|
||||
ToyA.template = xml`
|
||||
<Layout className="'toy_a'" display="props.display">
|
||||
<t t-set-slot="control-panel-top-right">
|
||||
<t t-set-slot="layout-actions">
|
||||
<div class="toy_a_search" />
|
||||
</t>
|
||||
<ToyB display="props.display"/>
|
||||
|
|
@ -252,21 +291,21 @@ QUnit.module("Views", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test("Simple rendering: with dynamically displayed search", async (assert) => {
|
||||
let displayControlPanelTopRight = true;
|
||||
let displayLayoutActions = true;
|
||||
class ToyComponent extends Component {
|
||||
get display() {
|
||||
return {
|
||||
...this.props.display,
|
||||
controlPanel: {
|
||||
...this.props.display.controlPanel,
|
||||
"top-right": displayControlPanelTopRight,
|
||||
layoutActions: displayLayoutActions,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
ToyComponent.template = xml`
|
||||
<Layout display="display">
|
||||
<t t-set-slot="control-panel-top-right">
|
||||
<t t-set-slot="layout-actions">
|
||||
<div class="toy_search_bar" />
|
||||
</t>
|
||||
<div class="toy_content" />
|
||||
|
|
@ -280,16 +319,16 @@ QUnit.module("Views", (hooks) => {
|
|||
searchViewId: false,
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_control_panel .o_cp_top_right .toy_search_bar");
|
||||
assert.containsOnce(target, ".o_control_panel .o_control_panel_actions .toy_search_bar");
|
||||
assert.containsOnce(target, ".o_component_with_search_panel .o_search_panel");
|
||||
assert.containsNone(target, ".o_cp_searchview");
|
||||
assert.containsOnce(target, ".o_content > .toy_content");
|
||||
|
||||
displayControlPanelTopRight = false;
|
||||
displayLayoutActions = false;
|
||||
comp.render();
|
||||
await nextTick();
|
||||
|
||||
assert.containsNone(target, ".o_control_panel .o_cp_top_right .toy_search_bar");
|
||||
assert.containsNone(target, ".o_control_panel .o_control_panel_actions .toy_search_bar");
|
||||
assert.containsOnce(target, ".o_component_with_search_panel .o_search_panel");
|
||||
assert.containsNone(target, ".o_cp_searchview");
|
||||
assert.containsOnce(target, ".o_content > .toy_content");
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,24 @@
|
|||
/** @odoo-module **/
|
||||
import { Component, xml, useState, onError } from "@odoo/owl";
|
||||
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { useRecordObserver } from "@web/model/relational_model/utils";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { Record } from "@web/views/record";
|
||||
import { click, getFixture, mount } from "../helpers/utils";
|
||||
import { setupViewRegistries } from "../views/helpers";
|
||||
|
||||
import { Component, xml, useState } from "@odoo/owl";
|
||||
import { CharField } from "@web/views/fields/char/char_field";
|
||||
import { Many2OneField } from "@web/views/fields/many2one/many2one_field";
|
||||
import { Many2ManyTagsField } from "@web/views/fields/many2many_tags/many2many_tags_field";
|
||||
import { Record } from "@web/model/record";
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import {
|
||||
click,
|
||||
editInput,
|
||||
getFixture,
|
||||
getNodesTextContent,
|
||||
mount,
|
||||
nextTick,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
|
@ -111,7 +123,7 @@ QUnit.module("Record Component", (hooks) => {
|
|||
);
|
||||
assert.verifySteps([
|
||||
"/web/dataset/call_kw/partner/fields_get",
|
||||
"/web/dataset/call_kw/partner/read",
|
||||
"/web/dataset/call_kw/partner/web_read",
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -127,7 +139,7 @@ QUnit.module("Record Component", (hooks) => {
|
|||
Parent.template = xml`
|
||||
<Record resModel="'partner'" resId="state.resId" fieldNames="['foo']" t-slot-scope="data">
|
||||
<Field name="'foo'" record="data.record"/>
|
||||
<button t-on-click="() => this.state.resId++">Next</button>
|
||||
<button class="my-btn" t-on-click="() => this.state.resId++">Next</button>
|
||||
</Record>`;
|
||||
const env = await makeTestEnv({
|
||||
serverData,
|
||||
|
|
@ -138,12 +150,12 @@ QUnit.module("Record Component", (hooks) => {
|
|||
await mount(Parent, target, { env, dev: true });
|
||||
assert.verifySteps([
|
||||
"/web/dataset/call_kw/partner/fields_get",
|
||||
"/web/dataset/call_kw/partner/read",
|
||||
"/web/dataset/call_kw/partner/web_read",
|
||||
]);
|
||||
assert.containsOnce(target, ".o_field_char:contains(yop)");
|
||||
await click(target.querySelector("button"));
|
||||
await click(target.querySelector("button.my-btn"));
|
||||
assert.containsOnce(target, ".o_field_char:contains(blip)");
|
||||
assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
|
||||
assert.verifySteps(["/web/dataset/call_kw/partner/web_read"]);
|
||||
});
|
||||
|
||||
QUnit.test("predefined fields and values", async function (assert) {
|
||||
|
|
@ -167,7 +179,7 @@ QUnit.module("Record Component", (hooks) => {
|
|||
}
|
||||
Parent.components = { Record, Field };
|
||||
Parent.template = xml`
|
||||
<Record resModel="'partner'" fieldNames="['foo']" fields="fields" initialValues="values" t-slot-scope="data">
|
||||
<Record resModel="'partner'" fieldNames="['foo']" fields="fields" values="values" t-slot-scope="data">
|
||||
<Field name="'foo'" record="data.record"/>
|
||||
</Record>
|
||||
`;
|
||||
|
|
@ -183,4 +195,523 @@ QUnit.module("Record Component", (hooks) => {
|
|||
assert.verifySteps([]);
|
||||
assert.strictEqual(target.querySelector(".o_field_widget input").value, "abc");
|
||||
});
|
||||
|
||||
QUnit.test("provides a way to handle changes in the record", async function (assert) {
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.fields = {
|
||||
foo: {
|
||||
name: "foo",
|
||||
type: "char",
|
||||
},
|
||||
bar: {
|
||||
name: "bar",
|
||||
type: "boolean",
|
||||
},
|
||||
};
|
||||
this.values = {
|
||||
foo: "abc",
|
||||
bar: true,
|
||||
};
|
||||
}
|
||||
|
||||
onRecordChanged(record, changes) {
|
||||
assert.step("record changed");
|
||||
assert.strictEqual(record.model.constructor.name, "StandaloneRelationalModel");
|
||||
assert.deepEqual(changes, { foo: "753" });
|
||||
}
|
||||
}
|
||||
Parent.components = { Record, Field };
|
||||
Parent.template = xml`
|
||||
<Record resModel="'partner'" fieldNames="['foo']" fields="fields" values="values" t-slot-scope="data" onRecordChanged.bind="onRecordChanged">
|
||||
<Field name="'foo'" record="data.record"/>
|
||||
</Record>
|
||||
`;
|
||||
|
||||
await mount(Parent, target, {
|
||||
env: await makeTestEnv({
|
||||
serverData,
|
||||
mockRPC(route) {
|
||||
assert.step(route);
|
||||
},
|
||||
}),
|
||||
});
|
||||
assert.strictEqual(target.querySelector("[name='foo'] input").value, "abc");
|
||||
await editInput(target, "[name='foo'] input", "753");
|
||||
assert.verifySteps(["record changed"]);
|
||||
assert.strictEqual(target.querySelector("[name='foo'] input").value, "753");
|
||||
});
|
||||
|
||||
QUnit.test("provides a way to handle before/after saved the record", async function (assert) {
|
||||
class Parent extends Component {
|
||||
onRecordSaved(record) {
|
||||
assert.step("onRecordSaved");
|
||||
}
|
||||
|
||||
onWillSaveRecord(record) {
|
||||
assert.step("onWillSaveRecord");
|
||||
}
|
||||
}
|
||||
Parent.components = { Record, Field };
|
||||
Parent.template = xml`
|
||||
<Record resModel="'partner'" resId="1" fieldNames="['foo']" mode="'edit'" t-slot-scope="data" onRecordSaved="onRecordSaved" onWillSaveRecord="onWillSaveRecord">
|
||||
<button class="save" t-on-click="() => data.record.save()">Save</button>
|
||||
<Field name="'foo'" record="data.record"/>
|
||||
</Record>`;
|
||||
|
||||
const env = await makeTestEnv({
|
||||
serverData,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
},
|
||||
});
|
||||
await mount(Parent, target, { env });
|
||||
|
||||
await editInput(target, "[name='foo'] input", "abc");
|
||||
await click(target, "button.save");
|
||||
assert.verifySteps([
|
||||
"fields_get",
|
||||
"web_read",
|
||||
"onWillSaveRecord",
|
||||
"web_save",
|
||||
"onRecordSaved",
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("handles many2one fields", async function (assert) {
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
});
|
||||
|
||||
serverData.models = {
|
||||
bar: {
|
||||
records: [
|
||||
{ id: 1, display_name: "bar1" },
|
||||
{ id: 3, display_name: "abc" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.fields = {
|
||||
foo: {
|
||||
name: "foo",
|
||||
type: "many2one",
|
||||
relation: "bar",
|
||||
},
|
||||
};
|
||||
this.values = {
|
||||
foo: [1, "bar1"],
|
||||
};
|
||||
}
|
||||
|
||||
onRecordChanged(record, changes) {
|
||||
assert.step("record changed");
|
||||
assert.deepEqual(changes, { foo: 3 });
|
||||
assert.deepEqual(record.data, { foo: [3, "abc"] });
|
||||
}
|
||||
}
|
||||
Parent.components = { Record, Many2OneField };
|
||||
Parent.template = xml`
|
||||
<Record resModel="'partner'" fieldNames="['foo']" fields="fields" values="values" t-slot-scope="data" onRecordChanged.bind="onRecordChanged">
|
||||
<Many2OneField name="'foo'" record="data.record" relation="'bar'" value="data.record.data.foo"/>
|
||||
</Record>
|
||||
`;
|
||||
|
||||
await mount(Parent, target, {
|
||||
env: await makeTestEnv({
|
||||
serverData,
|
||||
mockRPC(route, args) {
|
||||
assert.step(route);
|
||||
},
|
||||
}),
|
||||
});
|
||||
assert.verifySteps([]);
|
||||
assert.strictEqual(target.querySelector(".o_field_many2one_selection input").value, "bar1");
|
||||
await editInput(target, ".o_field_many2one_selection input", "abc");
|
||||
assert.verifySteps(["/web/dataset/call_kw/bar/name_search"]);
|
||||
await click(target.querySelectorAll(".o-autocomplete--dropdown-item a")[0]);
|
||||
assert.verifySteps(["record changed"]);
|
||||
assert.strictEqual(target.querySelector(".o_field_many2one_selection input").value, "abc");
|
||||
});
|
||||
|
||||
QUnit.test("handles many2one fields (2)", async function (assert) {
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
});
|
||||
|
||||
serverData.models = {
|
||||
bar: {
|
||||
records: [
|
||||
{ id: 1, display_name: "bar1" },
|
||||
{ id: 3, display_name: "abc" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.fields = {
|
||||
foo: {
|
||||
name: "foo",
|
||||
type: "many2one",
|
||||
relation: "bar",
|
||||
},
|
||||
};
|
||||
this.values = {
|
||||
foo: 1,
|
||||
};
|
||||
}
|
||||
|
||||
onRecordChanged(record, changes) {
|
||||
assert.step("record changed");
|
||||
assert.deepEqual(changes, { foo: 3 });
|
||||
assert.deepEqual(record.data, { foo: [3, "abc"] });
|
||||
}
|
||||
}
|
||||
Parent.components = { Record, Many2OneField };
|
||||
Parent.template = xml`
|
||||
<Record resModel="'partner'" fieldNames="['foo']" fields="fields" values="values" t-slot-scope="data">
|
||||
<Many2OneField name="'foo'" record="data.record" relation="'bar'" value="data.record.data.foo"/>
|
||||
</Record>
|
||||
`;
|
||||
|
||||
await mount(Parent, target, {
|
||||
env: await makeTestEnv({
|
||||
serverData,
|
||||
mockRPC(route, args) {
|
||||
assert.step(route);
|
||||
},
|
||||
}),
|
||||
});
|
||||
assert.verifySteps(["/web/dataset/call_kw/bar/web_read"]);
|
||||
assert.strictEqual(target.querySelector(".o_field_many2one_selection input").value, "bar1");
|
||||
});
|
||||
|
||||
QUnit.test("handles many2one fields (3)", async function (assert) {
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
});
|
||||
|
||||
serverData.models = {
|
||||
bar: {
|
||||
records: [
|
||||
{ id: 1, display_name: "bar1" },
|
||||
{ id: 3, display_name: "abc" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.fields = {
|
||||
foo: {
|
||||
name: "foo",
|
||||
type: "many2one",
|
||||
relation: "bar",
|
||||
},
|
||||
};
|
||||
this.values = {
|
||||
foo: [1],
|
||||
};
|
||||
}
|
||||
|
||||
onRecordChanged(record, changes) {
|
||||
assert.step("record changed");
|
||||
assert.deepEqual(changes, { foo: 3 });
|
||||
assert.deepEqual(record.data, { foo: [3, "abc"] });
|
||||
}
|
||||
}
|
||||
Parent.components = { Record, Many2OneField };
|
||||
Parent.template = xml`
|
||||
<Record resModel="'partner'" fieldNames="['foo']" fields="fields" values="values" t-slot-scope="data">
|
||||
<Many2OneField name="'foo'" record="data.record" relation="'bar'" value="data.record.data.foo"/>
|
||||
</Record>
|
||||
`;
|
||||
|
||||
await mount(Parent, target, {
|
||||
env: await makeTestEnv({
|
||||
serverData,
|
||||
mockRPC(route, args) {
|
||||
assert.step(route);
|
||||
},
|
||||
}),
|
||||
});
|
||||
assert.verifySteps(["/web/dataset/call_kw/bar/web_read"]);
|
||||
assert.strictEqual(target.querySelector(".o_field_many2one_selection input").value, "bar1");
|
||||
});
|
||||
|
||||
QUnit.test("handles x2many fields", async function (assert) {
|
||||
serverData.models = {
|
||||
tag: {
|
||||
records: [
|
||||
{ id: 1, display_name: "bug" },
|
||||
{ id: 3, display_name: "ref" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.activeFields = {
|
||||
tags: {
|
||||
related: {
|
||||
activeFields: {
|
||||
display_name: {},
|
||||
},
|
||||
fields: {
|
||||
display_name: { name: "display_name", type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
this.fields = {
|
||||
tags: {
|
||||
name: "Tags",
|
||||
type: "many2many",
|
||||
relation: "tag",
|
||||
},
|
||||
};
|
||||
this.values = {
|
||||
tags: [1, 3],
|
||||
};
|
||||
}
|
||||
}
|
||||
Parent.components = { Record, Many2ManyTagsField };
|
||||
Parent.template = xml`
|
||||
<Record resModel="'partner'" fieldNames="['tags']" activeFields="activeFields" fields="fields" values="values" t-slot-scope="data">
|
||||
<Many2ManyTagsField name="'tags'" record="data.record"/>
|
||||
</Record>
|
||||
`;
|
||||
|
||||
await mount(Parent, target, {
|
||||
env: await makeTestEnv({
|
||||
serverData,
|
||||
mockRPC(route, args) {
|
||||
assert.step(route);
|
||||
},
|
||||
}),
|
||||
});
|
||||
assert.verifySteps(["/web/dataset/call_kw/tag/web_read"]);
|
||||
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_tag")), ["bug", "ref"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"supports passing dynamic values -- full control to the user of Record",
|
||||
async (assert) => {
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.fields = {
|
||||
foo: {
|
||||
name: "foo",
|
||||
type: "char",
|
||||
},
|
||||
bar: {
|
||||
name: "bar",
|
||||
type: "boolean",
|
||||
},
|
||||
};
|
||||
this.values = useState({
|
||||
foo: "abc",
|
||||
bar: true,
|
||||
});
|
||||
}
|
||||
|
||||
onRecordChanged(record, changes) {
|
||||
assert.step("record changed");
|
||||
assert.strictEqual(record.model.constructor.name, "StandaloneRelationalModel");
|
||||
assert.deepEqual(changes, { foo: "753" });
|
||||
this.values.foo = "357";
|
||||
}
|
||||
}
|
||||
Parent.components = { Record, Field };
|
||||
Parent.template = xml`
|
||||
<Record resModel="'partner'" fieldNames="['foo']" fields="fields" values="{ foo: values.foo }" t-slot-scope="data" onRecordChanged.bind="onRecordChanged">
|
||||
<Field name="'foo'" record="data.record"/>
|
||||
</Record>
|
||||
`;
|
||||
|
||||
await mount(Parent, target, {
|
||||
env: await makeTestEnv({
|
||||
serverData,
|
||||
mockRPC(route) {
|
||||
throw new Error("should not do any rpc");
|
||||
},
|
||||
}),
|
||||
});
|
||||
assert.strictEqual(target.querySelector("[name='foo'] input").value, "abc");
|
||||
await editInput(target, "[name='foo'] input", "753");
|
||||
assert.verifySteps(["record changed"]);
|
||||
await nextTick();
|
||||
assert.strictEqual(target.querySelector("[name='foo'] input").value, "357");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("can switch records", async (assert) => {
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.fields = {
|
||||
foo: {
|
||||
name: "foo",
|
||||
type: "char",
|
||||
},
|
||||
bar: {
|
||||
name: "bar",
|
||||
type: "boolean",
|
||||
},
|
||||
};
|
||||
this.state = useState({ currentId: 1, num: 0 });
|
||||
}
|
||||
|
||||
next() {
|
||||
this.state.currentId = 5;
|
||||
this.state.num++;
|
||||
}
|
||||
}
|
||||
Parent.components = { Record, Field };
|
||||
Parent.template = xml`
|
||||
<a id="increment" t-on-click="() => state.num++" t-esc="state.num" />
|
||||
<a id="next" t-on-click="next">NEXT</a>
|
||||
<Record resId="state.currentId" resModel="'partner'" fieldNames="['foo']" fields="fields" t-slot-scope="data">
|
||||
<Field name="'foo'" record="data.record"/>
|
||||
</Record>
|
||||
`;
|
||||
|
||||
await mount(Parent, target, {
|
||||
env: await makeTestEnv({
|
||||
serverData,
|
||||
mockRPC(route, { method, args, kwargs }) {
|
||||
assert.step(
|
||||
`${method} : ${JSON.stringify(args[0])} - ${JSON.stringify(
|
||||
kwargs.specification
|
||||
)}`
|
||||
);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
assert.verifySteps([`web_read : [1] - {"foo":{}}`]);
|
||||
const increment = target.querySelector("#increment");
|
||||
const field = target.querySelector("div[name='foo']");
|
||||
assert.strictEqual(increment.textContent, "0");
|
||||
assert.strictEqual(field.textContent, "yop");
|
||||
|
||||
await click(increment);
|
||||
// No reload when a render from upstream comes
|
||||
assert.verifySteps([]);
|
||||
assert.strictEqual(increment.textContent, "1");
|
||||
assert.strictEqual(field.textContent, "yop");
|
||||
|
||||
await click(target.querySelector("#next"));
|
||||
assert.verifySteps([`web_read : [5] - {"foo":{}}`]);
|
||||
assert.strictEqual(increment.textContent, "2");
|
||||
assert.strictEqual(field.textContent, "blop");
|
||||
});
|
||||
|
||||
QUnit.test("can switch records with values", async (assert) => {
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.fields = {
|
||||
foo: {
|
||||
name: "foo",
|
||||
type: "char",
|
||||
},
|
||||
bar: {
|
||||
name: "bar",
|
||||
type: "boolean",
|
||||
},
|
||||
};
|
||||
this.values = {
|
||||
foo: "abc",
|
||||
bar: true,
|
||||
};
|
||||
this.state = useState({ currentId: 99 });
|
||||
}
|
||||
|
||||
next() {
|
||||
this.state.currentId = 100;
|
||||
this.values = {
|
||||
foo: "def",
|
||||
bar: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
Parent.components = { Record, Field };
|
||||
Parent.template = xml`
|
||||
<a id="next" t-on-click="next">NEXT</a>
|
||||
<Record resId="state.currentId" resModel="'partner'" fieldNames="['foo']" fields="fields" values="values" t-slot-scope="data">
|
||||
<Field name="'foo'" record="data.record"/>
|
||||
</Record>
|
||||
`;
|
||||
let _record;
|
||||
patchWithCleanup(Record.components._Record.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
_record = this;
|
||||
},
|
||||
});
|
||||
|
||||
await mount(Parent, target, {
|
||||
env: await makeTestEnv({
|
||||
serverData,
|
||||
mockRPC(route) {
|
||||
assert.step(route);
|
||||
},
|
||||
}),
|
||||
});
|
||||
// No load since the values are provided to the record
|
||||
assert.verifySteps([]);
|
||||
const field = target.querySelector("div[name='foo']");
|
||||
// First values are loaded
|
||||
assert.strictEqual(field.textContent, "abc");
|
||||
// Verify that the underlying _Record Model root has the specified resId
|
||||
assert.strictEqual(_record.model.root.resId, 99);
|
||||
|
||||
await click(target.querySelector("#next"));
|
||||
// Still no load.
|
||||
assert.verifySteps([]);
|
||||
// Second values are loaded
|
||||
assert.strictEqual(field.textContent, "def");
|
||||
// Verify that the underlying _Record Model root has the updated resId
|
||||
assert.strictEqual(_record.model.root.resId, 100);
|
||||
});
|
||||
|
||||
QUnit.test("faulty useRecordObserver in widget", async (assert) => {
|
||||
patchWithCleanup(CharField.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
useRecordObserver((record, props) => {
|
||||
throw new Error("faulty record observer");
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { Record, Field };
|
||||
static template = xml`
|
||||
<t t-if="!state.error">
|
||||
<Record resId="1" resModel="'partner'" fieldNames="['foo']" fields="fields" values="values" t-slot-scope="data">
|
||||
<Field name="'foo'" record="data.record"/>
|
||||
</Record>
|
||||
</t>
|
||||
<div t-else="" class="error" t-esc="state.error.message" />`;
|
||||
setup() {
|
||||
this.state = useState({ error: false });
|
||||
onError((error) => {
|
||||
this.state.error = error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Parent, target, {
|
||||
env: await makeTestEnv({
|
||||
serverData,
|
||||
}),
|
||||
});
|
||||
assert.strictEqual(
|
||||
target.querySelector(".error").textContent,
|
||||
`The following error occurred in onWillStart: "faulty record observer"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,534 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { SampleServer } from "@web/views/sample_server";
|
||||
|
||||
const {
|
||||
MAIN_RECORDSET_SIZE,
|
||||
SEARCH_READ_LIMIT, // Limits
|
||||
SAMPLE_COUNTRIES,
|
||||
SAMPLE_PEOPLE,
|
||||
SAMPLE_TEXTS, // Text values
|
||||
MAX_COLOR_INT,
|
||||
MAX_FLOAT,
|
||||
MAX_INTEGER,
|
||||
MAX_MONETARY, // Number values
|
||||
SUB_RECORDSET_SIZE, // Records sise
|
||||
} = SampleServer;
|
||||
|
||||
/**
|
||||
* Transforms random results into deterministic ones.
|
||||
*/
|
||||
class DeterministicSampleServer extends SampleServer {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.arrayElCpt = 0;
|
||||
this.boolCpt = 0;
|
||||
this.subRecordIdCpt = 0;
|
||||
}
|
||||
_getRandomArrayEl(array) {
|
||||
return array[this.arrayElCpt++ % array.length];
|
||||
}
|
||||
_getRandomBool() {
|
||||
return Boolean(this.boolCpt++ % 2);
|
||||
}
|
||||
_getRandomSubRecordId() {
|
||||
return (this.subRecordIdCpt++ % SUB_RECORDSET_SIZE) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
let fields;
|
||||
QUnit.module(
|
||||
"Sample Server",
|
||||
{
|
||||
beforeEach() {
|
||||
fields = {
|
||||
"res.users": {
|
||||
display_name: { string: "Name", type: "char" },
|
||||
name: { string: "Reference", type: "char" },
|
||||
email: { string: "Email", type: "char" },
|
||||
phone_number: { string: "Phone number", type: "char" },
|
||||
brol_machin_url_truc: { string: "URL", type: "char" },
|
||||
urlemailphone: { string: "Whatever", type: "char" },
|
||||
active: { string: "Active", type: "boolean" },
|
||||
is_alive: { string: "Is alive", type: "boolean" },
|
||||
description: { string: "Description", type: "text" },
|
||||
birthday: { string: "Birthday", type: "date" },
|
||||
arrival_date: { string: "Date of arrival", type: "datetime" },
|
||||
height: { string: "Height", type: "float" },
|
||||
color: { string: "Color", type: "integer" },
|
||||
age: { string: "Age", type: "integer" },
|
||||
salary: { string: "Salary", type: "monetary" },
|
||||
currency: {
|
||||
string: "Currency",
|
||||
type: "many2one",
|
||||
relation: "res.currency",
|
||||
},
|
||||
manager_id: {
|
||||
string: "Manager",
|
||||
type: "many2one",
|
||||
relation: "res.users",
|
||||
},
|
||||
cover_image_id: {
|
||||
string: "Cover Image",
|
||||
type: "many2one",
|
||||
relation: "ir.attachment",
|
||||
},
|
||||
managed_ids: {
|
||||
string: "Managing",
|
||||
type: "one2many",
|
||||
relation: "res.users",
|
||||
},
|
||||
tag_ids: { string: "Tags", type: "many2many", relation: "tag" },
|
||||
type: {
|
||||
string: "Type",
|
||||
type: "selection",
|
||||
selection: [
|
||||
["client", "Client"],
|
||||
["partner", "Partner"],
|
||||
["employee", "Employee"],
|
||||
],
|
||||
},
|
||||
},
|
||||
"res.country": {
|
||||
display_name: { string: "Name", type: "char" },
|
||||
},
|
||||
hobbit: {
|
||||
display_name: { string: "Name", type: "char" },
|
||||
profession: {
|
||||
string: "Profession",
|
||||
type: "selection",
|
||||
selection: [
|
||||
["gardener", "Gardener"],
|
||||
["brewer", "Brewer"],
|
||||
["adventurer", "Adventurer"],
|
||||
],
|
||||
},
|
||||
age: { string: "Age", type: "integer" },
|
||||
},
|
||||
"ir.attachment": {
|
||||
display_name: { string: "Name", type: "char" },
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
function () {
|
||||
QUnit.module("Basic behaviour");
|
||||
|
||||
QUnit.test("Sample data: people type + all field names", async function (assert) {
|
||||
assert.expect(26);
|
||||
|
||||
const allFieldNames = Object.keys(fields["res.users"]);
|
||||
const server = new DeterministicSampleServer("res.users", fields["res.users"]);
|
||||
const { records } = await server.mockRpc({
|
||||
method: "web_search_read",
|
||||
model: "res.users",
|
||||
fields: allFieldNames,
|
||||
});
|
||||
const rec = records[0];
|
||||
|
||||
function assertFormat(fieldName, regex) {
|
||||
if (regex instanceof RegExp) {
|
||||
assert.ok(
|
||||
regex.test(rec[fieldName].toString()),
|
||||
`Field "${fieldName}" has the correct format`
|
||||
);
|
||||
} else {
|
||||
assert.strictEqual(
|
||||
typeof rec[fieldName],
|
||||
regex,
|
||||
`Field "${fieldName}" is of type ${regex}`
|
||||
);
|
||||
}
|
||||
}
|
||||
function assertBetween(fieldName, min, max) {
|
||||
const val = rec[fieldName];
|
||||
assert.ok(
|
||||
min <= val && val < max && parseInt(val, 10) === val,
|
||||
`Field "${fieldName}" should be an integer between ${min} and ${max}: ${val}`
|
||||
);
|
||||
}
|
||||
|
||||
// Basic fields
|
||||
assert.ok(SAMPLE_PEOPLE.includes(rec.display_name));
|
||||
assert.ok(SAMPLE_PEOPLE.includes(rec.name));
|
||||
assert.strictEqual(
|
||||
rec.email,
|
||||
`${rec.display_name.replace(/ /, ".").toLowerCase()}@sample.demo`
|
||||
);
|
||||
assertFormat("phone_number", /\+1 555 754 000\d/);
|
||||
assertFormat("brol_machin_url_truc", /http:\/\/sample\d\.com/);
|
||||
assert.strictEqual(rec.urlemailphone, false);
|
||||
assert.strictEqual(rec.active, true);
|
||||
assertFormat("is_alive", "boolean");
|
||||
assert.ok(SAMPLE_TEXTS.includes(rec.description));
|
||||
assertFormat("birthday", /\d{4}-\d{2}-\d{2}/);
|
||||
assertFormat("arrival_date", /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/);
|
||||
assert.ok(
|
||||
rec.height >= 0 && rec.height <= MAX_FLOAT,
|
||||
"Field height should be between 0 and 100"
|
||||
);
|
||||
assertBetween("color", 0, MAX_COLOR_INT);
|
||||
assertBetween("age", 0, MAX_INTEGER);
|
||||
assertBetween("salary", 0, MAX_MONETARY);
|
||||
|
||||
// check float field have 2 decimal rounding
|
||||
assert.strictEqual(rec.height, parseFloat(parseFloat(rec.height).toFixed(2)));
|
||||
|
||||
const selectionValues = fields["res.users"].type.selection.map((sel) => sel[0]);
|
||||
assert.ok(selectionValues.includes(rec.type));
|
||||
|
||||
// Relational fields
|
||||
assert.strictEqual(rec.currency[0], 1);
|
||||
// Currently we expect the currency name to be a latin string, which
|
||||
// is not important; in most case we only need the ID. The following
|
||||
// assertion can be removed if needed.
|
||||
assert.ok(SAMPLE_TEXTS.includes(rec.currency[1]));
|
||||
|
||||
assert.strictEqual(typeof rec.manager_id[0], "number");
|
||||
assert.ok(SAMPLE_PEOPLE.includes(rec.manager_id[1]));
|
||||
|
||||
assert.strictEqual(rec.cover_image_id, false);
|
||||
|
||||
assert.strictEqual(rec.managed_ids.length, 2);
|
||||
assert.ok(rec.managed_ids.every((id) => typeof id === "number"));
|
||||
|
||||
assert.strictEqual(rec.tag_ids.length, 2);
|
||||
assert.ok(rec.tag_ids.every((id) => typeof id === "number"));
|
||||
});
|
||||
|
||||
QUnit.test("Sample data: country type", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer("res.country", fields["res.country"]);
|
||||
const { records } = await server.mockRpc({
|
||||
method: "web_search_read",
|
||||
model: "res.country",
|
||||
fields: ["display_name"],
|
||||
});
|
||||
|
||||
assert.ok(SAMPLE_COUNTRIES.includes(records[0].display_name));
|
||||
});
|
||||
|
||||
QUnit.test("Sample data: any type", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const { records } = await server.mockRpc({
|
||||
method: "web_search_read",
|
||||
model: "hobbit",
|
||||
fields: ["display_name"],
|
||||
});
|
||||
|
||||
assert.ok(SAMPLE_TEXTS.includes(records[0].display_name));
|
||||
});
|
||||
|
||||
QUnit.module("RPC calls");
|
||||
|
||||
QUnit.test("Send 'search_read' RPC: valid field names", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "web_search_read",
|
||||
model: "hobbit",
|
||||
fields: ["display_name"],
|
||||
});
|
||||
|
||||
assert.deepEqual(Object.keys(result.records[0]), ["id", "display_name"]);
|
||||
assert.strictEqual(result.length, SEARCH_READ_LIMIT);
|
||||
assert.ok(/\w+/.test(result.records[0].display_name), "Display name has been mocked");
|
||||
});
|
||||
|
||||
QUnit.test("Send 'search_read' RPC: invalid field names", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "web_search_read",
|
||||
model: "hobbit",
|
||||
fields: ["name"],
|
||||
});
|
||||
|
||||
assert.deepEqual(Object.keys(result.records[0]), ["id", "name"]);
|
||||
assert.strictEqual(result.length, SEARCH_READ_LIMIT);
|
||||
assert.strictEqual(
|
||||
result.records[0].name,
|
||||
false,
|
||||
`Field "name" doesn't exist => returns false`
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'web_read_group' RPC: no group", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
server.setExistingGroups(null);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "web_read_group",
|
||||
model: "hobbit",
|
||||
groupBy: ["profession"],
|
||||
});
|
||||
|
||||
assert.deepEqual(result, {
|
||||
groups: [
|
||||
{
|
||||
__domain: [],
|
||||
profession: "adventurer",
|
||||
profession_count: 5,
|
||||
},
|
||||
{
|
||||
__domain: [],
|
||||
profession: "brewer",
|
||||
profession_count: 5,
|
||||
},
|
||||
{
|
||||
__domain: [],
|
||||
profession: "gardener",
|
||||
profession_count: 6,
|
||||
},
|
||||
],
|
||||
length: 3,
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test("Send 'web_read_group' RPC: 2 groups", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
const existingGroups = [
|
||||
{ value: "gardener", profession_count: 0 }, // fake group
|
||||
{ value: "adventurer", profession_count: 0 }, // fake group
|
||||
];
|
||||
server.setExistingGroups(existingGroups);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "web_read_group",
|
||||
model: "hobbit",
|
||||
groupBy: ["profession"],
|
||||
fields: [],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 2);
|
||||
assert.strictEqual(result.groups.length, 2);
|
||||
|
||||
assert.deepEqual(
|
||||
result.groups.map((g) => g.value),
|
||||
["gardener", "adventurer"]
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
result.groups.reduce((acc, g) => acc + g.profession_count, 0),
|
||||
MAIN_RECORDSET_SIZE
|
||||
);
|
||||
assert.ok(result.groups.every((g) => g.profession_count === g.__data.length));
|
||||
});
|
||||
|
||||
QUnit.test("Send 'web_read_group' RPC: all groups", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
const existingGroups = [
|
||||
{ value: "gardener", profession_count: 0 }, // fake group
|
||||
{ value: "brewer", profession_count: 0 }, // fake group
|
||||
{ value: "adventurer", profession_count: 0 }, // fake group
|
||||
];
|
||||
server.setExistingGroups(existingGroups);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "web_read_group",
|
||||
model: "hobbit",
|
||||
groupBy: ["profession"],
|
||||
fields: [],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 3);
|
||||
assert.strictEqual(result.groups.length, 3);
|
||||
|
||||
assert.deepEqual(
|
||||
result.groups.map((g) => g.value),
|
||||
["gardener", "brewer", "adventurer"]
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
result.groups.reduce((acc, g) => acc + g.profession_count, 0),
|
||||
MAIN_RECORDSET_SIZE
|
||||
);
|
||||
assert.ok(result.groups.every((g) => g.profession_count === g.__data.length));
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read_group' RPC: no group", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "read_group",
|
||||
model: "hobbit",
|
||||
fields: [],
|
||||
groupBy: [],
|
||||
});
|
||||
|
||||
assert.deepEqual(result, [
|
||||
{
|
||||
__count: MAIN_RECORDSET_SIZE,
|
||||
__domain: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read_group' RPC: groupBy", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "read_group",
|
||||
model: "hobbit",
|
||||
fields: [],
|
||||
groupBy: ["profession"],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 3);
|
||||
assert.deepEqual(
|
||||
result.map((g) => g.profession),
|
||||
["adventurer", "brewer", "gardener"]
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.reduce((acc, g) => acc + g.profession_count, 0),
|
||||
MAIN_RECORDSET_SIZE
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read_group' RPC: groupBy and field", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "read_group",
|
||||
model: "hobbit",
|
||||
fields: ["age:sum"],
|
||||
groupBy: ["profession"],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 3);
|
||||
assert.deepEqual(
|
||||
result.map((g) => g.profession),
|
||||
["adventurer", "brewer", "gardener"]
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.reduce((acc, g) => acc + g.profession_count, 0),
|
||||
MAIN_RECORDSET_SIZE
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.reduce((acc, g) => acc + g.age, 0),
|
||||
server.data.hobbit.records.reduce((acc, g) => acc + g.age, 0)
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read_group' RPC: multiple groupBys and lazy", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "read_group",
|
||||
model: "hobbit",
|
||||
fields: [],
|
||||
groupBy: ["profession", "age"],
|
||||
});
|
||||
|
||||
assert.ok("profession" in result[0]);
|
||||
assert.notOk("age" in result[0]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Send 'read_group' RPC: multiple groupBys and not lazy",
|
||||
async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "read_group",
|
||||
model: "hobbit",
|
||||
fields: [],
|
||||
groupBy: ["profession", "age"],
|
||||
lazy: false,
|
||||
});
|
||||
|
||||
assert.ok("profession" in result[0]);
|
||||
assert.ok("age" in result[0]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"Send 'read_group' RPC: multiple groupBys among which a many2many",
|
||||
async function (assert) {
|
||||
const server = new DeterministicSampleServer("res.users", fields["res.users"]);
|
||||
const result = await server.mockRpc({
|
||||
method: "read_group",
|
||||
model: "res.users",
|
||||
fields: [],
|
||||
groupBy: ["height", "tag_ids"],
|
||||
lazy: false,
|
||||
});
|
||||
assert.ok(typeof result[0].tag_ids[0] === "number");
|
||||
assert.ok(typeof result[0].tag_ids[1] === "string");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("Send 'read' RPC: no id", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "read",
|
||||
model: "hobbit",
|
||||
args: [[], ["display_name"]],
|
||||
});
|
||||
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read' RPC: one id", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: "read",
|
||||
model: "hobbit",
|
||||
args: [[1], ["display_name"]],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 1);
|
||||
assert.ok(/\w+/.test(result[0].display_name), "Display name has been mocked");
|
||||
assert.strictEqual(result[0].id, 1);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read' RPC: more than all available ids", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer("hobbit", fields.hobbit);
|
||||
|
||||
const amount = MAIN_RECORDSET_SIZE + 3;
|
||||
const ids = new Array(amount).fill().map((_, i) => i + 1);
|
||||
const result = await server.mockRpc({
|
||||
method: "read",
|
||||
model: "hobbit",
|
||||
args: [ids, ["display_name"]],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, MAIN_RECORDSET_SIZE);
|
||||
});
|
||||
|
||||
// To be implemented if needed
|
||||
// QUnit.test("Send 'read_progress_bar' RPC", async function (assert) { ... });
|
||||
}
|
||||
);
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Component, useState, xml } from "@odoo/owl";
|
||||
import { ViewScaleSelector } from "@web/views/view_components/view_scale_selector";
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import { click, getFixture, mount } from "@web/../tests/helpers/utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
|
||||
import { uiService } from "@web/core/ui/ui_service";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
let target;
|
||||
|
||||
QUnit.module("ViewComponents", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
target = getFixture();
|
||||
serviceRegistry.add("ui", uiService);
|
||||
serviceRegistry.add("hotkey", hotkeyService);
|
||||
});
|
||||
|
||||
QUnit.module("ViewScaleSelector");
|
||||
|
||||
QUnit.test("basic ViewScaleSelector component usage", async (assert) => {
|
||||
const env = await makeTestEnv();
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { ViewScaleSelector };
|
||||
static template = xml`<ViewScaleSelector t-props="compProps" />`;
|
||||
setup() {
|
||||
this.state = useState({
|
||||
scale: "week",
|
||||
});
|
||||
}
|
||||
get compProps() {
|
||||
return {
|
||||
scales: {
|
||||
day: {
|
||||
description: "Daily",
|
||||
},
|
||||
week: {
|
||||
description: "Weekly",
|
||||
hotkey: "o",
|
||||
},
|
||||
year: {
|
||||
description: "Yearly",
|
||||
},
|
||||
},
|
||||
isWeekendVisible: true,
|
||||
setScale: (scale) => {
|
||||
this.state.scale = scale;
|
||||
assert.step(scale);
|
||||
},
|
||||
toggleWeekendVisibility: () => {
|
||||
assert.step("toggleWeekendVisibility");
|
||||
},
|
||||
currentScale: this.state.scale,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Parent, target, { env });
|
||||
assert.containsOnce(target, ".o_view_scale_selector");
|
||||
assert.verifySteps([]);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_view_scale_selector").textContent,
|
||||
"Weekly",
|
||||
"toggler displays the right text"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".scale_button_selection").dataset.hotkey,
|
||||
"v",
|
||||
"toggler has the right hotkey"
|
||||
);
|
||||
|
||||
await click(target, ".scale_button_selection");
|
||||
assert.containsOnce(target, ".o_view_scale_selector .dropdown-menu", "a dropdown appeared");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_view_scale_selector .dropdown-menu .active").textContent,
|
||||
"Weekly",
|
||||
"the active option is selected"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_view_scale_selector .dropdown-menu span:nth-child(2)").dataset
|
||||
.hotkey,
|
||||
"o",
|
||||
"'week' scale has the right hotkey"
|
||||
);
|
||||
|
||||
await click(target, ".o_scale_button_day");
|
||||
assert.verifySteps(["day"]);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_view_scale_selector").textContent,
|
||||
"Daily",
|
||||
"the right text is displayed on the toggler"
|
||||
);
|
||||
|
||||
await click(target, ".scale_button_selection");
|
||||
await click(target, ".dropdown-item:last-child");
|
||||
assert.verifySteps(["toggleWeekendVisibility"]);
|
||||
});
|
||||
|
||||
QUnit.test("ViewScaleSelector with only one scale available", async (assert) => {
|
||||
const env = await makeTestEnv();
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { ViewScaleSelector };
|
||||
static template = xml`<ViewScaleSelector t-props="compProps" />`;
|
||||
setup() {
|
||||
this.state = useState({
|
||||
scale: "day",
|
||||
});
|
||||
}
|
||||
get compProps() {
|
||||
return {
|
||||
scales: {
|
||||
day: {
|
||||
description: "Daily",
|
||||
},
|
||||
},
|
||||
setScale: () => {},
|
||||
isWeekendVisible: false,
|
||||
toggleWeekendVisibility: () => {},
|
||||
currentScale: this.state.scale,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Parent, target, { env });
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_view_scale_selector",
|
||||
"toggler is not present as no other option is available"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -20,6 +20,11 @@ import { makeFakeUserService } from "../../helpers/mock_services";
|
|||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
async function exportAllAction(target) {
|
||||
await click(target, ".o_cp_action_menus .dropdown-toggle");
|
||||
await click(target, ".o_cp_action_menus .dropdown-item");
|
||||
}
|
||||
|
||||
QUnit.module("ViewDialogs", (hooks) => {
|
||||
let serverData;
|
||||
let target;
|
||||
|
|
@ -253,12 +258,13 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
mockRPC(route, args) {
|
||||
if (args.method === "create") {
|
||||
assert.strictEqual(args.model, "ir.exports");
|
||||
const [values] = args.args[0];
|
||||
assert.strictEqual(
|
||||
args.args[0].name,
|
||||
values.name,
|
||||
"Export template",
|
||||
"the template name is correctly sent"
|
||||
);
|
||||
return 2;
|
||||
return [2];
|
||||
}
|
||||
if (args.method === "search_read") {
|
||||
assert.deepEqual(
|
||||
|
|
@ -774,6 +780,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
activateElement: () => {},
|
||||
deactivateElement: () => {},
|
||||
size: 4,
|
||||
isSmall: true,
|
||||
};
|
||||
const fakeUIService = {
|
||||
start(env) {
|
||||
|
|
@ -987,7 +994,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
domain: [["bar", "!=", "glou"]],
|
||||
});
|
||||
|
||||
await click(target.querySelector(".o_list_export_xlsx"));
|
||||
await exportAllAction(target);
|
||||
});
|
||||
|
||||
QUnit.test("Direct export grouped list ", async function (assert) {
|
||||
|
|
@ -1039,7 +1046,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
domain: [["bar", "!=", "glou"]],
|
||||
});
|
||||
|
||||
await click(target.querySelector(".o_list_export_xlsx"));
|
||||
await exportAllAction(target);
|
||||
});
|
||||
|
||||
QUnit.test("Export dialog with duplicated fields", async function (assert) {
|
||||
|
|
@ -1087,6 +1094,40 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("Export dialog: no column_invisible fields in default export list", async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="foo"/>
|
||||
<field name="bar" column_invisible="1"/>
|
||||
</tree>`,
|
||||
actionMenus: {},
|
||||
mockRPC(route) {
|
||||
if (route === "/web/export/formats") {
|
||||
return Promise.resolve([{ tag: "csv", label: "CSV" }]);
|
||||
}
|
||||
if (route === "/web/export/get_fields") {
|
||||
return Promise.resolve(fetchedFields.root);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await openExportDataDialog();
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".modal .o_export_field",
|
||||
"there is only one field in export field list."
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".modal .o_export_field").textContent,
|
||||
"Foo",
|
||||
"the field to export corresponds to the visible one in the list view"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Export dialog: export list contains field with 'default_export: true'",
|
||||
async function (assert) {
|
||||
|
|
@ -1291,7 +1332,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
"should have 3 th, 1 for selector, 1 for columns, 1 for optional columns"
|
||||
);
|
||||
|
||||
await click(target.querySelector(".o_list_export_xlsx"));
|
||||
await exportAllAction(target);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import {
|
|||
nextTick,
|
||||
patchWithCleanup,
|
||||
triggerHotkey,
|
||||
makeDeferred,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { contains } from "@web/../tests/utils";
|
||||
import { makeView } from "@web/../tests/views/helpers";
|
||||
import { makeView, makeViewInDialog, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { createWebClient } from "@web/../tests/webclient/helpers";
|
||||
import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog";
|
||||
import { setupControlPanelServiceRegistry } from "@web/../tests/search/helpers";
|
||||
|
||||
QUnit.module("ViewDialogs", (hooks) => {
|
||||
let serverData;
|
||||
|
|
@ -67,7 +67,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
},
|
||||
};
|
||||
target = getFixture();
|
||||
setupControlPanelServiceRegistry();
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("FormViewDialog");
|
||||
|
|
@ -108,11 +108,11 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
"partner,false,form": `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<footer attrs="{'invisible': [('bar','=',False)]}">
|
||||
<footer invisible="not bar">
|
||||
<button>Hello</button>
|
||||
<button>World</button>
|
||||
</footer>
|
||||
<footer attrs="{'invisible': [('bar','!=',False)]}">
|
||||
<footer invisible="bar">
|
||||
<button>Foo</button>
|
||||
</footer>
|
||||
</form>`,
|
||||
|
|
@ -205,15 +205,16 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
</tree>`,
|
||||
};
|
||||
|
||||
await makeView({
|
||||
await makeViewInDialog({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `<form>
|
||||
arch: `
|
||||
<form>
|
||||
<field name="name"/>
|
||||
<field name="instrument" context="{'tree_view_ref': 'some_tree_view'}" open_target="new"/>
|
||||
</form>`,
|
||||
<field name="instrument" context="{'tree_view_ref': 'some_tree_view'}"/>
|
||||
</form>`,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === "get_formview_id") {
|
||||
return Promise.resolve(false);
|
||||
|
|
@ -234,7 +235,6 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
assert.deepEqual(
|
||||
args.kwargs.context,
|
||||
{
|
||||
base_model_name: "instrument",
|
||||
lang: "en",
|
||||
tree_view_ref: "some_other_tree_view",
|
||||
tz: "taht",
|
||||
|
|
@ -276,10 +276,10 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
|
||||
assert.containsOnce(target, ".o_dialog .o_form_view");
|
||||
assert.containsN(target, ".o_dialog .o_form_view button", 2);
|
||||
assert.verifySteps(["/web/webclient/load_menus", "get_views", "read"]);
|
||||
assert.verifySteps(["/web/webclient/load_menus", "get_views", "web_read"]);
|
||||
await click(target.querySelector(".o_dialog .o_form_view .btn1"));
|
||||
assert.containsOnce(target, ".o_dialog .o_form_view");
|
||||
assert.verifySteps(["method1", "read"]); // should re-read the record
|
||||
assert.verifySteps(["method1", "web_read"]); // should re-read the record
|
||||
await click(target.querySelector(".o_dialog .o_form_view .btn2"));
|
||||
assert.containsNone(target, ".o_dialog .o_form_view");
|
||||
assert.verifySteps(["method2"]); // should not read as we closed
|
||||
|
|
@ -299,7 +299,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
};
|
||||
let reject = true;
|
||||
function mockRPC(route, args) {
|
||||
if (args.method === "create" && reject) {
|
||||
if (args.method === "web_save" && reject) {
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
|
@ -343,6 +343,82 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
assert.containsNone(target, ".o_dialog .o_form_view");
|
||||
});
|
||||
|
||||
QUnit.test("Buttons are set as disabled on click", async function (assert) {
|
||||
serverData.views = {
|
||||
"partner,false,form": `
|
||||
<form string="Partner">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
`,
|
||||
};
|
||||
const def = makeDeferred();
|
||||
async function mockRPC(route, args) {
|
||||
if (args.method === "web_save") {
|
||||
await def;
|
||||
}
|
||||
}
|
||||
const webClient = await createWebClient({ serverData, mockRPC });
|
||||
webClient.env.services.dialog.add(FormViewDialog, {
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
await editInput(
|
||||
target.querySelector(".o_dialog .o_content .o_field_char .o_input"),
|
||||
"",
|
||||
"test"
|
||||
);
|
||||
|
||||
await click(target.querySelector(".o_dialog .modal-footer .o_form_button_save"));
|
||||
assert.strictEqual(
|
||||
target
|
||||
.querySelector(".o_dialog .modal-footer .o_form_button_save")
|
||||
.getAttribute("disabled"),
|
||||
"1"
|
||||
);
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
assert.containsNone(target, ".o_dialog .o_form_view");
|
||||
});
|
||||
|
||||
QUnit.test("FormViewDialog with discard button", async function (assert) {
|
||||
serverData.views = {
|
||||
"partner,false,form": `<form><field name="foo"/></form>`,
|
||||
};
|
||||
|
||||
const webClient = await createWebClient({ serverData });
|
||||
webClient.env.services.dialog.add(FormViewDialog, {
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
onRecordDiscarded: () => assert.step("discard"),
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_dialog .o_form_view");
|
||||
assert.containsOnce(target, ".o_dialog .modal-footer .o_form_button_cancel");
|
||||
await click(target.querySelector(".o_dialog .modal-footer .o_form_button_cancel"));
|
||||
assert.verifySteps(["discard"]);
|
||||
assert.containsNone(target, ".o_dialog .o_form_view");
|
||||
|
||||
webClient.env.services.dialog.add(FormViewDialog, {
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
onRecordDiscarded: () => assert.step("discard"),
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_dialog .o_form_view");
|
||||
await click(target.querySelector(".o_dialog .btn-close"));
|
||||
assert.verifySteps(["discard"]);
|
||||
assert.containsNone(target, ".o_dialog .o_form_view");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Save a FormViewDialog when a required field is empty don't close the dialog",
|
||||
async function (assert) {
|
||||
|
|
@ -378,48 +454,56 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
}
|
||||
);
|
||||
|
||||
QUnit.test("display a dialog if onchange result is a warning from within a dialog", async function (assert) {
|
||||
serverData.views = {
|
||||
"instrument,false,form": `<form><field name="display_name" /></form>`
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `<form><field name="instrument"/></form>`,
|
||||
resId: 2,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange" && args.model === "instrument") {
|
||||
assert.step("onchange warning")
|
||||
return Promise.resolve({
|
||||
warning: {
|
||||
title: "Warning",
|
||||
message: "You must first select a partner",
|
||||
type: "dialog",
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
QUnit.test(
|
||||
"display a dialog if onchange result is a warning from within a dialog",
|
||||
async function (assert) {
|
||||
serverData.views = {
|
||||
"instrument,false,form": `<form><field name="name" /></form>`,
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `<form><field name="instrument"/></form>`,
|
||||
resId: 2,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange" && args.model === "instrument") {
|
||||
assert.step("onchange warning");
|
||||
return Promise.resolve({
|
||||
value: {
|
||||
name: false,
|
||||
},
|
||||
warning: {
|
||||
title: "Warning",
|
||||
message: "You must first select a partner",
|
||||
type: "dialog",
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await editInput(target, ".o_field_widget[name=instrument] input", "tralala");
|
||||
await contains(".o_m2o_dropdown_option_create_edit a");
|
||||
await editInput(target, ".o_field_widget[name=instrument] input", "tralala");
|
||||
await contains(".o_m2o_dropdown_option_create_edit a");
|
||||
|
||||
await click(target.querySelector(".o_m2o_dropdown_option_create_edit a"));
|
||||
await contains(".modal.o_inactive_modal");
|
||||
assert.containsN(document.body, ".modal", 2);
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".modal:not(.o_inactive_modal) .modal-body").textContent,
|
||||
"You must first select a partner"
|
||||
);
|
||||
await click(target.querySelector(".o_m2o_dropdown_option_create_edit a"));
|
||||
await contains(".modal.o_inactive_modal");
|
||||
assert.containsN(document.body, ".modal", 2);
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".modal:not(.o_inactive_modal) .modal-body")
|
||||
.textContent,
|
||||
"You must first select a partner"
|
||||
);
|
||||
|
||||
await click(document.body.querySelector(".modal:not(.o_inactive_modal) button"))
|
||||
assert.containsOnce(target, ".modal");
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".modal:not(.o_inactive_modal) .modal-title").textContent,
|
||||
"Create Instruments"
|
||||
);
|
||||
await click(document.body.querySelector(".modal:not(.o_inactive_modal) button"));
|
||||
assert.containsOnce(target, ".modal");
|
||||
assert.strictEqual(
|
||||
document.body.querySelector(".modal:not(.o_inactive_modal) .modal-title")
|
||||
.textContent,
|
||||
"Create Instruments"
|
||||
);
|
||||
|
||||
assert.verifySteps(["onchange warning"])
|
||||
});
|
||||
assert.verifySteps(["onchange warning"]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ import {
|
|||
editFavoriteName,
|
||||
removeFacet,
|
||||
saveFavorite,
|
||||
toggleFavoriteMenu,
|
||||
toggleFilterMenu,
|
||||
toggleSearchBarMenu,
|
||||
toggleMenuItem,
|
||||
toggleSaveFavorite,
|
||||
} from "@web/../tests/search/helpers";
|
||||
|
|
@ -125,9 +124,6 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
fields: ["display_name", "foo", "bar"],
|
||||
groupby: ["bar"],
|
||||
orderby: "",
|
||||
expand: false,
|
||||
expand_orderby: null,
|
||||
expand_limit: null,
|
||||
lazy: true,
|
||||
limit: 80,
|
||||
offset: 0,
|
||||
|
|
@ -152,7 +148,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
["display_name", "ilike", "piou"],
|
||||
["foo", "ilike", "piou"],
|
||||
],
|
||||
fields: ["display_name", "foo"],
|
||||
specification: { display_name: {}, foo: {} },
|
||||
limit: 80,
|
||||
offset: 0,
|
||||
order: "",
|
||||
|
|
@ -171,7 +167,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
uid: 7,
|
||||
}, // not part of the test, may change
|
||||
domain: [["display_name", "like", "a"]],
|
||||
fields: ["display_name", "foo"],
|
||||
specification: { display_name: {}, foo: {} },
|
||||
limit: 80,
|
||||
offset: 0,
|
||||
order: "",
|
||||
|
|
@ -228,7 +224,6 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
const webClient = await createWebClient({ serverData, mockRPC });
|
||||
webClient.env.services.dialog.add(SelectCreateDialog, {
|
||||
noCreate: true,
|
||||
readonly: true, //Not used
|
||||
resModel: "partner",
|
||||
domain: [["id", "=", session.user_context.uid]],
|
||||
});
|
||||
|
|
@ -315,13 +310,13 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
if (route === "/web/dataset/call_kw/instrument/get_formview_id") {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
if (route === "/web/dataset/call_kw/instrument/create") {
|
||||
if (route === "/web/dataset/call_kw/instrument/web_save") {
|
||||
assert.deepEqual(
|
||||
args.args,
|
||||
[{ badassery: [[6, false, [1]]], name: "ABC" }],
|
||||
args.args[1],
|
||||
{ badassery: [[4, 1]], name: "ABC" },
|
||||
"The method create should have been called with the right arguments"
|
||||
);
|
||||
return Promise.resolve(false);
|
||||
return [{ id: 90 }];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -365,7 +360,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
|
||||
patchWithCleanup(listView.Controller.prototype, {
|
||||
setup() {
|
||||
this._super(...arguments);
|
||||
super.setup(...arguments);
|
||||
useSetupAction({
|
||||
getContext: () => ({ shouldBeInFilterContext: true }),
|
||||
});
|
||||
|
|
@ -407,13 +402,12 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
assert.containsN(target, ".o_data_row", 3, "should contain 3 records");
|
||||
|
||||
// filter on bar
|
||||
await toggleFilterMenu(target);
|
||||
await toggleSearchBarMenu(target);
|
||||
await toggleMenuItem(target, "Bar");
|
||||
|
||||
assert.containsN(target, ".o_data_row", 2, "should contain 2 records");
|
||||
|
||||
// save filter
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
await editFavoriteName(target, "some name");
|
||||
await saveFavorite(target);
|
||||
|
|
@ -486,6 +480,27 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
}
|
||||
);
|
||||
|
||||
QUnit.test("SelectCreateDialog: multiple clicks on record", async function (assert) {
|
||||
serverData.views = {
|
||||
"partner,false,list": `<tree><field name="display_name"/></tree>`,
|
||||
"partner,false,search": `<search><field name="foo"/></search>`,
|
||||
};
|
||||
const webClient = await createWebClient({ serverData });
|
||||
webClient.env.services.dialog.add(SelectCreateDialog, {
|
||||
resModel: "partner",
|
||||
onSelected: async function (records) {
|
||||
assert.step(`select record ${records[0]}`);
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
click(target.querySelector(".modal .o_data_row .o_data_cell"));
|
||||
click(target.querySelector(".modal .o_data_row .o_data_cell"));
|
||||
click(target.querySelector(".modal .o_data_row .o_data_cell"));
|
||||
await nextTick();
|
||||
assert.verifySteps(["select record 1"], "should have called onSelected only once");
|
||||
});
|
||||
|
||||
QUnit.test("SelectCreateDialog: default props, create a record", async function (assert) {
|
||||
serverData.views = {
|
||||
"partner,false,list": `<tree><field name="display_name"/></tree>`,
|
||||
|
|
@ -510,6 +525,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
|||
assert.containsOnce(target, ".o_dialog footer button.o_select_button");
|
||||
assert.containsOnce(target, ".o_dialog footer button.o_create_button");
|
||||
assert.containsOnce(target, ".o_dialog footer button.o_form_button_cancel");
|
||||
assert.containsNone(target, ".o_dialog .o_control_panel_main_buttons .o_list_button_add");
|
||||
|
||||
await click(target.querySelector(".o_dialog footer button.o_create_button"));
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ QUnit.module("View service", (hooks) => {
|
|||
fields: {},
|
||||
records: [],
|
||||
},
|
||||
"ir.ui.view": {
|
||||
fields: {},
|
||||
records: [],
|
||||
},
|
||||
};
|
||||
|
||||
const fakeUiService = {
|
||||
|
|
@ -109,76 +113,34 @@ QUnit.module("View service", (hooks) => {
|
|||
assert.verifySteps(["get_views", "get_views"]);
|
||||
});
|
||||
|
||||
QUnit.test("loadViews stores fields in cache", async (assert) => {
|
||||
assert.expect(2);
|
||||
|
||||
QUnit.test("clear cache when updating ir.ui.view", async (assert) => {
|
||||
const mockRPC = (route, args) => {
|
||||
if (route.includes("get_views")) {
|
||||
assert.step("get_views");
|
||||
}
|
||||
if (route.includes("fields_get")) {
|
||||
assert.step("fields_get");
|
||||
}
|
||||
};
|
||||
const loadView = () =>
|
||||
env.services.views.loadViews(
|
||||
{
|
||||
resModel: "take.five",
|
||||
views: [[99, "list"]],
|
||||
context: { default_field_value: 1 },
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
await makeMockServer(serverData, mockRPC);
|
||||
const env = await makeTestEnv();
|
||||
|
||||
await env.services.views.loadViews(
|
||||
{
|
||||
resModel: "take.five",
|
||||
views: [[99, "list"]],
|
||||
context: { default_field_value: 1 },
|
||||
},
|
||||
{}
|
||||
);
|
||||
await env.services.views.loadFields("take.five");
|
||||
|
||||
await loadView();
|
||||
assert.verifySteps(["get_views"]);
|
||||
});
|
||||
|
||||
QUnit.test("store loadFields calls in cache in success", async (assert) => {
|
||||
assert.expect(2);
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (route.includes("fields_get")) {
|
||||
assert.step("fields_get");
|
||||
}
|
||||
};
|
||||
|
||||
await makeMockServer(serverData, mockRPC);
|
||||
const env = await makeTestEnv();
|
||||
|
||||
await env.services.views.loadFields("take.five");
|
||||
await env.services.views.loadFields("take.five");
|
||||
|
||||
assert.verifySteps(["fields_get"]);
|
||||
});
|
||||
|
||||
QUnit.test("store loadFields calls in cache when failed", async (assert) => {
|
||||
assert.expect(5);
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (route.includes("fields_get")) {
|
||||
assert.step("fields_get");
|
||||
return Promise.reject("my little error");
|
||||
}
|
||||
};
|
||||
|
||||
await makeMockServer(serverData, mockRPC);
|
||||
const env = await makeTestEnv();
|
||||
|
||||
try {
|
||||
await env.services.views.loadFields("take.five");
|
||||
} catch (error) {
|
||||
assert.strictEqual(error, "my little error");
|
||||
}
|
||||
try {
|
||||
await env.services.views.loadFields("take.five");
|
||||
} catch (error) {
|
||||
assert.strictEqual(error, "my little error");
|
||||
}
|
||||
|
||||
assert.verifySteps(["fields_get", "fields_get"]);
|
||||
await loadView();
|
||||
assert.verifySteps([]); // cache works => no actual rpc
|
||||
await env.services.orm.unlink("ir.ui.view", [3]);
|
||||
await loadView();
|
||||
assert.verifySteps(["get_views"]); // cache was invalidated
|
||||
await env.services.orm.unlink("take.five", [3]);
|
||||
await loadView();
|
||||
assert.verifySteps([]); // cache was not invalidated
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
mount,
|
||||
nextTick,
|
||||
patchWithCleanup,
|
||||
mockTimeout,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
|
@ -16,6 +17,7 @@ import { View } from "@web/views/view";
|
|||
import { actionService } from "@web/webclient/actions/action_service";
|
||||
|
||||
import { Component, onWillStart, onWillUpdateProps, useState, xml } from "@odoo/owl";
|
||||
import { CallbackRecorder } from "@web/webclient/actions/action_hook";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
const viewRegistry = registry.category("views");
|
||||
|
|
@ -74,7 +76,7 @@ QUnit.module("Views", (hooks) => {
|
|||
class ToyController extends Component {
|
||||
setup() {
|
||||
this.class = "toy";
|
||||
this.template = xml`${this.props.arch}`;
|
||||
this.template = xml`${this.props.arch.outerHTML}`;
|
||||
}
|
||||
}
|
||||
ToyController.template = xml`<div t-attf-class="{{class}} {{props.className}}"><t t-call="{{ template }}"/></div>`;
|
||||
|
|
@ -121,9 +123,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
const { arch, fields, info } = this.props;
|
||||
assert.strictEqual(arch, serverData.views["animal,false,toy"]);
|
||||
assert.strictEqual(arch.outerHTML, serverData.views["animal,false,toy"]);
|
||||
assert.deepEqual(fields, {});
|
||||
assert.strictEqual(info.actionMenus, undefined);
|
||||
assert.strictEqual(this.env.config.viewId, false);
|
||||
|
|
@ -159,9 +161,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
const { arch, fields, info } = this.props;
|
||||
assert.strictEqual(arch, serverData.views["animal,1,toy"]);
|
||||
assert.strictEqual(arch.outerHTML, serverData.views["animal,1,toy"]);
|
||||
assert.deepEqual(fields, {});
|
||||
assert.strictEqual(info.actionMenus, undefined);
|
||||
assert.strictEqual(this.env.config.viewId, 1);
|
||||
|
|
@ -196,9 +198,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
const { arch, fields, info } = this.props;
|
||||
assert.strictEqual(arch, serverData.views["animal,1,toy"]);
|
||||
assert.strictEqual(arch.outerHTML, serverData.views["animal,1,toy"]);
|
||||
assert.deepEqual(fields, {});
|
||||
assert.strictEqual(info.actionMenus, undefined);
|
||||
assert.strictEqual(this.env.config.viewId, 1);
|
||||
|
|
@ -237,9 +239,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
const { arch, fields, info } = this.props;
|
||||
assert.strictEqual(arch, serverData.views["animal,false,toy"]);
|
||||
assert.strictEqual(arch.outerHTML, serverData.views["animal,false,toy"]);
|
||||
assert.deepEqual(fields, {});
|
||||
assert.strictEqual(info.actionMenus, undefined);
|
||||
assert.strictEqual(this.env.config.viewId, false);
|
||||
|
|
@ -280,9 +282,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
const { arch, fields, info } = this.props;
|
||||
assert.strictEqual(arch, serverData.views["animal,1,toy"]);
|
||||
assert.strictEqual(arch.outerHTML, serverData.views["animal,1,toy"]);
|
||||
assert.deepEqual(fields, {});
|
||||
assert.strictEqual(info.actionMenus, undefined);
|
||||
assert.strictEqual(this.env.config.viewId, 1);
|
||||
|
|
@ -326,9 +328,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
const { arch, fields, info } = this.props;
|
||||
assert.strictEqual(arch, `<toy>Specific arch content</toy>`);
|
||||
assert.strictEqual(arch.outerHTML, `<toy>Specific arch content</toy>`);
|
||||
assert.deepEqual(fields, {});
|
||||
assert.strictEqual(info.actionMenus, undefined);
|
||||
assert.strictEqual(this.env.config.viewId, undefined);
|
||||
|
|
@ -359,9 +361,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
const { arch, fields, info } = this.props;
|
||||
assert.strictEqual(arch, serverData.views["animal,false,toy"]);
|
||||
assert.strictEqual(arch.outerHTML, serverData.views["animal,false,toy"]);
|
||||
assert.deepEqual(fields, {});
|
||||
assert.deepEqual(info.actionMenus, {});
|
||||
assert.strictEqual(this.env.config.viewId, false);
|
||||
|
|
@ -399,9 +401,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
const { arch, fields, info } = this.props;
|
||||
assert.strictEqual(arch, `<toy>Specific arch content</toy>`);
|
||||
assert.strictEqual(arch.outerHTML, `<toy>Specific arch content</toy>`);
|
||||
assert.deepEqual(fields, {});
|
||||
assert.deepEqual(info.actionMenus, {});
|
||||
assert.strictEqual(this.env.config.viewId, false);
|
||||
|
|
@ -442,9 +444,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
const { arch, fields, info } = this.props;
|
||||
assert.strictEqual(arch, `<toy>Specific arch content</toy>`);
|
||||
assert.strictEqual(arch.outerHTML, `<toy>Specific arch content</toy>`);
|
||||
assert.deepEqual(fields, {});
|
||||
assert.deepEqual(info.actionMenus, {});
|
||||
assert.strictEqual(this.env.config.viewId, undefined);
|
||||
|
|
@ -478,13 +480,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
const {
|
||||
irFilters,
|
||||
searchViewArch,
|
||||
searchViewFields,
|
||||
searchViewId,
|
||||
} = this.props.info;
|
||||
super.setup();
|
||||
const { irFilters, searchViewArch, searchViewFields, searchViewId } =
|
||||
this.props.info;
|
||||
assert.strictEqual(searchViewArch, serverData.views["animal,false,search"]);
|
||||
assert.deepEqual(searchViewFields, serverData.models.animal.fields);
|
||||
assert.strictEqual(searchViewId, false);
|
||||
|
|
@ -526,13 +524,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
const {
|
||||
irFilters,
|
||||
searchViewArch,
|
||||
searchViewFields,
|
||||
searchViewId,
|
||||
} = this.props.info;
|
||||
super.setup();
|
||||
const { irFilters, searchViewArch, searchViewFields, searchViewId } =
|
||||
this.props.info;
|
||||
assert.strictEqual(searchViewArch, `<search/>`);
|
||||
assert.deepEqual(searchViewFields, {});
|
||||
assert.strictEqual(searchViewId, false);
|
||||
|
|
@ -570,13 +564,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
const {
|
||||
irFilters,
|
||||
searchViewArch,
|
||||
searchViewFields,
|
||||
searchViewId,
|
||||
} = this.props.info;
|
||||
super.setup();
|
||||
const { irFilters, searchViewArch, searchViewFields, searchViewId } =
|
||||
this.props.info;
|
||||
assert.strictEqual(searchViewArch, `<search/>`);
|
||||
assert.deepEqual(searchViewFields, {});
|
||||
assert.strictEqual(searchViewId, undefined);
|
||||
|
|
@ -613,13 +603,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
const {
|
||||
irFilters,
|
||||
searchViewArch,
|
||||
searchViewFields,
|
||||
searchViewId,
|
||||
} = this.props.info;
|
||||
super.setup();
|
||||
const { irFilters, searchViewArch, searchViewFields, searchViewId } =
|
||||
this.props.info;
|
||||
assert.strictEqual(searchViewArch, `<search/>`);
|
||||
assert.deepEqual(searchViewFields, {});
|
||||
assert.strictEqual(searchViewId, false);
|
||||
|
|
@ -678,13 +664,9 @@ QUnit.module("Views", (hooks) => {
|
|||
const ToyController = viewRegistry.get("toy").Controller;
|
||||
patchWithCleanup(ToyController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
const {
|
||||
irFilters,
|
||||
searchViewArch,
|
||||
searchViewFields,
|
||||
searchViewId,
|
||||
} = this.props.info;
|
||||
super.setup();
|
||||
const { irFilters, searchViewArch, searchViewFields, searchViewId } =
|
||||
this.props.info;
|
||||
assert.strictEqual(searchViewArch, `<search/>`);
|
||||
assert.deepEqual(searchViewFields, {});
|
||||
assert.strictEqual(searchViewId, undefined);
|
||||
|
|
@ -1126,7 +1108,7 @@ QUnit.module("Views", (hooks) => {
|
|||
});
|
||||
|
||||
QUnit.test("real life banner", async (assert) => {
|
||||
assert.expect(10);
|
||||
assert.expect(8);
|
||||
|
||||
serverData.views["animal,1,toy"] = `
|
||||
<toy banner_route="/mybody/isacage">
|
||||
|
|
@ -1151,14 +1133,12 @@ QUnit.module("Views", (hooks) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_onboarding_container collapse show">
|
||||
<div class="o_onboarding" />
|
||||
<div class="o_onboarding_wrap" />
|
||||
<a href="#" data-bs-toggle="modal" data-bs-target=".o_onboarding_modal" class="float-end o_onboarding_btn_close">
|
||||
<i class="fa fa-times" title="Close the onboarding panel" id="closeOnboarding"></i>
|
||||
</a>
|
||||
<div class="bannerContent">Content</div>
|
||||
</div>
|
||||
<div class="o_onboarding" />
|
||||
<div class="o_onboarding_wrap" />
|
||||
<a href="#" data-bs-toggle="modal" data-bs-target=".o_onboarding_modal" class="float-end o_onboarding_btn_close">
|
||||
<i class="fa fa-times" title="Close the onboarding panel" id="closeOnboarding"></i>
|
||||
</a>
|
||||
<div class="bannerContent">Content</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
|
@ -1172,6 +1152,7 @@ QUnit.module("Views", (hooks) => {
|
|||
return true;
|
||||
}
|
||||
};
|
||||
const { execRegisteredTimeouts } = mockTimeout();
|
||||
const config = {
|
||||
views: [[1, "toy"]],
|
||||
};
|
||||
|
|
@ -1182,31 +1163,17 @@ QUnit.module("Views", (hooks) => {
|
|||
};
|
||||
await mount(View, target, { env, props });
|
||||
|
||||
const prom = new Promise((resolve) => {
|
||||
const complete = (ev) => {
|
||||
if (ev.target.classList.contains("o_onboarding_container")) {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
// We need to handle both events, because the transition is not
|
||||
// always executed
|
||||
target.addEventListener("transitionend", complete);
|
||||
target.addEventListener("transitioncancel", complete);
|
||||
});
|
||||
|
||||
assert.verifySteps(["/mybody/isacage"]);
|
||||
assert.isNotVisible(target.querySelector(".modal"));
|
||||
assert.hasClass(target.querySelector(".o_onboarding_container"), "collapse show");
|
||||
assert.hasClass(target.querySelector(".o_onboarding_container"), "o-vertical-slide");
|
||||
|
||||
await click(target.querySelector("#closeOnboarding"));
|
||||
assert.isVisible(target.querySelector(".modal"));
|
||||
|
||||
await click(target.querySelector(".modal a[type='action']"));
|
||||
assert.verifySteps(["mah_method"]);
|
||||
await prom;
|
||||
assert.doesNotHaveClass(target.querySelector(".o_onboarding_container"), "show");
|
||||
assert.hasClass(target.querySelector(".o_onboarding_container"), "collapse");
|
||||
assert.isNotVisible(target.querySelector(".modal"));
|
||||
execRegisteredTimeouts();
|
||||
assert.containsNone(target, ".o_onboarding_container");
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -1425,7 +1392,7 @@ QUnit.module("Views", (hooks) => {
|
|||
});
|
||||
assert.deepEqual(domain, [[0, "=", 1]]);
|
||||
assert.deepEqual(groupBy, ["birthday"]);
|
||||
assert.deepEqual(orderBy, [{name: "bar", asc: true}]);
|
||||
assert.deepEqual(orderBy, [{ name: "bar", asc: true }]);
|
||||
}
|
||||
}
|
||||
ToyController.template = xml`<div/>`;
|
||||
|
|
@ -1439,7 +1406,7 @@ QUnit.module("Views", (hooks) => {
|
|||
domain: [[0, "=", 1]],
|
||||
groupBy: ["birthday"],
|
||||
context: { key: "val" },
|
||||
orderBy: [{name: "bar", asc: true}],
|
||||
orderBy: [{ name: "bar", asc: true }],
|
||||
};
|
||||
await mount(View, target, { env, props });
|
||||
}
|
||||
|
|
@ -1593,7 +1560,7 @@ QUnit.module("Views", (hooks) => {
|
|||
});
|
||||
assert.deepEqual(domain, ["&", [0, "=", 1], [1, "=", 1]]);
|
||||
assert.deepEqual(groupBy, ["name"]);
|
||||
assert.deepEqual(orderBy, [{name: "bar", asc: true}]);
|
||||
assert.deepEqual(orderBy, [{ name: "bar", asc: true }]);
|
||||
}
|
||||
}
|
||||
ToyController.template = xml`<div/>`;
|
||||
|
|
@ -1607,12 +1574,60 @@ QUnit.module("Views", (hooks) => {
|
|||
domain: [[0, "=", 1]],
|
||||
groupBy: ["birthday"],
|
||||
context: { search_default_filter: 1, search_default_group_by: 1 },
|
||||
orderBy: [{name: "bar", asc: true}],
|
||||
orderBy: [{ name: "bar", asc: true }],
|
||||
};
|
||||
await mount(View, target, { env, props });
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("multiple ways to pass classes for styling", async (assert) => {
|
||||
const env = await makeTestEnv({ serverData });
|
||||
const props = {
|
||||
resModel: "animal",
|
||||
type: "toy",
|
||||
className: "o_custom_class_from_props_1 o_custom_class_from_props_2",
|
||||
arch: `
|
||||
<toy
|
||||
js_class="toy_imp"
|
||||
class="o_custom_class_from_arch_1 o_custom_class_from_arch_2"
|
||||
/>
|
||||
`,
|
||||
fields: {},
|
||||
};
|
||||
await mount(View, target, { env, props });
|
||||
const view = target.querySelector(".o_toy_view");
|
||||
assert.hasClass(view, "o_toy_imp_view", "should have the class from js_class attribute");
|
||||
assert.hasClass(view, "o_custom_class_from_props_1", "should have the class from props");
|
||||
assert.hasClass(view, "o_custom_class_from_props_2", "should have the class from props");
|
||||
assert.hasClass(view, "o_custom_class_from_arch_1", "should have the class from arch");
|
||||
assert.hasClass(view, "o_custom_class_from_arch_2", "should have the class from arch");
|
||||
});
|
||||
|
||||
QUnit.test("callback recorders are moved from props to subenv", async (assert) => {
|
||||
assert.expect(5);
|
||||
class ToyController extends Component {
|
||||
setup() {
|
||||
assert.ok(this.env.__getGlobalState__ instanceof CallbackRecorder); // put in env by View
|
||||
assert.ok(this.env.__getContext__ instanceof CallbackRecorder); // put in env by View
|
||||
|
||||
assert.strictEqual(this.env.__getLocalState__, null); // set by View
|
||||
assert.strictEqual(this.env.__beforeLeave__, null); // set by View
|
||||
|
||||
assert.ok(this.env.__getOrderBy__ instanceof CallbackRecorder); // put in env by WithSearch
|
||||
}
|
||||
}
|
||||
ToyController.template = xml`<div/>`;
|
||||
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||
const env = await makeTestEnv({ serverData });
|
||||
const props = {
|
||||
type: "toy",
|
||||
resModel: "animal",
|
||||
__getGlobalState__: new CallbackRecorder(),
|
||||
__getContext__: new CallbackRecorder(),
|
||||
};
|
||||
await mount(View, target, { env, props });
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// update props
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ QUnit.module("Widgets", (hooks) => {
|
|||
let fileInput;
|
||||
patchWithCleanup(AttachDocumentWidget.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
fileInput = this.fileInput;
|
||||
},
|
||||
});
|
||||
|
|
@ -75,10 +75,10 @@ QUnit.module("Widgets", (hooks) => {
|
|||
assert.deepEqual(args.kwargs.attachment_ids, [5, 2]);
|
||||
return true;
|
||||
}
|
||||
if (args.method === "write") {
|
||||
if (args.method === "web_save") {
|
||||
assert.deepEqual(args.args[1], { display_name: "yop" });
|
||||
}
|
||||
if (args.method === "read") {
|
||||
if (args.method === "web_read") {
|
||||
assert.deepEqual(args.args[0], [1]);
|
||||
}
|
||||
},
|
||||
|
|
@ -88,13 +88,13 @@ QUnit.module("Widgets", (hooks) => {
|
|||
<field name="display_name" required="1"/>
|
||||
</form>`,
|
||||
});
|
||||
assert.verifySteps(["get_views", "read"]);
|
||||
assert.verifySteps(["get_views", "web_read"]);
|
||||
|
||||
await editInput(target, "[name='display_name'] input", "yop");
|
||||
await click(target, ".o_attach_document");
|
||||
fileInput.dispatchEvent(new Event("change"));
|
||||
await nextTick();
|
||||
assert.verifySteps(["write", "read", "post", "my_action", "read"]);
|
||||
assert.verifySteps(["web_save", "post", "my_action", "web_read"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
|
|
@ -103,7 +103,7 @@ QUnit.module("Widgets", (hooks) => {
|
|||
let fileInput;
|
||||
patchWithCleanup(AttachDocumentWidget.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
super.setup();
|
||||
fileInput = this.fileInput;
|
||||
},
|
||||
});
|
||||
|
|
@ -132,10 +132,10 @@ QUnit.module("Widgets", (hooks) => {
|
|||
assert.deepEqual(args.kwargs.attachment_ids, [5, 2]);
|
||||
return true;
|
||||
}
|
||||
if (args.method === "create") {
|
||||
assert.deepEqual(args.args[0], { display_name: "yop" });
|
||||
if (args.method === "web_save") {
|
||||
assert.deepEqual(args.args[1], { display_name: "yop" });
|
||||
}
|
||||
if (args.method === "read") {
|
||||
if (args.method === "web_read") {
|
||||
assert.deepEqual(args.args[0], [2]);
|
||||
}
|
||||
},
|
||||
|
|
@ -151,7 +151,7 @@ QUnit.module("Widgets", (hooks) => {
|
|||
await click(target, ".o_attach_document");
|
||||
fileInput.dispatchEvent(new Event("change"));
|
||||
await nextTick();
|
||||
assert.verifySteps(["create", "read", "post", "my_action", "read"]);
|
||||
assert.verifySteps(["web_save", "post", "my_action", "web_read"]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
/** @odoo-module **/
|
||||
import { getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
||||
QUnit.module("Widgets", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
bar: { string: "Bar", type: "boolean" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("DocumentationLink");
|
||||
|
||||
QUnit.test("documentation_link: relative path", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="/applications/technical/web/settings/this_is_a_test.html"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.hasAttrValue(
|
||||
target.querySelector(".o_doc_link"),
|
||||
"href",
|
||||
"https://www.odoo.com/documentation/1.0/applications/technical/web/settings/this_is_a_test.html"
|
||||
);
|
||||
});
|
||||
QUnit.test("documentation_link: absoluth path (http)", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="http://www.odoo.com/"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.hasAttrValue(target.querySelector(".o_doc_link"), "href", "http://www.odoo.com/");
|
||||
});
|
||||
QUnit.test("documentation_link: absoluth path (https)", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="https://www.odoo.com/"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.hasAttrValue(target.querySelector(".o_doc_link"), "href", "https://www.odoo.com/");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
const viewData = {
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData: {
|
||||
models: {
|
||||
partner: {
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
resId: 1,
|
||||
arch: `<form><widget name="notification_alert"/></form>`,
|
||||
};
|
||||
|
||||
QUnit.module("Widgets", (hooks) => {
|
||||
hooks.beforeEach(setupViewRegistries);
|
||||
|
||||
QUnit.module("NotificationAlert");
|
||||
|
||||
QUnit.test(
|
||||
"notification alert should be displayed when notification denied",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
patchWithCleanup(browser, { Notification: { permission: "denied" } });
|
||||
await makeView(viewData);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".o_widget_notification_alert .alert",
|
||||
"notification alert should be displayed when notification denied"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"notification alert should not be displayed when notification granted",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
patchWithCleanup(browser, { Notification: { permission: "granted" } });
|
||||
await makeView(viewData);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".o_widget_notification_alert .alert",
|
||||
"notification alert should not be displayed when notification granted"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"notification alert should not be displayed when notification default",
|
||||
async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
patchWithCleanup(browser, { Notification: { permission: "default" } });
|
||||
await makeView(viewData);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
".o_widget_notification_alert .alert",
|
||||
"notification alert should not be displayed when notification default"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -19,7 +19,6 @@ QUnit.module("Widgets", (hooks) => {
|
|||
type: "many2one",
|
||||
relation: "product",
|
||||
},
|
||||
__last_update: { type: "datetime" },
|
||||
sign: { string: "Signature", type: "binary" },
|
||||
},
|
||||
records: [
|
||||
|
|
@ -55,7 +54,7 @@ QUnit.module("Widgets", (hooks) => {
|
|||
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
this._super.apply(this, arguments);
|
||||
super.setup(...arguments);
|
||||
assert.strictEqual(this.props.signature.name, "");
|
||||
},
|
||||
});
|
||||
|
|
@ -97,7 +96,7 @@ QUnit.module("Widgets", (hooks) => {
|
|||
QUnit.test("Signature widget: full_name option", async function (assert) {
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
this._super.apply(this, arguments);
|
||||
super.setup(...arguments);
|
||||
assert.step(this.props.signature.name);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ QUnit.module("Widgets", ({ beforeEach }) => {
|
|||
</form>
|
||||
`,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "write") {
|
||||
if (method === "web_save") {
|
||||
writeCall++;
|
||||
if (writeCall === 1) {
|
||||
assert.ok(args[1].sun, "value of sunday should be true");
|
||||
|
|
@ -92,7 +92,7 @@ QUnit.module("Widgets", ({ beforeEach }) => {
|
|||
assert.containsNone(
|
||||
fixture,
|
||||
".form-check input:disabled",
|
||||
"all inputs should be enabled in readonly mode"
|
||||
"all inputs should be enabled in edit mode"
|
||||
);
|
||||
|
||||
await click(fixture.querySelector("td:nth-child(7) input"));
|
||||
|
|
@ -136,6 +136,42 @@ QUnit.module("Widgets", ({ beforeEach }) => {
|
|||
);
|
||||
});
|
||||
|
||||
QUnit.test("week recurrence widget readonly modifiers", async (assert) => {
|
||||
registry.category("services", makeFakeLocalizationService({ weekStart: 1 }));
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<widget name="week_days" readonly="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
const labelsTexts = [...fixture.querySelectorAll(".o_recurrent_weekday_label")].map((el) =>
|
||||
el.innerText.trim()
|
||||
);
|
||||
assert.deepEqual(
|
||||
labelsTexts,
|
||||
["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||
"labels should be short week names"
|
||||
);
|
||||
|
||||
assert.containsN(
|
||||
fixture,
|
||||
".form-check input:disabled",
|
||||
7,
|
||||
"all inputs should be disabled in readonly mode"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"week recurrence widget show week start as per language configuration",
|
||||
async (assert) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue