[REF] web: remove LegacyComponent

Before this commit, components used `el` and `trigger` but this works
thanks to LegacyComponent. LegacyComponent has been introduced to ease
the transition from owl 1 to owl 2.

This commit replaces the uses of `el` by `useRef` and `trigger` by
adding callback in props. Without these owl 1 features, components
no longer need to extend LegacyComponent.
This commit also adapts the tests to use `getFixture` instead of `el`.

closes odoo/odoo#85903

Related: odoo/enterprise#25032
Signed-off-by: Samuel Degueldre <sad@odoo.com>
Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Simon Genin (ges) <ges@odoo.com>
Co-authored-by: luvi <luvi@odoo.com>
Co-authored-by: Jorge Pinna Puissant <jpp@odoo.com>
This commit is contained in:
Michael (mcm)
2022-03-08 09:08:54 +00:00
co-authored by Aaron Bohy Simon Genin luvi Jorge Pinna Puissant
parent 3ff07b1a01
commit 8a5721bc7d
54 changed files with 338 additions and 346 deletions
@@ -15,8 +15,8 @@
<div class="o_command_category">
<t t-foreach="category.commands" t-as="command" t-key="command.keyId">
<t t-set="commandIndex" t-value="state.commands.indexOf(command)" />
<div t-attf-id="o_command_{{commandIndex}}" class="o_command" t-att-class="{ focused: state.selectedCommand === command }" t-on-click="() => this.onCommandClicked(commandIndex)" t-on-mouseenter="() => this.onCommandMouseEnter(commandIndex)" t-on-close="() => this.props.closeMe()" t-on-execute-command="() => this.executeCommand(command)">
<t t-component="command.Component || DefaultCommandItem" name="command.name" searchValue="clearSearchValue" t-props="command.props">
<div t-attf-id="o_command_{{commandIndex}}" class="o_command" t-att-class="{ focused: state.selectedCommand === command }" t-on-click="() => this.onCommandClicked(commandIndex)" t-on-mouseenter="() => this.onCommandMouseEnter(commandIndex)" t-on-close="() => this.props.closeMe()">
<t t-component="command.Component || DefaultCommandItem" name="command.name" searchValue="clearSearchValue" t-props="command.props" executeCommand="() => this.executeCommand(command)">
<t t-set-slot="name">
<span class="o_command_name">
<t t-foreach="command.splitName" t-as="name" t-key="name_index">
@@ -7,18 +7,17 @@ import { registry } from "@web/core/registry";
import { capitalize } from "@web/core/utils/strings";
import { getVisibleElements } from "@web/core/utils/ui";
import { DefaultCommandItem } from "./command_palette";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { Component } = owl;
const commandSetupRegistry = registry.category("command_setup");
commandSetupRegistry.add("default", {
emptyMessage: _lt("No command found"),
});
export class HotkeyCommandItem extends LegacyComponent {
export class HotkeyCommandItem extends Component {
setup() {
useHotkey(this.props.hotkey, () => {
this.trigger("execute-command");
});
useHotkey(this.props.hotkey, this.props.executeCommand);
}
getKeysToPress(command) {
@@ -3,9 +3,9 @@
import { localization } from "@web/core/l10n/localization";
import { registry } from "@web/core/registry";
import { useAutofocus } from "@web/core/utils/hooks";
import { LegacyComponent } from "@web/legacy/legacy_component";
const {
Component,
onMounted,
onWillUpdateProps,
onWillUnmount,
@@ -49,8 +49,9 @@ const luxonFormatToMomentFormat = (format) => {
* stringified since tempusdominus only works with moment objects.
* @extends Component
*/
export class DatePicker extends LegacyComponent {
export class DatePicker extends Component {
setup() {
this.root = useRef("root");
this.inputRef = useRef("input");
this.state = useState({ warning: false });
@@ -68,9 +69,9 @@ export class DatePicker extends LegacyComponent {
}
onMounted() {
window.$(this.el).on("show.datetimepicker", () => this.inputRef.el.select());
window.$(this.el).on("hide.datetimepicker", () => this.onDateChange());
window.$(this.el).on("error.datetimepicker", () => false); // Ignores datepicker errors
window.$(this.root.el).on("show.datetimepicker", () => this.inputRef.el.select());
window.$(this.root.el).on("hide.datetimepicker", () => this.onDateChange());
window.$(this.root.el).on("error.datetimepicker", () => false); // Ignores datepicker errors
this.bootstrapDateTimePicker(this.props);
this.updateInput();
@@ -91,7 +92,7 @@ export class DatePicker extends LegacyComponent {
}
onWillUnmount() {
window.$(this.el).off(); // Removes all jQuery events
window.$(this.root.el).off(); // Removes all jQuery events
this.bootstrapDateTimePicker("destroy");
}
@@ -169,7 +170,7 @@ export class DatePicker extends LegacyComponent {
}
commandOrParams = params;
}
window.$(this.el).datetimepicker(commandOrParams);
window.$(this.root.el).datetimepicker(commandOrParams);
}
//---------------------------------------------------------------------
@@ -3,7 +3,7 @@
<t t-name="web.DatePicker" owl="1">
<div class="o_datepicker" aria-atomic="true" t-att-id="datePickerId" data-target-input="nearest">
<div class="o_datepicker" aria-atomic="true" t-att-id="datePickerId" data-target-input="nearest" t-ref="root">
<input type="text"
class="o_datepicker_input o_input datetimepicker-input"
t-att-name="props.name"
+2 -3
View File
@@ -2,11 +2,10 @@
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
import { useActiveElement } from "../ui/ui_service";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { useRef, useChildSubEnv, xml } = owl;
const { Component, useRef, useChildSubEnv, xml } = owl;
export class Dialog extends LegacyComponent {
export class Dialog extends Component {
setup() {
if (this.constructor === Dialog) {
throw new Error(
@@ -4,9 +4,9 @@ import { useBus, useService } from "@web/core/utils/hooks";
import { usePosition } from "../position/position_hook";
import { useDropdownNavigation } from "./dropdown_navigation_hook";
import { localization } from "../l10n/localization";
import { LegacyComponent } from "@web/legacy/legacy_component";
const {
Component,
EventBus,
onWillStart,
useEffect,
@@ -40,7 +40,7 @@ export const DROPDOWN = Symbol("Dropdown");
/**
* @extends Component
*/
export class Dropdown extends LegacyComponent {
export class Dropdown extends Component {
setup() {
this.state = useState({
open: this.props.startOpen,
@@ -1,6 +1,7 @@
/** @odoo-module **/
import { DROPDOWN } from "./dropdown";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { Component } = owl;
/**
* @enum {string}
@@ -11,7 +12,7 @@ const ParentClosingMode = {
AllParents: "all",
};
export class DropdownItem extends LegacyComponent {
export class DropdownItem extends Component {
/**
* Tells the parent dropdown that an item was selected and closes the
* parent(s) dropdown according the the parentClosingMode prop.
@@ -1,9 +1,8 @@
/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { useEffect, useExternalListener } = owl;
const { Component, useEffect, useExternalListener, useState } = owl;
/**
* @typedef Common
@@ -34,15 +33,16 @@ const { useEffect, useExternalListener } = owl;
* importing this file. The usual way to do that would be to use the effect
* service (by triggering the 'show_effect' event)
*/
export class RainbowMan extends LegacyComponent {
export class RainbowMan extends Component {
setup() {
useExternalListener(document.body, "click", this.closeRainbowMan);
this.state = useState({ isFading: false });
this.delay = RainbowMan.rainbowFadeouts[this.props.fadeout];
if (this.delay) {
useEffect(
() => {
const timeout = browser.setTimeout(() => {
this.el.classList.add("o_reward_fading");
this.state.isFading = true;
}, this.delay);
return () => browser.clearTimeout(timeout);
},
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.RainbowMan" owl="1">
<div class="o_reward" t-on-animationend="onAnimationEnd">
<div class="o_reward" t-att-class="{ o_reward_fading: state.isFading }" t-on-animationend="onAnimationEnd">
<svg class="o_reward_rainbow" viewBox="0 0 340 180">
<path d="M270,170a100,100,0,0,0-200,0" style="stroke:#FF9E80;"/>
<path d="M290,170a120,120,0,0,0-240,0" style="stroke:#FFE57F;"/>
@@ -1,9 +1,8 @@
/** @odoo-module **/
import { useService } from "@web/core/utils/hooks";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { useRef } = owl;
const { Component, useRef } = owl;
/**
* Custom file input
@@ -23,7 +22,7 @@ const { useRef } = owl;
* @param {string} [props.multi_upload=false] Whether the input should allow
* to upload multiple files at once.
*/
export class FileInput extends LegacyComponent {
export class FileInput extends Component {
setup() {
this.http = useService("http");
this.fileInputRef = useRef("file-input");
@@ -58,7 +57,7 @@ export class FileInput extends LegacyComponent {
if (parsedFileData.error) {
throw new Error(parsedFileData.error);
}
this.trigger("uploaded", { files: parsedFileData });
this.props.onUpload(parsedFileData);
}
/**
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.FileInput" owl="1">
<span class="o_file_input" aria-atomic="true" t-on-uploaded="(ev) => props.onUpload(ev.detail.files)">
<span class="o_file_input" aria-atomic="true">
<span class="o_file_input_trigger" t-on-click.prevent="onTriggerClicked">
<t t-slot="default">
<button class="btn btn-primary">Choose File</button>
@@ -18,7 +18,6 @@ Popover.defaultProps = {
position: "bottom",
};
Popover.props = {
close: { type: Function },
popoverClass: {
optional: true,
type: String,
@@ -4,8 +4,6 @@
<t t-name="web.PopoverWowl" owl="1">
<div role="tooltip" class="o_popover"
t-att-class="props.popoverClass"
t-on-popover-compute="compute"
t-on-popover-closed="props.close"
t-ref="popper"
>
<t t-slot="default" />
@@ -21,12 +21,19 @@ class PopoverController extends Component {
get popoverProps() {
return {
close: this.props.close,
target: this.target,
position: this.props.position,
popoverClass: this.props.popoverClass,
};
}
get popoverComponentProps() {
return {
close: this.props.close,
...this.props.props,
};
}
get target() {
if (typeof this.props.target === "string") {
return document.querySelector(this.props.target);
@@ -56,7 +63,7 @@ PopoverController.defaultProps = {
};
PopoverController.template = xml/*xml*/ `
<Popover t-props="popoverProps">
<t t-component="props.Component" t-props="props.props"/>
<t t-component="props.Component" t-props="popoverComponentProps"/>
</Popover>
`;
@@ -5,6 +5,7 @@ const { Component } = owl;
export class Tooltip extends Component {}
Tooltip.template = "web.Tooltip";
Tooltip.props = {
close: Function,
tooltip: { type: String, optional: true },
template: { type: String, optional: true },
info: { optional: true },
@@ -83,6 +83,7 @@ export function useBus(bus, eventName, callback) {
// -----------------------------------------------------------------------------
/**
* @deprecated
* The useListener hook offers an alternative to Owl's classical event
* registration mechanism (with attribute 't-on-eventName' in xml).
*
@@ -228,19 +228,21 @@ odoo.define('web.OwlDialog', function (require) {
this.displayed.splice(this.displayed.indexOf(dialog), 1);
// Activate last dialog and update body class
const lastDialog = this.displayed[this.displayed.length - 1];
let modalEl;
if (lastDialog) {
if (lastDialog.el) {
// legacy dialog
lastDialog.el.focus();
modalEl = lastDialog.$modal[0];
} else if (lastDialog.modalRef) {
// Owl 2 / Wowl dialogs (because of legacy_dialogs.js patch on Dialog)
lastDialog.modalRef.el.focus();
modalEl = lastDialog.modalRef.el;
} else {
// Owl dialog | LegacyAdaptedDialog
lastDialog.dialogRef.el.focus();
modalEl = lastDialog.dialogRef.el;
}
const modalEl = lastDialog.modalRef ?
// Owl dialog | LegacyAdaptedDialog
lastDialog.modalRef.el :
// Legacy dialog
lastDialog.$modal[0];
modalEl.classList.remove('o_inactive_modal');
modalEl.setAttribute("tabindex", "-1");
} else {
@@ -1,7 +1,5 @@
/** @odoo-module **/
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* @deprecated
* This component SHOULD NOT be used!
@@ -41,11 +39,8 @@ export class LegacyComponent extends owl.Component {
return null;
}
if (hasOwnProperty.call(bdom, "component")) {
return bdom.component.el;
} else {
return bdom.firstNode();
}
const el = (bdom.component && bdom.component.el) || bdom.firstNode();
return el.nodeType === Node.ELEMENT_NODE ? el : null;
}
/**
* Add a new method to owl Components to ensure that no performed RPC is
@@ -1,11 +1,10 @@
/** @odoo-module **/
import { Dropdown } from "@web/core/dropdown/dropdown";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { useState } = owl;
const { Component, useState } = owl;
export class CustomGroupByItem extends LegacyComponent {
export class CustomGroupByItem extends Component {
setup() {
this.state = useState({});
if (this.props.fields.length) {
+3 -2
View File
@@ -2,7 +2,8 @@
import { ControlPanel } from "@web/search/control_panel/control_panel";
import { SearchPanel } from "@web/search/search_panel/search_panel";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { Component } = owl;
/**
* @param {Object} params
@@ -16,7 +17,7 @@ export const extractLayoutComponents = (params) => {
};
};
export class Layout extends LegacyComponent {
export class Layout extends Component {
setup() {
const { display = {} } = this.env.searchModel || {};
this.components = extractLayoutComponents(this.env.config);
@@ -6,19 +6,19 @@ import { registry } from "@web/core/registry";
import { KeepLast } from "@web/core/utils/concurrency";
import { useAutofocus, useBus, useService } from "@web/core/utils/hooks";
import { fuzzyTest } from "@web/core/utils/search";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { useExternalListener, useState } = owl;
const { Component, useExternalListener, useRef, useState } = owl;
const parsers = registry.category("parsers");
const CHAR_FIELDS = ["char", "html", "many2many", "many2one", "one2many", "text"];
let nextItemId = 1;
export class SearchBar extends LegacyComponent {
export class SearchBar extends Component {
setup() {
this.fields = this.env.searchModel.searchViewFields;
this.searchItems = this.env.searchModel.getSearchItems((f) => f.type === "field");
this.root = useRef("root");
// core state
this.state = useState({
@@ -221,7 +221,7 @@ export class SearchBar extends LegacyComponent {
* @param {number} [index]
*/
focusFacet(index) {
const facets = this.el.getElementsByClassName("o_searchview_facet");
const facets = this.root.el.getElementsByClassName("o_searchview_facet");
if (facets.length) {
if (!index) {
facets[facets.length - 1].focus();
@@ -295,7 +295,7 @@ export class SearchBar extends LegacyComponent {
break;
}
case "ArrowRight": {
const facets = this.el.getElementsByClassName("o_searchview_facet");
const facets = this.root.el.getElementsByClassName("o_searchview_facet");
if (facetIndex === facets.length - 1) {
this.inputRef.el.focus();
} else {
@@ -441,7 +441,7 @@ export class SearchBar extends LegacyComponent {
* @param {MouseEvent} ev
*/
onWindowClick(ev) {
if (this.items.length && !this.el.contains(ev.target)) {
if (this.items.length && !this.root.el.contains(ev.target)) {
this.resetState();
}
}
@@ -76,7 +76,7 @@
</t>
<t t-name="web.SearchBar" owl="1">
<div class="o_cp_searchview" role="search">
<div class="o_cp_searchview" role="search" t-ref="root">
<div class="o_searchview" role="search" aria-autocomplete="list">
<i class="o_searchview_icon oi oi-search"
role="img"
@@ -1,8 +1,6 @@
/** @odoo-module **/
import { LegacyComponent } from "@web/legacy/legacy_component";
const { onMounted, onWillStart, onWillUpdateProps, useState } = owl;
const { Component, onMounted, onWillStart, onWillUpdateProps, useRef, useState } = owl;
/**
* Search panel
@@ -13,12 +11,13 @@ const { onMounted, onWillStart, onWillUpdateProps, useState } = owl;
* values (categories or ungrouped filters) or groups of values (grouped filters).
* Its state is directly affected by its model (@see SearchModel).
*/
export class SearchPanel extends LegacyComponent {
export class SearchPanel extends Component {
setup() {
this.state = useState({
active: {},
expanded: {},
});
this.root = useRef("root");
this.scrollTop = 0;
this.hasImportedState = false;
@@ -33,7 +32,7 @@ export class SearchPanel extends LegacyComponent {
onMounted(() => {
this.updateGroupHeadersChecked();
if (this.hasImportedState) {
this.el.scroll({ top: this.scrollTop });
this.root.el.scroll({ top: this.scrollTop });
}
});
@@ -58,7 +57,7 @@ export class SearchPanel extends LegacyComponent {
exportState() {
const exported = {
expanded: this.state.expanded,
scrollTop: this.el.scrollTop,
scrollTop: this.root.el.scrollTop,
};
return JSON.stringify(exported);
}
@@ -182,7 +181,7 @@ export class SearchPanel extends LegacyComponent {
* headers according to the state of their values.
*/
updateGroupHeadersChecked() {
const groups = this.el.querySelectorAll(":scope .o_search_panel_filter_group");
const groups = this.root.el.querySelectorAll(":scope .o_search_panel_filter_group");
for (const group of groups) {
const header = group.querySelector(":scope .o_search_panel_group_header input");
const vals = [...group.querySelectorAll(":scope .o_search_panel_filter_value input")];
@@ -2,7 +2,7 @@
<templates id="template" xml:space="preserve">
<t t-name="web.SearchPanel" owl="1">
<div class="o_search_panel" t-att-class="env.searchModel.searchPanelInfo.className">
<div class="o_search_panel" t-att-class="env.searchModel.searchPanelInfo.className" t-ref="root">
<section t-foreach="sections" t-as="section" t-key="section.id"
t-attf-class="o_search_panel_section o_search_panel_{{ section.type }}"
>
@@ -3,14 +3,13 @@
import { useBus, useService } from "@web/core/utils/hooks";
import { SearchModel } from "@web/search/search_model";
import { CallbackRecorder, useSetupAction } from "@web/webclient/actions/action_hook";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { onWillStart, onWillUpdateProps, toRaw, useSubEnv } = owl;
const { Component, onWillStart, onWillUpdateProps, toRaw, useSubEnv } = owl;
export const SEARCH_KEYS = ["comparison", "context", "domain", "groupBy", "orderBy"];
const OTHER_SEARCH_KEYS = ["irFilters", "searchViewArch", "searchViewFields", "searchViewId"];
export class WithSearch extends LegacyComponent {
export class WithSearch extends Component {
setup() {
this.Component = this.props.Component;
@@ -7,9 +7,8 @@ import { SEP } from "./graph_model";
import { sortBy } from "@web/core/utils/arrays";
import { useAssets } from "@web/core/assets";
import { renderToString } from "@web/core/utils/render";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { onWillUnmount, useEffect, useRef } = owl;
const { Component, onWillUnmount, useEffect, useRef } = owl;
const NO_DATA = _lt("No data");
@@ -39,10 +38,11 @@ function shortenLabel(label) {
return shortLabel;
}
export class GraphRenderer extends LegacyComponent {
export class GraphRenderer extends Component {
setup() {
this.model = this.props.model;
this.rootRef = useRef("root");
this.canvasRef = useRef("canvas");
this.containerRef = useRef("container");
@@ -99,16 +99,16 @@ export class GraphRenderer extends LegacyComponent {
*/
customTooltip(data, metaData, tooltipModel) {
const { measure, measures, disableLinking, mode } = metaData;
this.el.style.cursor = "";
this.rootRef.el.style.cursor = "";
this.removeTooltips();
if (tooltipModel.opacity === 0 || tooltipModel.dataPoints.length === 0) {
return;
}
if (!disableLinking && mode !== "line") {
this.el.style.cursor = "pointer";
this.rootRef.el.style.cursor = "pointer";
}
const chartAreaTop = this.chart.chartArea.top;
const viewContentTop = this.el.getBoundingClientRect().top;
const viewContentTop = this.rootRef.el.getBoundingClientRect().top;
const innerHTML = renderToString("web.GraphRenderer.CustomTooltip", {
maxWidth: getMaxWidth(this.chart.chartArea),
measure: measures[measure].string,
@@ -548,7 +548,7 @@ export class GraphRenderer extends LegacyComponent {
if (this.legendTooltip || text === fullText) {
return;
}
const viewContentTop = this.el.getBoundingClientRect().top;
const viewContentTop = this.rootRef.el.getBoundingClientRect().top;
const legendTooltip = Object.assign(document.createElement("div"), {
className: "o_tooltip_legend",
innerText: fullText,
@@ -28,7 +28,7 @@
</t>
<t t-name="web.GraphRenderer" owl="1">
<div t-att-class="'o_graph_renderer o_renderer ' + props.class">
<div t-att-class="'o_graph_renderer o_renderer ' + props.class" t-ref="root">
<div class="o_graph_canvas_container" t-ref="container">
<canvas t-ref="canvas" />
</div>
@@ -3,7 +3,7 @@
import { useDebugCategory } from "@web/core/debug/debug_context";
import { useSetupAction } from "@web/webclient/actions/action_hook";
import { registry } from "@web/core/registry";
import { useListener, useService } from "@web/core/utils/hooks";
import { useService } from "@web/core/utils/hooks";
import { evaluateExpr } from "@web/core/py_js/py";
const { useComponent, xml } = owl;
@@ -53,7 +53,6 @@ export function useViewArch(arch, params = {}) {
* @param {Function} [params.reload] The function to execute to reload, if a button has data-reload-on-close
*/
export function useActionLinks({ resModel, reload }) {
const selector = `a[type="action"]`;
const component = useComponent();
const keepLast = component.env.keepLast;
@@ -128,5 +127,10 @@ export function useActionLinks({ resModel, reload }) {
checkAndCollapseBootstrap(target);
}
useListener("click", selector, handler);
return (ev) => {
const a = ev.target.closest(`a[type="action"]`);
if (a && ev.currentTarget.contains(a)) {
handler(ev);
}
};
}
@@ -3,16 +3,15 @@
import { loadAssets } from "@web/core/assets";
import { useService } from "@web/core/utils/hooks";
import { useActionLinks } from "@web/views/helpers/view_hook";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { markup, onWillStart, xml } = owl;
const { Component, markup, onWillStart, xml } = owl;
export class OnboardingBanner extends LegacyComponent {
export class OnboardingBanner extends Component {
setup() {
this.rpc = useService("rpc");
this.user = useService("user");
const resModel = "searchModel" in this.env ? this.env.searchModel.resModel : undefined;
useActionLinks({
this.handleActionLinks = useActionLinks({
resModel,
reload: async () => {
this.bannerHTML = await this.loadBanner(this.env.config.bannerRoute);
@@ -53,5 +52,5 @@ export class OnboardingBanner extends LegacyComponent {
}
}
OnboardingBanner.template = xml`<div class="w-100" t-out="bannerHTML"/>`;
OnboardingBanner.template = xml`<div class="w-100" t-on-click="handleActionLinks" t-out="bannerHTML"/>`;
OnboardingBanner.props = {};
@@ -5,16 +5,16 @@ import { localization } from "@web/core/l10n/localization";
import { registry } from "@web/core/registry";
import { PivotGroupByMenu } from "@web/views/pivot/pivot_group_by_menu";
import fieldUtils from "web.field_utils";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { onWillUpdateProps } = owl;
const { Component, onWillUpdateProps, useRef } = owl;
const formatterRegistry = registry.category("formatters");
export class PivotRenderer extends LegacyComponent {
export class PivotRenderer extends Component {
setup() {
this.model = this.props.model;
this.table = this.model.getTable();
this.l10n = localization;
this.tableRef = useRef("table");
onWillUpdateProps(this.onWillUpdateProps);
}
@@ -136,7 +136,7 @@ export class PivotRenderer extends LegacyComponent {
}
index += 1; // row groupbys column
}
this.el
this.tableRef.el
.querySelectorAll("td:nth-child(" + (index + 1) + ")")
.forEach((elt) => elt.classList.add("o_cell_hover"));
}
@@ -144,7 +144,7 @@ export class PivotRenderer extends LegacyComponent {
* Remove the hover on the columns.
*/
onMouseLeave() {
this.el
this.tableRef.el
.querySelectorAll(".o_cell_hover")
.forEach((elt) => elt.classList.remove("o_cell_hover"));
}
@@ -4,10 +4,14 @@
<t t-name="web.PivotRenderer" owl="1">
<div class="o_pivot table-responsive">
<table class="table-hover table table-sm table-bordered bg-white" t-att-class="{
o_enable_linking: !model.metaData.disableLinking,
o_sample_data_disabled: model.useSampleModel,
}">
<table
class="table-hover table table-sm table-bordered bg-white"
t-att-class="{
o_enable_linking: !model.metaData.disableLinking,
o_sample_data_disabled: model.useSampleModel,
}"
t-ref="table"
>
<thead>
<tr t-foreach="table.headers" t-as="row" t-key="'header_' + row_index">
<t t-foreach="row" t-as="cell" t-key="'header_row_' + cell_index">
+3 -4
View File
@@ -8,9 +8,8 @@ import { deepCopy } from "@web/core/utils/objects";
import { extractLayoutComponents } from "@web/search/layout";
import { WithSearch } from "@web/search/with_search/with_search";
import { useActionLinks } from "@web/views/helpers/view_hook";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { onWillUpdateProps, onWillStart, toRaw, useSubEnv } = owl;
const { Component, onWillUpdateProps, onWillStart, toRaw, useSubEnv } = owl;
const viewRegistry = registry.category("views");
/** @typedef {Object} Config
@@ -125,7 +124,7 @@ const STANDARD_PROPS = [
"searchModel",
];
export class View extends LegacyComponent {
export class View extends Component {
setup() {
const { arch, fields, resModel, searchViewArch, searchViewFields, type } = this.props;
@@ -153,7 +152,7 @@ export class View extends LegacyComponent {
},
});
useActionLinks({ resModel });
this.handleActionLinks = useActionLinks({ resModel });
onWillStart(this.onWillStart);
onWillUpdateProps(this.onWillUpdateProps);
+1 -1
View File
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.View" owl="1">
<div t-attf-class="o_action o_view_controller o_{{env.config.viewType}}_view">
<div t-attf-class="o_action o_view_controller o_{{env.config.viewType}}_view" t-on-click="handleActionLinks">
<WithSearch t-props="withSearchProps"/>
</div>
</t>
@@ -6,9 +6,8 @@ import { useService, useBus } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { debounce } from "@web/core/utils/timing";
import { ErrorHandler } from "@web/core/utils/components";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { onWillDestroy, useExternalListener, useEffect, useRef } = owl;
const { Component, onWillDestroy, useExternalListener, useEffect, useRef } = owl;
const systrayRegistry = registry.category("systray");
const getBoundingClientRect = Element.prototype.getBoundingClientRect;
@@ -31,11 +30,12 @@ MenuDropdown.props.xmlid = {
optional: true,
};
export class NavBar extends LegacyComponent {
export class NavBar extends Component {
setup() {
this.currentAppSectionsExtra = [];
this.actionService = useService("action");
this.menuService = useService("menu");
this.root = useRef("root");
this.appSubMenus = useRef("appSubMenus");
const debouncedAdapt = debounce(this.adapt.bind(this), 250);
onWillDestroy(() => debouncedAdapt.cancel());
@@ -95,7 +95,8 @@ export class NavBar extends LegacyComponent {
* By the end of this method another render may occur depending on the adaptation result.
*/
async adapt() {
if (!this.el) {
if (!this.root.el) {
/** @todo do we still need this check? */
// currently, the promise returned by 'render' is resolved at the end of
// the rendering even if the component has been destroyed meanwhile, so we
// may get here and have this.el unset
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.NavBar" owl="1">
<header>
<header class="o_navbar" t-ref="root">
<nav
class="o_main_navbar"
data-command-category="navbar"
@@ -6,11 +6,10 @@ import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { browser } from "@web/core/browser/browser";
import { symmetricalDifference } from "@web/core/utils/arrays";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { useState } = owl;
const { Component, useState } = owl;
export class SwitchCompanyMenu extends LegacyComponent {
export class SwitchCompanyMenu extends Component {
setup() {
this.companyService = useService("company");
this.currentCompany = this.companyService.currentCompany;
+11 -12
View File
@@ -8,9 +8,8 @@ import { Dialog } from "@web/core/dialog/dialog";
import { makeTestEnv } from "../helpers/mock_env";
import { click, destroy, getFixture, mount } from "../helpers/utils";
import { makeFakeDialogService } from "../helpers/mock_services";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { useEffect, useState, xml } = owl;
const { Component, useEffect, useState, xml } = owl;
const serviceRegistry = registry.category("services");
let parent;
let target;
@@ -95,7 +94,7 @@ QUnit.module("Components", (hooks) => {
QUnit.test("simple rendering with two dialogs", async function (assert) {
assert.expect(3);
class Parent extends LegacyComponent {}
class Parent extends Component {}
Parent.template = xml`
<div>
<SimpleDialog title="'First Title'">
@@ -124,7 +123,7 @@ QUnit.module("Components", (hooks) => {
assert.expect(3);
const env = await makeTestEnv();
class MyDialog extends SimpleDialog {}
class Parent extends LegacyComponent {
class Parent extends Component {
close() {
assert.step("close");
}
@@ -148,7 +147,7 @@ QUnit.module("Components", (hooks) => {
const env = await makeTestEnv();
assert.expect(3);
class MyDialog extends SimpleDialog {}
class Parent extends LegacyComponent {
class Parent extends Component {
close() {
assert.step("close");
}
@@ -181,7 +180,7 @@ QUnit.module("Components", (hooks) => {
<button class="btn btn-primary">The Second Button</button>
</div>
`;
class Parent extends LegacyComponent {
class Parent extends Component {
setup() {
super.setup();
this.state = useState({
@@ -203,7 +202,7 @@ QUnit.module("Components", (hooks) => {
QUnit.test("embed an arbitrary component in a dialog is possible", async function (assert) {
assert.expect(6);
class SubComponent extends LegacyComponent {
class SubComponent extends Component {
_onClick() {
assert.step("subcomponent-clicked");
this.props.onClicked();
@@ -212,7 +211,7 @@ QUnit.module("Components", (hooks) => {
SubComponent.template = xml`
<div class="o_subcomponent" t-esc="props.text" t-on-click="_onClick"/>
`;
class Parent extends LegacyComponent {
class Parent extends Component {
_onSubcomponentClicked() {
assert.step("message received by parent");
}
@@ -234,7 +233,7 @@ QUnit.module("Components", (hooks) => {
QUnit.test("dialog without header/footer", async function (assert) {
assert.expect(4);
class Parent extends LegacyComponent {}
class Parent extends Component {}
Parent.template = xml`
<SimpleDialog renderHeader="false" renderFooter="false"/>
`;
@@ -249,7 +248,7 @@ QUnit.module("Components", (hooks) => {
QUnit.test("dialog size can be chosen", async function (assert) {
assert.expect(5);
class Parent extends LegacyComponent {}
class Parent extends Component {}
Parent.template = xml`
<div>
<SimpleDialog contentClass="'xl'" size="'modal-xl'"/>
@@ -281,7 +280,7 @@ QUnit.module("Components", (hooks) => {
QUnit.test("dialog can be rendered on fullscreen", async function (assert) {
assert.expect(2);
class Parent extends LegacyComponent {}
class Parent extends Component {}
Parent.template = xml`
<div><SimpleDialog fullscreen="true"/></div>
`;
@@ -294,7 +293,7 @@ QUnit.module("Components", (hooks) => {
QUnit.test("can be the UI active element", async function (assert) {
assert.expect(3);
class Parent extends LegacyComponent {
class Parent extends Component {
setup() {
this.ui = useService("ui");
this.modal = null;
@@ -15,13 +15,14 @@ import {
nextTick,
patchWithCleanup,
} from "../../helpers/utils";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { markup, xml } = owl;
const { Component, markup, xml } = owl;
const serviceRegistry = registry.category("services");
const mainComponentRegistry = registry.category("main_components");
class Parent extends LegacyComponent {
let target;
class Parent extends Component {
setup() {
this.EffectContainer = mainComponentRegistry.get("EffectContainer");
this.NotificationContainer = mainComponentRegistry.get("NotificationContainer");
@@ -36,7 +37,6 @@ Parent.template = xml`
async function makeParent() {
const env = await makeTestEnv({ serviceRegistry });
const target = getFixture();
const parent = await mount(Parent, target, { env });
return parent;
}
@@ -56,6 +56,8 @@ QUnit.module("Effect Service", (hooks) => {
serviceRegistry.add("effect", effectService);
serviceRegistry.add("notification", notificationService);
serviceRegistry.add("localization", makeFakeLocalizationService());
target = getFixture();
});
QUnit.test("effect service displays a rainbowman by default", async function (assert) {
@@ -64,8 +66,8 @@ QUnit.module("Effect Service", (hooks) => {
await nextTick();
execRegisteredTimeouts();
assert.containsOnce(parent.el, ".o_reward");
assert.strictEqual(parent.el.querySelector(".o_reward").innerText, "Well Done!");
assert.containsOnce(target, ".o_reward");
assert.strictEqual(target.querySelector(".o_reward").innerText, "Well Done!");
});
QUnit.test("rainbowman effect with show_effect: false", async function (assert) {
@@ -76,8 +78,8 @@ QUnit.module("Effect Service", (hooks) => {
await nextTick();
execRegisteredTimeouts();
assert.containsNone(parent.el, ".o_reward");
assert.containsOnce(parent.el, ".o_notification");
assert.containsNone(target, ".o_reward");
assert.containsOnce(target, ".o_notification");
});
QUnit.test("rendering a rainbowman destroy after animation", async function (assert) {
@@ -86,17 +88,17 @@ QUnit.module("Effect Service", (hooks) => {
await nextTick();
execRegisteredTimeouts();
assert.containsOnce(parent, ".o_reward");
assert.containsOnce(parent, ".o_reward_rainbow");
assert.containsOnce(target, ".o_reward");
assert.containsOnce(target, ".o_reward_rainbow");
assert.strictEqual(
parent.el.querySelector(".o_reward_msg_content").innerHTML,
target.querySelector(".o_reward_msg_content").innerHTML,
"<div>Congrats!</div>"
);
const ev = new AnimationEvent("animationend", { animationName: "reward-fading-reverse" });
parent.el.querySelector(".o_reward").dispatchEvent(ev);
target.querySelector(".o_reward").dispatchEvent(ev);
await nextTick();
assert.containsNone(parent, ".o_reward");
assert.containsNone(target, ".o_reward");
});
QUnit.test("rendering a rainbowman destroy on click", async function (assert) {
@@ -106,11 +108,11 @@ QUnit.module("Effect Service", (hooks) => {
await nextTick();
execRegisteredTimeouts();
assert.containsOnce(parent.el, ".o_reward");
assert.containsOnce(parent.el, ".o_reward_rainbow");
assert.containsOnce(target, ".o_reward");
assert.containsOnce(target, ".o_reward_rainbow");
await click(parent.el);
assert.containsNone(parent, ".o_reward");
await click(target);
assert.containsNone(target, ".o_reward");
});
QUnit.test("rendering a rainbowman with an escaped message", async function (assert) {
@@ -120,18 +122,15 @@ QUnit.module("Effect Service", (hooks) => {
await nextTick();
execRegisteredTimeouts();
assert.containsOnce(parent.el, ".o_reward");
assert.containsOnce(parent.el, ".o_reward_rainbow");
assert.strictEqual(
parent.el.querySelector(".o_reward_msg_content").textContent,
"Congrats!"
);
assert.containsOnce(target, ".o_reward");
assert.containsOnce(target, ".o_reward_rainbow");
assert.strictEqual(target.querySelector(".o_reward_msg_content").textContent, "Congrats!");
});
QUnit.test("rendering a rainbowman with a custom component", async function (assert) {
assert.expect(2);
const props = { foo: "bar" };
class Custom extends LegacyComponent {
class Custom extends Component {
setup() {
assert.deepEqual(this.props, props, "should have received these props");
}
@@ -143,7 +142,7 @@ QUnit.module("Effect Service", (hooks) => {
await nextTick();
execRegisteredTimeouts();
assert.strictEqual(
parent.el.querySelector(".o_reward_msg_content").innerHTML,
target.querySelector(".o_reward_msg_content").innerHTML,
`<div class="custom">foo is bar</div>`
);
});
@@ -20,9 +20,8 @@ import {
makeFakeRPCService,
} from "../../helpers/mock_services";
import { makeDeferred, nextTick, patchWithCleanup } from "../../helpers/utils";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
const errorDialogRegistry = registry.category("error_dialogs");
const errorHandlerRegistry = registry.category("error_handlers");
const serviceRegistry = registry.category("services");
@@ -84,7 +83,7 @@ QUnit.test(
"handle custom RPC_ERROR of type='server' and associated custom dialog class",
async (assert) => {
assert.expect(2);
class CustomDialog extends LegacyComponent {}
class CustomDialog extends Component {}
CustomDialog.template = xml`<RPCErrorDialog title="'Strange Error'"/>`;
CustomDialog.components = { RPCErrorDialog };
const error = new RPCError();
@@ -120,10 +119,10 @@ QUnit.test(
"handle normal RPC_ERROR of type='server' and associated custom dialog class",
async (assert) => {
assert.expect(2);
class CustomDialog extends LegacyComponent {}
class CustomDialog extends Component {}
CustomDialog.template = xml`<RPCErrorDialog title="'Strange Error'"/>`;
CustomDialog.components = { RPCErrorDialog };
class NormalDialog extends LegacyComponent {}
class NormalDialog extends Component {}
NormalDialog.template = xml`<RPCErrorDialog title="'Normal Error'"/>`;
NormalDialog.components = { RPCErrorDialog };
const error = new RPCError();
@@ -8,6 +8,8 @@ import { getFixture, mount } from "../helpers/utils";
const serviceRegistry = registry.category("services");
let target;
// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------
@@ -23,10 +25,6 @@ async function createFileInput(config) {
serviceRegistry.add("http", fakeHTTPService);
const env = await makeTestEnv();
const target = getFixture();
if (config.onUploaded) {
target.addEventListener("uploaded", config.onUploaded);
}
const fileInput = await mount(FileInput, target, {
env,
@@ -39,7 +37,11 @@ async function createFileInput(config) {
// Tests
// -----------------------------------------------------------------------------
QUnit.module("Components", () => {
QUnit.module("Components", ({ beforeEach }) => {
beforeEach(() => {
target = getFixture();
});
// This module cannot be tested as thoroughly as we want it to be:
// browsers do not let scripts programmatically assign values to inputs
// of type file
@@ -48,7 +50,7 @@ QUnit.module("Components", () => {
QUnit.test("Upload a file: default props", async function (assert) {
assert.expect(6);
const fileInput = await createFileInput({
await createFileInput({
mockPost: (route, params) => {
assert.deepEqual(params, {
csrf_token: odoo.csrf_token,
@@ -59,10 +61,10 @@ QUnit.module("Components", () => {
},
props: {},
});
const input = fileInput.el.querySelector("input");
const input = target.querySelector("input");
assert.strictEqual(
fileInput.el.innerText.trim().toUpperCase(),
target.querySelector(".o_file_input").innerText.trim().toUpperCase(),
"CHOOSE FILE",
"File input total text should match its given inner element's text"
);
@@ -77,13 +79,20 @@ QUnit.module("Components", () => {
QUnit.test("Upload a file: custom attachment", async function (assert) {
assert.expect(6);
const fileInput = await createFileInput({
await createFileInput({
props: {
accepted_file_extensions: ".png",
action: "/web/binary/upload_attachment",
id: 5,
model: "res.model",
multi_upload: true,
onUpload(files) {
assert.strictEqual(
files.length,
0,
"'files' property should be an empty array"
);
},
},
mockPost: (route, params) => {
assert.deepEqual(params, {
@@ -95,15 +104,8 @@ QUnit.module("Components", () => {
assert.step(route);
return "[]";
},
onUploaded(ev) {
assert.strictEqual(
ev.detail.files.length,
0,
"'files' property should be an empty array"
);
},
});
const input = fileInput.el.querySelector("input");
const input = target.querySelector("input");
assert.strictEqual(input.accept, ".png", "Input should now only accept pngs");
@@ -10,14 +10,13 @@ import { translatedTerms, _lt } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { patch, unpatch } from "@web/core/utils/patch";
import { session } from "@web/session";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
const { DateTime, Settings } = luxon;
const terms = { Hello: "Bonjour" };
const serviceRegistry = registry.category("services");
class TestComponent extends LegacyComponent {}
class TestComponent extends Component {}
/**
* Patches the 'lang' of the user session and context.
@@ -17,9 +17,8 @@ import {
patchWithCleanup,
} from "../../helpers/utils";
import { registerCleanup } from "../../helpers/cleanup";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
let isXHRMocked = false;
const serviceRegistry = registry.category("services");
@@ -126,7 +125,7 @@ QUnit.test("rpc with simple routes", async (assert) => {
});
QUnit.test("rpc coming from destroyed components are left pending", async (assert) => {
class MyComponent extends LegacyComponent {
class MyComponent extends Component {
setup() {
this.rpc = useService("rpc");
}
@@ -163,7 +162,7 @@ QUnit.test("rpc coming from destroyed components are left pending", async (asser
QUnit.test("rpc initiated from destroyed components throw exception", async (assert) => {
assert.expect(1);
class MyComponent extends LegacyComponent {
class MyComponent extends Component {
setup() {
this.rpc = useService("rpc");
}
@@ -4,7 +4,6 @@ import { popoverService } from "@web/core/popover/popover_service";
import { registry } from "@web/core/registry";
import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env";
import { click, getFixture, mount, nextTick } from "../../helpers/utils";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { Component, xml } = owl;
@@ -132,8 +131,8 @@ QUnit.test("close callback", async (assert) => {
QUnit.test("sub component triggers close", async (assert) => {
assert.containsOnce(fixture, ".o_popover_container");
class Comp extends LegacyComponent {}
Comp.template = xml`<div id="comp" t-on-click="() => this.trigger('popover-closed')">in popover</div>`;
class Comp extends Component {}
Comp.template = xml`<div id="comp" t-on-click="() => this.props.close()">in popover</div>`;
popovers.add(popoverTarget, Comp, {});
await nextTick();
@@ -15,7 +15,7 @@ QUnit.module("utils", () => {
QUnit.module("useAutofocus");
QUnit.test("useAutofocus: simple usecase", async function (assert) {
class MyComponent extends LegacyComponent {
class MyComponent extends Component {
setup() {
this.inputRef = useAutofocus();
}
@@ -41,7 +41,7 @@ QUnit.module("utils", () => {
});
QUnit.test("useAutofocus: conditional autofocus", async function (assert) {
class MyComponent extends LegacyComponent {
class MyComponent extends Component {
setup() {
this.inputRef = useAutofocus();
this.showInput = true;
@@ -113,7 +113,7 @@ QUnit.module("utils", () => {
QUnit.module("useBus");
QUnit.test("useBus hook: simple usecase", async function (assert) {
class MyComponent extends LegacyComponent {
class MyComponent extends Component {
setup() {
useBus(this.env.bus, "test-event", this.myCallback);
}
@@ -144,13 +144,13 @@ QUnit.module("utils", () => {
useListener("click", () => assert.step("click"));
}
}
MyComponent.template = xml`<button>Click Me</button>`;
MyComponent.template = xml`<button class="root">Click Me</button>`;
const env = await makeTestEnv();
const target = getFixture();
const comp = await mount(MyComponent, target, { env });
await mount(MyComponent, target, { env });
await click(comp.el);
await click(target.querySelector(".root"));
assert.verifySteps(["click"]);
});
@@ -162,7 +162,7 @@ QUnit.module("utils", () => {
}
}
MyComponent.template = xml`
<div>
<div class="root">
<button t-if="flag">Click Here</button>
<button t-else="">
<span>or Here</span>
@@ -173,15 +173,15 @@ QUnit.module("utils", () => {
const target = getFixture();
const comp = await mount(MyComponent, target, { env });
await click(comp.el);
await click(target.querySelector(".root"));
assert.verifySteps([]);
await click(comp.el.querySelector("button"));
await click(target.querySelector("button"));
assert.verifySteps(["click"]);
comp.flag = false;
comp.render();
await nextTick();
await click(comp.el.querySelector("button span"));
await click(target.querySelector("button span"));
assert.verifySteps(["click"]);
});
@@ -193,7 +193,7 @@ QUnit.module("utils", () => {
}
}
MyComponent.template = xml`
<div>
<div class="root">
<button t-if="flag">Click Here</button>
<button t-else="">
<span>or Here</span>
@@ -204,21 +204,21 @@ QUnit.module("utils", () => {
const target = getFixture();
const comp = await mount(MyComponent, target, { env });
await click(comp.el);
await click(target.querySelector(".root"));
assert.verifySteps([]);
await click(comp.el.querySelector("button"));
await click(target.querySelector("button"));
assert.verifySteps(["click"]);
comp.flag = false;
await comp.render();
await click(comp.el.querySelector("button span"));
await click(target.querySelector("button span"));
assert.verifySteps(["click"]);
});
QUnit.module("useService");
QUnit.test("useService: unavailable service", async function (assert) {
class MyComponent extends LegacyComponent {
class MyComponent extends Component {
setup() {
useService("toy_service");
}
@@ -235,7 +235,7 @@ QUnit.module("utils", () => {
});
QUnit.test("useService: service that returns null", async function (assert) {
class MyComponent extends LegacyComponent {
class MyComponent extends Component {
setup() {
this.toyService = useService("toy_service");
}
@@ -8,9 +8,8 @@ import testUtils from "web.test_utils";
import { registerCleanup } from "../../helpers/cleanup";
import { click, getFixture, legacyExtraNextTick, patchWithCleanup } from "../../helpers/utils";
import { createWebClient, doAction, getActionManagerServerData } from "./../helpers";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
let serverData;
let target;
@@ -100,7 +99,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("can execute client actions from tag name", async function (assert) {
assert.expect(4);
class ClientAction extends LegacyComponent {}
class ClientAction extends Component {}
ClientAction.template = xml`<div class="o_client_action_test">Hello World</div>`;
actionRegistry.add("HelloWorldTest", ClientAction);
@@ -232,7 +231,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("action can use a custom control panel (legacy)", async function (assert) {
assert.expect(1);
class CustomControlPanel extends LegacyComponent {}
class CustomControlPanel extends Component {}
CustomControlPanel.template = xml`
<div class="custom-control-panel">My custom control panel</div>
`;
@@ -328,7 +327,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("ClientAction receives breadcrumbs and exports title (wowl)", async (assert) => {
assert.expect(4);
class ClientAction extends LegacyComponent {
class ClientAction extends Component {
setup() {
this.breadcrumbTitle = "myOwlAction";
const { breadcrumbs } = this.env.config;
@@ -359,7 +358,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("ClientAction receives arbitrary props from doAction (wowl)", async (assert) => {
assert.expect(1);
class ClientAction extends LegacyComponent {
class ClientAction extends Component {
setup() {
assert.strictEqual(this.props.division, "bell");
}
@@ -13,9 +13,8 @@ import { legacyExtraNextTick, nextTick } from "../../helpers/utils";
import { registry } from "@web/core/registry";
import testUtils from "web.test_utils";
import { useSetupView } from "@web/views/helpers/view_hook";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
const actionRegistry = registry.category("actions");
let serverData;
@@ -444,7 +443,7 @@ QUnit.module("ActionManager", (hooks) => {
async function (assert) {
assert.expect(3);
const slowWillStartDef = testUtils.makeTestPromise();
class ClientAction extends LegacyComponent {
class ClientAction extends Component {
setup() {
owl.onWillStart(() => slowWillStartDef);
}
@@ -592,7 +591,7 @@ QUnit.module("ActionManager", (hooks) => {
let def = Promise.resolve();
let id = 1;
class ToyView extends LegacyComponent {
class ToyView extends Component {
setup() {
this.id = id++;
assert.step(JSON.stringify(this.props.state || "no state"));
@@ -5,9 +5,8 @@ import { createWebClient, doAction, getActionManagerServerData } from "./../help
import { registerCleanup } from "../../helpers/cleanup";
import { click, getFixture, nextTick, patchWithCleanup } from "../../helpers/utils";
import { errorService } from "@web/core/errors/error_service";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
let serverData;
let target;
@@ -23,7 +22,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("error in a client action (at rendering)", async function (assert) {
assert.expect(4);
class Boom extends LegacyComponent {}
class Boom extends Component {}
Boom.template = xml`<div><t t-esc="a.b.c"/></div>`;
actionRegistry.add("Boom", Boom);
@@ -54,7 +53,7 @@ QUnit.module("ActionManager", (hooks) => {
registry.category("services").add("error", errorService);
class Boom extends LegacyComponent {
class Boom extends Component {
setup() {
this.boom = false;
}
@@ -26,9 +26,8 @@ import {
setupWebClientRegistries,
} from "./../helpers";
import { errorService } from "@web/core/errors/error_service";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
let serverData;
let target;
@@ -182,7 +181,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("properly load client actions", async function (assert) {
assert.expect(3);
class ClientAction extends LegacyComponent {}
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) {
@@ -1014,7 +1013,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("concurrent hashchange during action mounting -- 1", async (assert) => {
assert.expect(5);
class MyAction extends LegacyComponent {
class MyAction extends Component {
setup() {
owl.onMounted(() => {
assert.step("myAction mounted");
@@ -1045,7 +1044,7 @@ QUnit.module("ActionManager", (hooks) => {
const baseURL = new URL(browser.location.href).toString();
class MyAction extends LegacyComponent {
class MyAction extends Component {
setup() {
owl.onMounted(() => {
assert.step("myAction mounted");
@@ -12,9 +12,8 @@ import {
patchWithCleanup,
} from "../../helpers/utils";
import { createWebClient, doAction, getActionManagerServerData } from "./../helpers";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
let serverData;
let target;
@@ -77,7 +76,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("actions can push state", async (assert) => {
assert.expect(5);
class ClientActionPushes extends LegacyComponent {
class ClientActionPushes extends Component {
setup() {
this.router = useService("router");
}
@@ -105,7 +104,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("actions override previous state", async (assert) => {
assert.expect(5);
class ClientActionPushes extends LegacyComponent {
class ClientActionPushes extends Component {
setup() {
this.router = useService("router");
}
@@ -134,7 +133,7 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("actions override previous state from menu click", async (assert) => {
assert.expect(3);
class ClientActionPushes extends LegacyComponent {
class ClientActionPushes extends Component {
setup() {
this.router = useService("router");
}
@@ -17,9 +17,8 @@ import { registerCleanup } from "../../helpers/cleanup";
import { errorService } from "@web/core/errors/error_service";
import { useService } from "@web/core/utils/hooks";
import { ClientErrorDialog } from "@web/core/errors/error_dialogs";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { onMounted, xml } = owl;
const { Component, onMounted, xml } = owl;
let serverData;
let target;
@@ -339,7 +338,7 @@ QUnit.module("ActionManager", (hooks) => {
onUnhandledRejection: () => {},
});
class ErrorClientAction extends LegacyComponent {
class ErrorClientAction extends Component {
setup() {
throw new Error("my error");
}
@@ -347,11 +346,11 @@ QUnit.module("ActionManager", (hooks) => {
ErrorClientAction.template = xml`<div/>`;
registry.category("actions").add("failing", ErrorClientAction);
class ClientActionTargetNew extends LegacyComponent {}
class ClientActionTargetNew extends Component {}
ClientActionTargetNew.template = xml`<div class="my_action_new" />`;
registry.category("actions").add("clientActionNew", ClientActionTargetNew);
class ClientAction extends LegacyComponent {
class ClientAction extends Component {
setup() {
this.action = useService("action");
}
+1 -2
View File
@@ -52,7 +52,6 @@ import { ClientActionAdapter, ViewAdapter } from "@web/legacy/action_adapters";
import { commandService } from "@web/core/commands/command_service";
import { CustomFavoriteItem } from "@web/search/favorite_menu/custom_favorite_item";
import { standaloneAdapter } from "web.OwlCompatibility";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { Component, onMounted, xml } = owl;
@@ -317,7 +316,7 @@ export async function loadState(env, state) {
export function getActionManagerServerData() {
// additional basic client action
class TestClientAction extends LegacyComponent {}
class TestClientAction extends Component {}
TestClientAction.template = xml`
<div class="test_client_action">
ClientAction_<t t-esc="props.action.params?.description"/>
@@ -17,6 +17,7 @@ import { uiService } from "@web/core/ui/ui_service";
import { session } from "@web/session";
const serviceRegistry = registry.category("services");
let target;
async function createSwitchCompanyMenu(routerParams = {}, toggleDelay = 0) {
patchWithCleanup(MobileSwitchCompanyMenu, { toggleDelay });
@@ -34,13 +35,13 @@ async function createSwitchCompanyMenu(routerParams = {}, toggleDelay = 0) {
});
}
const env = await makeTestEnv();
const target = getFixture();
const scMenu = await mount(MobileSwitchCompanyMenu, target, { env });
return scMenu;
}
QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
QUnit.module("MobileSwitchCompanyMenu", (hooks) => {
hooks.beforeEach(() => {
target = getFixture();
patchWithCleanup(session.user_companies, {
allowed_companies: {
1: { id: 1, name: "Hermit" },
@@ -57,34 +58,34 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
QUnit.test("basic rendering", async (assert) => {
assert.expect(13);
const scMenu = await createSwitchCompanyMenu();
await createSwitchCompanyMenu();
const scMenuEl = target.querySelector(".o_burger_menu_companies");
assert.strictEqual(scMenu.el.tagName.toUpperCase(), "DIV");
assert.hasClass(scMenu.el, "o_burger_menu_companies");
assert.containsN(scMenu, ".toggle_company", 3);
assert.containsN(scMenu, ".log_into", 3);
assert.containsOnce(scMenu.el, ".fa-check-square");
assert.containsN(scMenu.el, ".fa-square-o", 2);
assert.strictEqual(scMenuEl.tagName.toUpperCase(), "DIV");
assert.hasClass(scMenuEl, "o_burger_menu_companies");
assert.containsN(scMenuEl, ".toggle_company", 3);
assert.containsN(scMenuEl, ".log_into", 3);
assert.containsOnce(scMenuEl, ".fa-check-square");
assert.containsN(scMenuEl, ".fa-square-o", 2);
assert.strictEqual(
scMenu.el.querySelectorAll(".menu_companies_item")[0].textContent,
scMenuEl.querySelectorAll(".menu_companies_item")[0].textContent,
"Hermit(current)"
);
assert.strictEqual(
scMenu.el.querySelectorAll(".menu_companies_item")[1].textContent,
scMenuEl.querySelectorAll(".menu_companies_item")[1].textContent,
"Herman's"
);
assert.strictEqual(
scMenu.el.querySelectorAll(".menu_companies_item")[2].textContent,
scMenuEl.querySelectorAll(".menu_companies_item")[2].textContent,
"Heroes TM"
);
assert.hasClass(scMenu.el.querySelectorAll(".menu_companies_item i")[0], "fa-check-square");
assert.hasClass(scMenu.el.querySelectorAll(".menu_companies_item i")[1], "fa-square-o");
assert.hasClass(scMenu.el.querySelectorAll(".menu_companies_item i")[2], "fa-square-o");
assert.hasClass(scMenuEl.querySelectorAll(".menu_companies_item i")[0], "fa-check-square");
assert.hasClass(scMenuEl.querySelectorAll(".menu_companies_item i")[1], "fa-square-o");
assert.hasClass(scMenuEl.querySelectorAll(".menu_companies_item i")[2], "fa-square-o");
assert.strictEqual(scMenu.el.textContent, "CompaniesHermit(current)Herman'sHeroes TM");
assert.strictEqual(scMenuEl.textContent, "CompaniesHermit(current)Herman'sHeroes TM");
});
QUnit.test("companies can be toggled: toggle a second company", async (assert) => {
@@ -96,6 +97,7 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
prom.resolve();
}
const scMenu = await createSwitchCompanyMenu({ onPushState });
const scMenuEl = target.querySelector(".o_burger_menu_companies");
/**
* [x] **Company 1**
@@ -104,18 +106,18 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
assert.containsN(scMenuEl, "[data-company-id]", 3);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 2);
/**
* [x] **Company 1**
* [x] Company 2 -> toggle
* [ ] Company 3
*/
await click(scMenu.el.querySelectorAll(".toggle_company")[1]);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 1);
await click(scMenuEl.querySelectorAll(".toggle_company")[1]);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 1);
await prom;
assert.verifySteps(["cids=1%2C2"]);
});
@@ -129,6 +131,7 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
prom.resolve();
}
const scMenu = await createSwitchCompanyMenu({ onPushState }, 50);
const scMenuEl = target.querySelector(".o_burger_menu_companies");
/**
* [x] **Company 1**
@@ -137,20 +140,20 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
assert.containsN(scMenuEl, "[data-company-id]", 3);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 2);
/**
* [ ] **Company 1** -> toggle all
* [x] Company 2 -> toggle all
* [x] Company 3 -> toggle all
*/
await click(scMenu.el.querySelectorAll(".toggle_company")[0]);
await click(scMenu.el.querySelectorAll(".toggle_company")[1]);
await click(scMenu.el.querySelectorAll(".toggle_company")[2]);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 1);
await click(scMenuEl.querySelectorAll(".toggle_company")[0]);
await click(scMenuEl.querySelectorAll(".toggle_company")[1]);
await click(scMenuEl.querySelectorAll(".toggle_company")[2]);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 1);
assert.verifySteps([]);
await prom; // await toggle promise
@@ -166,6 +169,7 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
},
});
const scMenu = await createSwitchCompanyMenu();
const scMenuEl = target.querySelector(".o_burger_menu_companies");
/**
* [x] **Company 1**
@@ -175,21 +179,21 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
assert.deepEqual(scMenu.env.services.router.current.hash, { cids: 1 });
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
assert.containsN(scMenuEl, "[data-company-id]", 3);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 2);
/**
* [ ] **Company 1** -> toggle off
* [ ] Company 2
* [ ] Company 3
*/
await click(scMenu.el.querySelectorAll(".toggle_company")[0]);
await click(scMenuEl.querySelectorAll(".toggle_company")[0]);
assert.deepEqual(scMenu.env.services.router.current.hash, { cids: 1 });
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-squarqe", 0);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 3);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-squarqe", 0);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 3);
});
QUnit.test("single company mode: companies can be logged in", async (assert) => {
@@ -199,6 +203,7 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
assert.step(url.split("#")[1]);
}
const scMenu = await createSwitchCompanyMenu({ onPushState });
const scMenuEl = target.querySelector(".o_burger_menu_companies");
/**
* [x] **Company 1**
@@ -207,16 +212,16 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
assert.containsN(scMenuEl, "[data-company-id]", 3);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 2);
/**
* [x] **Company 1**
* [ ] Company 2 -> log into
* [ ] Company 3
*/
await click(scMenu.el.querySelectorAll(".log_into")[1]);
await click(scMenuEl.querySelectorAll(".log_into")[1]);
assert.verifySteps(["cids=2"]);
});
@@ -228,6 +233,7 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
}
Object.assign(browser.location, { hash: "cids=3%2C1" });
const scMenu = await createSwitchCompanyMenu({ onPushState });
const scMenuEl = target.querySelector(".o_burger_menu_companies");
/**
* [x] Company 1
@@ -236,16 +242,16 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [3, 1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3);
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 1);
assert.containsN(scMenuEl, "[data-company-id]", 3);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 1);
/**
* [x] Company 1
* [ ] Company 2 -> log into
* [x] **Company 3**
*/
await click(scMenu.el.querySelectorAll(".log_into")[1]);
await click(scMenuEl.querySelectorAll(".log_into")[1]);
assert.verifySteps(["cids=2%2C3%2C1"]);
});
@@ -257,6 +263,7 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
}
Object.assign(browser.location, { hash: "cids=2%2C3" });
const scMenu = await createSwitchCompanyMenu({ onPushState });
const scMenuEl = target.querySelector(".o_burger_menu_companies");
/**
* [ ] Company 1
@@ -265,16 +272,16 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [2, 3]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 2);
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 1);
assert.containsN(scMenuEl, "[data-company-id]", 3);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 2);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 1);
/**
* [ ] Company 1
* [x] **Company 2**
* [x] Company 3 -> log into
*/
await click(scMenu.el.querySelectorAll(".log_into")[2]);
await click(scMenuEl.querySelectorAll(".log_into")[2]);
assert.verifySteps(["cids=3%2C2"]);
});
@@ -285,6 +292,7 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
assert.step(url.split("#")[1]);
}
const scMenu = await createSwitchCompanyMenu({ onPushState }, 50);
const scMenuEl = target.querySelector(".o_burger_menu_companies");
/**
* [x] **Company 1**
@@ -293,18 +301,18 @@ QUnit.module("MobileMobileSwitchCompanyMenu", (hooks) => {
*/
assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [1]);
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
assert.containsN(scMenu.el, "[data-company-id]", 3);
assert.containsN(scMenu.el, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenu.el, "[data-company-id] .fa-square-o", 2);
assert.containsN(scMenuEl, "[data-company-id]", 3);
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 1);
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 2);
/**
* [ ] **Company 1** -> toggled
* [ ] Company 2 -> logged in
* [ ] Company 3 -> toggled
*/
await click(scMenu.el.querySelectorAll(".toggle_company")[2]);
await click(scMenu.el.querySelectorAll(".toggle_company")[0]);
await click(scMenu.el.querySelectorAll(".log_into")[1]);
await click(scMenuEl.querySelectorAll(".toggle_company")[2]);
await click(scMenuEl.querySelectorAll(".toggle_company")[0]);
await click(scMenuEl.querySelectorAll(".log_into")[1]);
assert.verifySteps(["cids=2"]);
});
});
@@ -18,18 +18,19 @@ import {
patchWithCleanup,
makeDeferred,
} from "../helpers/utils";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
const systrayRegistry = registry.category("systray");
const serviceRegistry = registry.category("services");
class MySystrayItem extends LegacyComponent {}
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);
@@ -51,10 +52,9 @@ QUnit.module("Navbar", {
QUnit.test("can be rendered", async (assert) => {
const env = await makeTestEnv(baseConfig);
const target = getFixture();
const navbar = await mount(NavBar, target, { env });
await mount(NavBar, target, { env });
assert.containsOnce(
navbar.el,
target,
".o_navbar_apps_menu button.dropdown-toggle",
"1 apps menu toggler present"
);
@@ -62,9 +62,8 @@ QUnit.test("can be rendered", async (assert) => {
QUnit.test("dropdown menu can be toggled", async (assert) => {
const env = await makeTestEnv(baseConfig);
const target = getFixture();
const navbar = await mount(NavBar, target, { env });
const dropdown = navbar.el.querySelector(".o_navbar_apps_menu");
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");
@@ -81,11 +80,10 @@ QUnit.test("data-menu-xmlid attribute on AppsMenu items", async (assert) => {
5: { id: 5, children: [], name: "Sub menu", appID: 1, xmlid: "menu_5" },
};
const env = await makeTestEnv(baseConfig);
const target = getFixture();
const navbar = await mount(NavBar, target, { env });
await mount(NavBar, target, { env });
// check apps
const appsMenu = navbar.el.querySelector(".o_navbar_apps_menu");
const appsMenu = target.querySelector(".o_navbar_apps_menu");
await click(appsMenu, "button.dropdown-toggle");
const menuItems = appsMenu.querySelectorAll("a");
assert.strictEqual(
@@ -102,21 +100,20 @@ QUnit.test("data-menu-xmlid attribute on AppsMenu items", async (assert) => {
// check menus
env.services.menu.setCurrentMenu(1);
await nextTick();
assert.containsOnce(navbar, ".o_menu_sections .dropdown-item[data-menu-xmlid=menu_3]");
assert.containsOnce(target, ".o_menu_sections .dropdown-item[data-menu-xmlid=menu_3]");
// check sub menus toggler
assert.containsOnce(navbar, ".o_menu_sections button.dropdown-toggle[data-menu-xmlid=menu_4]");
assert.containsOnce(target, ".o_menu_sections button.dropdown-toggle[data-menu-xmlid=menu_4]");
// check sub menus
await click(navbar.el.querySelector(".o_menu_sections .dropdown-toggle"));
assert.containsOnce(navbar, ".o_menu_sections .dropdown-item[data-menu-xmlid=menu_5]");
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);
const target = getFixture();
const navbar = await mount(NavBar, target, { env });
const dropdown = navbar.el.querySelector(".o_navbar_apps_menu");
await mount(NavBar, target, { env });
const dropdown = target.querySelector(".o_navbar_apps_menu");
// Open apps menu
await click(dropdown, "button.dropdown-toggle");
assert.containsOnce(
@@ -137,19 +134,18 @@ QUnit.test("navbar can display current active app", async (assert) => {
QUnit.test("navbar can display systray items", async (assert) => {
const env = await makeTestEnv(baseConfig);
const target = getFixture();
const navbar = await mount(NavBar, target, { env });
assert.containsOnce(navbar.el, "li.my-item");
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 LegacyComponent {}
class MyItem1 extends Component {}
MyItem1.template = xml`<li class="my-item-1">my item 1</li>`;
class MyItem2 extends LegacyComponent {}
class MyItem2 extends Component {}
MyItem2.template = xml`<li class="my-item-2">my item 2</li>`;
class MyItem3 extends LegacyComponent {}
class MyItem3 extends Component {}
MyItem3.template = xml`<li class="my-item-3">my item 3</li>`;
class MyItem4 extends LegacyComponent {}
class MyItem4 extends Component {}
MyItem4.template = xml`<li class="my-item-4">my item 4</li>`;
clearRegistryWithCleanup(systrayRegistry);
@@ -158,9 +154,8 @@ QUnit.test("navbar can display systray items ordered based on their sequence", a
systrayRegistry.add("addon.myitem3", { Component: MyItem3 }, { sequence: 100 });
systrayRegistry.add("addon.myitem4", { Component: MyItem4 });
const env = await makeTestEnv(baseConfig);
const target = getFixture();
const navbar = await mount(NavBar, target, { env });
const menuSystray = navbar.el.getElementsByClassName("o_menu_systray")[0];
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");
});
@@ -188,51 +183,50 @@ QUnit.test("can adapt with 'more' menu sections behavior", async (assert) => {
const env = await makeTestEnv(baseConfig);
// Force the parent width, to make this test independent of screen size
const target = getFixture();
target.style.width = "1080px";
// Set menu and mount
env.services.menu.setCurrentMenu(1);
const navbar = await mount(MyNavbar, target, { env });
await mount(MyNavbar, target, { env });
assert.containsN(
navbar.el,
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(navbar.el, ".o_menu_sections_more", "the 'more' menu should not exist");
assert.containsNone(target, ".o_menu_sections_more", "the 'more' menu should not exist");
// Force minimal width and dispatch window resize event
navbar.el.style.width = "0%";
target.style.width = "0%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.containsOnce(
navbar.el,
target,
".o_menu_sections > *:not(.d-none)",
"only one menu section should be displayed"
);
assert.containsOnce(
navbar.el,
target,
".o_menu_sections_more:not(.d-none)",
"the displayed menu section should be the 'more' menu"
);
// Open the more menu
await click(navbar.el, ".o_menu_sections_more .dropdown-toggle");
await click(target, ".o_menu_sections_more .dropdown-toggle");
assert.deepEqual(
[...navbar.el.querySelectorAll(".dropdown-menu > *")].map((el) => el.textContent),
[...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
navbar.el.style.width = "100%";
target.style.width = "100%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.containsN(
navbar.el,
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(navbar.el, ".o_menu_sections_more", "the 'more' menu should not exist");
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",
@@ -272,13 +266,12 @@ QUnit.test(
baseConfig.serverData.menus = newMenus;
// Force the parent width, to make this test independent of screen size
const target = getFixture();
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(navbar.el.offsetWidth, 600);
assert.strictEqual(target.querySelector(".o_navbar").offsetWidth, 600);
assert.strictEqual(adaptCount, 1);
assert.strictEqual(
adaptRenderCount,
@@ -287,10 +280,10 @@ QUnit.test(
);
// Force minimal width and dispatch window resize event
navbar.el.style.width = "0%";
target.querySelector(".o_navbar").style.width = "0%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.strictEqual(navbar.el.offsetWidth, 0);
assert.strictEqual(target.querySelector(".o_navbar").offsetWidth, 0);
assert.strictEqual(adaptCount, 2);
assert.strictEqual(
adaptRenderCount,
@@ -315,7 +308,7 @@ QUnit.test(
);
// Force 40% width and dispatch window resize event
navbar.el.style.width = "40%";
target.querySelector(".o_navbar").style.width = "40%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.strictEqual(
@@ -331,7 +324,7 @@ QUnit.test(
);
// Reset to full width and dispatch window resize event
navbar.el.style.width = "100%";
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");
@@ -373,46 +366,45 @@ QUnit.test("'more' menu sections properly updated on app change", async (assert)
const env = await makeTestEnv(baseConfig);
// Force the parent width, to make this test independent of screen size
const target = getFixture();
target.style.width = "1080px";
// Set App1 menu and mount
env.services.menu.setCurrentMenu(1);
const navbar = await mount(NavBar, target, { env });
await mount(NavBar, target, { env });
// Force minimal width and dispatch window resize event
navbar.el.style.width = "0%";
target.style.width = "0%";
window.dispatchEvent(new Event("resize"));
await nextTick();
assert.containsOnce(
navbar.el,
target,
".o_menu_sections > *:not(.d-none)",
"only one menu section should be displayed"
);
assert.containsOnce(
navbar.el,
target,
".o_menu_sections_more:not(.d-none)",
"the displayed menu section should be the 'more' menu"
);
// Open the more menu
await click(navbar.el, ".o_menu_sections_more .dropdown-toggle");
await click(target, ".o_menu_sections_more .dropdown-toggle");
assert.deepEqual(
[...navbar.el.querySelectorAll(".dropdown-menu > *")].map((el) => el.textContent),
[...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(navbar.el, ".o_menu_sections_more .dropdown-toggle");
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(navbar.el, ".o_menu_sections_more .dropdown-toggle");
await click(target, ".o_menu_sections_more .dropdown-toggle");
assert.deepEqual(
[...navbar.el.querySelectorAll(".dropdown-menu > *")].map((el) => el.textContent),
[...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"
);
@@ -438,8 +430,6 @@ QUnit.test("Do not execute adapt when navbar is destroyed", async (assert) => {
}
const env = await makeTestEnv(baseConfig);
const target = getFixture();
// Set menu and mount
env.services.menu.setCurrentMenu(1);
const navbar = await mount(MyNavbar, target, { env });
@@ -13,9 +13,8 @@ import { WebClient } from "@web/webclient/webclient";
import { clearRegistryWithCleanup, makeTestEnv } from "../helpers/mock_env";
import { fakeTitleService } from "../helpers/mock_services";
import { destroy, getFixture, mount, patchWithCleanup, triggerEvent } from "../helpers/utils";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { xml } = owl;
const { Component, xml } = owl;
const mainComponentRegistry = registry.category("main_components");
const serviceRegistry = registry.category("services");
@@ -48,7 +47,7 @@ QUnit.test("can be rendered", async (assert) => {
QUnit.test("can render a main component", async (assert) => {
assert.expect(1);
class MyComponent extends LegacyComponent {}
class MyComponent extends Component {}
MyComponent.template = xml`<span class="chocolate">MyComponent</span>`;
clearRegistryWithCleanup(mainComponentRegistry);
mainComponentRegistry.add("mycomponent", { Component: MyComponent });
@@ -75,7 +74,7 @@ QUnit.test("control-click propagation stopped on <a href/>", async (assert) => {
},
});
class MyComponent extends LegacyComponent {
class MyComponent extends Component {
/** @param {MouseEvent} ev */
onclick(ev) {
assert.step(ev.ctrlKey ? "ctrl-click" : "click");
@@ -89,8 +88,8 @@ QUnit.test("control-click propagation stopped on <a href/>", async (assert) => {
// Mount the component as standalone and control-click the <a href/>
const standaloneComponent = await mount(MyComponent, target, { env });
assert.verifySteps([]);
await triggerEvent(standaloneComponent.el, "", "click", { ctrlKey: false });
await triggerEvent(standaloneComponent.el, "", "click", { ctrlKey: true });
await triggerEvent(target.querySelector(".MyComponent"), "", "click", { ctrlKey: false });
await triggerEvent(target.querySelector(".MyComponent"), "", "click", { ctrlKey: true });
assert.verifySteps(["click", "ctrl-click"]);
destroy(standaloneComponent);