19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -0,0 +1,75 @@
import { expect, test } from "@odoo/hoot";
import { contains, defineModels, fields, models, mountView } from "@web/../tests/web_test_helpers";
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
class Partner extends models.Model {
lines = fields.One2many({ relation: "lines_sections" });
_records = [
{
id: 1,
lines: [1, 2],
},
];
}
class LinesSections extends models.Model {
_name = "lines_sections";
is_page = fields.Boolean();
title = fields.Char();
random_questions_count = fields.Integer({ string: "Question Count" });
_records = [
{
id: 1,
is_page: true,
title: "firstSectionTitle",
random_questions_count: 4,
},
{
id: 2,
is_page: false,
title: "recordTitle",
random_questions_count: 5,
},
];
_views = {
form: /* xml */ `
<form>
<field name="title" />
</form>
`,
};
}
defineModels([Partner, LinesSections]);
defineMailModels();
test("button is visible in the edited record and allows to open that record", async () => {
await mountView({
type: "form",
resModel: "partner",
resId: 1,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<list>
<field name="is_page" invisible="1" />
<field name="title" widget="survey_description_page"/>
<field name="random_questions_count" />
</list>
</field>
</form>
`,
});
expect("td.o_survey_description_page_cell").toHaveCount(2);
expect("button.o_icon_button").toHaveCount(0);
await contains(".o_data_cell").click();
expect(".o_data_row button.o_icon_button").toHaveCount(1);
expect(".modal .o_form_view").toHaveCount(0);
await contains("button.o_icon_button").click();
expect(".modal .o_form_view").toHaveCount(1);
});

View file

@ -1,89 +0,0 @@
/** @odoo-module */
import { click, getFixture } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
QUnit.module("DescriptionPageField", (hooks) => {
let serverData;
let target;
hooks.beforeEach(() => {
target = getFixture();
serverData = {
models: {
partner: {
fields: { lines: { type: "one2many", relation: "lines_sections" } },
records: [
{
id: 1,
lines: [1, 2],
},
],
},
lines_sections: {
fields: {
is_page: { type: "boolean" },
title: { type: "char", string: "Title" },
random_questions_count: { type: "number", string: "Question Count" },
},
records: [
{
id: 1,
is_page: true,
title: "firstSectionTitle",
random_questions_count: 4,
},
{
id: 2,
is_page: false,
title: "recordTitle",
random_questions_count: 5,
},
],
},
},
views: {
"lines_sections,false,form": `
<form>
<field name="title" />
</form>
`,
},
};
setupViewRegistries();
});
QUnit.test(
"button is visible in the edited record and allows to open that record",
async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="is_page" invisible="1" />
<field name="title" widget="survey_description_page"/>
<field name="random_questions_count" />
</tree>
</field>
</form>
`,
});
assert.containsN(target, "td.o_survey_description_page_cell", 2);
assert.containsNone(target, "button.o_icon_button");
await click(target.querySelector(".o_data_cell"));
assert.containsOnce(target.querySelector(".o_data_row"), "button.o_icon_button");
assert.containsNone(target, ".modal .o_form_view");
await click(target, "button.o_icon_button");
assert.containsOnce(target, ".modal .o_form_view");
}
);
});

View file

@ -0,0 +1,179 @@
import { expect, test } from "@odoo/hoot";
import { contains, defineModels, fields, models, mountView } from "@web/../tests/web_test_helpers";
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
class Partner extends models.Model {
lines = fields.One2many({ relation: "lines_sections" });
_records = [
{
id: 1,
lines: [1, 2],
},
];
}
class LinesSections extends models.Model {
_name = "lines_sections";
is_page = fields.Boolean();
title = fields.Char();
random_questions_count = fields.Integer({ string: "Question Count" });
sequence = fields.Integer();
question_type = fields.Char();
_records = [
{
id: 1,
sequence: 1,
is_page: true,
question_type: false,
title: "firstSectionTitle",
random_questions_count: 4,
},
{
id: 2,
sequence: 2,
is_page: false,
question_type: "simple_choice",
title: "recordTitle",
random_questions_count: 5,
},
];
_views = {
form: /* xml */ `
<form>
<field name="title" />
</form>
`,
};
}
defineModels([Partner, LinesSections]);
defineMailModels();
const SELECTORS = {
section: "tr.o_is_section > td.o_survey_description_page_cell",
numberQuestions: "tr.o_is_section > [name='random_questions_count']",
};
test("normal list view", async () => {
await mountView({
type: "form",
resModel: "partner",
resId: 1,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<list>
<field name="sequence" widget="handle"/>
<field name="title" widget="survey_description_page"/>
<field name="question_type" />
<field name="is_page" column_invisible="1"/>
</list>
</field>
</form>
`,
});
expect("td.o_survey_description_page_cell").toHaveCount(2); // Check if we have the two rows in the list
expect("tr.o_is_section").toHaveCount(1); // Check if we have only one section row
expect(SELECTORS.section).toHaveProperty("colSpan", 2);
await contains(SELECTORS.section).click();
expect(SELECTORS.section + " div.input-group").toHaveCount(1);
});
test("list view with random count", async () => {
await mountView({
type: "form",
resModel: "partner",
resId: 1,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<list>
<field name="sequence" widget="handle"/>
<field name="title" widget="survey_description_page"/>
<field name="question_type" />
<field name="is_page" column_invisible="1"/>
<field name="random_questions_count"/>
</list>
</field>
</form>
`,
});
expect("td.o_survey_description_page_cell").toHaveCount(2); // Check if we have the two rows in the list
expect("tr.o_is_section").toHaveCount(1); // Check if we have only one section row
expect(SELECTORS.section).toHaveProperty("colSpan", 2);
// We can edit the section title
await contains(SELECTORS.section).click();
expect(SELECTORS.section + " div.input-group").toHaveCount(1);
// We can edit the number of random questions selected
await contains(SELECTORS.numberQuestions).click();
expect(SELECTORS.numberQuestions + " div").toHaveCount(1);
});
test("list view with random but with question_type at the left of the title", async () => {
await mountView({
type: "form",
resModel: "partner",
resId: 1,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<list>
<field name="sequence" widget="handle"/>
<field name="question_type" />
<field name="title" widget="survey_description_page"/>
<field name="is_page" column_invisible="1"/>
<field name="random_questions_count"/>
</list>
</field>
</form>
`,
});
expect("td.o_survey_description_page_cell").toHaveCount(2); // Check if we have the two rows in the list
expect("tr.o_is_section").toHaveCount(1); // Check if we have only one section row
expect(SELECTORS.section).toHaveProperty("colSpan", 1);
await contains(SELECTORS.section).click();
expect(SELECTORS.section + " div.input-group").toHaveCount(1);
await contains(SELECTORS.numberQuestions).click();
expect(SELECTORS.numberQuestions + " div").toHaveCount(1);
});
test("list view with random and question_type at the beginning of row", async () => {
await mountView({
type: "form",
resModel: "partner",
resId: 1,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<list>
<field name="sequence" widget="handle"/>
<field name="random_questions_count"/>
<field name="question_type" />
<field name="title" widget="survey_description_page"/>
<field name="is_page" column_invisible="1"/>
</list>
</field>
</form>
`,
});
expect("td.o_survey_description_page_cell").toHaveCount(2); // Check if we have the two rows in the list
expect("tr.o_is_section").toHaveCount(1); // Check if we have only one section row
expect(SELECTORS.section).toHaveProperty("colSpan", 1);
await contains(SELECTORS.section).click();
expect(SELECTORS.section + " div.input-group").toHaveCount(1);
await contains(SELECTORS.numberQuestions).click();
expect(SELECTORS.numberQuestions + " div").toHaveCount(1);
});

View file

@ -1,207 +0,0 @@
/** @odoo-module */
import { click, getFixture } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
QUnit.module("QuestionPageListRenderer", (hooks) => {
let serverData;
let target;
hooks.beforeEach(() => {
target = getFixture();
serverData = {
models: {
partner: {
fields: { lines: { type: "one2many", relation: "lines_sections" } },
records: [
{
id: 1,
lines: [1, 2],
},
],
},
lines_sections: {
fields: {
sequence: { type: "number" },
is_page: { type: "boolean" },
title: { type: "char", string: "Title" },
question_type: { type: "string" },
random_questions_count: { type: "number", string: "Question Count" },
},
records: [
{
id: 1,
sequence: 1,
is_page: true,
question_type: false,
title: "firstSectionTitle",
random_questions_count: 4,
},
{
id: 2,
sequence: 2,
is_page: false,
question_type: 'simple_choice',
title: "recordTitle",
random_questions_count: 5,
},
],
},
},
views: {
"lines_sections,false,form": `
<form>
<field name="title" />
</form>
`,
},
};
setupViewRegistries();
});
QUnit.test(
"normal list view",
async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="sequence" widget="handle"/>
<field name="title" widget="survey_description_page"/>
<field name="question_type" />
<field name="is_page" invisible="1"/>
</tree>
</field>
</form>
`,
});
assert.containsN(target, "td.o_survey_description_page_cell", 2); // Check if we have the two rows in the list
assert.containsOnce(target, "tr.o_is_section"); // Check if we have only one section row
const section = target.querySelector("tr.o_is_section > td.o_survey_description_page_cell");
assert.strictEqual(section.colSpan, 2, 'The section should have a colspan of 1');
await click(section);
assert.containsOnce(section, 'div.input-group');
}
);
QUnit.test(
"list view with random count",
async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="sequence" widget="handle"/>
<field name="title" widget="survey_description_page"/>
<field name="question_type" />
<field name="is_page" invisible="1"/>
<field name="random_questions_count"/>
</tree>
</field>
</form>
`,
});
assert.containsN(target, "td.o_survey_description_page_cell", 2); // Check if we have the two rows in the list
assert.containsOnce(target, "tr.o_is_section"); // Check if we have only one section row
const section = target.querySelector("tr.o_is_section > td.o_survey_description_page_cell");
assert.strictEqual(section.colSpan, 2, 'The section should have a colspan of 2');
// We can edit the section title
await click(section);
assert.containsOnce(section, 'div.input-group');
//We can edit the number of random questions selected
const numberQuestions = target.querySelector("tr.o_is_section > [name='random_questions_count']");
await click(numberQuestions);
assert.containsOnce(numberQuestions, 'div');
}
);
QUnit.test(
"list view with random but with question_type at the left of the title",
async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="sequence" widget="handle"/>
<field name="question_type" />
<field name="title" widget="survey_description_page"/>
<field name="is_page" invisible="1"/>
<field name="random_questions_count"/>
</tree>
</field>
</form>
`,
});
assert.containsN(target, "td.o_survey_description_page_cell", 2); // Check if we have the two rows in the list
assert.containsOnce(target, "tr.o_is_section"); // Check if we have only one section row
const section = target.querySelector("tr.o_is_section > td.o_survey_description_page_cell");
assert.strictEqual(section.colSpan, 1, 'The section should have a colspan of 1');
await click(section);
assert.containsOnce(section, 'div.input-group');
const numberQuestions = target.querySelector("tr.o_is_section > [name='random_questions_count']");
await click(numberQuestions);
assert.containsOnce(numberQuestions, 'div');
}
);
QUnit.test(
"list view with random and question_type at the beginning of row",
async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="sequence" widget="handle"/>
<field name="random_questions_count"/>
<field name="question_type" />
<field name="title" widget="survey_description_page"/>
<field name="is_page" invisible="1"/>
</tree>
</field>
</form>
`,
});
assert.containsN(target, "td.o_survey_description_page_cell", 2); // Check if we have the two rows in the list
assert.containsOnce(target, "tr.o_is_section"); // Check if we have only one section row
const section = target.querySelector("tr.o_is_section > td.o_survey_description_page_cell");
assert.strictEqual(section.colSpan, 1, 'The section should have a colspan of 1');
await click(section);
assert.containsOnce(section, 'div.input-group');
const numberQuestions = target.querySelector("tr.o_is_section > [name='random_questions_count']");
await click(numberQuestions);
assert.containsOnce(numberQuestions, 'div');
}
);
});

View file

@ -0,0 +1,284 @@
import { expect, test } from "@odoo/hoot";
import {
contains,
defineModels,
fields,
makeServerError,
models,
mountView,
onRpc,
} from "@web/../tests/web_test_helpers";
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
import { animationFrame, press, queryAll, queryOne } from "@odoo/hoot-dom";
class Survey extends models.Model {
question_and_page_ids = fields.One2many({ relation: "survey_question" });
favorite_color = fields.Char({ string: "Favorite color" });
_records = [
{
id: 1,
question_and_page_ids: [1, 2],
favorite_color: "",
},
];
}
class SurveyQuestion extends models.Model {
_name = "survey_question";
is_page = fields.Boolean();
title = fields.Char();
random_questions_count = fields.Integer({ string: "Question Count" });
_records = [
{
id: 1,
is_page: true,
title: "firstSectionTitle",
random_questions_count: 4,
},
{
id: 2,
is_page: false,
title: "recordTitle",
random_questions_count: 5,
},
];
_views = {
form: /* xml */ `
<form>
<field name="title" />
</form>
`,
};
}
defineModels([Survey, SurveyQuestion]);
defineMailModels();
test("basic rendering", async () => {
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<field name="question_and_page_ids" widget="question_page_one2many">
<list>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</list>
</field>
</form>
`,
});
expect(".o_field_x2many .o_list_renderer table.o_section_list_view").toHaveCount(1);
expect(".o_data_row").toHaveCount(2);
const rows = queryAll(".o_data_row");
expect(rows[0]).toHaveClass("o_is_section fw-bold");
expect(rows[0]).toHaveText("firstSectionTitle 4");
expect(rows[1]).toHaveText("recordTitle 5");
expect(queryOne("td[name=title]", { root: rows[0] })).toHaveAttribute("colspan", "1");
expect(queryOne("td[name=title]", { root: rows[1] })).not.toHaveAttribute("colspan");
});
test("click on section behaves as usual in readonly mode", async () => {
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<field name="question_and_page_ids" widget="question_page_one2many">
<list>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</list>
</field>
</form>
`,
readonly: true,
});
await contains(".o_data_cell").click();
expect(".o_selected_row").toHaveCount(0);
expect(".modal .o_form_view").toHaveCount(1);
});
test("click on section edit the section in place", async () => {
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<field name="question_and_page_ids" widget="question_page_one2many">
<list>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</list>
</field>
</form>`,
});
await contains(".o_data_cell").click();
expect(".o_is_section").toHaveClass("o_selected_row");
expect(".modal .o_form_view").toHaveCount(0);
});
test("click on real line saves form and opens a dialog", async () => {
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<field name="favorite_color"/>
<field name="question_and_page_ids" widget="question_page_one2many">
<list>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</list>
</field>
</form>
`,
});
onRpc("survey", "web_save", () => {
expect.step("save parent form");
});
await contains("[name='favorite_color'] input").edit("Yellow");
await contains(".o_data_row:nth-child(2) .o_data_cell").click();
// Edit content to trigger the expected actual save at row opening
expect.verifySteps(["save parent form"]);
expect(".o_selected_row").toHaveCount(1);
expect(".modal .o_form_view").toHaveCount(1);
});
test("A validation error from saving parent form notifies and prevents dialog from closing", async () => {
expect.errors(1);
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<field name="question_and_page_ids" widget="question_page_one2many">
<list>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</list>
</field>
</form>
`,
});
const error = makeServerError({
description: "This isn't right!",
type: "ValidationError",
});
onRpc("survey", "web_save", () => {
expect.step("save parent form");
throw error;
});
await contains(".o_data_row:nth-child(2) .o_data_cell").click();
await contains(".o_dialog:not(.o_inactive_modal) .modal-body [name='title'] input").edit(
"Invalid RecordTitle"
);
await contains(".o_dialog:not(.o_inactive_modal) .o_form_button_save").click();
expect.verifySteps(["save parent form"]);
expect.verifyErrors(["This isn't right!"]);
await animationFrame();
expect(".o_notification").toHaveCount(1);
expect(".modal .o_form_view").toHaveCount(1);
expect(".modal-dialog .o_form_button_save").toHaveCount(1);
expect(".modal-dialog .o_form_button_save[disabled='1']").toHaveCount(0);
});
test.tags("desktop");
test("can create section inline", async () => {
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<field name="question_and_page_ids" widget="question_page_one2many">
<list>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
<control>
<create string="add line" />
<create string="add section" context="{'default_is_page': true}" />
</control>
</list>
</field>
</form>
`,
});
expect(".o_selected_row").toHaveCount(0);
await contains(".o_field_x2many_list_row_add a:eq(1)").click();
expect(".o_selected_row.o_is_section").toHaveCount(1);
expect(".modal .o_form_view").toHaveCount(0);
});
test("creates real record in form dialog", async () => {
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<field name="question_and_page_ids" widget="question_page_one2many">
<list>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
<control>
<create string="add line" />
<create string="add section" context="{'default_is_page': true}" />
</control>
</list>
</field>
</form>
`,
});
await contains(".o_field_x2many_list_row_add a").click();
expect(".o_selected_row").toHaveCount(0);
expect(".modal .o_form_view").toHaveCount(1);
});
test("press enter with focus in a edited section pass the section in readonly mode", async () => {
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<field name="question_and_page_ids" widget="question_page_one2many">
<list>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</list>
</field>
</form>
`,
});
await contains(".o_data_row .o_data_cell").click();
expect(".o_selected_row.o_is_section").toHaveCount(1);
await contains("[name='title'] input").edit("a");
press("Enter");
expect(".o_selected_row.o_is_section").toHaveCount(0);
expect(".o_is_section [name=title]").toHaveText("a");
});

View file

@ -1,247 +0,0 @@
/** @odoo-module */
import { click, editInput, getFixture, nextTick, triggerHotkey } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
QUnit.module("QuestionPageOneToManyField", (hooks) => {
let serverData;
let target;
hooks.beforeEach(() => {
target = getFixture();
serverData = {
models: {
partner: {
fields: { lines: { type: "one2many", relation: "lines_sections" } },
records: [
{
id: 1,
lines: [1, 2],
},
],
},
lines_sections: {
fields: {
is_page: { type: "boolean" },
title: { type: "char", string: "Title" },
random_questions_count: { type: "number", string: "Question Count" },
},
records: [
{
id: 1,
is_page: true,
title: "firstSectionTitle",
random_questions_count: 4,
},
{
id: 2,
is_page: false,
title: "recordTitle",
random_questions_count: 5,
},
],
},
},
views: {
"lines_sections,false,form": `
<form>
<field name="title" />
</form>
`,
},
};
setupViewRegistries();
});
QUnit.test("basic rendering", async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</tree>
</field>
</form>
`,
});
assert.containsOnce(target, ".o_field_x2many .o_list_renderer table.o_section_list_view");
assert.containsN(target, ".o_data_row", 2);
const rows = target.querySelectorAll(".o_data_row");
assert.hasClass(rows[0], "o_is_section");
assert.hasClass(rows[0], "fw-bold");
assert.strictEqual(rows[0].textContent, "firstSectionTitle4");
assert.strictEqual(rows[1].textContent, "recordTitle5");
assert.strictEqual(rows[0].querySelector("td[name=title]").getAttribute("colspan"), "1");
assert.strictEqual(rows[1].querySelector("td[name=title]").getAttribute("colspan"), null);
});
QUnit.test("click on section behaves as usual in readonly mode", async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</tree>
</field>
</form>
`,
mode: "readonly",
});
await click(target.querySelector(".o_data_cell"));
assert.containsNone(target, ".o_selected_row");
assert.containsOnce(target, ".modal .o_form_view");
});
QUnit.test("click on section edit the section in place", async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</tree>
</field>
</form>`,
});
await click(target.querySelector(".o_data_cell"));
assert.hasClass(target.querySelector(".o_is_section"), "o_selected_row");
assert.containsNone(target, ".modal .o_form_view");
});
QUnit.test("click on real line opens a dialog", async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</tree>
</field>
</form>
`,
});
await click(target.querySelector(".o_data_row:nth-child(2) .o_data_cell"));
assert.containsNone(target, ".o_selected_row");
assert.containsOnce(target, ".modal .o_form_view");
});
QUnit.test("can create section inline", async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
<control>
<create string="add line" />
<create string="add section" context="{'default_is_page': true}" />
</control>
</tree>
</field>
</form>
`,
});
assert.containsNone(target, ".o_selected_row");
await click(target.querySelectorAll(".o_field_x2many_list_row_add a")[1]);
assert.containsOnce(target, ".o_selected_row.o_is_section");
assert.containsNone(target, ".modal .o_form_view");
});
QUnit.test("creates real record in form dialog", async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
<control>
<create string="add line" />
<create string="add section" context="{'default_is_page': true}" />
</control>
</tree>
</field>
</form>
`,
});
await click(target.querySelector(".o_field_x2many_list_row_add a"));
assert.containsNone(target, ".o_selected_row");
assert.containsOnce(target, ".modal .o_form_view");
});
QUnit.test(
"press enter with focus in a edited section pass the section in readonly mode",
async (assert) => {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="lines" widget="question_page_one2many">
<tree>
<field name="is_page" invisible="1" />
<field name="title" />
<field name="random_questions_count" />
</tree>
</field>
</form>
`,
});
await click(target.querySelector(".o_data_row .o_data_cell"));
assert.containsOnce(target, ".o_selected_row.o_is_section");
await editInput(target, "[name='title'] input", "a");
triggerHotkey("Enter");
await nextTick();
assert.containsNone(target, ".o_selected_row.o_is_section");
assert.strictEqual(target.querySelector(".o_is_section [name=title]").innerText, "a");
}
);
});

View file

@ -0,0 +1,141 @@
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
import { expect, test } from "@odoo/hoot";
import { animationFrame } from "@odoo/hoot-dom";
import { contains, defineModels, fields, models, mountView } from "@web/../tests/web_test_helpers";
class Survey extends models.Model {
question_and_page_ids = fields.One2many({ relation: "survey_question" });
_records = [
{
id: 1,
question_and_page_ids: [1, 2],
},
];
}
class SurveyQuestion extends models.Model {
_name = "survey_question";
title = fields.Char();
sequence = fields.Integer();
triggering_question_ids = fields.Many2many({
string: "Triggering question",
relation: "survey_question",
required: false,
});
triggering_answer_ids = fields.Many2many({
string: "Triggering answers",
relation: "survey_question_answer",
required: false,
});
_records = [
{
id: 1,
sequence: 1,
title: "Question 1",
},
{
id: 2,
sequence: 2,
title: "Question 2",
triggering_question_ids: [1],
triggering_answer_ids: [1],
},
];
_views = {
form: /* xml */ `
<form>
<group>
<field name="title"/>
<field name="triggering_question_ids" invisible="1"/>
<field name="triggering_answer_ids" invisible="1" widget="many2many_tags"/>
</group>
</form>
`,
};
}
class SurveyQuestionAnswer extends models.Model {
_name = "survey_question_answer";
name = fields.Char();
_records = [
{
id: 1,
name: "Question 1: Answer 1",
},
];
}
defineModels([Survey, SurveyQuestion, SurveyQuestionAnswer]);
defineMailModels();
test("dynamic rendering of surveyQuestionTriggerError rows", async () => {
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<field name="question_and_page_ids">
<list>
<field name="sequence" widget="handle"/>
<field name="title"/>
<field name="triggering_question_ids" invisible="1"/>
<field name="triggering_answer_ids" invisible="1" widget="many2many_tags"/> <!-- widget to fetch display_name -->
<widget name="survey_question_trigger"/>
</list>
</field>
</form>
`,
});
const firstDataRow = ".o_data_row:eq(0)";
const secondDataRow = ".o_data_row:eq(1)";
const q1TriggerDiv = `${firstDataRow} td.o_data_cell div.o_widget_survey_question_trigger`;
const q2TriggerDiv = `${secondDataRow} td.o_data_cell div.o_widget_survey_question_trigger`;
expect(".o_field_x2many .o_list_renderer table.o_list_table").toHaveCount(1);
expect(".o_data_row").toHaveCount(2);
expect(firstDataRow).toHaveText("Question 1");
expect(`${q1TriggerDiv} button`).toHaveCount(0);
expect(secondDataRow).toHaveText("Question 2");
expect(`${q2TriggerDiv} button`).toHaveCount(1);
// Question 2 is correctly placed after Question 1
expect(`${q2TriggerDiv} button i`).not.toHaveClass("text-warning");
expect(`${q2TriggerDiv} button i`).toHaveAttribute(
"data-tooltip",
'Displayed if "Question 1: Answer 1".',
{ message: "Trigger tooltip should be 'Displayed if \"Question 1: Answer 1\".'." }
);
// drag and drop Question 2 (triggered) before Question 1 (trigger)
await contains("tbody tr:nth-child(2) .o_handle_cell").dragAndDrop("tbody tr:nth-child(1)");
await animationFrame();
expect(firstDataRow).toHaveText("Question 2");
expect(`${q1TriggerDiv} button`).toHaveCount(1);
expect(`${q1TriggerDiv} button i`).toHaveClass("text-warning");
expect(`${q1TriggerDiv} button i`).toHaveAttribute(
"data-tooltip",
'⚠ Triggers based on the following questions will not work because they are positioned after this question:\n"Question 1".',
{ message: "Trigger tooltip should have been changed to misplacement error message." }
);
// drag and drop Question 1 (trigger) back before Question 2 (triggered)
await contains("tbody tr:nth-child(2) .o_handle_cell").dragAndDrop("tbody tr:nth-child(1)");
await animationFrame();
expect(".o_data_row:eq(1)").toHaveText("Question 2");
expect(`${q2TriggerDiv} button i`).not.toHaveClass("text-warning");
expect(`${q2TriggerDiv} button i`).toHaveAttribute(
"data-tooltip",
'Displayed if "Question 1: Answer 1".',
{ message: "Trigger tooltip should be back to 'Displayed if \"Question 1: Answer 1\".'." }
);
});

View file

@ -0,0 +1,125 @@
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
import { expect, test } from "@odoo/hoot";
import { animationFrame, manuallyDispatchProgrammaticEvent, queryOne } from "@odoo/hoot-dom";
import {
contains,
defineModels,
fields,
models,
mountView,
onRpc,
} from "@web/../tests/web_test_helpers";
class Survey extends models.Model {
question_and_page_ids = fields.One2many({ relation: "survey_question" });
session_speed_rating = fields.Boolean({ string: "Speed Reward", onChange: () => {} });
session_speed_rating_time_limit = fields.Integer({
string: "Speed Reward Time (s)",
onChange: () => {},
});
_records = [
{
id: 1,
question_and_page_ids: [1],
session_speed_rating: false,
session_speed_rating_time_limit: 30,
},
];
}
class SurveyQuestion extends models.Model {
_name = "survey_question";
title = fields.Char();
is_time_customized = fields.Boolean({ string: "Is time customized" });
is_time_limited = fields.Boolean({ string: "Is time limited" });
survey_id = fields.Many2one({ relation: "survey", string: "Survey" });
time_limit = fields.Integer({ string: "Time limit (s)" });
_records = [
{
id: 1,
is_time_customized: false,
is_time_limited: false,
survey_id: 1,
time_limit: 30,
title: "Question 1",
},
];
}
defineModels([Survey, SurveyQuestion]);
defineMailModels();
test("Auto update of is_time_customized", async () => {
await mountView({
type: "form",
resModel: "survey",
resId: 1,
arch: `
<form>
<group>
<field name="session_speed_rating"/>
<field name="session_speed_rating_time_limit"/>
</group>
<field name="question_and_page_ids" no-label="1" mode="list">
<list>
<field name="title"/>
<field name="is_time_limited"/>
<field name="time_limit"/>
<field name="is_time_customized"/>
</list>
<form>
<group>
<field name="is_time_customized"/>
<field name="is_time_limited" readonly="0"
widget="boolean_update_flag"
options="{'flagFieldName': 'is_time_customized'}"
context="{'referenceValue': parent.session_speed_rating}"/>
<field name="time_limit" readonly="0"
widget="integer_update_flag"
options="{'flagFieldName': 'is_time_customized'}"
context="{'referenceValue': parent.session_speed_rating_time_limit}"/>
</group>
</form>
</field>
</form>
`,
});
onRpc("survey", "onchange", () => ({
value: { question_and_page_ids: [[1, 1, { is_time_customized: false }]] },
}));
// Open question
await contains("tr.o_data_row > td.o_list_char").click();
expect("div[name='is_time_customized'] input").not.toBeChecked();
// set question "is_time_limited" => true
await contains("div[name='is_time_limited'] input").click();
await animationFrame();
expect("div[name='is_time_customized'] input").toBeChecked(); // widget-triggered update to `true` based on `is_time_limited`
// save question
await contains("div.modal-dialog button.o_form_button_save").click();
await animationFrame();
// set survey "session_speed_rating" => true
await contains("div[name='session_speed_rating'] input").click();
await animationFrame();
// check that questions "is_time_limited" === true and "is_time_customized" === false after survey onchange
expect("td.o_field_cell[name='is_time_limited'] input").toBeChecked();
expect("td.o_field_cell[name='is_time_customized'] input").not.toBeChecked();
// Open question again
await contains("tr.o_data_row > td.o_list_char").click();
await contains("div[name='time_limit'] input").edit(20);
// TODO: JUM (events concurrency)
await manuallyDispatchProgrammaticEvent(queryOne("div[name='time_limit'] input"), "change");
await animationFrame();
expect("div[name='is_time_customized'] input").toBeChecked(); // widget-triggered update to `true` based on `time_limit`
await contains("div[name='time_limit'] input").edit(30);
// TODO: JUM (events concurrency)
await manuallyDispatchProgrammaticEvent(queryOne("div[name='time_limit'] input"), "change");
await animationFrame();
expect("div[name='is_time_customized'] input").not.toBeChecked(); // widget-triggered update to `false` based on `time_limit`
// set question "is_time_limited" => false
await contains("div[name='is_time_limited'] input").click();
await animationFrame();
expect("div[name='is_time_customized'] input").toBeChecked(); // widget-triggered update to `false` based on `is_time_limited`
});