Eliminate Python dependency: embed frontend assets in odoo-go
- Copy all OWL frontend assets (JS/CSS/XML/fonts/images) into frontend/ directory (2925 files, 43MB) — no more runtime reads from Python Odoo - Replace OdooAddonsPath config with FrontendDir pointing to local frontend/ - Rewire bundle.go, static.go, templates.go, webclient.go to read from frontend/ instead of external Python Odoo addons directory - Auto-detect frontend/ and build/ dirs relative to binary in main.go - Delete obsolete Python helper scripts (tools/*.py) The Go server is now fully self-contained: single binary + frontend/ folder. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
126
frontend/account/static/tests/account_move_form.test.js
Normal file
126
frontend/account/static/tests/account_move_form.test.js
Normal file
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
click,
|
||||
insertText,
|
||||
openFormView,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import { asyncStep, contains, defineModels, fields, onRpc, models, waitForSteps} from "@web/../tests/web_test_helpers";
|
||||
import { defineAccountModels } from "./account_test_helpers";
|
||||
|
||||
defineAccountModels();
|
||||
|
||||
test("When I switch tabs, it saves", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const accountMove = pyEnv["account.move"].create({ name: "move0" });
|
||||
await start();
|
||||
onRpc("account.move", "web_save", () => {
|
||||
asyncStep("tab saved");
|
||||
});
|
||||
await openFormView("account.move", accountMove, {
|
||||
arch: `<form js_class='account_move_form'>
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page id="invoice_tab" name="invoice_tab" string="Invoice Lines">
|
||||
<field name="name"/>
|
||||
</page>
|
||||
<page id="aml_tab" string="Journal Items" name="aml_tab"></page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
await insertText("[name='name'] input", "somebody save me!");
|
||||
triggerHotkey("Enter");
|
||||
await click('a[name="aml_tab"]');
|
||||
await waitForSteps(["tab saved"]);
|
||||
});
|
||||
|
||||
test("Confirmation dialog on delete contains a warning", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const accountMove = pyEnv["account.move"].create({ name: "move0" });
|
||||
await start();
|
||||
onRpc("account.move", "check_move_sequence_chain", () => {
|
||||
return false;
|
||||
});
|
||||
await openFormView("account.move", accountMove, {
|
||||
arch: `<form js_class='account_move_form'>
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page id="invoice_tab" name="invoice_tab" string="Invoice Lines">
|
||||
<field name="name"/>
|
||||
</page>
|
||||
<page id="aml_tab" string="Journal Items" name="aml_tab"></page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
await contains(".o_cp_action_menus button").click();
|
||||
await contains(".o_menu_item:contains(Delete)").click();
|
||||
expect(".o_dialog div.text-danger").toHaveText("This operation will create a gap in the sequence.", {
|
||||
message: "warning message has been added in the dialog"
|
||||
});
|
||||
});
|
||||
class AccountMove extends models.Model {
|
||||
line_ids = fields.One2many({
|
||||
string: "Invoice Lines",
|
||||
relation: "account.move.line",
|
||||
})
|
||||
|
||||
_records = [{ id: 1, name: "account.move" }]
|
||||
}
|
||||
class AccountMoveLine extends models.Model {
|
||||
name = fields.Char();
|
||||
product_id = fields.Many2one({
|
||||
string:"Product",
|
||||
relation:"product",
|
||||
});
|
||||
move_id = fields.Many2one({
|
||||
string: "Account Move",
|
||||
relation: "account.move",
|
||||
})
|
||||
}
|
||||
class Product extends models.Model {
|
||||
name = fields.Char();
|
||||
_records = [{ id: 1, name: "testProduct" }];
|
||||
}
|
||||
|
||||
defineModels({ Product, AccountMoveLine, AccountMove });
|
||||
|
||||
test("Update description on product line", async() => {
|
||||
const pyEnv = await startServer();
|
||||
const productId = pyEnv["product"].browse([1]);
|
||||
const accountMove = pyEnv["account.move"].browse([1]);
|
||||
pyEnv["account.move"].write([accountMove[0].id], {
|
||||
invoice_line_ids: [[0, 0, { name: productId[0].name, product_id: productId[0].id }]],
|
||||
});
|
||||
await start();
|
||||
onRpc("account.move", "web_save", () => { asyncStep("save")});
|
||||
await openFormView("account.move", accountMove[0].id, {
|
||||
arch: `<form js_class="account_move_form">
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page id="invoice_tab" name="invoice_tab" string="Invoice Lines">
|
||||
<field name="invoice_line_ids" mode="list" widget="product_label_section_and_note_field_o2m">
|
||||
<list name="journal_items" editable="bottom" string="Journal Items">
|
||||
<field name="product_id" widget="product_label_section_and_note_field" readonly="0"/>
|
||||
<field name="name" widget="section_and_note_text" optional="show"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await click(".o_many2one");
|
||||
await contains("#labelVisibilityButtonId").click()
|
||||
await insertText("textarea[placeholder='Enter a description']", "testDescription");
|
||||
await click(".o_form_button_save");
|
||||
await waitForSteps(["save"]);
|
||||
|
||||
const line = pyEnv["account.move.line"].browse([1])[0];
|
||||
expect(line.name).toBe("testProduct\ntestDescription");
|
||||
|
||||
});
|
||||
13
frontend/account/static/tests/account_test_helpers.js
Normal file
13
frontend/account/static/tests/account_test_helpers.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { AccountMove } from "./mock_server/mock_models/account_move";
|
||||
import { AccountMoveLine } from "./mock_server/mock_models/account_move_line";
|
||||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { defineModels } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export const accountModels = {
|
||||
AccountMove,
|
||||
AccountMoveLine,
|
||||
};
|
||||
|
||||
export function defineAccountModels() {
|
||||
return defineModels({ ...mailModels, ...accountModels });
|
||||
}
|
||||
188
frontend/account/static/tests/account_widgets.test.js
Normal file
188
frontend/account/static/tests/account_widgets.test.js
Normal file
@@ -0,0 +1,188 @@
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { setInputFiles } from "@odoo/hoot-dom";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
mockService,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
class Partner extends models.Model {
|
||||
name = fields.Char();
|
||||
type = fields.Char();
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 7,
|
||||
name: "first record",
|
||||
type: "purchase",
|
||||
},
|
||||
];
|
||||
_views = {
|
||||
form: `
|
||||
<form>
|
||||
<widget name="account_file_uploader"/>
|
||||
<field name="name" required="1"/>
|
||||
</form>
|
||||
`,
|
||||
list: `
|
||||
<list>
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
`,
|
||||
search: `<search/>`,
|
||||
};
|
||||
}
|
||||
|
||||
class AccountPaymentTerm extends models.Model {
|
||||
_name = "account_payment_term";
|
||||
|
||||
line_ids = fields.One2many({
|
||||
string: "Payment Term Lines",
|
||||
relation: "account_payment_term_line",
|
||||
});
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
line_ids: [1, 2],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
class AccountPaymentTermLine extends models.Model {
|
||||
_name = "account_payment_term_line";
|
||||
|
||||
value_amount = fields.Float({ string: "Due" });
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
value_amount: 0,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value_amount: 50,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([AccountPaymentTerm, AccountPaymentTermLine, Partner]);
|
||||
defineMailModels();
|
||||
|
||||
describe("AccountFileUploader", () => {
|
||||
test("widget contains context based on the record despite field not in view", async () => {
|
||||
onRpc("ir.attachment", "create", () => {
|
||||
expect.step("create ir.attachment");
|
||||
return [99];
|
||||
});
|
||||
|
||||
onRpc("account.journal", "create_document_from_attachment", ({ kwargs }) => {
|
||||
expect.step("create_document_from_attachment");
|
||||
expect(kwargs.context.default_journal_id).toBe(7, {
|
||||
message: "create documents in correct journal",
|
||||
});
|
||||
expect(kwargs.context.default_move_type).toBe("in_invoice", {
|
||||
message: "create documents with correct move type",
|
||||
});
|
||||
return {
|
||||
name: "Generated Documents",
|
||||
domain: [],
|
||||
res_model: "partner",
|
||||
type: "ir.actions.act_window",
|
||||
context: {},
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
view_mode: "list, form",
|
||||
};
|
||||
});
|
||||
mockService("action", {
|
||||
doAction(action) {
|
||||
expect.step("doAction");
|
||||
expect(action.type).toBe("ir.actions.act_window", {
|
||||
message: "do action after documents created",
|
||||
});
|
||||
},
|
||||
});
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 7,
|
||||
});
|
||||
|
||||
expect(".o_widget_account_file_uploader").toHaveCount(1);
|
||||
const file = new File(["test"], "fake_file.txt", { type: "text/plain" });
|
||||
await contains(".o_widget_account_file_uploader a").click();
|
||||
await setInputFiles([file]);
|
||||
await expect.waitForSteps([
|
||||
"create ir.attachment",
|
||||
"create_document_from_attachment",
|
||||
"doAction",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AccountMoveUploadKanbanView", () => {
|
||||
test.tags("desktop");
|
||||
test("can render AccountMoveUploadKanbanView", async () => {
|
||||
Partner._views.kanban = `
|
||||
<kanban js_class="account_documents_kanban">
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<field name="name"/>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
`;
|
||||
onRpc("res.company", "search_read", () => [{ id: 1, country_code: "US" }]);
|
||||
await mountView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
});
|
||||
|
||||
expect(".o_control_panel .o_button_upload_bill:visible").toHaveCount(1);
|
||||
expect(".o_kanban_record:not(.o_kanban_ghost)").toHaveCount(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PaymentTermsLineWidget", () => {
|
||||
test("records don't get abandoned after clicking globally or on an exisiting record", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "account_payment_term",
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="line_ids" widget="payment_term_line_ids">
|
||||
<list string="Payment Terms" editable="top">
|
||||
<field name="value_amount"/>
|
||||
|
||||
</list>
|
||||
</field>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
expect(".o_data_row").toHaveCount(2);
|
||||
// click the add button
|
||||
await contains(".o_field_x2many_list_row_add > a").click();
|
||||
// make sure the new record is added
|
||||
expect(".o_data_row").toHaveCount(3);
|
||||
// global click
|
||||
await contains(".o_form_view").click();
|
||||
// make sure the new record is still there
|
||||
expect(".o_data_row").toHaveCount(3);
|
||||
// click the add button again
|
||||
await contains(".o_field_x2many_list_row_add > a").click();
|
||||
// make sure the new record is added
|
||||
expect(".o_data_row").toHaveCount(4);
|
||||
// click on an existing record
|
||||
await contains(".o_data_row .o_data_cell").click();
|
||||
// make sure the new record is still there
|
||||
expect(".o_data_row").toHaveCount(4);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import { queryFirst } from "@odoo/hoot-dom";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fieldInput,
|
||||
fields,
|
||||
models,
|
||||
mountView,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { defineAccountModels } from "./account_test_helpers";
|
||||
|
||||
class Account extends models.Model {
|
||||
_name = "account.account";
|
||||
_inherit = [];
|
||||
|
||||
code = fields.Char({
|
||||
string: "Code",
|
||||
trim: true,
|
||||
});
|
||||
placeholder_code = fields.Char();
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
placeholder_code: "Placeholder Code",
|
||||
},
|
||||
];
|
||||
|
||||
_views = {
|
||||
list: /* xml */ `
|
||||
<list editable="top" create="1" delete="1">
|
||||
<field name="placeholder_code" column_invisible="1" />
|
||||
<field name="code" widget="char_with_placeholder_field" options="{'placeholder_field': 'placeholder_code'}" />
|
||||
</list>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
defineAccountModels();
|
||||
defineModels([Account]);
|
||||
test.tags("desktop");
|
||||
test("List: placeholder_field shows as text/placeholder", async () => {
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "account.account",
|
||||
});
|
||||
|
||||
const firstCellSelector = "tbody td:not(.o_list_record_selector):first";
|
||||
|
||||
expect(`${firstCellSelector} span`).toHaveText("Placeholder Code", {
|
||||
message: "placeholder_field should be the text value",
|
||||
});
|
||||
|
||||
expect(`${firstCellSelector} span`).toHaveClass("text-muted", {
|
||||
message: "placeholder_field should be greyed out",
|
||||
});
|
||||
|
||||
await contains(firstCellSelector).click();
|
||||
expect(queryFirst(firstCellSelector).parentElement).toHaveClass("o_selected_row", {
|
||||
message: "should be set as edit mode",
|
||||
});
|
||||
expect(`${firstCellSelector} input`).toHaveValue("", {
|
||||
message: "once in edit mode, should have no value in input",
|
||||
});
|
||||
expect(`${firstCellSelector} input`).toHaveAttribute("placeholder", "Placeholder Code", {
|
||||
message: "once in edit mode, should have placeholder_field as placeholder",
|
||||
});
|
||||
await fieldInput("code").edit("100001", { confirm: false });
|
||||
|
||||
await contains(".o_list_button_save").click();
|
||||
expect(firstCellSelector).toHaveText("100001", {
|
||||
message: "entered value should be saved",
|
||||
});
|
||||
expect(firstCellSelector).not.toHaveClass("text-muted", {
|
||||
message: "field should not be greyed out",
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountMove extends models.ServerModel {
|
||||
_name = "account.move";
|
||||
|
||||
get_extra_print_items() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountMoveLine extends models.ServerModel {
|
||||
_name = "account.move.line";
|
||||
}
|
||||
1515
frontend/account/static/tests/section_and_note.test.js
Normal file
1515
frontend/account/static/tests/section_and_note.test.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
import { addSectionFromProductCatalog, showProductColumn } from "@account/js/tours/tour_utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("test_use_product_catalog_on_invoice", {
|
||||
steps: () => [
|
||||
{
|
||||
content: "Click Catalog Button",
|
||||
trigger: "button[name=action_add_from_catalog]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Add a Product",
|
||||
trigger: ".o_kanban_record:contains(Test Product)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait for it",
|
||||
trigger: ".o_product_added",
|
||||
},
|
||||
{
|
||||
content: "Back to Invoice",
|
||||
trigger: ".o-kanban-button-back",
|
||||
run: "click",
|
||||
},
|
||||
...showProductColumn(),
|
||||
{
|
||||
content: "Ensure product is added",
|
||||
trigger: ".o_field_product_label_section_and_note_cell:contains(Test Product)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add('test_add_section_from_product_catalog_on_invoice', {
|
||||
steps: () => addSectionFromProductCatalog()
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { registry } from '@web/core/registry';
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
registry.category("web_tour.tours").add("deductible_amount_column", {
|
||||
url: "/odoo/vendor-bills/new",
|
||||
steps: () => [
|
||||
{
|
||||
content: "Add item",
|
||||
trigger: "div[name='invoice_line_ids'] .o_field_x2many_list_row_add a:contains('Add a line')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Edit name",
|
||||
trigger: ".o_field_widget[name='name'] .o_input",
|
||||
run: "edit Laptop"
|
||||
},
|
||||
{
|
||||
content: "Edit deductible amount",
|
||||
trigger: ".o_field_widget[name='deductible_amount'] > .o_input",
|
||||
run: "edit 80"
|
||||
},
|
||||
{
|
||||
content: "Set Bill Date",
|
||||
trigger: "input[data-field=invoice_date]",
|
||||
run: "edit 2025-12-01",
|
||||
},
|
||||
...stepUtils.saveForm(),
|
||||
]})
|
||||
147
frontend/account/static/tests/tours/tax_group_tests.js
Normal file
147
frontend/account/static/tests/tours/tax_group_tests.js
Normal file
@@ -0,0 +1,147 @@
|
||||
import { accountTourSteps } from "@account/js/tours/account";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
registry.category("web_tour.tours").add('account_tax_group', {
|
||||
url: "/odoo",
|
||||
steps: () => [
|
||||
...accountTourSteps.goToAccountMenu("Go to Invoicing"),
|
||||
{
|
||||
content: "Go to Vendors",
|
||||
trigger: 'span:contains("Vendors")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Go to Bills",
|
||||
trigger: 'a:contains("Bills")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_breadcrumb .text-truncate:contains(Bills)",
|
||||
},
|
||||
{
|
||||
content: "Create new bill",
|
||||
trigger: '.o_control_panel_main_buttons .o_list_button_add',
|
||||
run: "click",
|
||||
},
|
||||
// Set a vendor
|
||||
{
|
||||
content: "Add vendor",
|
||||
trigger: 'div.o_field_widget.o_field_res_partner_many2one[name="partner_id"] div input',
|
||||
run: "edit Account Tax Group Partner",
|
||||
},
|
||||
{
|
||||
content: "Valid vendor",
|
||||
trigger: '.ui-menu-item a:contains("Account Tax Group Partner")',
|
||||
run: "click",
|
||||
},
|
||||
// Show product column
|
||||
{
|
||||
content: "Open line fields list",
|
||||
trigger: ".o_optional_columns_dropdown_toggle",
|
||||
run: "click"
|
||||
},
|
||||
{
|
||||
content: "Show product column",
|
||||
trigger: '.o-dropdown-item input[name="product_id"]',
|
||||
run: "click"
|
||||
},
|
||||
{
|
||||
content: "Close line fields list",
|
||||
trigger: ".o_optional_columns_dropdown_toggle",
|
||||
run: "click"
|
||||
},
|
||||
// Add First product
|
||||
{
|
||||
content: "Add items",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_field_x2many_list_row_add a:contains("Add a line")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Select input",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_selected_row .o_list_many2one[name="product_id"] input',
|
||||
run: "edit Account Tax Group Product",
|
||||
},
|
||||
{
|
||||
content: "Valid item",
|
||||
trigger: '.ui-menu-item-wrapper:contains("Account Tax Group Product")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Set Bill Date",
|
||||
trigger: "input[data-field=invoice_date]",
|
||||
run: "edit 2025-12-01",
|
||||
},
|
||||
// Save account.move
|
||||
...stepUtils.saveForm(),
|
||||
// Edit tax group amount
|
||||
{
|
||||
content: "Edit tax group amount",
|
||||
trigger: '.o_tax_group_edit',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Modify the input value",
|
||||
trigger: '.o_tax_group_edit_input input',
|
||||
run() {
|
||||
this.anchor.value = 200;
|
||||
this.anchor.select();
|
||||
this.anchor.blur();
|
||||
},
|
||||
},
|
||||
// Check new value for total (with modified tax_group_amount).
|
||||
{
|
||||
content: "Valid total amount",
|
||||
trigger: 'span[name="amount_total"]:contains("800")',
|
||||
run: "click",
|
||||
},
|
||||
// Modify the quantity of the object
|
||||
{
|
||||
content: "Select item quantity",
|
||||
trigger: 'div[name="invoice_line_ids"] tbody tr.o_data_row .o_list_number[name="quantity"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Change item quantity",
|
||||
trigger: 'div[name="invoice_line_ids"] tbody tr.o_data_row .o_list_number[name="quantity"] input',
|
||||
run: "edit 2",
|
||||
},
|
||||
{
|
||||
content: "Valid the new value",
|
||||
trigger: 'div[name="invoice_line_ids"] tbody tr.o_data_row .o_list_number[name="quantity"] input',
|
||||
run: "press Enter",
|
||||
},
|
||||
// Check new tax group value
|
||||
{
|
||||
content: "Check new value of tax group",
|
||||
trigger: '.o_tax_group_amount_value:contains("120")',
|
||||
run: "click",
|
||||
},
|
||||
// Save form
|
||||
...stepUtils.saveForm(),
|
||||
// Check new tax group value
|
||||
{
|
||||
content: "Check new value of tax group",
|
||||
trigger: '.o_tax_group_amount_value:contains("120")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Edit tax value",
|
||||
trigger: '.o_tax_group_edit_input input',
|
||||
run: "edit 2 && click body",
|
||||
},
|
||||
{
|
||||
content: "Check new value of total",
|
||||
trigger: '.oe_subtotal_footer_separator:contains("1,202")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Discard changes",
|
||||
trigger: '.o_form_button_cancel',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Check tax value is reset",
|
||||
trigger: '.o_tax_group_amount_value:contains("120")',
|
||||
},
|
||||
]});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add('tests_shared_js_python', {
|
||||
url: "/account/init_tests_shared_js_python",
|
||||
steps: () => [
|
||||
{
|
||||
content: "Click",
|
||||
trigger: 'button',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait",
|
||||
trigger: 'button.text-success',
|
||||
timeout: 3000,
|
||||
},
|
||||
]});
|
||||
138
frontend/account/static/tests/x2many_buttons.test.js
Normal file
138
frontend/account/static/tests/x2many_buttons.test.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
mockService,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
class AccountMove extends models.Model {
|
||||
_name = "account.move";
|
||||
|
||||
name = fields.Char();
|
||||
duplicated_ref_ids = fields.Many2many({
|
||||
string: "Duplicated Bills",
|
||||
relation: "account.move",
|
||||
});
|
||||
ref = fields.Char({ string: "Bill Reference" });
|
||||
|
||||
_records = [
|
||||
// for the sake of mocking data, we don't care about the consistency of duplicated refs across records
|
||||
{ id: 1, display_name: "Bill 1", duplicated_ref_ids: [2, 3], ref: "b1" },
|
||||
{ id: 2, display_name: "Bill 2", duplicated_ref_ids: [1], ref: "b2" },
|
||||
{ id: 3, display_name: "Bill 3", duplicated_ref_ids: [1], ref: "b3" },
|
||||
{ id: 4, display_name: "Bill 4", duplicated_ref_ids: [1, 2, 3], ref: "b4" },
|
||||
{ id: 5, display_name: "Bill 5", duplicated_ref_ids: [], ref: "b5" },
|
||||
{ id: 6, display_name: "Bill 6", duplicated_ref_ids: [1, 2, 3, 4, 5], ref: "b6" },
|
||||
];
|
||||
|
||||
_views = {
|
||||
form: `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="ref"/>
|
||||
<field name="duplicated_ref_ids" widget="x2many_buttons"/>
|
||||
</form>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
defineModels([AccountMove]);
|
||||
defineMailModels();
|
||||
|
||||
test("component rendering: less than 3 records on field", async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 1,
|
||||
type: "form",
|
||||
});
|
||||
expect(".o_field_x2many_buttons").toHaveCount(1);
|
||||
expect(".o_field_x2many_buttons button").toHaveCount(2);
|
||||
});
|
||||
|
||||
test("component rendering: exactly 3 records on field", async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 4,
|
||||
type: "form",
|
||||
});
|
||||
expect(".o_field_x2many_buttons").toHaveCount(1);
|
||||
expect(".o_field_x2many_buttons button").toHaveCount(3);
|
||||
});
|
||||
|
||||
test("component rendering: more than 3 records on field", async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 6,
|
||||
type: "form",
|
||||
});
|
||||
expect(".o_field_x2many_buttons").toHaveCount(1);
|
||||
expect(".o_field_x2many_buttons button").toHaveCount(4);
|
||||
expect(".o_field_x2many_buttons button:eq(3)").toHaveText("... (View all)");
|
||||
});
|
||||
|
||||
test("edit record and check if edits get discarded when click on one of the buttons and redirects to proper record", async () => {
|
||||
onRpc("account.move", "action_open_business_doc", ({ args }) => {
|
||||
expect.step("action_open_business_doc");
|
||||
expect(args.length).toBe(1);
|
||||
expect(args[0]).toBe(2);
|
||||
return {
|
||||
res_model: "account.move",
|
||||
res_id: 2,
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "form"]],
|
||||
};
|
||||
});
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 1,
|
||||
type: "form",
|
||||
});
|
||||
|
||||
await contains("[name='ref'] input").edit("new ref");
|
||||
expect("[name='ref'] input").toHaveValue("new ref");
|
||||
await contains(".o_field_x2many_buttons button").click();
|
||||
expect("[name='ref'] input").toHaveValue("b1");
|
||||
expect.verifySteps(["action_open_business_doc"]);
|
||||
});
|
||||
|
||||
// test if clicking on last button redirects to records in list view
|
||||
test("redirect to list view and discards edits when clicking on last button with more than 3 records on field", async () => {
|
||||
expect.assertions(3);
|
||||
mockService("action", {
|
||||
doAction(action) {
|
||||
expect(action).toEqual({
|
||||
domain: [["id", "in", [1, 2, 3, 4, 5]]],
|
||||
name: "Duplicated Bills",
|
||||
res_model: "account.move",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
context: {
|
||||
list_view_ref: "account.view_duplicated_moves_tree_js",
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 6,
|
||||
type: "form",
|
||||
});
|
||||
await contains("[name='ref'] input").edit("new ref");
|
||||
expect("[name='ref'] input").toHaveValue("new ref");
|
||||
await contains(".o_field_x2many_buttons button:eq(3)").click();
|
||||
expect("[name='ref'] input").toHaveValue("b6");
|
||||
});
|
||||
Reference in New Issue
Block a user