Odoo18-Base/addons/web/static/tests/webclient/actions/load_state_tests.js
2025-03-10 11:12:23 +07:00

1149 lines
43 KiB
JavaScript

/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { registry } from "@web/core/registry";
import { WebClient } from "@web/webclient/webclient";
import testUtils from "web.test_utils";
import core from "web.core";
import AbstractAction from "web.AbstractAction";
import { registerCleanup } from "../../helpers/cleanup";
import { makeTestEnv } from "../../helpers/mock_env";
import {
click,
getFixture,
legacyExtraNextTick,
patchWithCleanup,
mount,
nextTick,
makeDeferred,
editInput,
} from "../../helpers/utils";
import { pagerNext, toggleFilterMenu, toggleMenuItem } from "@web/../tests/search/helpers";
import { session } from "@web/session";
import {
createWebClient,
doAction,
getActionManagerServerData,
loadState,
setupWebClientRegistries,
} from "./../helpers";
import { errorService } from "@web/core/errors/error_service";
import { Component, xml } from "@odoo/owl";
let serverData;
let target;
const actionRegistry = registry.category("actions");
QUnit.module("ActionManager", (hooks) => {
hooks.beforeEach(() => {
serverData = getActionManagerServerData();
target = getFixture();
});
QUnit.module("Load State");
QUnit.test("action loading", async (assert) => {
assert.expect(2);
const webClient = await createWebClient({ serverData });
await loadState(webClient, { action: 1001 });
assert.containsOnce(target, ".test_client_action");
assert.strictEqual(target.querySelector(".o_menu_brand").textContent, "App1");
});
QUnit.test("menu loading", async (assert) => {
assert.expect(2);
const webClient = await createWebClient({ serverData });
await loadState(webClient, { menu_id: 2 });
assert.strictEqual(
target.querySelector(".test_client_action").textContent.trim(),
"ClientAction_Id 2"
);
assert.strictEqual(target.querySelector(".o_menu_brand").textContent, "App2");
});
QUnit.test("action and menu loading", async (assert) => {
assert.expect(3);
const webClient = await createWebClient({ serverData });
await loadState(webClient, {
action: 1001,
menu_id: 2,
});
assert.strictEqual(
target.querySelector(".test_client_action").textContent.trim(),
"ClientAction_Id 1"
);
assert.strictEqual(target.querySelector(".o_menu_brand").textContent, "App2");
assert.deepEqual(webClient.env.services.router.current.hash, {
action: 1001,
menu_id: 2,
});
});
QUnit.test("initial loading with action id", async (assert) => {
assert.expect(4);
const hash = "#action=1001";
Object.assign(browser.location, { hash });
setupWebClientRegistries();
const mockRPC = (route) => assert.step(route);
const env = await makeTestEnv({ serverData, mockRPC });
assert.verifySteps(["/web/action/load", "/web/webclient/load_menus"]);
await mount(WebClient, getFixture(), { env });
assert.verifySteps([]);
});
QUnit.test("initial loading with action tag", async (assert) => {
assert.expect(3);
const hash = "#action=__test__client__action__";
Object.assign(browser.location, { hash });
setupWebClientRegistries();
const mockRPC = (route) => assert.step(route);
const env = await makeTestEnv({ serverData, mockRPC });
assert.verifySteps(["/web/webclient/load_menus"]);
await mount(WebClient, getFixture(), { env });
assert.verifySteps([]);
});
QUnit.test("fallback on home action if no action found", async (assert) => {
assert.expect(2);
patchWithCleanup(session, { home_action_id: 1001 });
await createWebClient({ serverData });
await testUtils.nextTick(); // wait for the navbar to be updated
assert.containsOnce(target, ".test_client_action");
assert.strictEqual(target.querySelector(".o_menu_brand").innerText, "App1");
});
QUnit.test("correctly sends additional context", async (assert) => {
assert.expect(1);
const hash = "#action=1001&active_id=4&active_ids=4,8";
Object.assign(browser.location, { hash });
function mockRPC(route, params) {
if (route === "/web/action/load") {
assert.deepEqual(params, {
action_id: 1001,
additional_context: {
active_id: 4,
active_ids: [4, 8],
},
});
}
}
await createWebClient({ serverData, mockRPC });
});
QUnit.test("supports action as xmlId", async (assert) => {
assert.expect(2);
const webClient = await createWebClient({ serverData });
await loadState(webClient, {
action: "wowl.client_action",
});
assert.strictEqual(
target.querySelector(".test_client_action").textContent.trim(),
"ClientAction_xmlId"
);
assert.containsNone(target, ".o_menu_brand");
});
QUnit.test("supports opening action in dialog", async (assert) => {
assert.expect(3);
serverData.actions["wowl.client_action"].target = "new";
const webClient = await createWebClient({ serverData });
await loadState(webClient, {
action: "wowl.client_action",
});
assert.containsOnce(target, ".test_client_action");
assert.containsOnce(target, ".modal .test_client_action");
assert.containsNone(target, ".o_menu_brand");
});
QUnit.test("should not crash on invalid state", async function (assert) {
assert.expect(3);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
await loadState(webClient, {
res_model: "partner",
});
assert.strictEqual($(target).text(), "", "should display nothing");
assert.verifySteps(["/web/webclient/load_menus"]);
});
QUnit.test("properly load client actions", async function (assert) {
assert.expect(3);
class ClientAction extends Component {}
ClientAction.template = xml`<div class="o_client_action_test">Hello World</div>`;
actionRegistry.add("HelloWorldTest", ClientAction);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
webClient.env.bus.trigger("test:hashchange", {
action: "HelloWorldTest",
});
await testUtils.nextTick();
assert.strictEqual(
$(target).find(".o_client_action_test").text(),
"Hello World",
"should have correctly rendered the client action"
);
assert.verifySteps(["/web/webclient/load_menus"]);
});
QUnit.test("properly load act window actions", async function (assert) {
assert.expect(7);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
webClient.env.bus.trigger("test:hashchange", {
action: 1,
});
await testUtils.nextTick();
await legacyExtraNextTick();
assert.containsOnce(target, ".o_control_panel");
assert.containsOnce(target, ".o_kanban_view");
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"get_views",
"web_search_read",
]);
});
QUnit.test("properly load records", async function (assert) {
assert.expect(6);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
webClient.env.bus.trigger("test:hashchange", {
id: 2,
model: "partner",
});
await testUtils.nextTick();
await legacyExtraNextTick();
assert.containsOnce(target, ".o_form_view");
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item").text(),
"Second record",
"should have opened the second record"
);
assert.verifySteps(["/web/webclient/load_menus", "get_views", "read"]);
});
QUnit.test("properly load records with existing first APP", async function (assert) {
assert.expect(7);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
// simulate a real scenario with a first app (e.g. Discuss), to ensure that we don't
// fallback on that first app when only a model and res_id are given in the url
serverData.menus = {
root: { id: "root", children: [1, 2], name: "root", appID: "root" },
1: { id: 1, children: [], name: "App1", appID: 1, actionID: 1001, xmlid: "menu_1" },
2: { id: 2, children: [], name: "App2", appID: 2, actionID: 1002, xmlid: "menu_2" },
};
const hash = "#id=2&model=partner";
Object.assign(browser.location, { hash });
await createWebClient({ serverData, mockRPC });
await testUtils.nextTick();
assert.containsOnce(target, ".o_form_view");
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item").text(),
"Second record",
"should have opened the second record"
);
assert.containsNone(target, ".o_menu_brand");
assert.verifySteps(["/web/webclient/load_menus", "get_views", "read"]);
});
QUnit.test("properly load default record", async function (assert) {
assert.expect(6);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
webClient.env.bus.trigger("test:hashchange", {
action: 3,
id: "",
model: "partner",
view_type: "form",
});
await testUtils.nextTick();
await legacyExtraNextTick();
assert.containsOnce(target, ".o_form_view");
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"get_views",
"onchange",
]);
});
QUnit.test("load requested view for act window actions", async function (assert) {
assert.expect(7);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
webClient.env.bus.trigger("test:hashchange", {
action: 3,
view_type: "kanban",
});
await testUtils.nextTick();
await legacyExtraNextTick();
assert.containsNone(target, ".o_list_view");
assert.containsOnce(target, ".o_kanban_view");
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"get_views",
"web_search_read",
]);
});
QUnit.test(
"lazy load multi record view if mono record one is requested",
async function (assert) {
assert.expect(12);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
webClient.env.bus.trigger("test:hashchange", {
action: 3,
id: 2,
view_type: "form",
});
await testUtils.nextTick();
await legacyExtraNextTick();
assert.containsNone(target, ".o_list_view");
assert.containsOnce(target, ".o_form_view");
assert.containsN(target, ".o_control_panel .breadcrumb-item", 2);
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item:last").text(),
"Second record",
"breadcrumbs should contain the display_name of the opened record"
);
// go back to List
await testUtils.dom.click($(target).find(".o_control_panel .breadcrumb a"));
await legacyExtraNextTick();
assert.containsOnce(target, ".o_list_view");
assert.containsNone(target, ".o_form_view");
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"get_views",
"read",
"web_search_read",
]);
}
);
QUnit.test("lazy load multi record view with previous action", async function (assert) {
assert.expect(6);
const webClient = await createWebClient({ serverData });
await doAction(webClient, 4);
assert.containsOnce(
target,
".o_control_panel .breadcrumb li",
"there should be one controller in the breadcrumbs"
);
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb li").text(),
"Partners Action 4",
"breadcrumbs should contain the display_name of the opened record"
);
await doAction(webClient, 3, {
props: { resId: 2 },
viewType: "form",
});
assert.containsN(
target,
".o_control_panel .breadcrumb li",
3,
"there should be three controllers in the breadcrumbs"
);
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb li").text(),
"Partners Action 4PartnersSecond record",
"the breadcrumb elements should be correctly ordered"
);
// go back to List
await testUtils.dom.click($(target).find(".o_control_panel .breadcrumb a:last"));
await legacyExtraNextTick();
assert.containsN(
target,
".o_control_panel .breadcrumb li",
2,
"there should be two controllers in the breadcrumbs"
);
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb li").text(),
"Partners Action 4Partners",
"the breadcrumb elements should be correctly ordered"
);
});
QUnit.test(
"lazy loaded multi record view with failing mono record one",
async function (assert) {
assert.expect(3);
const mockRPC = async function (route, args) {
if (args && args.method === "read") {
return Promise.reject();
}
};
const webClient = await createWebClient({ serverData, mockRPC });
await loadState(webClient, {
action: 3,
id: 2,
view_type: "form",
});
assert.containsNone(target, ".o_form_view");
assert.containsNone(target, ".o_list_view");
await doAction(webClient, 1);
assert.containsOnce(target, ".o_kanban_view");
}
);
QUnit.test("change the viewType of the current action", async function (assert) {
assert.expect(14);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 3);
assert.containsOnce(target, ".o_list_view");
// switch to kanban view
webClient.env.bus.trigger("test:hashchange", {
action: 3,
view_type: "kanban",
});
await testUtils.nextTick();
await legacyExtraNextTick();
assert.containsNone(target, ".o_list_view");
assert.containsOnce(target, ".o_kanban_view");
// switch to form view, open record 4
webClient.env.bus.trigger("test:hashchange", {
action: 3,
id: 4,
view_type: "form",
});
await testUtils.nextTick();
await legacyExtraNextTick();
assert.containsNone(target, ".o_kanban_view");
assert.containsOnce(target, ".o_form_view");
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(),
"Fourth record",
"should have opened the requested record"
);
// verify steps to ensure that the whole action hasn't been re-executed
// (if it would have been, /web/action/load and get_views would appear
// several times)
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"get_views",
"web_search_read",
"web_search_read",
"read",
]);
});
QUnit.test("change the id of the current action", async function (assert) {
assert.expect(12);
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
// execute action 3 and open the first record in a form view
await doAction(webClient, 3);
await testUtils.dom.click($(target).find(".o_list_view .o_data_cell:first"));
await legacyExtraNextTick();
assert.containsOnce(target, ".o_form_view");
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item:last").text(),
"First record",
"should have opened the first record"
);
// switch to record 4
webClient.env.bus.trigger("test:hashchange", {
action: 3,
id: 4,
view_type: "form",
});
await testUtils.nextTick();
await legacyExtraNextTick();
assert.containsOnce(target, ".o_form_view");
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(),
"Fourth record",
"should have switched to the requested record"
);
// verify steps to ensure that the whole action hasn't been re-executed
// (if it would have been, /web/action/load and get_views would appear
// twice)
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"get_views",
"web_search_read",
"read",
"read",
]);
});
QUnit.test("should push the correct state at the right time", async function (assert) {
// formerly "should not push a loaded state"
assert.expect(7);
const pushState = browser.history.pushState;
patchWithCleanup(browser, {
history: Object.assign({}, browser.history, {
pushState() {
pushState(...arguments);
assert.step("push_state");
},
}),
});
const webClient = await createWebClient({ serverData });
let currentHash = webClient.env.services.router.current.hash;
assert.deepEqual(currentHash, {});
await loadState(webClient, { action: 3 });
currentHash = webClient.env.services.router.current.hash;
assert.deepEqual(currentHash, {
action: 3,
model: "partner",
view_type: "list",
});
assert.verifySteps(["push_state"], "should have pushed the final state");
await testUtils.dom.click($(target).find("tr .o_data_cell:first"));
await legacyExtraNextTick();
currentHash = webClient.env.services.router.current.hash;
assert.deepEqual(currentHash, {
action: 3,
id: 1,
model: "partner",
view_type: "form",
});
assert.verifySteps(["push_state"], "should push the state of it changes afterwards");
});
QUnit.test("should not push a loaded state of a legacy client action", async function (assert) {
assert.expect(6);
const ClientAction = AbstractAction.extend({
init: function (parent, action, options) {
this._super.apply(this, arguments);
this.controllerID = options.controllerID;
},
start: function () {
const $button = $("<button id='client_action_button'>").text("Click Me!");
$button.on("click", () => {
this.trigger_up("push_state", {
controllerID: this.controllerID,
state: { someValue: "X" },
});
});
this.$el.append($button);
return this._super.apply(this, arguments);
},
});
const pushState = browser.history.pushState;
patchWithCleanup(browser, {
history: Object.assign({}, browser.history, {
pushState() {
pushState(...arguments);
assert.step("push_state");
},
}),
});
core.action_registry.add("ClientAction", ClientAction);
const webClient = await createWebClient({ serverData });
let currentHash = webClient.env.services.router.current.hash;
assert.deepEqual(currentHash, {});
await loadState(webClient, { action: 9 });
currentHash = webClient.env.services.router.current.hash;
assert.deepEqual(currentHash, {
action: 9,
});
assert.verifySteps([], "should not push the loaded state");
await testUtils.dom.click($(target).find("#client_action_button"));
await legacyExtraNextTick();
assert.verifySteps(["push_state"], "should push the state of it changes afterwards");
currentHash = webClient.env.services.router.current.hash;
assert.deepEqual(currentHash, {
action: 9,
someValue: "X",
});
delete core.action_registry.map.ClientAction;
});
QUnit.test("change a param of an ir.actions.client in the url", async function (assert) {
assert.expect(12);
const ClientAction = AbstractAction.extend({
hasControlPanel: true,
init: function (parent, action) {
this._super.apply(this, arguments);
const context = action.context;
this.a = (context.params && context.params.a) || "default value";
},
start: function () {
assert.step("start");
this.$(".o_content").text(this.a);
this.$el.addClass("o_client_action");
this.trigger_up("push_state", {
controllerID: this.controllerID,
state: { a: this.a },
});
return this._super.apply(this, arguments);
},
});
const pushState = browser.history.pushState;
patchWithCleanup(browser, {
history: Object.assign({}, browser.history, {
pushState() {
pushState(...arguments);
assert.step("push_state");
},
}),
});
core.action_registry.add("ClientAction", ClientAction);
const webClient = await createWebClient({ serverData });
let currentHash = webClient.env.services.router.current.hash;
assert.deepEqual(currentHash, {});
// execute the client action
await doAction(webClient, 9);
assert.verifySteps(["start", "push_state"]);
currentHash = webClient.env.services.router.current.hash;
assert.deepEqual(currentHash, {
action: 9,
a: "default value",
});
assert.strictEqual(
$(target).find(".o_client_action .o_content").text(),
"default value",
"should have rendered the client action"
);
assert.containsN(
target,
".o_control_panel .breadcrumb-item",
1,
"there should be one controller in the breadcrumbs"
);
// update param 'a' in the url
await loadState(webClient, {
action: 9,
a: "new value",
});
assert.verifySteps(["start"]); // No push state since the hash hasn't changed
currentHash = webClient.env.services.router.current.hash;
assert.deepEqual(currentHash, {
action: 9,
a: "new value",
});
assert.strictEqual(
$(target).find(".o_client_action .o_content").text(),
"new value",
"should have rerendered the client action with the correct param"
);
assert.containsN(
target,
".o_control_panel .breadcrumb-item",
1,
"there should still be one controller in the breadcrumbs"
);
delete core.action_registry.map.ClientAction;
});
QUnit.test("load a window action without id (in a multi-record view)", async function (assert) {
assert.expect(14);
patchWithCleanup(browser.sessionStorage, {
getItem(k) {
assert.step(`getItem session ${k}`);
return this._super(k);
},
setItem(k, v) {
assert.step(`setItem session ${k}`);
return this._super(k, v);
},
});
const mockRPC = async (route, args) => {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 4);
assert.containsOnce(target, ".o_kanban_view", "should display a kanban view");
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item").text(),
"Partners Action 4",
"breadcrumbs should display the display_name of the action"
);
await loadState(webClient, {
model: "partner",
view_type: "list",
});
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item").text(),
"Partners Action 4",
"should still be in the same action"
);
assert.containsNone(target, ".o_kanban_view", "should no longer display a kanban view");
assert.containsOnce(target, ".o_list_view", "should display a list view");
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"get_views",
"web_search_read",
"setItem session current_action",
"getItem session current_action",
"web_search_read",
"setItem session current_action",
]);
});
QUnit.test("load state supports being given menu_id alone", async function (assert) {
assert.expect(7);
serverData.menus[666] = {
id: 666,
children: [],
name: "App1",
appID: 1,
actionID: 1,
};
const mockRPC = async function (route) {
assert.step(route);
};
const webClient = await createWebClient({ serverData, mockRPC });
await loadState(webClient, { menu_id: 666 });
assert.containsOnce(target, ".o_kanban_view", "should display a kanban view");
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item").text(),
"Partners Action 1",
"breadcrumbs should display the display_name of the action"
);
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/web_search_read",
]);
});
QUnit.test("load state supports #home", async function (assert) {
assert.expect(6);
serverData.menus = {
root: { id: "root", children: [1], name: "root", appID: "root" },
1: { id: 1, children: [], name: "App1", appID: 1, actionID: 1 },
};
const webClient = await createWebClient({ serverData });
await legacyExtraNextTick();
assert.containsOnce(target, ".o_kanban_view"); // action 1 (default app)
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item").text(),
"Partners Action 1"
);
await loadState(webClient, { action: 3 });
assert.containsOnce(target, ".o_list_view"); // action 3
assert.strictEqual($(target).find(".o_control_panel .breadcrumb-item").text(), "Partners");
await loadState(webClient, { home: 1 });
assert.containsOnce(target, ".o_kanban_view"); // action 1 (default app)
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item").text(),
"Partners Action 1"
);
});
QUnit.test("load state supports #home as initial state", async function (assert) {
assert.expect(7);
serverData.menus = {
root: { id: "root", children: [1], name: "root", appID: "root" },
1: { id: 1, children: [], name: "App1", appID: 1, actionID: 1 },
};
const hash = "#home=1";
Object.assign(browser.location, { hash });
const mockRPC = async function (route) {
assert.step(route);
};
await createWebClient({ serverData, mockRPC });
await legacyExtraNextTick();
assert.containsOnce(target, ".o_kanban_view", "should display a kanban view");
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item").text(),
"Partners Action 1"
);
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/web_search_read",
]);
});
QUnit.test("load state: in a form view, remove the id from the state", async function (assert) {
assert.expect(13);
serverData.actions[999] = {
id: 999,
name: "Partner",
res_model: "partner",
type: "ir.actions.act_window",
views: [
[false, "list"],
[666, "form"],
],
};
const mockRPC = async (route) => {
assert.step(route);
};
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 999, { viewType: "form", props: { resId: 2 } });
assert.containsOnce(target, ".o_form_view");
assert.containsN(target, ".breadcrumb-item", 2);
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item.active").text(),
"Second record"
);
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/read",
]);
await loadState(webClient, {
action: 999,
view_type: "form",
id: "",
});
assert.verifySteps(["/web/dataset/call_kw/partner/onchange"]);
assert.containsOnce(target, ".o_form_view .o_form_editable");
assert.containsN(target, ".breadcrumb-item", 2);
assert.strictEqual(
$(target).find(".o_control_panel .breadcrumb-item.active").text(),
"New"
);
});
QUnit.test("hashchange does not trigger canberemoved right away", async function (assert) {
assert.expect(9);
const ClientAction = AbstractAction.extend({
start() {
this.$el.text("Hello World");
this.$el.addClass("o_client_action_test");
},
canBeRemoved() {
assert.step("canBeRemoved");
return this._super.apply(this, arguments);
},
});
const ClientAction2 = AbstractAction.extend({
start() {
this.$el.text("Hello World");
this.$el.addClass("o_client_action_test_2");
},
canBeRemoved() {
assert.step("canBeRemoved_2");
return this._super.apply(this, arguments);
},
});
const pushState = browser.history.pushState;
patchWithCleanup(browser, {
history: Object.assign({}, browser.history, {
pushState() {
pushState(...arguments);
assert.step("hashSet");
},
}),
});
core.action_registry.add("ClientAction", ClientAction);
core.action_registry.add("ClientAction2", ClientAction2);
const webClient = await createWebClient({ serverData });
assert.verifySteps([]);
await doAction(webClient, 9);
assert.verifySteps(["hashSet"]);
assert.containsOnce(target, ".o_client_action_test");
assert.verifySteps([]);
await doAction(webClient, "ClientAction2");
assert.containsOnce(target, ".o_client_action_test_2");
assert.verifySteps(["canBeRemoved", "hashSet"]);
delete core.action_registry.map.ClientAction;
delete core.action_registry.map.ClientAction2;
});
QUnit.test("state with integer active_ids should not crash", async function (assert) {
assert.expect(2);
const mockRPC = async (route, args) => {
if (route === "/web/action/run") {
assert.strictEqual(args.action_id, 2);
assert.deepEqual(args.context.active_ids, [3]);
return new Promise(() => {});
}
};
const webClient = await createWebClient({ serverData, mockRPC });
await loadState(webClient, {
action: 2,
active_ids: 3,
});
});
QUnit.test(
"url form view type switch from list or kanban doesn't timeout",
async function (assert) {
assert.expect(3);
const webClient = await createWebClient({ serverData });
await doAction(webClient, 3);
assert.containsOnce(target, ".o_list_view", "should now display the list view");
await testUtils.controlPanel.switchView(target, "kanban");
await legacyExtraNextTick();
assert.containsOnce(target, ".o_kanban_view", "should now display the kanban view");
const hash = webClient.env.services.router.current.hash;
hash.view_type = "form";
await loadState(webClient.env, hash);
assert.containsOnce(
target,
".o_form_view .o_form_editable",
"should now display the form view in edit mode"
);
}
);
QUnit.test(
"charge a form view via url, then switch to view list, the search view is correctly initialized",
async function (assert) {
assert.expect(2);
serverData.views = {
...serverData.views,
"partner,false,search": `
<search>
<filter name="filter" string="Filter" domain="[('foo', '=', 'yop')]"/>
</search>
`,
};
const webClient = await createWebClient({ serverData });
await loadState(webClient.env, {
action: 3,
model: "partner",
view_type: "form",
});
await click(target.querySelector(".o_control_panel .breadcrumb-item"));
await legacyExtraNextTick();
assert.containsN(target, ".o_list_view .o_data_row", 5);
await toggleFilterMenu(target);
await toggleMenuItem(target, "Filter");
assert.containsN(target, ".o_list_view .o_data_row", 1);
}
);
QUnit.test("should not crash while commiting changes", async (assert) => {
serverData.views["partner,false,form"] = `<form><field name="display_name" /></form>`;
const webClient = await createWebClient({ serverData });
await doAction(
webClient,
{
type: "ir.actions.act_window",
id: 1337,
res_id: 1,
res_model: "partner",
views: [[false, "form"]],
},
{ props: { resIds: [1, 2] } }
);
assert.strictEqual(target.querySelector(".breadcrumb").textContent, "First record");
await pagerNext(target);
assert.strictEqual(target.querySelector(".breadcrumb").textContent, "Second record");
await editInput(target, "[name=display_name] input", "new name");
// without saving we now make a loadState which should commit changes
await loadState(webClient, { action: 1337, id: 1, model: "partner", view_type: "form" });
assert.strictEqual(target.querySelector(".breadcrumb").textContent, "First record");
// loadState again just to check if changes were commited
await loadState(webClient, { action: 1337, id: 2, model: "partner", view_type: "form" });
assert.strictEqual(target.querySelector(".breadcrumb").textContent, "new name");
});
QUnit.test("initial action crashes", async (assert) => {
assert.expect(8);
const handler = (ev) => {
// need to preventDefault to remove error from console (so python test pass)
ev.preventDefault();
};
window.addEventListener("unhandledrejection", handler);
registerCleanup(() => window.removeEventListener("unhandledrejection", handler));
patchWithCleanup(QUnit, {
onUnhandledRejection: () => {},
});
browser.location.hash = "#action=__test__client__action__&menu_id=1";
const ClientAction = registry.category("actions").get("__test__client__action__");
class Override extends ClientAction {
setup() {
super.setup();
assert.step("clientAction setup");
throw new Error("my error");
}
}
registry.category("actions").add("__test__client__action__", Override, { force: true });
registry.category("services").add("error", errorService);
const webClient = await createWebClient({ serverData });
assert.verifySteps(["clientAction setup"]);
await nextTick();
assert.containsOnce(target, ".o_dialog_error");
await click(target, ".modal-header .btn-close");
assert.containsNone(target, ".o_dialog_error");
await click(target, "nav .o_navbar_apps_menu .dropdown-toggle ");
assert.containsN(target, ".dropdown-item.o_app", 3);
assert.containsNone(target, ".o_menu_brand");
assert.strictEqual(target.querySelector(".o_action_manager").innerHTML, "");
assert.deepEqual(webClient.env.services.router.current.hash, {
action: "__test__client__action__",
menu_id: 1,
});
});
QUnit.test("concurrent hashchange during action mounting -- 1", async (assert) => {
const hashchangeDef = makeDeferred();
class MyAction extends Component {
setup() {
owl.onMounted(() => {
assert.step("myAction mounted");
browser.addEventListener("hashchange", () => {
hashchangeDef.resolve();
});
browser.location.hash = "#action=__test__client__action__&menu_id=1";
});
}
}
MyAction.template = xml`<div class="not-here" />`;
registry.category("actions").add("myAction", MyAction);
browser.location.hash = "#action=myAction";
const webClient = await createWebClient({ serverData });
assert.verifySteps(["myAction mounted"]);
assert.containsOnce(target, ".not-here");
// hashchange event isn't trigerred synchronously, so we have to wait for it
await hashchangeDef;
await nextTick();
assert.containsNone(target, ".not-here");
assert.containsOnce(target, ".test_client_action");
assert.deepEqual(webClient.env.services.router.current.hash, {
action: "__test__client__action__",
menu_id: 1,
});
});
QUnit.test("concurrent hashchange during action mounting -- 2", async (assert) => {
assert.expect(5);
const baseURL = new URL(browser.location.href).toString();
class MyAction extends Component {
setup() {
owl.onMounted(() => {
assert.step("myAction mounted");
const newURL = baseURL + "#action=__test__client__action__&menu_id=1";
// immediate triggering
window.dispatchEvent(new HashChangeEvent("hashchange", { newURL }));
});
}
}
MyAction.template = xml`<div class="not-here" />`;
registry.category("actions").add("myAction", MyAction);
browser.location.hash = "#action=myAction";
const webClient = await createWebClient({ serverData });
assert.verifySteps(["myAction mounted"]);
await nextTick();
assert.containsNone(target, ".not-here");
assert.containsOnce(target, ".test_client_action");
assert.deepEqual(webClient.env.services.router.current.hash, {
action: "__test__client__action__",
menu_id: 1,
});
});
QUnit.test(
"'no content helper' is markuped when action is retrieved from session storage",
async function (assert) {
// for the no content helper to show, we empty the data
serverData.models.partner.records = [];
serverData.actions[4].help = "<p>Some nice help</p>";
patchWithCleanup(browser.sessionStorage, {
getItem(k) {
assert.step(`getItem session ${k}`);
return this._super(k);
},
});
const webClient = await createWebClient({ serverData });
await doAction(webClient, 4);
assert.containsOnce(target, ".o_kanban_view", "should display a kanban view");
assert.strictEqual(
"Some nice help",
target.querySelector(".o_nocontent_help").innerText
);
await loadState(webClient, {
model: "partner",
view_type: "list",
});
assert.containsNone(target, ".o_kanban_view", "should no longer display a kanban view");
assert.containsOnce(target, ".o_list_view", "should display a list view");
assert.strictEqual(
"Some nice help",
target.querySelector(".o_nocontent_help").innerText
);
assert.verifySteps(["getItem session current_action"]);
}
);
});