mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 09:52:02 +02:00
vanilla 17.0
This commit is contained in:
parent
d72e748793
commit
a9bcec8e91
1986 changed files with 1613876 additions and 568976 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue