[REF] web,*: regroup custom hooks in core/utils/hooks
*base_automation,iap,im_livechat,point_of_sale This commit introduces a new file in core/utils: hooks.js This file contains custom hooks (useEffect, useService, useBus...). The useHotkey hook is tightly related to hotkeys, so it has been moved to core/hotkeys. closes odoo/odoo#74535 Related: odoo/enterprise#20003 Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { ErrorDialog } from "@web/core/errors/error_dialogs";
|
||||
import session from "web.session";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class BaseAutomationErrorDialog extends ErrorDialog {
|
||||
setup() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module */
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
class InsufficientCreditDialog extends Dialog {
|
||||
setup() {
|
||||
|
||||
@@ -99,9 +99,8 @@ Help your customers with this chat, and analyse their feedback.
|
||||
'web/static/src/core/browser/browser.js',
|
||||
'web/static/src/core/browser/feature_detection.js',
|
||||
'web/static/src/core/dialog/dialog.js',
|
||||
'web/static/src/core/effect_hook.js',
|
||||
'web/static/src/core/errors/error_dialogs.js',
|
||||
'web/static/src/core/hotkey_hook.js',
|
||||
'web/static/src/core/utils/hooks.js',
|
||||
'web/static/src/core/hotkeys/hotkey_service.js',
|
||||
'web/static/src/core/l10n/dates.js',
|
||||
'web/static/src/core/l10n/localization.js',
|
||||
@@ -113,7 +112,6 @@ Help your customers with this chat, and analyse their feedback.
|
||||
'web/static/src/core/notifications/notification_container.js',
|
||||
'web/static/src/core/notifications/notification_service.js',
|
||||
'web/static/src/core/registry.js',
|
||||
'web/static/src/core/service_hook.js',
|
||||
'web/static/src/core/ui/block_ui.js',
|
||||
'web/static/src/core/ui/ui_service.js',
|
||||
'web/static/src/core/user_service.js',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import Chrome from "point_of_sale.Chrome";
|
||||
import Registries from "point_of_sale.Registries";
|
||||
import { configureGui } from "point_of_sale.Gui";
|
||||
import { useBus } from "@web/core/bus_hook";
|
||||
import { useBus } from "@web/core/utils/hooks";
|
||||
import PosComponent from "point_of_sale.PosComponent";
|
||||
import PopupControllerMixin from "point_of_sale.PopupControllerMixin";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useEffect } from "./effect_hook";
|
||||
|
||||
const { useComponent } = owl.hooks;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Hook functions
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Focus a given selector as soon as it appears in the DOM and if it was not
|
||||
* displayed before. If the selected target is an input|textarea, set the selection
|
||||
* at the end.
|
||||
*
|
||||
* @param {Object} [params]
|
||||
* @param {string} [params.selector='autofocus'] default: select the first element
|
||||
* with an `autofocus` attribute.
|
||||
* @returns {Function} function that forces the focus on the next update if visible.
|
||||
*/
|
||||
export function useAutofocus(params = {}) {
|
||||
const comp = useComponent();
|
||||
// Prevent autofocus in mobile
|
||||
if (comp.env.isSmall) {
|
||||
return () => {};
|
||||
}
|
||||
const selector = params.selector || "[autofocus]";
|
||||
let forceFocusCount = 0;
|
||||
useEffect(
|
||||
function autofocus(target) {
|
||||
if (target) {
|
||||
target.focus();
|
||||
if (["INPUT", "TEXTAREA"].includes(target.tagName)) {
|
||||
const inputEl = target;
|
||||
inputEl.selectionStart = inputEl.selectionEnd = inputEl.value.length;
|
||||
}
|
||||
}
|
||||
},
|
||||
() => [comp.el.querySelector(selector), forceFocusCount]
|
||||
);
|
||||
|
||||
return function focusOnUpdate() {
|
||||
forceFocusCount++; // force the effect to rerun on next patch
|
||||
};
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
import { useEffect } from "./effect_hook";
|
||||
const { useComponent } = owl.hooks;
|
||||
|
||||
/**
|
||||
* This file contains various custom hooks.
|
||||
* Their inner working is rather simple:
|
||||
* Each custom hook simply hook itself to any number of owl lifecycle hooks.
|
||||
* You can then use them just like an owl hook in any Component
|
||||
* e.g.:
|
||||
* import { useBus } from "@web/core/bus_hook";
|
||||
* ...
|
||||
* setup() {
|
||||
* ...
|
||||
* useBus(someBus, someEvent, callback)
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Hook functions
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Ensures a bus event listener is attached and cleared the proper way.
|
||||
*
|
||||
* @param {EventBus} bus
|
||||
* @param {string} eventName
|
||||
* @param {Callback} callback
|
||||
*/
|
||||
export function useBus(bus, eventName, callback) {
|
||||
const component = useComponent();
|
||||
useEffect(
|
||||
() => {
|
||||
bus.on(eventName, component, callback);
|
||||
return () => bus.off(eventName, component);
|
||||
},
|
||||
() => []
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
import { registry } from "../registry";
|
||||
import { memoize } from "../utils/functions";
|
||||
import { useEffect } from "../effect_hook";
|
||||
import { useEffect } from "@web/core/utils/hooks";
|
||||
|
||||
const { useEnv, useSubEnv } = owl.hooks;
|
||||
const debugRegistry = registry.category("debug");
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useCurrentDebugContext } from "./debug_context";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class DebugMenu extends Component {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { useBus } from "@web/core/bus_hook";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useHotkey } from "../hotkey_hook";
|
||||
import { useService } from "../service_hook";
|
||||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
||||
import { useActiveElement } from "../ui/ui_service";
|
||||
|
||||
const { Component, hooks } = owl;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useBus } from "../bus_hook";
|
||||
import { useService } from "../service_hook";
|
||||
import { useEffect } from "../effect_hook";
|
||||
import { useBus, useEffect, useService } from "@web/core/utils/hooks";
|
||||
import { scrollTo } from "../utils/scrolling";
|
||||
import { ParentClosingMode } from "./dropdown_item";
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/** @odoo-module */
|
||||
const { onMounted, onWillPatch, onPatched, onWillUnmount } = owl.hooks;
|
||||
|
||||
const NO_OP = () => {};
|
||||
/**
|
||||
* @callback Effect
|
||||
* @param {...any} dependencies the dependencies computed by computeDependencies
|
||||
* @returns {void|(()=>void)} a cleanup function that reverses the side
|
||||
* effects of the effect callback.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This hook will run a callback when a component is mounted and patched, and
|
||||
* will run a cleanup function before patching and before unmounting the
|
||||
* the component.
|
||||
*
|
||||
* @param {Effect} effect the effect to run on component mount and/or patch
|
||||
* @param {()=>any[]} [computeDependencies=()=>[NaN]] a callback to compute
|
||||
* dependencies that will decide if the effect needs to be cleaned up and
|
||||
* run again. If the dependencies did not change, the effect will not run
|
||||
* again. The default value returns an array containing only NaN because
|
||||
* NaN !== NaN, which will cause the effect to rerun on every patch.
|
||||
*/
|
||||
export function useEffect(effect, computeDependencies = () => [NaN]) {
|
||||
let cleanup, dependencies;
|
||||
onMounted(() => {
|
||||
dependencies = computeDependencies();
|
||||
cleanup = effect(...dependencies) || NO_OP;
|
||||
});
|
||||
|
||||
let shouldReapplyOnPatch = false;
|
||||
onWillPatch(() => {
|
||||
const newDeps = computeDependencies();
|
||||
shouldReapplyOnPatch = newDeps.some((val, i) => val !== dependencies[i]);
|
||||
if (shouldReapplyOnPatch) {
|
||||
cleanup();
|
||||
dependencies = newDeps;
|
||||
}
|
||||
});
|
||||
onPatched(() => {
|
||||
if (shouldReapplyOnPatch) {
|
||||
cleanup = effect(...dependencies) || NO_OP;
|
||||
}
|
||||
});
|
||||
|
||||
onWillUnmount(() => cleanup());
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "../browser/browser";
|
||||
import { useEffect } from "../effect_hook";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { useEffect } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component, hooks } = owl;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { browser } from "../browser/browser";
|
||||
import { Dialog } from "../dialog/dialog";
|
||||
import { _lt } from "../l10n/translation";
|
||||
import { registry } from "../registry";
|
||||
import { useService } from "../service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { capitalize } from "../utils/strings";
|
||||
|
||||
const { hooks } = owl;
|
||||
@@ -20,7 +20,7 @@ export const odooExceptionTitleMap = new Map(
|
||||
"odoo.exceptions.UserError": _lt("User Error"),
|
||||
"odoo.exceptions.ValidationError": _lt("Validation Error"),
|
||||
"odoo.exceptions.AccessError": _lt("Access Error"),
|
||||
"odoo.exceptions.Warning": _lt("Warning")
|
||||
"odoo.exceptions.Warning": _lt("Warning"),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ErrorDialog extends Dialog {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({
|
||||
showTraceback: false
|
||||
showTraceback: false,
|
||||
});
|
||||
}
|
||||
onClickClipboard() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "../service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component, hooks, QWeb } = owl;
|
||||
const { useRef } = hooks;
|
||||
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
/** @odoo-module */
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "./service_hook";
|
||||
import { useEffect } from "./effect_hook";
|
||||
import { useEffect, useService } from "@web/core/utils/hooks";
|
||||
|
||||
/**
|
||||
* This hook will register/unregister the given registration
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "../service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { onWillUnmount, useComponent } = owl.hooks;
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { SERVICES_METADATA } from "../env";
|
||||
|
||||
const { useComponent } = owl.hooks;
|
||||
|
||||
/**
|
||||
* Import a service into a component
|
||||
*
|
||||
* @param {string} serviceName
|
||||
* @returns {any}
|
||||
*/
|
||||
export function useService(serviceName) {
|
||||
const component = useComponent();
|
||||
const { services } = component.env;
|
||||
if (!(serviceName in services)) {
|
||||
throw new Error(`Service ${serviceName} is not available`);
|
||||
}
|
||||
const service = services[serviceName];
|
||||
if (serviceName in SERVICES_METADATA) {
|
||||
if (service instanceof Function) {
|
||||
return protectMethod(component, null, service);
|
||||
} else {
|
||||
const methods = SERVICES_METADATA[serviceName];
|
||||
const result = Object.create(service);
|
||||
for (let method of methods) {
|
||||
result[method] = protectMethod(component, service, service[method]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
function protectMethod(component, caller, fn) {
|
||||
return async (...args) => {
|
||||
if (component.__owl__.status === 5 /* DESTROYED */) {
|
||||
throw new Error("Component is destroyed");
|
||||
}
|
||||
const result = await fn.call(caller, ...args);
|
||||
return component.__owl__.status === 5 ? new Promise(() => {}) : result;
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useEffect, useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import { useEffect } from "@web/core/effect_hook";
|
||||
import { BlockUI } from "./block_ui";
|
||||
|
||||
const { Component, core, hooks } = owl;
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { SERVICES_METADATA } from "@web/env";
|
||||
|
||||
const { onMounted, onWillPatch, onPatched, onWillUnmount, useComponent } = owl.hooks;
|
||||
|
||||
/**
|
||||
* This file contains various custom hooks.
|
||||
* Their inner working is rather simple:
|
||||
* Each custom hook simply hooks itself to any number of owl lifecycle hooks.
|
||||
* You can then use them just like an owl hook in any Component
|
||||
* e.g.:
|
||||
* import { useBus } from "@web/core/utils/hooks";
|
||||
* ...
|
||||
* setup() {
|
||||
* ...
|
||||
* useBus(someBus, someEvent, callback)
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// useAutofocus
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Focus a given selector as soon as it appears in the DOM and if it was not
|
||||
* displayed before. If the selected target is an input|textarea, set the selection
|
||||
* at the end.
|
||||
*
|
||||
* @param {Object} [params]
|
||||
* @param {string} [params.selector='autofocus'] default: select the first element
|
||||
* with an `autofocus` attribute.
|
||||
* @returns {Function} function that forces the focus on the next update if visible.
|
||||
*/
|
||||
export function useAutofocus(params = {}) {
|
||||
const comp = useComponent();
|
||||
// Prevent autofocus in mobile
|
||||
if (comp.env.isSmall) {
|
||||
return () => {};
|
||||
}
|
||||
const selector = params.selector || "[autofocus]";
|
||||
let forceFocusCount = 0;
|
||||
useEffect(
|
||||
function autofocus(target) {
|
||||
if (target) {
|
||||
target.focus();
|
||||
if (["INPUT", "TEXTAREA"].includes(target.tagName)) {
|
||||
const inputEl = target;
|
||||
inputEl.selectionStart = inputEl.selectionEnd = inputEl.value.length;
|
||||
}
|
||||
}
|
||||
},
|
||||
() => [comp.el.querySelector(selector), forceFocusCount]
|
||||
);
|
||||
|
||||
return function focusOnUpdate() {
|
||||
forceFocusCount++; // force the effect to rerun on next patch
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// useBus
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Ensures a bus event listener is attached and cleared the proper way.
|
||||
*
|
||||
* @param {EventBus} bus
|
||||
* @param {string} eventName
|
||||
* @param {Callback} callback
|
||||
*/
|
||||
export function useBus(bus, eventName, callback) {
|
||||
const component = useComponent();
|
||||
useEffect(
|
||||
() => {
|
||||
bus.on(eventName, component, callback);
|
||||
return () => bus.off(eventName, component);
|
||||
},
|
||||
() => []
|
||||
);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// useEffect
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const NO_OP = () => {};
|
||||
/**
|
||||
* @callback Effect
|
||||
* @param {...any} dependencies the dependencies computed by computeDependencies
|
||||
* @returns {void|(()=>void)} a cleanup function that reverses the side
|
||||
* effects of the effect callback.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This hook will run a callback when a component is mounted and patched, and
|
||||
* will run a cleanup function before patching and before unmounting the
|
||||
* the component.
|
||||
*
|
||||
* @param {Effect} effect the effect to run on component mount and/or patch
|
||||
* @param {()=>any[]} [computeDependencies=()=>[NaN]] a callback to compute
|
||||
* dependencies that will decide if the effect needs to be cleaned up and
|
||||
* run again. If the dependencies did not change, the effect will not run
|
||||
* again. The default value returns an array containing only NaN because
|
||||
* NaN !== NaN, which will cause the effect to rerun on every patch.
|
||||
*/
|
||||
export function useEffect(effect, computeDependencies = () => [NaN]) {
|
||||
let cleanup, dependencies;
|
||||
onMounted(() => {
|
||||
dependencies = computeDependencies();
|
||||
cleanup = effect(...dependencies) || NO_OP;
|
||||
});
|
||||
|
||||
let shouldReapplyOnPatch = false;
|
||||
onWillPatch(() => {
|
||||
const newDeps = computeDependencies();
|
||||
shouldReapplyOnPatch = newDeps.some((val, i) => val !== dependencies[i]);
|
||||
if (shouldReapplyOnPatch) {
|
||||
cleanup();
|
||||
dependencies = newDeps;
|
||||
}
|
||||
});
|
||||
onPatched(() => {
|
||||
if (shouldReapplyOnPatch) {
|
||||
cleanup = effect(...dependencies) || NO_OP;
|
||||
}
|
||||
});
|
||||
|
||||
onWillUnmount(() => cleanup());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// useService
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function _protectMethod(component, caller, fn) {
|
||||
return async (...args) => {
|
||||
if (component.__owl__.status === 5 /* DESTROYED */) {
|
||||
throw new Error("Component is destroyed");
|
||||
}
|
||||
const result = await fn.call(caller, ...args);
|
||||
return component.__owl__.status === 5 ? new Promise(() => {}) : result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a service into a component
|
||||
*
|
||||
* @param {string} serviceName
|
||||
* @returns {any}
|
||||
*/
|
||||
export function useService(serviceName) {
|
||||
const component = useComponent();
|
||||
const { services } = component.env;
|
||||
if (!(serviceName in services)) {
|
||||
throw new Error(`Service ${serviceName} is not available`);
|
||||
}
|
||||
const service = services[serviceName];
|
||||
if (serviceName in SERVICES_METADATA) {
|
||||
if (service instanceof Function) {
|
||||
return _protectMethod(component, null, service);
|
||||
} else {
|
||||
const methods = SERVICES_METADATA[serviceName];
|
||||
const result = Object.create(service);
|
||||
for (let method of methods) {
|
||||
result[method] = _protectMethod(component, service, service[method]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return service;
|
||||
}
|
||||
@@ -6,8 +6,7 @@ import { ComponentAdapter } from "web.OwlCompatibility";
|
||||
import { objectToQuery } from "../core/browser/router_service";
|
||||
import { useDebugCategory } from "../core/debug/debug_context";
|
||||
import { Dialog } from "../core/dialog/dialog";
|
||||
import { useEffect } from "../core/effect_hook";
|
||||
import { useService } from "../core/service_hook";
|
||||
import { useEffect, useService } from "@web/core/utils/hooks";
|
||||
import { ViewNotFoundError } from "../webclient/actions/action_service";
|
||||
import { cleanDomFromBootstrap, wrapSuccessOrFail, mapDoActionOptionAPI } from "./utils";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ odoo.define('web.custom_hooks', function (require) {
|
||||
"use strict";
|
||||
|
||||
const { Component } = owl;
|
||||
const { useEffect } = require("@web/core/effect_hook");
|
||||
const { useEffect } = require("@web/core/utils/hooks");
|
||||
|
||||
/**
|
||||
* Focus a given selector as soon as it appears in the DOM and if it was not
|
||||
|
||||
@@ -3,7 +3,7 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
|
||||
const field_utils = require('web.field_utils');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useEffect } = require('@web/core/effect_hook');
|
||||
const { useEffect } = require("@web/core/utils/hooks");
|
||||
|
||||
/**
|
||||
* This file defines the Owl version of the AbstractField. Specific fields
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module alias=web.PivotRenderer **/
|
||||
|
||||
import { useEffect } from "@web/core/effect_hook";
|
||||
import { useEffect } from "@web/core/utils/hooks";
|
||||
import DropdownMenu from 'web.DropdownMenu';
|
||||
import DropdownMenuItem from 'web.DropdownMenuItem';
|
||||
import OwlAbstractRenderer from '../abstract_renderer_owl';
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
|
||||
import { Dialog } from "../core/dialog/dialog";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { useEffect } from "@web/core/effect_hook";
|
||||
import { useEffect } from "@web/core/utils/hooks";
|
||||
import OwlDialog from "web.OwlDialog";
|
||||
|
||||
const { hooks } = owl;
|
||||
|
||||
/**
|
||||
* This is a patch of the new Dialog class.
|
||||
* Its purpose is to inform the old "active/inactive" mechanism.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
const { Component, hooks, tags } = owl;
|
||||
|
||||
import { useService } from "../core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useSetupAction } from "../webclient/actions/action_hook";
|
||||
import legacyViewRegistry from "web.view_registry";
|
||||
import { ViewAdapter } from "./action_adapters";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Dialog } from "../../core/dialog/dialog";
|
||||
import { DebugMenu } from "../../core/debug/debug_menu";
|
||||
import { useOwnDebugContext } from "../../core/debug/debug_context";
|
||||
import { useEffect } from "../../core/effect_hook";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { DebugMenu } from "@web/core/debug/debug_menu";
|
||||
import { useOwnDebugContext } from "@web/core/debug/debug_context";
|
||||
import { useEffect } from "@web/core/utils/hooks";
|
||||
|
||||
const { hooks } = owl;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { getScrollPosition, setScrollPosition } from "../../core/utils/scrolling";
|
||||
import { useEffect } from "../../core/effect_hook";
|
||||
import { getScrollPosition, setScrollPosition } from "@web/core/utils/scrolling";
|
||||
import { useEffect } from "@web/core/utils/hooks";
|
||||
|
||||
const { useComponent } = owl.hooks;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { cleanDomFromBootstrap } from "@web/legacy/utils";
|
||||
import { browser } from "../../core/browser/browser";
|
||||
import { useBus } from "../../core/bus_hook";
|
||||
import { makeContext } from "../../core/context";
|
||||
import { download } from "../../core/network/download";
|
||||
import { evaluateExpr } from "../../core/py_js/py";
|
||||
import { registry } from "../../core/registry";
|
||||
import { KeepLast } from "../../core/utils/concurrency";
|
||||
import { sprintf } from "../../core/utils/strings";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { useBus } from "@web/core/utils/hooks";
|
||||
import { makeContext } from "@web/core/context";
|
||||
import { download } from "@web/core/network/download";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { KeepLast } from "@web/core/utils/concurrency";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { ActionDialog } from "./action_dialog";
|
||||
|
||||
/** @typedef {number|false} ActionId */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
|
||||
const { utils, Component } = owl;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useEffect } from "@web/core/effect_hook";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useEffect, useService } from "@web/core/utils/hooks";
|
||||
|
||||
/**
|
||||
* @typedef {import("./command_service").CommandServiceAddOptions} CommandServiceAddOptions
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useAutofocus } from "@web/core/autofocus_hook";
|
||||
import { useHotkey } from "@web/core/hotkey_hook";
|
||||
import { useAutofocus, useService } from "@web/core/utils/hooks";
|
||||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { KeepLast } from "@web/core/utils/concurrency";
|
||||
import { scrollTo } from "@web/core/utils/scrolling";
|
||||
import { fuzzyLookup } from "@web/core/utils/search";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { isMacOS } from "@web/core/browser/feature_detection";
|
||||
import { useHotkey } from "@web/core/hotkey_hook";
|
||||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { capitalize } from "@web/core/utils/strings";
|
||||
import { getVisibleElements } from "@web/core/utils/ui";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "../../core/browser/browser";
|
||||
import { registry } from "../../core/registry";
|
||||
import { useService } from "../../core/service_hook";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component, useState } = owl;
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Dropdown } from "../../core/dropdown/dropdown";
|
||||
import { DropdownItem } from "../../core/dropdown/dropdown_item";
|
||||
import { useService } from "../../core/service_hook";
|
||||
import { useEffect } from "../../core/effect_hook";
|
||||
import { registry } from "../../core/registry";
|
||||
import { debounce } from "../../core/utils/timing";
|
||||
import { ErrorHandler, NotUpdatable } from "../../core/utils/components";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { useEffect, useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import { ErrorHandler, NotUpdatable } from "@web/core/utils/components";
|
||||
|
||||
const { Component, hooks } = owl;
|
||||
const { useExternalListener, useRef } = hooks;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "../../core/service_hook";
|
||||
import { registry } from "../../core/registry";
|
||||
import { browser } from "../../core/browser/browser";
|
||||
import { symmetricalDifference } from "../../core/utils/arrays";
|
||||
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";
|
||||
|
||||
const { Component, hooks } = owl;
|
||||
const { useState } = hooks;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "../../core/browser/browser";
|
||||
import { DropdownItem } from "../../core/dropdown/dropdown_item";
|
||||
import { useService } from "../../core/service_hook";
|
||||
import { registry } from "../../core/registry";
|
||||
import { useEffect } from "../../core/effect_hook";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useEffect, useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "../core/service_hook";
|
||||
import { useBus } from "../core/bus_hook";
|
||||
import { ActionContainer } from "./actions/action_container";
|
||||
import { NavBar } from "./navbar/navbar";
|
||||
import { useEffect } from "@web/core/effect_hook";
|
||||
import { useBus, useEffect, useService } from "@web/core/utils/hooks";
|
||||
import { NotUpdatable } from "../core/utils/components";
|
||||
import { MainComponentsContainer } from "../core/main_components_container";
|
||||
import { useOwnDebugContext } from "../core/debug/debug_context";
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useBus } from "@web/core/bus_hook";
|
||||
import { makeTestEnv } from "../helpers/mock_env";
|
||||
import { getFixture, nextTick } from "../helpers/utils";
|
||||
|
||||
const { Component, mount, tags } = owl;
|
||||
|
||||
QUnit.module("useBus");
|
||||
|
||||
QUnit.test("useBus hook: simple usecase", async function (assert) {
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useBus(this.env.bus, "test-event", this.myCallback);
|
||||
}
|
||||
myCallback() {
|
||||
assert.step("callback");
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
const comp = await mount(MyComponent, { env, target });
|
||||
env.bus.trigger("test-event");
|
||||
await nextTick();
|
||||
assert.verifySteps(["callback"]);
|
||||
|
||||
comp.unmount();
|
||||
env.bus.trigger("test-event");
|
||||
await nextTick();
|
||||
assert.verifySteps([]);
|
||||
|
||||
comp.destroy();
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { uiService } from "@web/core/ui/ui_service";
|
||||
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
import { useEffect } from "@web/core/effect_hook";
|
||||
import { makeTestEnv } from "../helpers/mock_env";
|
||||
import { getFixture } from "../helpers/utils";
|
||||
|
||||
const { Component, tags, hooks, mount } = owl;
|
||||
const { useState } = hooks;
|
||||
|
||||
QUnit.module("useEffect");
|
||||
|
||||
QUnit.test("useEffect: effect runs on mount, is reapplied on patch, and is cleaned up on unmount and before reapplying", async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
let cleanupRun = 0;
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
value: 0,
|
||||
});
|
||||
useEffect(
|
||||
() => {
|
||||
assert.step(`value is ${this.state.value}`);
|
||||
return () => assert.step(`cleaning up for value = ${this.state.value} (cleanup ${cleanupRun++})`);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
const component = await mount(MyComponent, { env, target });
|
||||
|
||||
assert.step("before state mutation");
|
||||
component.state.value++;
|
||||
// Wait for an owl render
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
assert.step("after state mutation");
|
||||
await component.unmount();
|
||||
|
||||
assert.verifySteps([
|
||||
"value is 0",
|
||||
"before state mutation",
|
||||
// While one might expect value to be 0 at cleanup, because the value is
|
||||
// read during cleanup from the state rather than captured by a dependency
|
||||
// it already has the new value. Having this in business code is a symptom
|
||||
// of a missing dependency and can lead to bugs.
|
||||
"cleaning up for value = 1 (cleanup 0)",
|
||||
"value is 1",
|
||||
"after state mutation",
|
||||
"cleaning up for value = 1 (cleanup 1)",
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("useEffect: dependencies prevent effects from rerunning when unchanged", async function (assert) {
|
||||
assert.expect(21);
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
a: 0,
|
||||
b: 0,
|
||||
});
|
||||
useEffect(
|
||||
(a) => {
|
||||
assert.step(`Effect a: ${a}`);
|
||||
return () => assert.step(`cleaning up for a: ${a}`);
|
||||
},
|
||||
() => [this.state.a]
|
||||
);
|
||||
useEffect(
|
||||
(b) => {
|
||||
assert.step(`Effect b: ${b}`);
|
||||
return () => assert.step(`cleaning up for b: ${b}`);
|
||||
},
|
||||
() => [this.state.b]
|
||||
);
|
||||
useEffect(
|
||||
(a, b) => {
|
||||
assert.step(`Effect ab: {a: ${a}, b: ${b}}`);
|
||||
return () => assert.step(`cleaning up for ab: {a: ${a}, b: ${b}}`);
|
||||
},
|
||||
() => [this.state.a, this.state.b]
|
||||
);
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
assert.step("before mount");
|
||||
const component = await mount(MyComponent, { env, target });
|
||||
assert.step("after mount");
|
||||
|
||||
assert.step("before state mutation: a");
|
||||
component.state.a++;
|
||||
// Wait for an owl render
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
assert.step("after state mutation: a");
|
||||
|
||||
assert.step("before state mutation: b");
|
||||
component.state.b++;
|
||||
// Wait for an owl render
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
assert.step("after state mutation: b");
|
||||
await component.unmount();
|
||||
|
||||
assert.verifySteps([
|
||||
// All effects run on mount
|
||||
"before mount",
|
||||
"Effect a: 0",
|
||||
"Effect b: 0",
|
||||
"Effect ab: {a: 0, b: 0}",
|
||||
"after mount",
|
||||
|
||||
"before state mutation: a",
|
||||
// Cleanups run in reverse order
|
||||
"cleaning up for ab: {a: 0, b: 0}",
|
||||
// Cleanup for b is not run
|
||||
"cleaning up for a: 0",
|
||||
|
||||
"Effect a: 1",
|
||||
// Effect b is not run
|
||||
"Effect ab: {a: 1, b: 0}",
|
||||
"after state mutation: a",
|
||||
|
||||
"before state mutation: b",
|
||||
"cleaning up for ab: {a: 1, b: 0}",
|
||||
"cleaning up for b: 0",
|
||||
// Cleanup for a is not run
|
||||
|
||||
// Effect a is not run
|
||||
"Effect b: 1",
|
||||
"Effect ab: {a: 1, b: 1}",
|
||||
"after state mutation: b",
|
||||
|
||||
// All cleanups run on unmount
|
||||
"cleaning up for ab: {a: 1, b: 1}",
|
||||
"cleaning up for b: 1",
|
||||
"cleaning up for a: 1",
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("useEffect: effect with empty dependency list never reruns", async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
value: 0,
|
||||
});
|
||||
useEffect(
|
||||
() => {
|
||||
assert.step(`value is ${this.state.value}`);
|
||||
return () => assert.step(`cleaning up for ${this.state.value}`);
|
||||
},
|
||||
() => []
|
||||
);
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div t-esc="state.value"/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
const component = await mount(MyComponent, { env, target });
|
||||
|
||||
assert.step("before state mutation");
|
||||
component.state.value++;
|
||||
// Wait for an owl render
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
assert.equal(component.el.textContent, 1, "Value was correctly changed inside the component");
|
||||
assert.step("after state mutation");
|
||||
await component.unmount();
|
||||
|
||||
assert.verifySteps([
|
||||
"value is 0",
|
||||
"before state mutation",
|
||||
// no cleanup or effect caused by mutation
|
||||
"after state mutation",
|
||||
// Value being clean
|
||||
"cleaning up for 1",
|
||||
]);
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { useHotkey } from "@web/core/hotkey_hook";
|
||||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { uiService, useActiveElement } from "@web/core/ui/ui_service";
|
||||
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { browser } from "@web/core/browser/browser";
|
||||
import { ConnectionAbortedError, rpcService } from "@web/core/network/rpc_service";
|
||||
import { notificationService } from "@web/core/notifications/notification_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { patch, unpatch } from "@web/core/utils/patch";
|
||||
import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env";
|
||||
import { makeMockXHR } from "../../helpers/mock_services";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { ormService } from "@web/core/orm_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { makeTestEnv } from "../helpers/mock_env";
|
||||
import { getFixture } from "../helpers/utils";
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { makeTestEnv } from "../helpers/mock_env";
|
||||
import { getFixture } from "../helpers/utils";
|
||||
|
||||
const { Component, mount, tags } = owl;
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
QUnit.module("useService");
|
||||
|
||||
QUnit.test("useService: unavailable service", async function (assert) {
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useService("toy_service");
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
try {
|
||||
await mount(MyComponent, { env, target });
|
||||
} catch (e) {
|
||||
assert.strictEqual(e.message, "Service toy_service is not available");
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test("useService: service that returns null", async function (assert) {
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.toyService = useService("toy_service");
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
serviceRegistry.add("toy_service", {
|
||||
name: "toy_service",
|
||||
start: () => {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
|
||||
const comp = await mount(MyComponent, { env, target });
|
||||
assert.strictEqual(comp.toyService, null);
|
||||
comp.unmount();
|
||||
});
|
||||
@@ -0,0 +1,275 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useBus, useEffect, useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { makeTestEnv } from "../../helpers/mock_env";
|
||||
import { getFixture, nextTick } from "../../helpers/utils";
|
||||
|
||||
const { Component, mount, tags, useState } = owl;
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
QUnit.module("utils", () => {
|
||||
QUnit.module("Hooks", () => {
|
||||
QUnit.module("useBus");
|
||||
|
||||
QUnit.test("useBus hook: simple usecase", async function (assert) {
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useBus(this.env.bus, "test-event", this.myCallback);
|
||||
}
|
||||
myCallback() {
|
||||
assert.step("callback");
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
const comp = await mount(MyComponent, { env, target });
|
||||
env.bus.trigger("test-event");
|
||||
await nextTick();
|
||||
assert.verifySteps(["callback"]);
|
||||
|
||||
comp.unmount();
|
||||
env.bus.trigger("test-event");
|
||||
await nextTick();
|
||||
assert.verifySteps([]);
|
||||
|
||||
comp.destroy();
|
||||
});
|
||||
|
||||
QUnit.module("useEffect");
|
||||
|
||||
QUnit.test(
|
||||
"useEffect: effect runs on mount, is reapplied on patch, and is cleaned up on unmount and before reapplying",
|
||||
async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
let cleanupRun = 0;
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
value: 0,
|
||||
});
|
||||
useEffect(() => {
|
||||
assert.step(`value is ${this.state.value}`);
|
||||
return () =>
|
||||
assert.step(
|
||||
`cleaning up for value = ${
|
||||
this.state.value
|
||||
} (cleanup ${cleanupRun++})`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
const component = await mount(MyComponent, { env, target });
|
||||
|
||||
assert.step("before state mutation");
|
||||
component.state.value++;
|
||||
// Wait for an owl render
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
assert.step("after state mutation");
|
||||
await component.unmount();
|
||||
|
||||
assert.verifySteps([
|
||||
"value is 0",
|
||||
"before state mutation",
|
||||
// While one might expect value to be 0 at cleanup, because the value is
|
||||
// read during cleanup from the state rather than captured by a dependency
|
||||
// it already has the new value. Having this in business code is a symptom
|
||||
// of a missing dependency and can lead to bugs.
|
||||
"cleaning up for value = 1 (cleanup 0)",
|
||||
"value is 1",
|
||||
"after state mutation",
|
||||
"cleaning up for value = 1 (cleanup 1)",
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"useEffect: dependencies prevent effects from rerunning when unchanged",
|
||||
async function (assert) {
|
||||
assert.expect(21);
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
a: 0,
|
||||
b: 0,
|
||||
});
|
||||
useEffect(
|
||||
(a) => {
|
||||
assert.step(`Effect a: ${a}`);
|
||||
return () => assert.step(`cleaning up for a: ${a}`);
|
||||
},
|
||||
() => [this.state.a]
|
||||
);
|
||||
useEffect(
|
||||
(b) => {
|
||||
assert.step(`Effect b: ${b}`);
|
||||
return () => assert.step(`cleaning up for b: ${b}`);
|
||||
},
|
||||
() => [this.state.b]
|
||||
);
|
||||
useEffect(
|
||||
(a, b) => {
|
||||
assert.step(`Effect ab: {a: ${a}, b: ${b}}`);
|
||||
return () => assert.step(`cleaning up for ab: {a: ${a}, b: ${b}}`);
|
||||
},
|
||||
() => [this.state.a, this.state.b]
|
||||
);
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
assert.step("before mount");
|
||||
const component = await mount(MyComponent, { env, target });
|
||||
assert.step("after mount");
|
||||
|
||||
assert.step("before state mutation: a");
|
||||
component.state.a++;
|
||||
// Wait for an owl render
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
assert.step("after state mutation: a");
|
||||
|
||||
assert.step("before state mutation: b");
|
||||
component.state.b++;
|
||||
// Wait for an owl render
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
assert.step("after state mutation: b");
|
||||
await component.unmount();
|
||||
|
||||
assert.verifySteps([
|
||||
// All effects run on mount
|
||||
"before mount",
|
||||
"Effect a: 0",
|
||||
"Effect b: 0",
|
||||
"Effect ab: {a: 0, b: 0}",
|
||||
"after mount",
|
||||
|
||||
"before state mutation: a",
|
||||
// Cleanups run in reverse order
|
||||
"cleaning up for ab: {a: 0, b: 0}",
|
||||
// Cleanup for b is not run
|
||||
"cleaning up for a: 0",
|
||||
|
||||
"Effect a: 1",
|
||||
// Effect b is not run
|
||||
"Effect ab: {a: 1, b: 0}",
|
||||
"after state mutation: a",
|
||||
|
||||
"before state mutation: b",
|
||||
"cleaning up for ab: {a: 1, b: 0}",
|
||||
"cleaning up for b: 0",
|
||||
// Cleanup for a is not run
|
||||
|
||||
// Effect a is not run
|
||||
"Effect b: 1",
|
||||
"Effect ab: {a: 1, b: 1}",
|
||||
"after state mutation: b",
|
||||
|
||||
// All cleanups run on unmount
|
||||
"cleaning up for ab: {a: 1, b: 1}",
|
||||
"cleaning up for b: 1",
|
||||
"cleaning up for a: 1",
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"useEffect: effect with empty dependency list never reruns",
|
||||
async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
value: 0,
|
||||
});
|
||||
useEffect(
|
||||
() => {
|
||||
assert.step(`value is ${this.state.value}`);
|
||||
return () => assert.step(`cleaning up for ${this.state.value}`);
|
||||
},
|
||||
() => []
|
||||
);
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div t-esc="state.value"/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
const component = await mount(MyComponent, { env, target });
|
||||
|
||||
assert.step("before state mutation");
|
||||
component.state.value++;
|
||||
// Wait for an owl render
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
assert.equal(
|
||||
component.el.textContent,
|
||||
1,
|
||||
"Value was correctly changed inside the component"
|
||||
);
|
||||
assert.step("after state mutation");
|
||||
await component.unmount();
|
||||
|
||||
assert.verifySteps([
|
||||
"value is 0",
|
||||
"before state mutation",
|
||||
// no cleanup or effect caused by mutation
|
||||
"after state mutation",
|
||||
// Value being clean
|
||||
"cleaning up for 1",
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.module("useService");
|
||||
|
||||
QUnit.test("useService: unavailable service", async function (assert) {
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useService("toy_service");
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
try {
|
||||
await mount(MyComponent, { env, target });
|
||||
} catch (e) {
|
||||
assert.strictEqual(e.message, "Service toy_service is not available");
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test("useService: service that returns null", async function (assert) {
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.toyService = useService("toy_service");
|
||||
}
|
||||
}
|
||||
MyComponent.template = tags.xml`<div/>`;
|
||||
|
||||
serviceRegistry.add("toy_service", {
|
||||
name: "toy_service",
|
||||
start: () => {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
const env = await makeTestEnv();
|
||||
const target = getFixture();
|
||||
|
||||
const comp = await mount(MyComponent, { env, target });
|
||||
assert.strictEqual(comp.toyService, null);
|
||||
comp.unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import testUtils from "web.test_utils";
|
||||
import { click, legacyExtraNextTick, nextTick, patchWithCleanup } from "../../helpers/utils";
|
||||
import { createWebClient, doAction, getActionManagerServerData } from "./../helpers";
|
||||
|
||||
@@ -4,16 +4,11 @@ import testUtils from "web.test_utils";
|
||||
import core from "web.core";
|
||||
import AbstractAction from "web.AbstractAction";
|
||||
import { registry } from "@web/core/registry";
|
||||
import {
|
||||
click,
|
||||
legacyExtraNextTick,
|
||||
patchWithCleanup,
|
||||
makeDeferred,
|
||||
} from "../../helpers/utils";
|
||||
import { click, legacyExtraNextTick, patchWithCleanup, makeDeferred } from "../../helpers/utils";
|
||||
import { createWebClient, doAction, getActionManagerServerData } from "./../helpers";
|
||||
import { registerCleanup } from "../../helpers/cleanup";
|
||||
import { errorService } from "@web/core/errors/error_service";
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ClientErrorDialog } from "@web/core/errors/error_dialogs";
|
||||
|
||||
let serverData;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "@web/core/service_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user