Files
odoo_source/addons/web/static/tests/webclient/navbar_tests.js
T
+2 48ef812a63 [REF] web: kanban, list and form views in owl
This commit introduces the new kanban, list and form views written
in owl. Even though it contains the implementation of those 3 views,
only the kanban view is activated for now (the list and form views
aren't 100% ready yet, so they aren't added to the view registry).

Alongside the views, the fields (<field name="..."/> in archs) and
widgets (<widget name="..." in archs) of web/ have been implemented
in owl as well. Legacy ones remaining in other addons are supported
in our new views, thanks to a compatibility layer. The goal is to convert
them asap though.

Legacy views, fields and widgets are kept for now, which explains the
number of added lines in this PR (around half of them concern tests).
They are still extended by custom code in other addons, that still need
to be converted (a lot of them are already on the way). Moreover,
they are still used in Studio as well. The plan is to lazy load them in
the Studio bundle when all custom code extending them will be
converted. Studio will be converted for v17.

Part-of: odoo/odoo#92475
Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Bruno Boi <boi@odoo.com>
Co-authored-by: Géry Debongnie <ged@odoo.com>
Co-authored-by: Samuel Degueldre <sad@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Co-authored-by: Simon Genin (ges) <ges@odoo.com>
Co-authored-by: Francois (fge) <fge@odoo.com>
Co-authored-by: Michael Mattiello (mcm) <mcm@odoo.com>
Co-authored-by: Julien Mougenot <jum@odoo.com>
Co-authored-by: Lucas Perais (lpe) <lpe@odoo.com>
Co-authored-by: Jorge Pinna Puissant <jpp@odoo.com>
Co-authored-by: luvi <luvi@odoo.com>
2022-06-30 09:29:44 +02:00

482 lines
19 KiB
JavaScript

/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { notificationService } from "@web/core/notifications/notification_service";
import { menuService } from "@web/webclient/menus/menu_service";
import { registry } from "@web/core/registry";
import { ormService } from "@web/core/orm_service";
import { uiService } from "@web/core/ui/ui_service";
import { viewService } from "@web/views/view_service";
import { actionService } from "@web/webclient/actions/action_service";
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { NavBar } from "@web/webclient/navbar/navbar";
import { clearRegistryWithCleanup, makeTestEnv } from "../helpers/mock_env";
import {
click,
destroy,
getFixture,
mount,
nextTick,
patchWithCleanup,
mockTimeout,
} from "../helpers/utils";
const { Component, xml, onRendered } = owl;
const systrayRegistry = registry.category("systray");
const serviceRegistry = registry.category("services");
class MySystrayItem extends Component {}
MySystrayItem.template = xml`<li class="my-item">my item</li>`;
let baseConfig;
let target;
QUnit.module("Navbar", {
async beforeEach() {
target = getFixture();
serviceRegistry.add("menu", menuService);
serviceRegistry.add("action", actionService);
serviceRegistry.add("notification", notificationService);
serviceRegistry.add("hotkey", hotkeyService);
serviceRegistry.add("ui", uiService);
serviceRegistry.add("view", viewService); // #action-serv-leg-compat-js-class
serviceRegistry.add("orm", ormService); // #action-serv-leg-compat-js-class
systrayRegistry.add("addon.myitem", { Component: MySystrayItem });
patchWithCleanup(browser, {
setTimeout: (handler, delay, ...args) => handler(...args),
clearTimeout: () => {},
});
const menus = {
root: { id: "root", children: [1], name: "root", appID: "root" },
1: { id: 1, children: [], name: "App0", appID: 1 },
};
const serverData = { menus };
baseConfig = { serverData };
},
});
QUnit.test("can be rendered", async (assert) => {
const env = await makeTestEnv(baseConfig);
await mount(NavBar, target, { env });
assert.containsOnce(
target,
".o_navbar_apps_menu button.dropdown-toggle",
"1 apps menu toggler present"
);
});
QUnit.test("dropdown menu can be toggled", async (assert) => {
const env = await makeTestEnv(baseConfig);
await mount(NavBar, target, { env });
const dropdown = target.querySelector(".o_navbar_apps_menu");
await click(dropdown, "button.dropdown-toggle");
assert.containsOnce(dropdown, ".dropdown-menu");
await click(dropdown, "button.dropdown-toggle");
assert.containsNone(dropdown, ".dropdown-menu");
});
QUnit.test("href attribute on apps menu items", async (assert) => {
baseConfig.serverData.menus = {
root: { id: "root", children: [1], name: "root", appID: "root" },
1: { id: 1, children: [2], name: "My app", appID: 1, actionID: 339 },
};
const env = await makeTestEnv(baseConfig);
await mount(NavBar, target, { env });
const appsMenu = target.querySelector(".o_navbar_apps_menu");
await click(appsMenu, "button.dropdown-toggle");
const dropdownItem = target.querySelector(".o_navbar_apps_menu .dropdown-item");
assert.strictEqual(dropdownItem.getAttribute("href"), "#menu_id=1&action=339");
});
QUnit.test("data-menu-xmlid attribute on AppsMenu items", async (assert) => {
baseConfig.serverData.menus = {
root: { id: "root", children: [1, 2], name: "root", appID: "root" },
1: { id: 1, children: [3, 4], name: "App0 with xmlid", appID: 1, xmlid: "wowl" },
2: { id: 2, children: [], name: "App1 without xmlid", appID: 2 },
3: { id: 3, children: [], name: "Menu without children", appID: 1, xmlid: "menu_3" },
4: { id: 4, children: [5], name: "Menu with children", appID: 1, xmlid: "menu_4" },
5: { id: 5, children: [], name: "Sub menu", appID: 1, xmlid: "menu_5" },
};
const env = await makeTestEnv(baseConfig);
await mount(NavBar, target, { env });
// check apps
const appsMenu = target.querySelector(".o_navbar_apps_menu");
await click(appsMenu, "button.dropdown-toggle");
const menuItems = appsMenu.querySelectorAll("a");
assert.strictEqual(
menuItems[0].dataset.menuXmlid,
"wowl",
"first menu item should have the correct data-menu-xmlid attribute set"
);
assert.strictEqual(
menuItems[1].dataset.menuXmlid,
undefined,
"second menu item should not have any data-menu-xmlid attribute set"
);
// check menus
env.services.menu.setCurrentMenu(1);
await nextTick();
assert.containsOnce(target, ".o_menu_sections .dropdown-item[data-menu-xmlid=menu_3]");
// check sub menus toggler
assert.containsOnce(target, ".o_menu_sections button.dropdown-toggle[data-menu-xmlid=menu_4]");
// check sub menus
await click(target.querySelector(".o_menu_sections .dropdown-toggle"));
assert.containsOnce(target, ".o_menu_sections .dropdown-item[data-menu-xmlid=menu_5]");
});
QUnit.test("navbar can display current active app", async (assert) => {
const env = await makeTestEnv(baseConfig);
await mount(NavBar, target, { env });
const dropdown = target.querySelector(".o_navbar_apps_menu");
// Open apps menu
await click(dropdown, "button.dropdown-toggle");
assert.containsOnce(
dropdown,
".dropdown-menu .dropdown-item:not(.focus)",
"should not show the current active app as the menus service has not loaded an app yet"
);
// Activate an app
env.services.menu.setCurrentMenu(1);
await nextTick();
assert.containsOnce(
dropdown,
".dropdown-menu .dropdown-item.focus",
"should show the current active app"
);
});
QUnit.test("navbar can display systray items", async (assert) => {
const env = await makeTestEnv(baseConfig);
await mount(NavBar, target, { env });
assert.containsOnce(target, "li.my-item");
});
QUnit.test("navbar can display systray items ordered based on their sequence", async (assert) => {
class MyItem1 extends Component {}
MyItem1.template = xml`<li class="my-item-1">my item 1</li>`;
class MyItem2 extends Component {}
MyItem2.template = xml`<li class="my-item-2">my item 2</li>`;
class MyItem3 extends Component {}
MyItem3.template = xml`<li class="my-item-3">my item 3</li>`;
class MyItem4 extends Component {}
MyItem4.template = xml`<li class="my-item-4">my item 4</li>`;
clearRegistryWithCleanup(systrayRegistry);
systrayRegistry.add("addon.myitem2", { Component: MyItem2 });
systrayRegistry.add("addon.myitem1", { Component: MyItem1 }, { sequence: 0 });
systrayRegistry.add("addon.myitem3", { Component: MyItem3 }, { sequence: 100 });
systrayRegistry.add("addon.myitem4", { Component: MyItem4 });
const env = await makeTestEnv(baseConfig);
await mount(NavBar, target, { env });
const menuSystray = target.getElementsByClassName("o_menu_systray")[0];
assert.containsN(menuSystray, "li", 4, "four systray items should be displayed");
assert.strictEqual(menuSystray.innerText, "my item 3\nmy item 4\nmy item 2\nmy item 1");
});
QUnit.test("navbar updates after adding a systray item", async (assert) => {
class MyItem1 extends Component {}
MyItem1.template = xml`<li class="my-item-1">my item 1</li>`;
clearRegistryWithCleanup(systrayRegistry);
systrayRegistry.add("addon.myitem1", { Component: MyItem1 });
const env = await makeTestEnv(baseConfig);
patchWithCleanup(NavBar.prototype, {
setup() {
onRendered(() => {
if (!systrayRegistry.contains("addon.myitem2")) {
class MyItem2 extends Component {}
MyItem2.template = xml`<li class="my-item-2">my item 2</li>`;
systrayRegistry.add("addon.myitem2", { Component: MyItem2 });
}
});
this._super();
},
});
await mount(NavBar, target, { env });
const menuSystray = target.getElementsByClassName("o_menu_systray")[0];
assert.containsN(menuSystray, "li", 2, "2 systray items should be displayed");
});
QUnit.test("can adapt with 'more' menu sections behavior", async (assert) => {
class MyNavbar extends NavBar {
async adapt() {
await super.adapt();
const sectionsCount = this.currentAppSections.length;
const hiddenSectionsCount = this.currentAppSectionsExtra.length;
assert.step(`adapt -> hide ${hiddenSectionsCount}/${sectionsCount} sections`);
}
}
const newMenus = {
root: { id: "root", children: [1, 2], name: "root", appID: "root" },
1: { id: 1, children: [10, 11, 12], name: "App0", appID: 1 },
10: { id: 10, children: [], name: "Section 10", appID: 1 },
11: { id: 11, children: [], name: "Section 11", appID: 1 },
12: { id: 12, children: [120, 121, 122], name: "Section 12", appID: 1 },
120: { id: 120, children: [], name: "Section 120", appID: 1 },
121: { id: 121, children: [], name: "Section 121", appID: 1 },
122: { id: 122, children: [], name: "Section 122", appID: 1 },
};
baseConfig.serverData.menus = newMenus;
const env = await makeTestEnv(baseConfig);
// Force the parent width, to make this test independent of screen size
target.style.width = "1080px";
// Set menu and mount
env.services.menu.setCurrentMenu(1);
await mount(MyNavbar, target, { env });
assert.containsN(
target,
".o_menu_sections > *:not(.o_menu_sections_more):not(.d-none)",
3,
"should have 3 menu sections displayed (that are not the 'more' menu)"
);
assert.containsNone(target, ".o_menu_sections_more", "the 'more' menu should not exist");
// Force minimal width and dispatch window resize event
target.style.width = "0%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.containsOnce(
target,
".o_menu_sections > *:not(.d-none)",
"only one menu section should be displayed"
);
assert.containsOnce(
target,
".o_menu_sections_more:not(.d-none)",
"the displayed menu section should be the 'more' menu"
);
// Open the more menu
await click(target, ".o_menu_sections_more .dropdown-toggle");
assert.deepEqual(
[...target.querySelectorAll(".dropdown-menu > *")].map((el) => el.textContent),
["Section 10", "Section 11", "Section 12", "Section 120", "Section 121", "Section 122"],
"'more' menu should contain all hidden sections in correct order"
);
// Reset to full width and dispatch window resize event
target.style.width = "100%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.containsN(
target,
".o_menu_sections > *:not(.o_menu_sections_more):not(.d-none)",
3,
"should have 3 menu sections displayed (that are not the 'more' menu)"
);
assert.containsNone(target, ".o_menu_sections_more", "the 'more' menu should not exist");
// Check the navbar adaptation calls
assert.verifySteps([
"adapt -> hide 0/3 sections",
"adapt -> hide 3/3 sections",
"adapt -> hide 0/3 sections",
]);
});
QUnit.test(
"'more' menu sections adaptations do not trigger render in some cases",
async (assert) => {
let adaptRunning = false;
let adaptCount = 0;
let adaptRenderCount = 0;
class MyNavbar extends NavBar {
async adapt() {
adaptRunning = true;
adaptCount++;
await super.adapt();
adaptRunning = false;
}
async render() {
if (adaptRunning) {
adaptRenderCount++;
}
await super.render(...arguments);
}
}
const newMenus = {
root: { id: "root", children: [1], name: "root", appID: "root" },
1: { id: 1, children: [11, 12, 13], name: "App1", appID: 1 },
11: { id: 11, children: [], name: "Section 1", appID: 1 },
12: { id: 12, children: [], name: "Section 2", appID: 1 },
13: { id: 13, children: [], name: "Section 3", appID: 1 },
};
baseConfig.serverData.menus = newMenus;
// Force the parent width, to make this test independent of screen size
target.style.width = "600px";
const env = await makeTestEnv(baseConfig);
const navbar = await mount(MyNavbar, target, { env });
assert.strictEqual(navbar.currentAppSections.length, 0, "0 app sub menus");
assert.strictEqual(target.querySelector(".o_navbar").offsetWidth, 600);
assert.strictEqual(adaptCount, 1);
assert.strictEqual(
adaptRenderCount,
0,
"during adapt, render not triggered as the navbar has no app sub menus"
);
// Force minimal width and dispatch window resize event
target.querySelector(".o_navbar").style.width = "0%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.strictEqual(target.querySelector(".o_navbar").offsetWidth, 0);
assert.strictEqual(adaptCount, 2);
assert.strictEqual(
adaptRenderCount,
0,
"during adapt, render not triggered as the navbar has no app sub menus"
);
// Set menu
env.services.menu.setCurrentMenu(1);
await nextTick();
assert.strictEqual(navbar.currentAppSections.length, 3, "3 app sub menus");
assert.strictEqual(
navbar.currentAppSectionsExtra.length,
3,
"all app sub menus are inside the more menu"
);
assert.strictEqual(adaptCount, 3);
assert.strictEqual(
adaptRenderCount,
1,
"during adapt, render triggered as the navbar does not have enough space for app sub menus"
);
// Force 40% width and dispatch window resize event
target.querySelector(".o_navbar").style.width = "40%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.strictEqual(
navbar.currentAppSectionsExtra.length,
3,
"all app sub menus are STILL inside the more menu"
);
assert.strictEqual(adaptCount, 4);
assert.strictEqual(
adaptRenderCount,
1,
"during adapt, render not triggered as the more menu dropdown is STILL the same"
);
// Reset to full width and dispatch window resize event
target.querySelector(".o_navbar").style.width = "100%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.strictEqual(navbar.currentAppSections.length, 3, "still 3 app sub menus");
assert.strictEqual(
navbar.currentAppSectionsExtra.length,
0,
"all app sub menus are NO MORE inside the more menu"
);
assert.strictEqual(adaptCount, 5);
assert.strictEqual(
adaptRenderCount,
2,
"during adapt, render triggered as the more menu dropdown is NO MORE the same"
);
}
);
QUnit.test("'more' menu sections properly updated on app change", async (assert) => {
const newMenus = {
root: { id: "root", children: [1, 2], name: "root", appID: "root" },
// First App
1: { id: 1, children: [10, 11, 12], name: "App1", appID: 1 },
10: { id: 10, children: [], name: "Section 10", appID: 1 },
11: { id: 11, children: [], name: "Section 11", appID: 1 },
12: { id: 12, children: [120, 121, 122], name: "Section 12", appID: 1 },
120: { id: 120, children: [], name: "Section 120", appID: 1 },
121: { id: 121, children: [], name: "Section 121", appID: 1 },
122: { id: 122, children: [], name: "Section 122", appID: 1 },
// Second App
2: { id: 2, children: [20, 21, 22], name: "App2", appID: 2 },
20: { id: 20, children: [], name: "Section 20", appID: 2 },
21: { id: 21, children: [], name: "Section 21", appID: 2 },
22: { id: 22, children: [220, 221, 222], name: "Section 22", appID: 2 },
220: { id: 220, children: [], name: "Section 220", appID: 2 },
221: { id: 221, children: [], name: "Section 221", appID: 2 },
222: { id: 222, children: [], name: "Section 222", appID: 2 },
};
baseConfig.serverData.menus = newMenus;
const env = await makeTestEnv(baseConfig);
// Force the parent width, to make this test independent of screen size
target.style.width = "1080px";
// Set App1 menu and mount
env.services.menu.setCurrentMenu(1);
await mount(NavBar, target, { env });
// Force minimal width and dispatch window resize event
target.style.width = "0%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.containsOnce(
target,
".o_menu_sections > *:not(.d-none)",
"only one menu section should be displayed"
);
assert.containsOnce(
target,
".o_menu_sections_more:not(.d-none)",
"the displayed menu section should be the 'more' menu"
);
// Open the more menu
await click(target, ".o_menu_sections_more .dropdown-toggle");
assert.deepEqual(
[...target.querySelectorAll(".dropdown-menu > *")].map((el) => el.textContent),
["Section 10", "Section 11", "Section 12", "Section 120", "Section 121", "Section 122"],
"'more' menu should contain App1 sections"
);
// Close the more menu
await click(target, ".o_menu_sections_more .dropdown-toggle");
// Set App2 menu
env.services.menu.setCurrentMenu(2);
await nextTick();
// Open the more menu
await click(target, ".o_menu_sections_more .dropdown-toggle");
assert.deepEqual(
[...target.querySelectorAll(".dropdown-menu > *")].map((el) => el.textContent),
["Section 20", "Section 21", "Section 22", "Section 220", "Section 221", "Section 222"],
"'more' menu should contain App2 sections"
);
});
QUnit.test("Do not execute adapt when navbar is destroyed", async (assert) => {
assert.expect(5);
const { execRegisteredTimeouts } = mockTimeout();
class MyNavbar extends NavBar {
async adapt() {
assert.step("adapt NavBar");
return super.adapt();
}
}
const env = await makeTestEnv(baseConfig);
// Set menu and mount
env.services.menu.setCurrentMenu(1);
const navbar = await mount(MyNavbar, target, { env });
assert.verifySteps(["adapt NavBar"]);
window.dispatchEvent(new Event("resize"));
execRegisteredTimeouts();
assert.verifySteps(["adapt NavBar"]);
window.dispatchEvent(new Event("resize"));
destroy(navbar);
execRegisteredTimeouts();
assert.verifySteps([]);
});