/** @odoo-module **/ import { browser } from "@web/core/browser/browser"; import { registry } from "@web/core/registry"; import { editView } from "@web/views/debug_items"; import { clearUncommittedChanges } from "@web/webclient/actions/action_service"; import { listView } from "@web/views/list/list_view"; import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog"; import { useSetupAction } from "@web/webclient/actions/action_hook"; import testUtils from "web.test_utils"; import { session } from "@web/session"; import { click, getFixture, editInput, getNodesTextContent, legacyExtraNextTick, makeDeferred, nextTick, patchWithCleanup, clickSave, } from "../../helpers/utils"; import { createWebClient, doAction, getActionManagerServerData, loadState } from "./../helpers"; import { errorService } from "../../../src/core/errors/error_service"; import { RPCError } from "@web/core/network/rpc_service"; import { registerCleanup } from "../../helpers/cleanup"; import { WarningDialog } from "@web/core/errors/error_dialogs"; import { makeFakeUserService, fakeCookieService } from "@web/../tests/helpers/mock_services"; import * as cpHelpers from "@web/../tests/search/helpers"; import { onMounted } from "@odoo/owl"; let serverData; let target; const serviceRegistry = registry.category("services"); QUnit.module("ActionManager", (hooks) => { hooks.beforeEach(() => { serverData = getActionManagerServerData(); target = getFixture(); }); QUnit.module("Window Actions"); QUnit.test("can execute act_window actions from db ID", async function (assert) { assert.expect(7); const mockRPC = async (route, args) => { assert.step((args && args.method) || route); }; const webClient = await createWebClient({ serverData, mockRPC }); await doAction(webClient, 1); assert.containsOnce( document.body, ".o_control_panel", "should have rendered a control panel" ); assert.containsOnce(target, ".o_kanban_view", "should have rendered a kanban view"); assert.verifySteps([ "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", ]); }); QUnit.test("sidebar is present in list view", async function (assert) { assert.expect(4); serverData.models.partner.toolbar = { print: [{ name: "Print that record" }], }; const mockRPC = async (route, args) => { if (args && args.method === "get_views") { assert.strictEqual( args.kwargs.options.toolbar, true, "should ask for toolbar information" ); } }; const webClient = await createWebClient({ serverData, mockRPC }); await doAction(webClient, 3); assert.containsNone(target, ".o_cp_action_menus"); await click(target.querySelector("input.form-check-input")); assert.isVisible( $(target).find('.o_cp_action_menus button.dropdown-toggle:contains("Print")')[0] ); assert.isVisible( $(target).find('.o_cp_action_menus button.dropdown-toggle:contains("Action")')[0] ); }); QUnit.test("can switch between views", async function (assert) { assert.expect(19); const mockRPC = async (route, args) => { assert.step((args && args.method) || route); }; const webClient = await createWebClient({ serverData, mockRPC }); await doAction(webClient, 3); assert.containsOnce(target, ".o_list_view", "should display the list view"); // switch to kanban view await cpHelpers.switchView(target, "kanban"); await legacyExtraNextTick(); assert.containsNone(target, ".o_list_view", "should no longer display the list view"); assert.containsOnce(target, ".o_kanban_view", "should display the kanban view"); // switch back to list view await cpHelpers.switchView(target, "list"); await legacyExtraNextTick(); assert.containsOnce(target, ".o_list_view", "should display the list view"); assert.containsNone(target, ".o_kanban_view", "should no longer display the kanban view"); // open a record in form view await testUtils.dom.click(target.querySelector(".o_list_view .o_data_cell")); await legacyExtraNextTick(); assert.containsNone(target, ".o_list_view", "should no longer display the list view"); assert.containsOnce(target, ".o_form_view", "should display the form view"); assert.strictEqual( $(target).find(".o_field_widget[name=foo] input").val(), "yop", "should have opened the correct record" ); // go back to list view using the breadcrumbs await testUtils.dom.click(target.querySelector(".o_control_panel .breadcrumb a")); await legacyExtraNextTick(); assert.containsOnce(target, ".o_list_view", "should display the list view"); assert.containsNone(target, ".o_form_view", "should no longer display the form view"); assert.verifySteps([ "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", "web_search_read", "web_search_read", "read", "web_search_read", ]); }); QUnit.test("switching into a view with mode=edit lands in edit mode", async function (assert) { serverData.views["partner,1,kanban"] = `
`; serverData.actions[1] = { id: 1, xml_id: "action_1", name: "Partners Action 1 patched", res_model: "partner", type: "ir.actions.act_window", views: [ [false, "kanban"], [false, "form"], ], }; const mockRPC = async (route, args) => { assert.step((args && args.method) || route); }; const webClient = await createWebClient({ serverData, mockRPC }); await doAction(webClient, 1); assert.containsOnce(target, ".o_kanban_view", "should display the kanban view"); // quick create record await testUtils.dom.click(target.querySelector(".o-kanban-button-new")); await editInput(target, ".o_field_widget[name=display_name] input", "New name"); // edit quick-created record await testUtils.dom.click(target.querySelector(".o_kanban_edit")); assert.containsOnce( target, ".o_form_view .o_form_editable", "should display the form view in edit mode" ); assert.verifySteps([ "/web/webclient/load_menus", "/web/action/load", "get_views", "web_read_group", "web_search_read", "web_search_read", "onchange", "name_create", "read", "read", ]); }); QUnit.test( "orderedBy in context is not propagated when executing another action", async function (assert) { assert.expect(6); serverData.models.partner.fields.foo.sortable = true; serverData.views["partner,false,form"] = `
`; serverData.models.partner.filters = [ { id: 1, context: "{}", domain: "[]", sort: "[]", is_default: true, name: "My filter", }, ]; let searchReadCount = 1; const mockRPC = async (route, args) => { if (args.method === "web_search_read") { args = args || {}; if (searchReadCount === 1) { assert.strictEqual(args.model, "partner"); assert.notOk(args.sort); } if (searchReadCount === 2) { assert.strictEqual(args.model, "partner"); assert.strictEqual(args.kwargs.order, "foo ASC"); } if (searchReadCount === 3) { assert.strictEqual(args.model, "pony"); assert.notOk(args.sort); } searchReadCount += 1; } }; const webClient = await createWebClient({ serverData, mockRPC }); await doAction(webClient, 3); // Sort records await click(target.querySelector(".o_list_view th.o_column_sortable")); // Get to the form view of the model, on the first record await click(target.querySelector(".o_data_cell")); // Execute another action by clicking on the button within the form await click(target.querySelector('button[name="8"]')); } ); QUnit.test("breadcrumbs are updated when switching between views", async function (assert) { assert.expect(15); const webClient = await createWebClient({ serverData }); await doAction(webClient, 3); assert.containsOnce( target, ".o_control_panel .breadcrumb-item", "there should be one controller in the breadcrumbs" ); assert.strictEqual( $(target).find(".o_control_panel .breadcrumb-item").text(), "Partners", "breadcrumbs should display the display_name of the action" ); // switch to kanban view await cpHelpers.switchView(target, "kanban"); await legacyExtraNextTick(); assert.containsOnce( target, ".o_control_panel .breadcrumb-item", "there should still be one controller in the breadcrumbs" ); assert.strictEqual( $(target).find(".o_control_panel .breadcrumb-item").text(), "Partners", "breadcrumbs should still display the display_name of the action" ); // open a record in form view await testUtils.dom.click(target.querySelector(".o_kanban_view .o_kanban_record")); await legacyExtraNextTick(); assert.containsN( target, ".o_control_panel .breadcrumb-item", 2, "there should be two controllers in the breadcrumbs" ); assert.strictEqual( $(target).find(".o_control_panel .breadcrumb-item:last").text(), "First record" ); // go back to kanban view using the breadcrumbs await testUtils.dom.click(target.querySelector(".o_control_panel .breadcrumb a")); await legacyExtraNextTick(); assert.containsOnce( target, ".o_control_panel .breadcrumb-item", "there should be one controller in the breadcrumbs" ); assert.strictEqual( $(target).find(".o_control_panel .breadcrumb-item").text(), "Partners", "breadcrumbs should display the display_name of the action" ); // switch back to list view await cpHelpers.switchView(target, "list"); await legacyExtraNextTick(); assert.containsOnce( target, ".o_control_panel .breadcrumb-item", "there should still be one controller in the breadcrumbs" ); assert.strictEqual( $(target).find(".o_control_panel .breadcrumb-item").text(), "Partners", "breadcrumbs should still display the display_name of the action" ); // open a record in form view await testUtils.dom.click(target.querySelector(".o_list_view .o_data_cell")); await legacyExtraNextTick(); assert.containsN( target, ".o_control_panel .breadcrumb-item", 2, "there should be two controllers in the breadcrumbs" ); assert.strictEqual( $(target).find(".o_control_panel .breadcrumb-item:last").text(), "First record" ); // go back to list view using the breadcrumbs await testUtils.dom.click(target.querySelector(".o_control_panel .breadcrumb a")); await legacyExtraNextTick(); assert.containsOnce(target, ".o_list_view", "should be back on list view"); assert.containsOnce( target, ".o_control_panel .breadcrumb-item", "there should be one controller in the breadcrumbs" ); assert.strictEqual( $(target).find(".o_control_panel .breadcrumb-item").text(), "Partners", "breadcrumbs should display the display_name of the action" ); }); QUnit.test("switch buttons are updated when switching between views", async function (assert) { assert.expect(13); const webClient = await createWebClient({ serverData }); await doAction(webClient, 3); assert.containsN( target, ".o_control_panel button.o_switch_view", 2, "should have two switch buttons (list and kanban)" ); assert.containsOnce( target, ".o_control_panel button.o_switch_view.active", "should have only one active button" ); assert.hasClass( target.querySelector(".o_control_panel .o_switch_view"), "o_list", "list switch button should be the first one" ); assert.hasClass( target.querySelector(".o_control_panel .o_switch_view.o_list"), "active", "list should be the active view" ); // switch to kanban view await cpHelpers.switchView(target, "kanban"); await legacyExtraNextTick(); assert.containsN( target, ".o_control_panel .o_switch_view", 2, "should still have two switch buttons (list and kanban)" ); assert.containsOnce( target, ".o_control_panel .o_switch_view.active", "should still have only one active button" ); assert.hasClass( target.querySelector(".o_control_panel .o_switch_view"), "o_list", "list switch button should still be the first one" ); assert.hasClass( target.querySelector(".o_control_panel .o_switch_view.o_kanban"), "active", "kanban should now be the active view" ); // switch back to list view await cpHelpers.switchView(target, "list"); await legacyExtraNextTick(); assert.containsN( target, ".o_control_panel .o_switch_view", 2, "should still have two switch buttons (list and kanban)" ); assert.hasClass( target.querySelector(".o_control_panel .o_switch_view.o_list"), "active", "list should now be the active view" ); // open a record in form view await testUtils.dom.click(target.querySelector(".o_list_view .o_data_cell")); await legacyExtraNextTick(); assert.containsNone( target, ".o_control_panel .o_switch_view", "should not have any switch buttons" ); // go back to list view using the breadcrumbs await testUtils.dom.click($(target).find(".o_control_panel .breadcrumb a")); await legacyExtraNextTick(); assert.containsN( target, ".o_control_panel .o_switch_view", 2, "should have two switch buttons (list and kanban)" ); assert.hasClass( target.querySelector(".o_control_panel .o_switch_view.o_list"), "active", "list should be the active view" ); }); QUnit.test("pager is updated when switching between views", async function (assert) { assert.expect(10); const webClient = await createWebClient({ serverData }); await doAction(webClient, 4); assert.strictEqual( $(target).find(".o_control_panel .o_pager_value").text(), "1-5", "value should be correct for kanban" ); assert.strictEqual( $(target).find(".o_control_panel .o_pager_limit").text(), "5", "limit should be correct for kanban" ); // switch to list view await cpHelpers.switchView(target, "list"); await legacyExtraNextTick(); assert.strictEqual( $(target).find(".o_control_panel .o_pager_value").text(), "1-3", "value should be correct for list" ); assert.strictEqual( $(target).find(".o_control_panel .o_pager_limit").text(), "5", "limit should be correct for list" ); // open a record in form view await testUtils.dom.click(target.querySelector(".o_list_view .o_data_cell")); await legacyExtraNextTick(); assert.strictEqual( $(target).find(".o_control_panel .o_pager_value").text(), "1", "value should be correct for form" ); assert.strictEqual( $(target).find(".o_control_panel .o_pager_limit").text(), "3", "limit should be correct for form" ); // go back to list view using the breadcrumbs await testUtils.dom.click(target.querySelector(".o_control_panel .breadcrumb a")); await legacyExtraNextTick(); assert.strictEqual( $(target).find(".o_control_panel .o_pager_value").text(), "1-3", "value should be correct for list" ); assert.strictEqual( $(target).find(".o_control_panel .o_pager_limit").text(), "5", "limit should be correct for list" ); // switch back to kanban view await cpHelpers.switchView(target, "kanban"); await legacyExtraNextTick(); assert.strictEqual( $(target).find(".o_control_panel .o_pager_value").text(), "1-5", "value should be correct for kanban" ); assert.strictEqual( $(target).find(".o_control_panel .o_pager_limit").text(), "5", "limit should be correct for kanban" ); }); QUnit.test("Props are updated and kept when switching/restoring views", async (assert) => { serverData.views["partner,false,form"] = /* xml */ `
`; const mockRPC = async (_route, { args, method, model }) => { if (method === "get_formview_action") { return { res_id: args[0][0], res_model: model, type: "ir.actions.act_window", views: [[false, "form"]], }; } }; const webClient = await createWebClient({ serverData, mockRPC }); await doAction(webClient, 3); // 5 records initially assert.containsN(target, ".o_data_row", 5); await click(target, ".o_data_row:first-of-type .o_data_cell"); // Open 1 / 5 assert.strictEqual(target.querySelector(".o_field_char input").value, "First record"); assert.deepEqual(cpHelpers.getPagerValue(target), [1]); assert.strictEqual(cpHelpers.getPagerLimit(target), 5); await click(target, ".o_field_many2one .o_external_button"); // Click on M2O -> 1 / 1 assert.strictEqual(target.querySelector(".o_field_char input").value, "Third record"); assert.deepEqual(cpHelpers.getPagerValue(target), [1]); assert.strictEqual(cpHelpers.getPagerLimit(target), 1); await click(target, ".o_back_button"); // Back to 1 / 5 assert.strictEqual(target.querySelector(".o_field_char input").value, "First record"); assert.deepEqual(cpHelpers.getPagerValue(target), [1]); assert.strictEqual(cpHelpers.getPagerLimit(target), 5); await cpHelpers.pagerNext(target); // Next page -> 2 / 5 assert.strictEqual(target.querySelector(".o_field_char input").value, "Second record"); assert.deepEqual(cpHelpers.getPagerValue(target), [2]); assert.strictEqual(cpHelpers.getPagerLimit(target), 5); await click(target, ".o_field_many2one .o_external_button"); // Click on M2O -> still 1 / 1 assert.strictEqual(target.querySelector(".o_field_char input").value, "Third record"); assert.deepEqual(cpHelpers.getPagerValue(target), [1]); assert.strictEqual(cpHelpers.getPagerLimit(target), 1); await click(target, ".o_back_button"); // Back to 2 / 5 assert.strictEqual(target.querySelector(".o_field_char input").value, "Second record"); assert.deepEqual(cpHelpers.getPagerValue(target), [2]); assert.strictEqual(cpHelpers.getPagerLimit(target), 5); }); QUnit.test("domain is kept when switching between views", async function (assert) { assert.expect(5); serverData.actions[3].search_view_id = [4, "a custom search view"]; const webClient = await createWebClient({ serverData }); await doAction(webClient, 3); assert.containsN(target, ".o_data_row", 5); // activate a domain await cpHelpers.toggleFilterMenu(target); await cpHelpers.toggleMenuItem(target, "Bar"); await legacyExtraNextTick(); assert.containsN(target, ".o_data_row", 2); // switch to kanban await cpHelpers.switchView(target, "kanban"); await legacyExtraNextTick(); assert.containsN(target, ".o_kanban_record:not(.o_kanban_ghost)", 2); // remove the domain await testUtils.dom.click(target.querySelector(".o_searchview .o_facet_remove")); await legacyExtraNextTick(); assert.containsN(target, ".o_kanban_record:not(.o_kanban_ghost)", 5); // switch back to list await cpHelpers.switchView(target, "list"); await legacyExtraNextTick(); assert.containsN(target, ".o_data_row", 5); }); QUnit.test("A new form view can be reloaded after a failed one", async function (assert) { assert.expect(5); const webClient = await createWebClient({ serverData }); await doAction(webClient, 3); await cpHelpers.switchView(target, "list"); assert.containsOnce(target, ".o_list_view", "The list view should be displayed"); // Click on the first record await testUtils.dom.click( $(target).find(".o_list_view .o_data_row:first .o_data_cell:first") ); await legacyExtraNextTick(); assert.containsOnce(target, ".o_form_view", "The form view should be displayed"); // Delete the current record await testUtils.controlPanel.toggleActionMenu(target); await testUtils.dom.click( Array.from(document.querySelectorAll(".o_menu_item")).find( (e) => e.textContent === "Delete" ) ); await legacyExtraNextTick(); assert.containsOnce(target, ".modal", "a confirm modal should be displayed"); await testUtils.dom.click(target.querySelector(".modal-footer button.btn-primary")); await legacyExtraNextTick(); // The form view is automatically switched to the next record // Go back to the previous (now deleted) record webClient.env.bus.trigger("test:hashchange", { model: "partner", id: 1, action: 3, view_type: "form", }); await testUtils.nextTick(); await legacyExtraNextTick(); // Go back to the list view webClient.env.bus.trigger("test:hashchange", { model: "partner", action: 3, view_type: "list", }); await testUtils.nextTick(); await legacyExtraNextTick(); assert.containsOnce(target, ".o_list_view", "should still display the list view"); await testUtils.dom.click( $(target).find(".o_list_view .o_data_row:first .o_data_cell:first") ); await legacyExtraNextTick(); assert.containsOnce( target, ".o_form_view", "The form view should still load after a previous failed update | reload" ); }); QUnit.test("there is no flickering when switching between views", async function (assert) { assert.expect(20); let def; const mockRPC = async (route, args) => { await def; }; const webClient = await createWebClient({ serverData, mockRPC }); await doAction(webClient, 3); // switch to kanban view def = testUtils.makeTestPromise(); await cpHelpers.switchView(target, "kanban"); await legacyExtraNextTick(); assert.containsOnce(target, ".o_list_view", "should still display the list view"); assert.containsNone(target, ".o_kanban_view", "shouldn't display the kanban view yet"); def.resolve(); await testUtils.nextTick(); await legacyExtraNextTick(); assert.containsNone(target, ".o_list_view", "shouldn't display the list view anymore"); assert.containsOnce(target, ".o_kanban_view", "should now display the kanban view"); // switch back to list view def = testUtils.makeTestPromise(); await cpHelpers.switchView(target, "list"); await legacyExtraNextTick(); assert.containsOnce(target, ".o_kanban_view", "should still display the kanban view"); assert.containsNone(target, ".o_list_view", "shouldn't display the list view yet"); def.resolve(); await testUtils.nextTick(); await legacyExtraNextTick(); assert.containsNone(target, ".o_kanban_view", "shouldn't display the kanban view anymore"); assert.containsOnce(target, ".o_list_view", "should now display the list view"); // open a record in form view def = testUtils.makeTestPromise(); await click(target.querySelector(".o_list_view .o_data_cell")); await legacyExtraNextTick(); assert.containsOnce(target, ".o_list_view", "should still display the list view"); assert.containsNone(target, ".o_form_view", "shouldn't display the form view yet"); assert.containsOnce( target, ".o_control_panel .breadcrumb-item", "there should still be one controller in the breadcrumbs" ); def.resolve(); await testUtils.nextTick(); await legacyExtraNextTick(); assert.containsNone(target, ".o_list_view", "should no longer display the list view"); assert.containsOnce(target, ".o_form_view", "should display the form view"); assert.containsN( target, ".o_control_panel .breadcrumb-item", 2, "there should be two controllers in the breadcrumbs" ); // go back to list view using the breadcrumbs def = testUtils.makeTestPromise(); await testUtils.dom.click($(target).find(".o_control_panel .breadcrumb a")); await legacyExtraNextTick(); assert.containsOnce(target, ".o_form_view", "should still display the form view"); assert.containsNone(target, ".o_list_view", "shouldn't display the list view yet"); assert.containsN( target, ".o_control_panel .breadcrumb-item", 2, "there should still be two controllers in the breadcrumbs" ); def.resolve(); await testUtils.nextTick(); await legacyExtraNextTick(); assert.containsNone(target, ".o_form_view", "should no longer display the form view"); assert.containsOnce(target, ".o_list_view", "should display the list view"); assert.containsOnce( target, ".o_control_panel .breadcrumb-item", "there should be one controller in the breadcrumbs" ); }); QUnit.test("breadcrumbs are updated when display_name changes", async function (assert) { assert.expect(4); const webClient = await createWebClient({ serverData }); await doAction(webClient, 3); // open a record in form view await click(target.querySelector(".o_list_view .o_data_cell")); await legacyExtraNextTick(); assert.containsN( target, ".o_control_panel .breadcrumb-item", 2, "there should be two controllers in the breadcrumbs" ); assert.strictEqual( $(target).find(".o_control_panel .breadcrumb-item:last").text(), "First record", "breadcrumbs should contain the display_name of the opened record" ); // switch to edit mode and change the display_name await editInput(target, ".o_field_widget[name=display_name] input", "New name"); await clickSave(target); assert.containsN( target, ".o_control_panel .breadcrumb-item", 2, "there should still be two controllers in the breadcrumbs" ); assert.strictEqual( $(target).find(".o_control_panel .breadcrumb-item:last").text(), "New name", "breadcrumbs should contain the display_name of the opened record" ); }); QUnit.test('reverse breadcrumb works on accesskey "b"', async function (assert) { assert.expect(4); const webClient = await createWebClient({ serverData }); await doAction(webClient, 3); // open a record in form view await click(target.querySelector(".o_list_view .o_data_cell")); await legacyExtraNextTick(); await testUtils.dom.click($(target).find(".o_form_view button:contains(Execute action)")); assert.containsN(target, ".o_control_panel .breadcrumb li", 3); let $previousBreadcrumb = $(target).find(".o_control_panel .breadcrumb li.active").prev(); assert.strictEqual( $previousBreadcrumb.attr("data-hotkey"), "b", "previous breadcrumb should have accessKey 'b'" ); await testUtils.dom.click($previousBreadcrumb); assert.containsN(target, ".o_control_panel .breadcrumb li", 2); $previousBreadcrumb = $(target).find(".o_control_panel .breadcrumb li.active").prev(); assert.strictEqual( $previousBreadcrumb.attr("data-hotkey"), "b", "previous breadcrumb should have accessKey 'b'" ); }); QUnit.test("reload previous controller when discarding a new record", async function (assert) { assert.expect(9); const mockRPC = async (route, args) => { assert.step((args && args.method) || route); }; const webClient = await createWebClient({ serverData, mockRPC }); await doAction(webClient, 3); // create a new record await testUtils.dom.click($(target).find(".o_control_panel .o_list_button_add")); await legacyExtraNextTick(); assert.containsOnce( target, ".o_form_view .o_form_editable", "should have opened the form view in edit mode" ); // discard await testUtils.dom.click($(target).find(".o_control_panel .o_form_button_cancel")); await legacyExtraNextTick(); assert.containsOnce(target, ".o_list_view", "should have switched back to the list view"); assert.verifySteps([ "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", "onchange", "web_search_read", ]); }); QUnit.test("requests for execute_action of type object are handled", async function (assert) { assert.expect(11); patchWithCleanup(session.user_context, { some_key: 2 }); const mockRPC = async (route, args) => { assert.step((args && args.method) || route); if (route === "/web/dataset/call_button") { assert.deepEqual( args, { args: [[1]], kwargs: { context: { lang: "en", uid: 7, tz: "taht", some_key: 2, }, }, method: "object", model: "partner", }, "should call route with correct arguments" ); const record = serverData.models.partner.records.find( (r) => r.id === args.args[0][0] ); record.foo = "value changed"; return Promise.resolve(false); } }; const webClient = await createWebClient({ serverData, mockRPC }); await doAction(webClient, 3); // open a record in form view await click(target.querySelector(".o_list_view .o_data_cell")); await legacyExtraNextTick(); assert.strictEqual( $(target).find(".o_field_widget[name=foo] input").val(), "yop", "check initial value of 'yop' field" ); // click on 'Call method' button (should call an Object method) await testUtils.dom.click($(target).find(".o_form_view button:contains(Call method)")); await legacyExtraNextTick(); assert.strictEqual( $(target).find(".o_field_widget[name=foo] input").val(), "value changed", "'yop' has been changed by the server, and should be updated in the UI" ); assert.verifySteps([ "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", "read", "object", "read", ]); }); QUnit.test( "requests for execute_action of type object: disable buttons (2)", async function (assert) { assert.expect(6); serverData.views["pony,44,form"] = `