mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 03:11:58 +02:00
vanilla 19.0
This commit is contained in:
parent
991d2234ca
commit
d1963a3c3a
3066 changed files with 1651266 additions and 922560 deletions
|
|
@ -0,0 +1,142 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { click, manuallyDispatchProgrammaticEvent } from "@odoo/hoot-dom";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
mockService,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { AttachDocumentWidget } from "@web/views/widgets/attach_document/attach_document";
|
||||
|
||||
class Partner extends models.Model {
|
||||
display_name = fields.Char({ string: "Displayed name" });
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
display_name: "first record",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([Partner]);
|
||||
|
||||
test("attach document widget calls action with attachment ids", async () => {
|
||||
// FIXME: This ugly hack is needed because the input is not attached in the DOM
|
||||
// The input should be attached to the component and hidden in some way to make
|
||||
// the interaction easier and more natural.
|
||||
let fileInput;
|
||||
patchWithCleanup(AttachDocumentWidget.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
fileInput = this.fileInput;
|
||||
},
|
||||
});
|
||||
|
||||
mockService("http", {
|
||||
post(route, params) {
|
||||
expect.step("post");
|
||||
expect(route).toBe("/web/binary/upload_attachment");
|
||||
expect(params.model).toBe("partner");
|
||||
expect(params.id).toBe(1);
|
||||
return '[{ "id": 5 }, { "id": 2 }]';
|
||||
},
|
||||
});
|
||||
|
||||
onRpc(({ args, kwargs, method, model }) => {
|
||||
expect.step(method);
|
||||
if (method === "my_action") {
|
||||
expect(model).toBe("partner");
|
||||
expect(args).toEqual([1]);
|
||||
expect(kwargs.attachment_ids).toEqual([5, 2]);
|
||||
return true;
|
||||
}
|
||||
if (method === "web_save") {
|
||||
expect(args[1]).toEqual({ display_name: "yop" });
|
||||
}
|
||||
if (method === "web_read") {
|
||||
expect(args[0]).toEqual([1]);
|
||||
}
|
||||
});
|
||||
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<widget name="attach_document" action="my_action" string="Attach document"/>
|
||||
<field name="display_name" required="1"/>
|
||||
</form>`,
|
||||
});
|
||||
expect.verifySteps(["get_views", "web_read"]);
|
||||
|
||||
await contains("[name='display_name'] input").edit("yop");
|
||||
await animationFrame();
|
||||
await click(".o_attach_document");
|
||||
await animationFrame();
|
||||
await manuallyDispatchProgrammaticEvent(fileInput, "change");
|
||||
await animationFrame();
|
||||
expect.verifySteps(["web_save", "post", "my_action", "web_read"]);
|
||||
});
|
||||
|
||||
test("attach document widget calls action with attachment ids on a new record", async () => {
|
||||
// FIXME: This ugly hack is needed because the input is not attached in the DOM
|
||||
// The input should be attached to the component and hidden in some way to make
|
||||
// the interaction easier and more natural.
|
||||
let fileInput;
|
||||
patchWithCleanup(AttachDocumentWidget.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
fileInput = this.fileInput;
|
||||
},
|
||||
});
|
||||
|
||||
mockService("http", {
|
||||
post(route, params) {
|
||||
expect.step("post");
|
||||
expect(route).toBe("/web/binary/upload_attachment");
|
||||
expect(params.model).toBe("partner");
|
||||
expect(params.id).toBe(2);
|
||||
return '[{ "id": 5 }, { "id": 2 }]';
|
||||
},
|
||||
});
|
||||
|
||||
onRpc(({ args, kwargs, method, model }) => {
|
||||
expect.step(method);
|
||||
if (method === "my_action") {
|
||||
expect(model).toBe("partner");
|
||||
expect(args).toEqual([2]);
|
||||
expect(kwargs.attachment_ids).toEqual([5, 2]);
|
||||
return true;
|
||||
}
|
||||
if (method === "web_save") {
|
||||
expect(args[1]).toEqual({ display_name: "yop" });
|
||||
}
|
||||
if (method === "web_read") {
|
||||
expect(args[0]).toEqual([2]);
|
||||
}
|
||||
});
|
||||
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<widget name="attach_document" action="my_action" string="Attach document"/>
|
||||
<field name="display_name" required="1"/>
|
||||
</form>`,
|
||||
});
|
||||
expect.verifySteps(["get_views", "onchange"]);
|
||||
await contains("[name='display_name'] input").edit("yop");
|
||||
await click(".o_attach_document");
|
||||
await animationFrame();
|
||||
await manuallyDispatchProgrammaticEvent(fileInput, "change");
|
||||
await animationFrame();
|
||||
expect.verifySteps(["web_save", "post", "my_action", "web_read"]);
|
||||
});
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
click,
|
||||
editInput,
|
||||
getFixture,
|
||||
nextTick,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { AttachDocumentWidget } from "@web/views/widgets/attach_document/attach_document";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
||||
QUnit.module("Widgets", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
display_name: "first record",
|
||||
},
|
||||
],
|
||||
onchanges: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("AttachDocument");
|
||||
|
||||
QUnit.test("attach document widget calls action with attachment ids", async function (assert) {
|
||||
let fileInput;
|
||||
patchWithCleanup(AttachDocumentWidget.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
fileInput = this.fileInput;
|
||||
},
|
||||
});
|
||||
|
||||
serviceRegistry.add("http", {
|
||||
start: () => ({
|
||||
post: (route, params) => {
|
||||
assert.step("post");
|
||||
assert.strictEqual(route, "/web/binary/upload_attachment");
|
||||
assert.strictEqual(params.model, "partner");
|
||||
assert.strictEqual(params.id, 1);
|
||||
return '[{ "id": 5 }, { "id": 2 }]';
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
resId: 1,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
if (args.method === "my_action") {
|
||||
assert.deepEqual(args.model, "partner");
|
||||
assert.deepEqual(args.args, [1]);
|
||||
assert.deepEqual(args.kwargs.attachment_ids, [5, 2]);
|
||||
return true;
|
||||
}
|
||||
if (args.method === "write") {
|
||||
assert.deepEqual(args.args[1], { display_name: "yop" });
|
||||
}
|
||||
if (args.method === "read") {
|
||||
assert.deepEqual(args.args[0], [1]);
|
||||
}
|
||||
},
|
||||
arch: `
|
||||
<form>
|
||||
<widget name="attach_document" action="my_action" string="Attach document"/>
|
||||
<field name="display_name" required="1"/>
|
||||
</form>`,
|
||||
});
|
||||
assert.verifySteps(["get_views", "read"]);
|
||||
|
||||
await editInput(target, "[name='display_name'] input", "yop");
|
||||
await click(target, ".o_attach_document");
|
||||
fileInput.dispatchEvent(new Event("change"));
|
||||
await nextTick();
|
||||
assert.verifySteps(["write", "read", "post", "my_action", "read"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"attach document widget calls action with attachment ids on a new record",
|
||||
async function (assert) {
|
||||
let fileInput;
|
||||
patchWithCleanup(AttachDocumentWidget.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
fileInput = this.fileInput;
|
||||
},
|
||||
});
|
||||
|
||||
serviceRegistry.add("http", {
|
||||
start: () => ({
|
||||
post: (route, params) => {
|
||||
assert.step("post");
|
||||
assert.strictEqual(route, "/web/binary/upload_attachment");
|
||||
assert.strictEqual(params.model, "partner");
|
||||
assert.strictEqual(params.id, 2);
|
||||
return '[{ "id": 5 }, { "id": 2 }]';
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method);
|
||||
if (args.method === "my_action") {
|
||||
assert.deepEqual(args.model, "partner");
|
||||
assert.deepEqual(args.args, [2]);
|
||||
assert.deepEqual(args.kwargs.attachment_ids, [5, 2]);
|
||||
return true;
|
||||
}
|
||||
if (args.method === "create") {
|
||||
assert.deepEqual(args.args[0], { display_name: "yop" });
|
||||
}
|
||||
if (args.method === "read") {
|
||||
assert.deepEqual(args.args[0], [2]);
|
||||
}
|
||||
},
|
||||
arch: `
|
||||
<form>
|
||||
<widget name="attach_document" action="my_action" string="Attach document"/>
|
||||
<field name="display_name" required="1"/>
|
||||
</form>`,
|
||||
});
|
||||
assert.verifySteps(["get_views", "onchange"]);
|
||||
|
||||
await editInput(target, "[name='display_name'] input", "yop");
|
||||
await click(target, ".o_attach_document");
|
||||
fileInput.dispatchEvent(new Event("change"));
|
||||
await nextTick();
|
||||
assert.verifySteps(["create", "read", "post", "my_action", "read"]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import {
|
||||
defineModels,
|
||||
fields,
|
||||
models,
|
||||
mountView,
|
||||
mountWithCleanup,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import { DocumentationLink } from "@web/views/widgets/documentation_link/documentation_link";
|
||||
|
||||
class Partner extends models.Model {
|
||||
bar = fields.Boolean();
|
||||
}
|
||||
|
||||
defineModels([Partner]);
|
||||
|
||||
test("documentation_link: default label and icon", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="/this_is_a_test.html"/>
|
||||
</form>`,
|
||||
});
|
||||
expect(".o_doc_link").toHaveText("View Documentation");
|
||||
expect("a.alert-link").toHaveCount(0);
|
||||
expect(".o_doc_link .fa-external-link").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("documentationLink: alert-link", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="/this_is_a_test.html" alert_link="true"/>
|
||||
</form>`,
|
||||
});
|
||||
expect("a.alert-link").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("DocumentationLink Component: alert-link", async () => {
|
||||
class Parent extends Component {
|
||||
static components = { DocumentationLink };
|
||||
static template = xml`
|
||||
<DocumentationLink path="'/this_is_a_test.html'" alertLink="true"/>`;
|
||||
static props = ["*"];
|
||||
}
|
||||
await mountWithCleanup(Parent);
|
||||
expect("a.alert-link").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("documentation_link: given label", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="/this_is_a_test.html" label="docdoc"/>
|
||||
</form>`,
|
||||
});
|
||||
expect(".o_doc_link").toHaveText("docdoc");
|
||||
expect(".o_doc_link .fa").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("documentation_link: given icon", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="/this_is_a_test.html" icon="fa-question-circle"/>
|
||||
</form>`,
|
||||
});
|
||||
expect(".o_doc_link").toHaveText("");
|
||||
expect(".o_doc_link .fa-question-circle").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("documentation_link: given label and icon", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="/this_is_a_test.html" label="docdoc" icon="fa-question-circle"/>
|
||||
</form>`,
|
||||
});
|
||||
expect(".o_doc_link").toHaveText("docdoc");
|
||||
expect(".o_doc_link .fa-question-circle").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("documentation_link: relative path", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="/applications/technical/web/settings/this_is_a_test.html"/>
|
||||
</form>`,
|
||||
});
|
||||
expect(".o_doc_link").toHaveAttribute(
|
||||
"href",
|
||||
"https://www.odoo.com/documentation/1.0/applications/technical/web/settings/this_is_a_test.html"
|
||||
);
|
||||
});
|
||||
|
||||
test("documentation_link: absolute path (http)", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="http://www.odoo.com/"/>
|
||||
</form>`,
|
||||
});
|
||||
expect(".o_doc_link").toHaveAttribute("href", "http://www.odoo.com/");
|
||||
});
|
||||
|
||||
test("documentation_link: absolute path (https)", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<widget name="documentation_link" path="https://www.odoo.com/"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
expect(".o_doc_link").toHaveAttribute("href", "https://www.odoo.com/");
|
||||
});
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { mockPermission } from "@odoo/hoot-mock";
|
||||
import { defineModels, models, mountView } from "@web/../tests/web_test_helpers";
|
||||
|
||||
class Partner extends models.Model {
|
||||
_records = [{ id: 1 }];
|
||||
}
|
||||
|
||||
defineModels([Partner]);
|
||||
|
||||
const viewData = {
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `<form><widget name="notification_alert"/></form>`,
|
||||
};
|
||||
|
||||
test("notification alert should be displayed when notification denied", async () => {
|
||||
mockPermission("notifications", "denied");
|
||||
await mountView(viewData);
|
||||
expect(".o_widget_notification_alert .alert").toHaveCount(1, {
|
||||
message: "notification alert should be displayed when notification denied",
|
||||
});
|
||||
});
|
||||
|
||||
test("notification alert should not be displayed when notification granted", async () => {
|
||||
mockPermission("notifications", "granted");
|
||||
await mountView(viewData);
|
||||
expect(".o_widget_notification_alert .alert").toHaveCount(0, {
|
||||
message: "notification alert should not be displayed when notification granted",
|
||||
});
|
||||
});
|
||||
|
||||
test("notification alert should not be displayed when notification default", async () => {
|
||||
mockPermission("notifications", "default");
|
||||
await mountView(viewData);
|
||||
expect(".o_widget_notification_alert .alert").toHaveCount(0, {
|
||||
message: "notification alert should not be displayed when notification default",
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
import { NameAndSignature } from "@web/core/signature/name_and_signature";
|
||||
import { SignatureWidget } from "@web/views/widgets/signature/signature";
|
||||
|
||||
import {
|
||||
defineModels,
|
||||
fields,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
patchWithCleanup,
|
||||
contains,
|
||||
clickModalButton,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { beforeEach, test, expect } from "@odoo/hoot";
|
||||
import { click, queryFirst, waitFor } from "@odoo/hoot-dom";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
|
||||
class Partner extends models.Model {
|
||||
display_name = fields.Char();
|
||||
product_id = fields.Many2one({ string: "Product Name", relation: "product" });
|
||||
sign = fields.Binary({ string: "Signature" });
|
||||
signature = fields.Char();
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
display_name: "Pop's Chock'lit",
|
||||
product_id: 7,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
class Product extends models.Model {
|
||||
name = fields.Char({ string: "Product Name" });
|
||||
_records = [
|
||||
{
|
||||
id: 7,
|
||||
name: "Veggie Burger",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([Partner, Product]);
|
||||
|
||||
beforeEach(async () => {
|
||||
onRpc("/web/sign/get_fonts/", () => ({}));
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Signature widget renders a Sign button on desktop", async () => {
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
expect(this.props.signature.name).toBe("");
|
||||
},
|
||||
});
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<header>
|
||||
<widget name="signature" string="Sign"/>
|
||||
</header>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
expect("button.o_sign_button").toHaveClass("btn-secondary", {
|
||||
message: `The button must have the 'btn-secondary' class as "highlight=0"`,
|
||||
});
|
||||
expect(".o_widget_signature button.o_sign_button").toHaveCount(1, {
|
||||
message: "Should have a signature widget button",
|
||||
});
|
||||
expect(".modal-dialog").toHaveCount(0, {
|
||||
message: "Should not have any modal",
|
||||
});
|
||||
// Clicks on the sign button to open the sign modal.
|
||||
await click(".o_widget_signature button.o_sign_button");
|
||||
await waitFor(".modal .modal-body");
|
||||
expect(".modal-dialog").toHaveCount(1, {
|
||||
message: "Should have one modal opened",
|
||||
});
|
||||
});
|
||||
|
||||
test.tags("mobile");
|
||||
test("Signature widget renders a Sign button on mobile", async () => {
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
expect(this.props.signature.name).toBe("");
|
||||
},
|
||||
});
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<header>
|
||||
<widget name="signature" string="Sign"/>
|
||||
</header>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await contains(`.o_cp_action_menus button:has(.fa-cog)`).click();
|
||||
expect("button.o_sign_button").toHaveClass("btn-secondary", {
|
||||
message: `The button must have the 'btn-secondary' class as "highlight=0"`,
|
||||
});
|
||||
expect(".o_widget_signature button.o_sign_button").toHaveCount(1, {
|
||||
message: "Should have a signature widget button",
|
||||
});
|
||||
expect(".modal-dialog").toHaveCount(0, {
|
||||
message: "Should not have any modal",
|
||||
});
|
||||
// Clicks on the sign button to open the sign modal.
|
||||
await click(".o_widget_signature button.o_sign_button");
|
||||
await waitFor(".modal .modal-body");
|
||||
expect(".modal-dialog").toHaveCount(1, {
|
||||
message: "Should have one modal opened",
|
||||
});
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Signature widget: full_name option on desktop", async () => {
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
expect.step(this.props.signature.name);
|
||||
},
|
||||
});
|
||||
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<header>
|
||||
<widget name="signature" string="Sign" full_name="display_name"/>
|
||||
</header>
|
||||
<field name="display_name"/>
|
||||
</form>`,
|
||||
});
|
||||
// Clicks on the sign button to open the sign modal.
|
||||
await click("span.o_sign_label");
|
||||
await waitFor(".modal .modal-body");
|
||||
expect(".modal .modal-body a.o_web_sign_auto_button").toHaveCount(1);
|
||||
expect.verifySteps(["Pop's Chock'lit"]);
|
||||
});
|
||||
|
||||
test.tags("mobile");
|
||||
test("Signature widget: full_name option on mobile", async () => {
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
expect.step(this.props.signature.name);
|
||||
},
|
||||
});
|
||||
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<header>
|
||||
<widget name="signature" string="Sign" full_name="display_name"/>
|
||||
</header>
|
||||
<field name="display_name"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await contains(`.o_cp_action_menus button:has(.fa-cog)`).click();
|
||||
// Clicks on the sign button to open the sign modal.
|
||||
await click("span.o_sign_label");
|
||||
await waitFor(".modal .modal-body");
|
||||
expect(".modal .modal-body a.o_web_sign_auto_button").toHaveCount(1);
|
||||
expect.verifySteps(["Pop's Chock'lit"]);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Signature widget: highlight option on desktop", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<header>
|
||||
<widget name="signature" string="Sign" highlight="1"/>
|
||||
</header>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
expect("button.o_sign_button").toHaveClass("btn-primary", {
|
||||
message: `The button must have the 'btn-primary' class as "highlight=1"`,
|
||||
});
|
||||
|
||||
// Clicks on the sign button to open the sign modal.
|
||||
await click(".o_widget_signature button.o_sign_button");
|
||||
await waitFor(".modal .modal-body");
|
||||
expect(".modal .modal-body a.o_web_sign_auto_button").toHaveCount(0);
|
||||
});
|
||||
|
||||
test.tags("mobile");
|
||||
test("Signature widget: highlight option on mobile", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<header>
|
||||
<widget name="signature" string="Sign" highlight="1"/>
|
||||
</header>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await contains(`.o_cp_action_menus button:has(.fa-cog)`).click();
|
||||
expect("button.o_sign_button").toHaveClass("btn-primary", {
|
||||
message: `The button must have the 'btn-primary' class as "highlight=1"`,
|
||||
});
|
||||
|
||||
// Clicks on the sign button to open the sign modal.
|
||||
await click(".o_widget_signature button.o_sign_button");
|
||||
await waitFor(".modal .modal-body");
|
||||
expect(".modal .modal-body a.o_web_sign_auto_button").toHaveCount(0);
|
||||
});
|
||||
|
||||
test.tags("mobile");
|
||||
test("Signature widget works inside of a dropdown", async () => {
|
||||
patchWithCleanup(SignatureWidget.prototype, {
|
||||
async onClickSignature() {
|
||||
await super.onClickSignature(...arguments);
|
||||
expect.step("onClickSignature");
|
||||
},
|
||||
async uploadSignature({ signatureImage }) {
|
||||
await super.uploadSignature(...arguments);
|
||||
expect.step("uploadSignature");
|
||||
},
|
||||
});
|
||||
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<button string="Dummy"/>
|
||||
<widget name="signature" string="Sign" full_name="display_name"/>
|
||||
</header>
|
||||
<field name="display_name" />
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
// change display_name to enable auto-sign feature
|
||||
await contains(".o_field_widget[name=display_name] input").edit("test");
|
||||
|
||||
// open the signature dialog
|
||||
await contains(".o_statusbar_buttons button:has(.oi-ellipsis-v").click();
|
||||
await contains(".o_widget_signature button.o_sign_button").click();
|
||||
await waitFor(".modal .modal-body");
|
||||
|
||||
// use auto-sign feature, might take a while
|
||||
await contains(".o_web_sign_auto_button").click();
|
||||
|
||||
expect(".modal-footer button.btn-primary").toHaveCount(1);
|
||||
|
||||
let maxDelay = 100;
|
||||
while (queryFirst(".modal-footer button.btn-primary")["disabled"] && maxDelay > 0) {
|
||||
await animationFrame();
|
||||
maxDelay--;
|
||||
}
|
||||
|
||||
expect(maxDelay).toBeGreaterThan(0, { message: "Timeout exceeded" });
|
||||
|
||||
// close the dialog and save the signature
|
||||
await clickModalButton({ text: "Adopt & Sign" });
|
||||
|
||||
expect(".modal-dialog").toHaveCount(0, { message: "Should have no modal opened" });
|
||||
expect.verifySteps(["onClickSignature", "uploadSignature"]);
|
||||
});
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
import { click, getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { NameAndSignature } from "@web/core/signature/name_and_signature";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
QUnit.module("Widgets", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
display_name: { string: "Name", type: "char" },
|
||||
product_id: {
|
||||
string: "Product Name",
|
||||
type: "many2one",
|
||||
relation: "product",
|
||||
},
|
||||
__last_update: { type: "datetime" },
|
||||
sign: { string: "Signature", type: "binary" },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
display_name: "Pop's Chock'lit",
|
||||
product_id: 7,
|
||||
},
|
||||
],
|
||||
onchanges: {},
|
||||
},
|
||||
product: {
|
||||
fields: {
|
||||
name: { string: "Product Name", type: "char" },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 7,
|
||||
display_name: "Veggie Burger",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("Signature Widget");
|
||||
|
||||
QUnit.test("Signature widget renders a Sign button", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
this._super.apply(this, arguments);
|
||||
assert.strictEqual(this.props.signature.name, "");
|
||||
},
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `<form>
|
||||
<header>
|
||||
<widget name="signature" string="Sign"/>
|
||||
</header>
|
||||
</form>`,
|
||||
mockRPC: async (route, args) => {
|
||||
if (route === "/web/sign/get_fonts/") {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.hasClass(
|
||||
target.querySelector("button.o_sign_button"),
|
||||
"btn-secondary",
|
||||
"The button must have the 'btn-secondary' class as \"highlight=0\""
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_widget_signature button.o_sign_button",
|
||||
"Should have a signature widget button"
|
||||
);
|
||||
assert.containsNone(target, ".modal-dialog", "Should not have any modal");
|
||||
|
||||
// Clicks on the sign button to open the sign modal.
|
||||
await click(target, ".o_widget_signature button.o_sign_button");
|
||||
assert.containsOnce(target, ".modal-dialog", "Should have one modal opened");
|
||||
});
|
||||
|
||||
QUnit.test("Signature widget: full_name option", async function (assert) {
|
||||
patchWithCleanup(NameAndSignature.prototype, {
|
||||
setup() {
|
||||
this._super.apply(this, arguments);
|
||||
assert.step(this.props.signature.name);
|
||||
},
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `<form>
|
||||
<header>
|
||||
<widget name="signature" string="Sign" full_name="display_name"/>
|
||||
</header>
|
||||
<field name="display_name"/>
|
||||
</form>`,
|
||||
mockRPC: async (route) => {
|
||||
if (route === "/web/sign/get_fonts/") {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
});
|
||||
// Clicks on the sign button to open the sign modal.
|
||||
await click(target, "span.o_sign_label");
|
||||
assert.containsOnce(target, ".modal .modal-body a.o_web_sign_auto_button");
|
||||
assert.verifySteps(["Pop's Chock'lit"]);
|
||||
});
|
||||
|
||||
QUnit.test("Signature widget: highlight option", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `<form>
|
||||
<header>
|
||||
<widget name="signature" string="Sign" highlight="1"/>
|
||||
</header>
|
||||
</form>`,
|
||||
mockRPC: async (route, args) => {
|
||||
if (route === "/web/sign/get_fonts/") {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.hasClass(
|
||||
target.querySelector("button.o_sign_button"),
|
||||
"btn-primary",
|
||||
"The button must have the 'btn-primary' class as \"highlight=1\""
|
||||
);
|
||||
// Clicks on the sign button to open the sign modal.
|
||||
await click(target, ".o_widget_signature button.o_sign_button");
|
||||
assert.containsNone(target, ".modal .modal-body a.o_web_sign_auto_button");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { click, queryAllTexts } from "@odoo/hoot-dom";
|
||||
import {
|
||||
clickSave,
|
||||
defineModels,
|
||||
defineParams,
|
||||
fields,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
class Partner extends models.Model {
|
||||
sun = fields.Boolean();
|
||||
mon = fields.Boolean();
|
||||
tue = fields.Boolean();
|
||||
wed = fields.Boolean();
|
||||
thu = fields.Boolean();
|
||||
fri = fields.Boolean();
|
||||
sat = fields.Boolean();
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
sun: false,
|
||||
mon: false,
|
||||
tue: false,
|
||||
wed: false,
|
||||
thu: false,
|
||||
fri: false,
|
||||
sat: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([Partner]);
|
||||
|
||||
test("simple week recurrence widget", async () => {
|
||||
expect.assertions(13);
|
||||
defineParams({ lang_parameters: { week_start: 1 } });
|
||||
let writeCall = 0;
|
||||
onRpc("web_save", ({ args }) => {
|
||||
writeCall++;
|
||||
if (writeCall === 1) {
|
||||
expect(args[1].sun).toBe(true);
|
||||
}
|
||||
if (writeCall === 2) {
|
||||
expect(args[1].sun).not.toBe(true);
|
||||
expect(args[1].mon).toBe(true);
|
||||
expect(args[1].tue).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `<form><sheet><group><widget name="week_days" /></group></sheet></form>`,
|
||||
});
|
||||
|
||||
expect(queryAllTexts(".o_recurrent_weekday_label")).toEqual(
|
||||
["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||
{ message: "labels should be short week names" }
|
||||
);
|
||||
expect(".form-check input:disabled").toHaveCount(0, {
|
||||
message: "all inputs should be enabled in edit mode",
|
||||
});
|
||||
|
||||
await click("td:nth-child(7) input");
|
||||
expect("td:nth-child(7) input").toBeChecked({
|
||||
message: "sunday checkbox should be checked",
|
||||
});
|
||||
|
||||
await clickSave();
|
||||
|
||||
await click("td:nth-child(1) input");
|
||||
expect("td:nth-child(1) input").toBeChecked({
|
||||
message: "monday checkbox should be checked",
|
||||
});
|
||||
|
||||
await click("td:nth-child(2) input");
|
||||
expect("td:nth-child(2) input").toBeChecked({
|
||||
message: "tuesday checkbox should be checked",
|
||||
});
|
||||
|
||||
// uncheck Sunday checkbox and check write call
|
||||
await click("td:nth-child(7) input");
|
||||
expect("td:nth-child(7) input").not.toBeChecked({
|
||||
message: "sunday checkbox should be unchecked",
|
||||
});
|
||||
|
||||
await clickSave();
|
||||
|
||||
expect("td:nth-child(7) input").not.toBeChecked({
|
||||
message: "sunday checkbox should be unchecked",
|
||||
});
|
||||
expect("td:nth-child(1) input").toBeChecked({ message: "monday checkbox should be checked" });
|
||||
expect("td:nth-child(2) input").toBeChecked({
|
||||
message: "tuesday checkbox should be checked",
|
||||
});
|
||||
});
|
||||
|
||||
test("week recurrence widget readonly modifiers", async () => {
|
||||
defineParams({ lang_parameters: { week_start: 1 } });
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `<form><sheet><group><widget name="week_days" readonly="1"/></group></sheet></form>`,
|
||||
});
|
||||
|
||||
expect(queryAllTexts(".o_recurrent_weekday_label")).toEqual(
|
||||
["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||
{ message: "labels should be short week names" }
|
||||
);
|
||||
expect(".form-check input:disabled").toHaveCount(7, {
|
||||
message: "all inputs should be disabled in readonly mode",
|
||||
});
|
||||
});
|
||||
|
||||
test("week recurrence widget show week start as per language configuration", async () => {
|
||||
defineParams({ lang_parameters: { week_start: 5 } });
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: /* xml */ `<form><sheet><group><widget name="week_days"/></group></sheet></form>`,
|
||||
});
|
||||
|
||||
expect(queryAllTexts(".o_recurrent_weekday_label")).toEqual(
|
||||
["Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"],
|
||||
{ message: "labels should be short week names" }
|
||||
);
|
||||
});
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { makeFakeLocalizationService } from "../../helpers/mock_services";
|
||||
import { getFixture, click, clickSave } from "../../helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "../helpers";
|
||||
|
||||
let serverData;
|
||||
let fixture;
|
||||
|
||||
QUnit.module("Widgets", ({ beforeEach }) => {
|
||||
beforeEach(() => {
|
||||
fixture = getFixture();
|
||||
setupViewRegistries();
|
||||
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
id: { type: "integer", string: "ID" },
|
||||
sun: { type: "boolean", string: "Sun" },
|
||||
mon: { type: "boolean", string: "Mon" },
|
||||
tue: { type: "boolean", string: "Tue" },
|
||||
wed: { type: "boolean", string: "Wed" },
|
||||
thu: { type: "boolean", string: "Thu" },
|
||||
fri: { type: "boolean", string: "Fri" },
|
||||
sat: { type: "boolean", string: "Sat" },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
sun: false,
|
||||
mon: false,
|
||||
tue: false,
|
||||
wed: false,
|
||||
thu: false,
|
||||
fri: false,
|
||||
sat: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
QUnit.module("WeekDays");
|
||||
|
||||
QUnit.test("simple week recurrence widget", async (assert) => {
|
||||
assert.expect(13);
|
||||
|
||||
let writeCall = 0;
|
||||
registry.category("services", makeFakeLocalizationService({ weekStart: 1 }));
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<widget name="week_days" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
`,
|
||||
mockRPC(route, { args, method }) {
|
||||
if (method === "write") {
|
||||
writeCall++;
|
||||
if (writeCall === 1) {
|
||||
assert.ok(args[1].sun, "value of sunday should be true");
|
||||
}
|
||||
if (writeCall === 2) {
|
||||
assert.notOk(args[1].sun, "value of sunday should be false");
|
||||
assert.ok(args[1].mon, "value of monday should be true");
|
||||
assert.ok(args[1].tue, "value of tuesday should be true");
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const labelsTexts = [...fixture.querySelectorAll(".o_recurrent_weekday_label")].map((el) =>
|
||||
el.innerText.trim()
|
||||
);
|
||||
assert.deepEqual(
|
||||
labelsTexts,
|
||||
["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||
"labels should be short week names"
|
||||
);
|
||||
|
||||
assert.containsNone(
|
||||
fixture,
|
||||
".form-check input:disabled",
|
||||
"all inputs should be enabled in readonly mode"
|
||||
);
|
||||
|
||||
await click(fixture.querySelector("td:nth-child(7) input"));
|
||||
assert.ok(
|
||||
fixture.querySelector("td:nth-child(7) input").checked,
|
||||
"sunday checkbox should be checked"
|
||||
);
|
||||
await clickSave(fixture);
|
||||
|
||||
await click(fixture.querySelector("td:nth-child(1) input"));
|
||||
assert.ok(
|
||||
fixture.querySelector("td:nth-child(1) input").checked,
|
||||
"monday checkbox should be checked"
|
||||
);
|
||||
|
||||
await click(fixture.querySelector("td:nth-child(2) input"));
|
||||
assert.ok(
|
||||
fixture.querySelector("td:nth-child(2) input").checked,
|
||||
"tuesday checkbox should be checked"
|
||||
);
|
||||
|
||||
// uncheck Sunday checkbox and check write call
|
||||
await click(fixture.querySelector("td:nth-child(7) input"));
|
||||
assert.notOk(
|
||||
fixture.querySelector("td:nth-child(7) input").checked,
|
||||
"sunday checkbox should be unchecked"
|
||||
);
|
||||
|
||||
await clickSave(fixture);
|
||||
assert.notOk(
|
||||
fixture.querySelector("td:nth-child(7) input").checked,
|
||||
"sunday checkbox should be unchecked"
|
||||
);
|
||||
assert.ok(
|
||||
fixture.querySelector("td:nth-child(1) input").checked,
|
||||
"monday checkbox should be checked"
|
||||
);
|
||||
assert.ok(
|
||||
fixture.querySelector("td:nth-child(2) input").checked,
|
||||
"tuesday checkbox should be checked"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"week recurrence widget show week start as per language configuration",
|
||||
async (assert) => {
|
||||
registry.category("services", makeFakeLocalizationService({ weekStart: 5 }));
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<widget name="week_days" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
const labels = [...fixture.querySelectorAll(".o_recurrent_weekday_label")].map((el) =>
|
||||
el.textContent.trim()
|
||||
);
|
||||
assert.deepEqual(
|
||||
labels,
|
||||
["Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"],
|
||||
"labels should be short week names"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue