899 lines
34 KiB
JavaScript
899 lines
34 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { browser } from "@web/core/browser/browser";
|
|
import { registry } from "@web/core/registry";
|
|
import { WebClient } from "@web/webclient/webclient";
|
|
import { makeTestEnv } from "../../helpers/mock_env";
|
|
import {
|
|
click,
|
|
getFixture,
|
|
patchWithCleanup,
|
|
mount,
|
|
nextTick,
|
|
makeDeferred,
|
|
editInput,
|
|
getNodesTextContent,
|
|
} from "../../helpers/utils";
|
|
import {
|
|
pagerNext,
|
|
switchView,
|
|
toggleMenuItem,
|
|
toggleSearchBarMenu,
|
|
} 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, onMounted, xml } from "@odoo/owl";
|
|
|
|
function getBreadCrumbTexts(target) {
|
|
return getNodesTextContent(target.querySelectorAll(".breadcrumb-item, .o_breadcrumb .active"));
|
|
}
|
|
|
|
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 nextTick(); // wait for the navbar to be updated
|
|
await nextTick(); // wait for the action to be displayed
|
|
|
|
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, { method }) {
|
|
assert.step(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, { method }) {
|
|
assert.step(method || route);
|
|
};
|
|
const webClient = await createWebClient({ serverData, mockRPC });
|
|
webClient.env.bus.trigger("test:hashchange", {
|
|
action: "HelloWorldTest",
|
|
});
|
|
await new Promise((r) => setTimeout(r)); // real "hashchange" event is triggered after a setTimeout [1]
|
|
// [1] https://github.com/odoo/odoo/blob/1882d8f89f760bd1ff8a2bf0ae798939402647a3/addons/web/static/tests/setup.js#L52
|
|
await nextTick();
|
|
await 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, { method }) {
|
|
assert.step(method || route);
|
|
};
|
|
const webClient = await createWebClient({ serverData, mockRPC });
|
|
webClient.env.bus.trigger("test:hashchange", {
|
|
action: 1,
|
|
});
|
|
await new Promise((r) => setTimeout(r)); // real "hashchange" event is triggered after a setTimeout [1]
|
|
await nextTick();
|
|
await nextTick();
|
|
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, { method }) {
|
|
assert.step(method || route);
|
|
};
|
|
const webClient = await createWebClient({ serverData, mockRPC });
|
|
webClient.env.bus.trigger("test:hashchange", {
|
|
id: 2,
|
|
model: "partner",
|
|
});
|
|
await new Promise((r) => setTimeout(r)); // real "hashchange" event is triggered after a setTimeout [1]
|
|
await nextTick();
|
|
await nextTick();
|
|
assert.containsOnce(target, ".o_form_view");
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Second record"]);
|
|
assert.verifySteps(["/web/webclient/load_menus", "get_views", "web_read"]);
|
|
});
|
|
|
|
QUnit.test("properly load records with existing first APP", async function (assert) {
|
|
assert.expect(7);
|
|
const mockRPC = async function (route, { method }) {
|
|
assert.step(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 nextTick();
|
|
assert.containsOnce(target, ".o_form_view");
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Second record"]);
|
|
assert.containsNone(target, ".o_menu_brand");
|
|
assert.verifySteps(["/web/webclient/load_menus", "get_views", "web_read"]);
|
|
});
|
|
|
|
QUnit.test("properly load default record", async function (assert) {
|
|
assert.expect(6);
|
|
const mockRPC = async function (route, { method }) {
|
|
assert.step(method || route);
|
|
};
|
|
const webClient = await createWebClient({ serverData, mockRPC });
|
|
webClient.env.bus.trigger("test:hashchange", {
|
|
action: 3,
|
|
id: "",
|
|
model: "partner",
|
|
view_type: "form",
|
|
});
|
|
await new Promise((r) => setTimeout(r)); // real "hashchange" event is triggered after a setTimeout [1]
|
|
await nextTick();
|
|
await nextTick();
|
|
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, { method }) {
|
|
assert.step(method || route);
|
|
};
|
|
const webClient = await createWebClient({ serverData, mockRPC });
|
|
webClient.env.bus.trigger("test:hashchange", {
|
|
action: 3,
|
|
view_type: "kanban",
|
|
});
|
|
await new Promise((r) => setTimeout(r)); // real "hashchange" event is triggered after a setTimeout [1]
|
|
await nextTick();
|
|
await nextTick();
|
|
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(11);
|
|
const mockRPC = async function (route, { method, kwargs }) {
|
|
if (method === "unity_read") {
|
|
assert.step(`unity_read ${kwargs.method}`);
|
|
} else {
|
|
assert.step(method || route);
|
|
}
|
|
};
|
|
const webClient = await createWebClient({ serverData, mockRPC });
|
|
webClient.env.bus.trigger("test:hashchange", {
|
|
action: 3,
|
|
id: 2,
|
|
view_type: "form",
|
|
});
|
|
await nextTick();
|
|
await nextTick();
|
|
assert.containsNone(target, ".o_list_view");
|
|
assert.containsOnce(target, ".o_form_view");
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners", "Second record"]);
|
|
// go back to List
|
|
await click(target.querySelector(".o_control_panel .breadcrumb a"));
|
|
assert.containsOnce(target, ".o_list_view");
|
|
assert.containsNone(target, ".o_form_view");
|
|
assert.verifySteps([
|
|
"/web/webclient/load_menus",
|
|
"/web/action/load",
|
|
"get_views",
|
|
"web_read",
|
|
"web_search_read",
|
|
]);
|
|
}
|
|
);
|
|
|
|
QUnit.test("lazy load multi record view with previous action", async function (assert) {
|
|
const webClient = await createWebClient({ serverData });
|
|
await doAction(webClient, 4);
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners Action 4"]);
|
|
await doAction(webClient, 3, {
|
|
props: { resId: 2 },
|
|
viewType: "form",
|
|
});
|
|
assert.deepEqual(getBreadCrumbTexts(target), [
|
|
"Partners Action 4",
|
|
"Partners",
|
|
"Second record",
|
|
]);
|
|
// go back to List
|
|
await click(target.querySelector(".o_control_panel .breadcrumb .o_back_button a"));
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners Action 4", "Partners"]);
|
|
});
|
|
|
|
QUnit.test(
|
|
"lazy loaded multi record view with failing mono record one",
|
|
async function (assert) {
|
|
assert.expect(3);
|
|
const mockRPC = async function (route, { method, kwargs }) {
|
|
if (method === "web_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(13);
|
|
const mockRPC = async function (route, { method }) {
|
|
assert.step(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 new Promise((r) => setTimeout(r)); // real "hashchange" event is triggered after a setTimeout [1]
|
|
await nextTick();
|
|
await nextTick();
|
|
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 new Promise((r) => setTimeout(r)); // real "hashchange" event is triggered after a setTimeout [1]
|
|
await nextTick();
|
|
await nextTick();
|
|
assert.containsNone(target, ".o_kanban_view");
|
|
assert.containsOnce(target, ".o_form_view");
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners", "Fourth 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",
|
|
"web_read",
|
|
]);
|
|
});
|
|
|
|
QUnit.test("change the id of the current action", async function (assert) {
|
|
assert.expect(11);
|
|
const mockRPC = async function (route, { method }) {
|
|
assert.step(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 click(target.querySelector(".o_list_view .o_data_cell"));
|
|
assert.containsOnce(target, ".o_form_view");
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners", "First record"]);
|
|
// switch to record 4
|
|
webClient.env.bus.trigger("test:hashchange", {
|
|
action: 3,
|
|
id: 4,
|
|
view_type: "form",
|
|
});
|
|
await new Promise((r) => setTimeout(r)); // real "hashchange" event is triggered after a setTimeout [1]
|
|
await nextTick();
|
|
await nextTick();
|
|
assert.containsOnce(target, ".o_form_view");
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners", "Fourth 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",
|
|
"web_read",
|
|
"web_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 click(target.querySelector("tr .o_data_cell"));
|
|
await nextTick();
|
|
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("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 super.getItem(k);
|
|
},
|
|
setItem(k, v) {
|
|
assert.step(`setItem session ${k}`);
|
|
return super.setItem(k, v);
|
|
},
|
|
});
|
|
const mockRPC = async (route, { method, kwargs }) => {
|
|
assert.step(method || route);
|
|
};
|
|
const webClient = await createWebClient({ serverData, mockRPC });
|
|
await doAction(webClient, 4);
|
|
assert.containsOnce(target, ".o_kanban_view", "should display a kanban view");
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners Action 4"]);
|
|
await loadState(webClient, {
|
|
model: "partner",
|
|
view_type: "list",
|
|
});
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners Action 4"]);
|
|
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.deepEqual(getBreadCrumbTexts(target), ["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 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 nextTick();
|
|
assert.containsOnce(target, ".o_kanban_view"); // action 1 (default app)
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners Action 1"]);
|
|
await loadState(webClient, { action: 3 });
|
|
assert.containsOnce(target, ".o_list_view"); // action 3
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["Partners"]);
|
|
await loadState(webClient, { home: 1 });
|
|
assert.containsOnce(target, ".o_kanban_view"); // action 1 (default app)
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["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 nextTick();
|
|
assert.containsOnce(target, ".o_kanban_view", "should display a kanban view");
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["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(11);
|
|
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.deepEqual(getBreadCrumbTexts(target), ["Partner", "Second record"]);
|
|
assert.verifySteps([
|
|
"/web/webclient/load_menus",
|
|
"/web/action/load",
|
|
"/web/dataset/call_kw/partner/get_views",
|
|
"/web/dataset/call_kw/partner/web_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.deepEqual(getBreadCrumbTexts(target), ["Partner", "New"]);
|
|
});
|
|
|
|
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 switchView(target, "kanban");
|
|
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"));
|
|
|
|
assert.containsN(target, ".o_list_view .o_data_row", 5);
|
|
|
|
await toggleSearchBarMenu(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.deepEqual(getBreadCrumbTexts(target), ["First record"]);
|
|
await pagerNext(target);
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["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.deepEqual(getBreadCrumbTexts(target), ["First record"]);
|
|
|
|
// loadState again just to check if changes were commited
|
|
await loadState(webClient, { action: 1337, id: 2, model: "partner", view_type: "form" });
|
|
assert.deepEqual(getBreadCrumbTexts(target), ["new name"]);
|
|
});
|
|
|
|
QUnit.test("initial action crashes", async (assert) => {
|
|
assert.expect(8);
|
|
assert.expectErrors();
|
|
|
|
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.expectErrors(["my error"]);
|
|
assert.containsOnce(target, ".o_error_dialog");
|
|
await click(target, ".modal-header .btn-close");
|
|
assert.containsNone(target, ".o_error_dialog");
|
|
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() {
|
|
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([]);
|
|
await nextTick();
|
|
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.containsNone(target, ".test_client_action");
|
|
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(6);
|
|
|
|
const baseURL = new URL(browser.location.href).toString();
|
|
|
|
class MyAction extends Component {
|
|
setup() {
|
|
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([]);
|
|
await nextTick();
|
|
assert.verifySteps(["myAction mounted"]);
|
|
|
|
await nextTick();
|
|
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 super.getItem(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"]);
|
|
}
|
|
);
|
|
});
|