diff --git a/addons/account/static/src/components/tax_totals/tax_totals.js b/addons/account/static/src/components/tax_totals/tax_totals.js index 499ec58d78f..27413903ccc 100644 --- a/addons/account/static/src/components/tax_totals/tax_totals.js +++ b/addons/account/static/src/components/tax_totals/tax_totals.js @@ -4,7 +4,7 @@ import { formatFloat, formatMonetary } from "@web/views/fields/formatters"; import { parseFloat } from "@web/views/fields/parsers"; import { standardFieldProps } from "@web/views/fields/standard_field_props"; import { registry } from "@web/core/registry"; -import { session } from "@web/session"; +import { getCurrency } from "@web/core/currency"; const { Component, onPatched, onWillUpdateProps, useRef, useState } = owl; @@ -123,7 +123,7 @@ export class TaxTotalsComponent extends Component { } get currency() { - return session.currencies[this.currencyId]; + return getCurrency(this.currencyId); } invalidate() { diff --git a/addons/crm/static/src/views/crm_kanban/crm_column_progress.js b/addons/crm/static/src/views/crm_kanban/crm_column_progress.js index 99f8fc718a0..2452bf91624 100644 --- a/addons/crm/static/src/views/crm_kanban/crm_column_progress.js +++ b/addons/crm/static/src/views/crm_kanban/crm_column_progress.js @@ -4,6 +4,7 @@ import { onWillStart } from "@odoo/owl"; import { useService } from "@web/core/utils/hooks"; import { ColumnProgress } from "@web/views/view_components/column_progress"; import { session } from "@web/session"; +import { getCurrency } from "@web/core/currency"; export class CrmColumnProgress extends ColumnProgress { static props = { @@ -29,7 +30,7 @@ export class CrmColumnProgress extends ColumnProgress { const title = rrField.string || this.env._t("Count"); let currency = false; if (value && rrField.currency_field) { - currency = session.currencies[session.company_currency_id]; + currency = getCurrency(session.company_currency_id); } return { value, currency, title }; } diff --git a/addons/mail/static/src/web/message_patch.js b/addons/mail/static/src/web/message_patch.js index 267da3edd01..61273c005fd 100644 --- a/addons/mail/static/src/web/message_patch.js +++ b/addons/mail/static/src/web/message_patch.js @@ -6,8 +6,8 @@ import { useService } from "@web/core/utils/hooks"; import { format } from "web.field_utils"; import { deserializeDateTime } from "@web/core/l10n/dates"; import { registry } from "@web/core/registry"; -import { session } from "@web/session"; import { _t } from "@web/core/l10n/translation"; +import { getCurrency } from "@web/core/currency"; const formatters = registry.category("formatters"); @@ -93,7 +93,7 @@ patch(Message.prototype, "mail/web", { case "monetary": return format.monetary(trackingValue.value, undefined, { currency: trackingValue.currencyId - ? session.currencies[trackingValue.currencyId] + ? getCurrency(trackingValue.currencyId) : undefined, forceString: true, }); diff --git a/addons/project/static/src/components/project_right_side_panel/project_right_side_panel.js b/addons/project/static/src/components/project_right_side_panel/project_right_side_panel.js index 50f41722f68..21cd096b121 100644 --- a/addons/project/static/src/components/project_right_side_panel/project_right_side_panel.js +++ b/addons/project/static/src/components/project_right_side_panel/project_right_side_panel.js @@ -2,13 +2,13 @@ import { useService } from '@web/core/utils/hooks'; import { formatFloat } from '@web/views/fields/formatters'; -import { session } from '@web/session'; import { ViewButton } from '@web/views/view_button/view_button'; import { FormViewDialog } from '@web/views/view_dialogs/form_view_dialog'; import { ProjectRightSidePanelSection } from './components/project_right_side_panel_section'; import { ProjectMilestone } from './components/project_milestone'; import { ProjectProfitability } from './components/project_profitability'; +import { getCurrency } from '@web/core/currency'; const { Component, onWillStart, useState } = owl; @@ -75,7 +75,7 @@ export class ProjectRightSidePanel extends Component { 'digits': [false, 0], 'noSymbol': true, }); - const currency = session.currencies[this.currencyId]; + const currency = getCurrency(this.currencyId); if (!currency) { return valueFormatted; } diff --git a/addons/stock/static/src/js/basic_model.js b/addons/stock/static/src/js/basic_model.js deleted file mode 100644 index 6a5653c6314..00000000000 --- a/addons/stock/static/src/js/basic_model.js +++ /dev/null @@ -1,16 +0,0 @@ -odoo.define('stock.BasicModel', function (require) { -"use strict"; - -var BasicModel = require('web.BasicModel'); -var localStorage = require('web.local_storage'); - -BasicModel.include({ - - _invalidateCache: function (dataPoint) { - this._super.apply(this, arguments); - if (dataPoint.model === 'stock.warehouse' && !localStorage.getItem('running_tour')) { - this.do_action('reload_context'); - } - } -}); -}); diff --git a/addons/stock/static/src/stock_warehouse_service.js b/addons/stock/static/src/stock_warehouse_service.js new file mode 100644 index 00000000000..112cc979a42 --- /dev/null +++ b/addons/stock/static/src/stock_warehouse_service.js @@ -0,0 +1,19 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { browser } from "@web/core/browser/browser"; +import { UPDATE_METHODS } from "@web/core/orm_service"; + +registry.category("services").add("stock_warehouse", { + dependencies: ["action"], + start(env, { action }) { + env.bus.addEventListener("RPC:RESPONSE", (ev) => { + const { model, method } = ev.detail.data.params; + if (model === "stock.warehouse") { + if (UPDATE_METHODS.includes(method) && !browser.localStorage.getItem("running_tour")) { + action.doAction("reload_context"); + } + } + }); + }, +}); diff --git a/addons/web/static/src/core/currency.js b/addons/web/static/src/core/currency.js new file mode 100644 index 00000000000..5e57b650b17 --- /dev/null +++ b/addons/web/static/src/core/currency.js @@ -0,0 +1,11 @@ +/** @odoo-module **/ + +import { session } from "@web/session"; + +export const currencies = session.currencies || {}; +// to make sure code is reading currencies from here +delete session.currencies; + +export function getCurrency(id) { + return currencies[id]; +} diff --git a/addons/web/static/src/core/network/rpc_service.js b/addons/web/static/src/core/network/rpc_service.js index 16781e6c73f..f47183485dc 100644 --- a/addons/web/static/src/core/network/rpc_service.js +++ b/addons/web/static/src/core/network/rpc_service.js @@ -56,16 +56,12 @@ export function jsonrpc(env, rpcId, url, params, settings = {}) { let rejectFn; const promise = new Promise((resolve, reject) => { rejectFn = reject; - if (!settings.silent) { - bus.trigger("RPC:REQUEST", data.id); - } + bus.trigger("RPC:REQUEST", { data, settings }); // handle success request.addEventListener("load", () => { if (request.status === 502) { // If Odoo is behind another server (eg.: nginx) - if (!settings.silent) { - bus.trigger("RPC:RESPONSE", data.id); - } + bus.trigger("RPC:RESPONSE", { data, settings }); reject(new ConnectionLostError(url)); return; } @@ -75,15 +71,11 @@ export function jsonrpc(env, rpcId, url, params, settings = {}) { } catch { // the response isn't json parsable, which probably means that the rpc request could // not be handled by the server, e.g. PoolError('The Connection Pool Is Full') - if (!settings.silent) { - bus.trigger("RPC:RESPONSE", data.id); - } + bus.trigger("RPC:RESPONSE", { data, settings }); return reject(new ConnectionLostError(url)); } const { error: responseError, result: responseResult } = params; - if (!settings.silent) { - bus.trigger("RPC:RESPONSE", data.id); - } + bus.trigger("RPC:RESPONSE", { data, settings }); if (!responseError) { return resolve(responseResult); } @@ -92,9 +84,7 @@ export function jsonrpc(env, rpcId, url, params, settings = {}) { }); // handle failure request.addEventListener("error", () => { - if (!settings.silent) { - bus.trigger("RPC:RESPONSE", data.id); - } + bus.trigger("RPC:RESPONSE", { data, settings }); reject(new ConnectionLostError(url)); }); // configure and send request @@ -110,9 +100,7 @@ export function jsonrpc(env, rpcId, url, params, settings = {}) { if (request.abort) { request.abort(); } - if (!settings.silent) { - bus.trigger("RPC:RESPONSE", data.id); - } + bus.trigger("RPC:RESPONSE", { data, settings }); if (rejectError) { rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort")); } diff --git a/addons/web/static/src/core/orm_service.js b/addons/web/static/src/core/orm_service.js index 3fbcbec6ac4..bb2cf6973b0 100644 --- a/addons/web/static/src/core/orm_service.js +++ b/addons/web/static/src/core/orm_service.js @@ -84,6 +84,8 @@ function validateArray(name, array) { } } +export const UPDATE_METHODS = ["unlink", "create", "write", "action_archive", "action_unarchive"]; + export class ORM { constructor(rpc, user) { /** @protected */ diff --git a/addons/web/static/src/legacy/js/core/session.js b/addons/web/static/src/legacy/js/core/session.js index 74093dcb59c..4b78538fbb6 100644 --- a/addons/web/static/src/legacy/js/core/session.js +++ b/addons/web/static/src/legacy/js/core/session.js @@ -301,23 +301,6 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { } }, - /** - * Reload the currencies (initially given in session_info). This is meant to - * be called when changes are made on 'res.currency' records (e.g. when - * (de-)activating a currency). For the sake of simplicity, we reload all - * session_info. - * - * FIXME: this whole currencies handling should be moved out of session. - * - * @returns {$.promise} - */ - reloadCurrencies: function () { - var self = this; - return this.rpc('/web/session/get_session_info').then(function (result) { - self.currencies = result.currencies; - }); - }, - setCompanies: function (main_company_id, company_ids) { var hash = $.bbq.getState(); hash.cids = company_ids.sort(function(a, b) { diff --git a/addons/web/static/src/legacy/js/public/public_root.js b/addons/web/static/src/legacy/js/public/public_root.js index 58bb299c19c..b33f03f080f 100644 --- a/addons/web/static/src/legacy/js/public/public_root.js +++ b/addons/web/static/src/legacy/js/public/public_root.js @@ -12,7 +12,6 @@ import lazyloader from "web.public.lazyloader"; import { makeLegacyNotificationService, - makeLegacyRpcService, makeLegacySessionService, makeLegacyDialogMappingService, mapLegacyEnvToWowlEnv, @@ -369,7 +368,6 @@ export async function createPublicRoot(RootWidget) { AbstractService.prototype.deployServices(legacyEnv); // add a bunch of mapping services that will redirect service calls from the legacy env // to the wowl env - serviceRegistry.add("legacy_rpc", makeLegacyRpcService(legacyEnv)); serviceRegistry.add("legacy_session", makeLegacySessionService(legacyEnv, session)); serviceRegistry.add("legacy_notification", makeLegacyNotificationService(legacyEnv)); serviceRegistry.add("legacy_dialog_mapping", makeLegacyDialogMappingService(legacyEnv)); diff --git a/addons/web/static/src/legacy/js/views/basic/basic_model.js b/addons/web/static/src/legacy/js/views/basic/basic_model.js index 211b0d7d10e..53ced035546 100644 --- a/addons/web/static/src/legacy/js/views/basic/basic_model.js +++ b/addons/web/static/src/legacy/js/views/basic/basic_model.js @@ -91,7 +91,6 @@ var Domain = require('web.Domain'); var session = require('web.session'); var utils = require('web.utils'); var viewUtils = require('web.viewUtils'); -var localStorage = require('web.local_storage'); var _t = core._t; @@ -144,15 +143,6 @@ var BasicModel = AbstractModel.extend({ // constants OPEN_GROUP_LIMIT: 10, // after this limit, groups are automatically folded - // list of models for which the DataManager's cache should be cleared on - // create, update and delete operations - noCacheModels: [ - 'ir.actions.act_window', - 'ir.filters', - 'ir.ui.view', - 'res.currency', - ], - /** * @override */ @@ -398,8 +388,6 @@ var BasicModel = AbstractModel.extend({ record.count--; } }); - // optionally clear the DataManager's cache - self._invalidateCache(records[0]); }); }, /** @@ -1192,9 +1180,6 @@ var BasicModel = AbstractModel.extend({ // Erase changes as they have been applied record._changes = {}; - // Optionally clear the DataManager's cache - self._invalidateCache(record); - self.unfreezeOrder(record.id); // Update the data directly or reload them @@ -1298,39 +1283,6 @@ var BasicModel = AbstractModel.extend({ const has_x_active = fields.includes('x_active'); return has_x_active?'x_active':undefined }, - /** - * Toggle the active value of given records (to archive/unarchive them) - * - * @param {Array} recordIDs local ids of the records to (un)archive - * @param {boolean} value false to archive, true to unarchive (value of the active field) - * @param {string} parentID id of the parent resource to reload - * @returns {Promise} resolves to the parent id - */ - toggleActive: function (recordIDs, parentID) { - var self = this; - var parent = this.localData[parentID]; - var resIDs = _.map(recordIDs, function (recordID) { - return self.localData[recordID].res_id; - }); - return this._rpc({ - model: parent.model, - method: 'toggle_active', - args: [resIDs], - }) - .then(function (action) { - // optionally clear the DataManager's cache - self._invalidateCache(parent); - if (!_.isEmpty(action)) { - return self.do_action(action, { - on_close: function () { - return self.trigger_up('reload'); - } - }); - } else { - return self.reload(parentID); - } - }); - }, /** * Archive the given records * @@ -1347,8 +1299,6 @@ var BasicModel = AbstractModel.extend({ args: [resIDs], }) .then(function (action) { - // optionally clear the DataManager's cache - self._invalidateCache(parent); if (!_.isEmpty(action)) { return new Promise(function (resolve, reject) { self.do_action(action, { @@ -1390,7 +1340,6 @@ var BasicModel = AbstractModel.extend({ }) .then(function (action) { // optionally clear the DataManager's cache - self._invalidateCache(parent); if (!_.isEmpty(action)) { return new Promise(function (resolve, reject) { self.do_action(action, { @@ -4044,31 +3993,6 @@ var BasicModel = AbstractModel.extend({ _getUngroupedListDomain(list) { return list.domain || []; }, - /** - * Invalidates the DataManager's cache if the main model (i.e. the model of - * its root parent) of the given dataPoint is a model in 'noCacheModels'. - * - * Reloads the currencies if the main model is 'res.currency'. - * Reloads the webclient if we modify a res.company, to (un)activate the - * multi-company environment if we are not in a tour test. - * - * @private - * @param {Object} dataPoint - */ - _invalidateCache: function (dataPoint) { - while (dataPoint.parentID) { - dataPoint = this.localData[dataPoint.parentID]; - } - if (dataPoint.model === 'res.currency') { - session.reloadCurrencies(); - } - if (dataPoint.model === 'res.company' && !localStorage.getItem('running_tour')) { - this.do_action('reload_context'); - } - if (_.contains(this.noCacheModels, dataPoint.model)) { - core.bus.trigger('clear_cache'); - } - }, /** * Returns true if the field is protected against changes, looking for a * readonly modifier unless there is a force_save modifier (checking first diff --git a/addons/web/static/src/legacy/legacy_setup.js b/addons/web/static/src/legacy/legacy_setup.js index 4395832a3f6..4b310f10cd6 100644 --- a/addons/web/static/src/legacy/legacy_setup.js +++ b/addons/web/static/src/legacy/legacy_setup.js @@ -3,7 +3,6 @@ import { registry } from "../core/registry"; import { makeLegacyNotificationService, - makeLegacyRpcService, makeLegacySessionService, makeLegacyDialogMappingService, makeLegacyCrashManagerService, @@ -34,8 +33,6 @@ export const legacySetupProm = new Promise((resolve) => { serviceRegistry.add("legacy_action_manager", legacyActionManagerService); // add a service to redirect rpc events triggered on the bus in the // legacy env on the bus in the wowl env - const legacyRpcService = makeLegacyRpcService(legacyEnv); - serviceRegistry.add("legacy_rpc", legacyRpcService); const legacySessionService = makeLegacySessionService(legacyEnv, session); serviceRegistry.add("legacy_session", legacySessionService); const legacyWebClientService = makeLegacyWebClientService(legacyEnv); diff --git a/addons/web/static/src/legacy/utils.js b/addons/web/static/src/legacy/utils.js index 0f312a717d3..f10d67e5754 100644 --- a/addons/web/static/src/legacy/utils.js +++ b/addons/web/static/src/legacy/utils.js @@ -15,22 +15,6 @@ import { useComponent } from "@odoo/owl"; export const wowlServicesSymbol = Symbol("wowlServices"); -export function makeLegacyRpcService(legacyEnv) { - return { - start(env) { - legacyEnv.bus.on("rpc_request", null, (rpcId) => { - env.bus.trigger("RPC:REQUEST", rpcId); - }); - legacyEnv.bus.on("rpc_response", null, (rpcId) => { - env.bus.trigger("RPC:RESPONSE", rpcId); - }); - legacyEnv.bus.on("rpc_response_failed", null, (rpcId) => { - env.bus.trigger("RPC:RESPONSE", rpcId); - }); - }, - }; -} - /** * Returns a service that maps legacy dialogs * to new environment services behavior. diff --git a/addons/web/static/src/views/fields/formatters.js b/addons/web/static/src/views/fields/formatters.js index d4d6b0896e6..b16e1e3b33a 100644 --- a/addons/web/static/src/views/fields/formatters.js +++ b/addons/web/static/src/views/fields/formatters.js @@ -6,9 +6,9 @@ import { _t } from "@web/core/l10n/translation"; import { registry } from "@web/core/registry"; import { escape, intersperse, nbsp, sprintf } from "@web/core/utils/strings"; import { isBinarySize } from "@web/core/utils/binary"; -import { session } from "@web/session"; import { markup } from "@odoo/owl"; +import { getCurrency } from "@web/core/currency"; // ----------------------------------------------------------------------------- // Helpers @@ -366,7 +366,7 @@ export function formatMonetary(value, options = {}) { const dataValue = options.data[currencyField]; currencyId = Array.isArray(dataValue) ? dataValue[0] : dataValue; } - const currency = session.currencies[currencyId]; + const currency = getCurrency(currencyId); const digits = options.digits || (currency && currency.digits); let formattedValue; diff --git a/addons/web/static/src/views/fields/monetary/monetary_field.js b/addons/web/static/src/views/fields/monetary/monetary_field.js index 9649b6d4fbc..0bbf1b1bba7 100644 --- a/addons/web/static/src/views/fields/monetary/monetary_field.js +++ b/addons/web/static/src/views/fields/monetary/monetary_field.js @@ -7,9 +7,9 @@ import { parseMonetary } from "../parsers"; import { useInputField } from "../input_field_hook"; import { useNumpadDecimal } from "../numpad_decimal_hook"; import { standardFieldProps } from "../standard_field_props"; -import { session } from "@web/session"; import { Component } from "@odoo/owl"; +import { getCurrency } from "@web/core/currency"; export class MonetaryField extends Component { static template = "web.MonetaryField"; @@ -44,8 +44,8 @@ export class MonetaryField extends Component { return currency && currency[0]; } get currency() { - if (!isNaN(this.currencyId) && this.currencyId in session.currencies) { - return session.currencies[this.currencyId]; + if (!isNaN(this.currencyId)) { + return getCurrency(this.currencyId) || null; } return null; } @@ -61,7 +61,7 @@ export class MonetaryField extends Component { if (!this.currency) { return null; } - return session.currencies[this.currencyId].digits; + return getCurrency(this.currencyId).digits; } get formattedValue() { diff --git a/addons/web/static/src/views/kanban/kanban_renderer.js b/addons/web/static/src/views/kanban/kanban_renderer.js index 773a0f70f55..e4377c26bc9 100644 --- a/addons/web/static/src/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/views/kanban/kanban_renderer.js @@ -9,7 +9,6 @@ import { registry } from "@web/core/registry"; import { useBus, useService } from "@web/core/utils/hooks"; import { useSortable } from "@web/core/utils/sortable"; import { sprintf } from "@web/core/utils/strings"; -import { session } from "@web/session"; import { archParseBoolean, isNull, isRelational } from "@web/views/utils"; import { ColumnProgress } from "@web/views/view_components/column_progress"; import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog"; @@ -350,11 +349,7 @@ export class KanbanRenderer extends Component { const { sumField } = this.props.list.model.progressAttributes; const value = group.getAggregates(sumField && sumField.name); const title = sumField ? sumField.string : this.env._t("Count"); - let currency = false; - if (sumField && value && sumField.currency_field) { - currency = session.currencies[session.company_currency_id]; - } - return { value, currency, title }; + return { value, title }; } generateGhostColumns() { diff --git a/addons/web/static/src/views/relational_model.js b/addons/web/static/src/views/relational_model.js index 13631e68105..d01eeb74228 100644 --- a/addons/web/static/src/views/relational_model.js +++ b/addons/web/static/src/views/relational_model.js @@ -298,32 +298,6 @@ class DataPoint { */ setup() {} - /** - * TODO WOWL: adapt this comment to new system - * Also, is datapoint the best place ? Could be in record. - * - * Invalidates the DataManager's cache if the main model (i.e. the model of - * its root parent) of the given dataPoint is a model in 'noCacheModels'. - * - * Reloads the currencies if the main model is 'res.currency'. - * Reloads the webclient if we modify a res.company, to (un)activate the - * multi-company environment if we are not in a tour test. - * - */ - invalidateCache() { - if (this.resModel === "res.currency") { - // TODO WOWL: this needs to be ported from basic model for the list view to have it. - // session.reloadCurrencies(); - // There is a test in form view but it uses the basic model for now. - } - if (this.resModel === "res.company") { - this.model.action.doAction("reload_context"); - } - if (this.model.noCacheModels.includes(this.resModel)) { - this.model.env.bus.trigger("CLEAR-CACHES"); - } - } - /** * @param {Object} [activeFields={}] */ @@ -566,7 +540,6 @@ export class Record extends DataPoint { await toggleArchive(this.model, this.resModel, [this.resId], true, this.context); await this.load(); this.model.notify(); - this.invalidateCache(); } async askChanges() { @@ -641,7 +614,6 @@ export class Record extends DataPoint { this._changes = {}; this.preloadedData = {}; } - this.invalidateCache(); } discard() { @@ -982,7 +954,6 @@ export class Record extends DataPoint { await toggleArchive(this.model, this.resModel, [this.resId], false, this.context); await this.load(); this.model.notify(); - this.invalidateCache(); } async update(changes, options) { @@ -1492,7 +1463,6 @@ export class Record extends DataPoint { this.data.id = this.resId; this.resIds.push(this.resId); this._changes = {}; - this.invalidateCache(); } else if (keys.length > 0) { try { await this.model.orm.write(this.resModel, [this.resId], changes, { context }); @@ -1503,7 +1473,6 @@ export class Record extends DataPoint { throw e; } this._changes = {}; - this.invalidateCache(); } this.isInQuickCreation = false; @@ -1655,7 +1624,6 @@ class DynamicList extends DataPoint { const resIds = await this.getResIds(isSelected); await toggleArchive(this.model, this.resModel, resIds, true, this.context); await this.model.load(); - this.invalidateCache(); return resIds; } @@ -1724,7 +1692,6 @@ class DynamicList extends DataPoint { const resIds = await this.getResIds(isSelected); await toggleArchive(this.model, this.resModel, resIds, false, this.context); await this.model.load(); - this.invalidateCache(); return resIds; } @@ -1770,7 +1737,6 @@ class DynamicList extends DataPoint { try { const context = this.context; await this.model.orm.write(this.resModel, resIds, changes, { context }); - this.invalidateCache(); validSelection.forEach((record) => { record.selected = false; }); @@ -3579,9 +3545,6 @@ export class RelationalModel extends Model { this.root = null; this.nextId = 1; - - // list of models for which the DataManager's cache should be cleared on create, update and delete operations - this.noCacheModels = ["ir.actions.act_window", "ir.filters", "ir.ui.view", "res.currency"]; } /** diff --git a/addons/web/static/src/views/view_service.js b/addons/web/static/src/views/view_service.js index 752781801b6..4c00ee5fa03 100644 --- a/addons/web/static/src/views/view_service.js +++ b/addons/web/static/src/views/view_service.js @@ -3,6 +3,7 @@ import { deepCopy } from "@web/core/utils/objects"; import { registry } from "@web/core/registry"; import { generateLegacyLoadViewsResult } from "@web/legacy/legacy_load_views"; +import { UPDATE_METHODS } from "@web/core/orm_service"; /** * @typedef {Object} IrFilter @@ -51,11 +52,21 @@ export const viewService = { start(env, { orm }) { let cache = {}; - env.bus.addEventListener("CLEAR-CACHES", () => { + function clearCache() { cache = {}; const processedArchs = registry.category("__processed_archs__"); processedArchs.content = {}; processedArchs.trigger("UPDATE"); + } + + env.bus.addEventListener("CLEAR-CACHES", clearCache); + env.bus.addEventListener("RPC:RESPONSE", (ev) => { + const { model, method } = ev.detail.data.params; + if (["ir.ui.view", "ir.filters"].includes(model)) { + if (UPDATE_METHODS.includes(method)) { + clearCache(); + } + } }); /** diff --git a/addons/web/static/src/webclient/actions/action_service.js b/addons/web/static/src/webclient/actions/action_service.js index b1ef19452a2..bb06fbd018b 100644 --- a/addons/web/static/src/webclient/actions/action_service.js +++ b/addons/web/static/src/webclient/actions/action_service.js @@ -14,6 +14,7 @@ import { View, ViewNotFoundError } from "@web/views/view"; import { ActionDialog } from "./action_dialog"; import { CallbackRecorder } from "./action_hook"; import { ReportAction } from "./reports/report_action"; +import { UPDATE_METHODS } from "@web/core/orm_service"; import { Component, @@ -111,6 +112,12 @@ function makeActionManager(env) { env.bus.addEventListener("CLEAR-CACHES", () => { actionCache = {}; }); + env.bus.addEventListener("RPC:RESPONSE", (ev) => { + const { model, method } = ev.detail.data.params; + if (model === "ir.actions.act_window" && UPDATE_METHODS.includes(method)) { + actionCache = {}; + } + }); // --------------------------------------------------------------------------- // misc @@ -836,7 +843,10 @@ function makeActionManager(env) { }); } if (action.close) { - return doAction({ type: "ir.actions.act_window_close" }, { onClose: options.onClose }); + return doAction( + { type: "ir.actions.act_window_close" }, + { onClose: options.onClose } + ); } else if (options.onClose) { options.onClose(); } diff --git a/addons/web/static/src/webclient/company_service.js b/addons/web/static/src/webclient/company_service.js index 7d3c4e8833f..a868ca77063 100644 --- a/addons/web/static/src/webclient/company_service.js +++ b/addons/web/static/src/webclient/company_service.js @@ -4,6 +4,7 @@ import { browser } from "@web/core/browser/browser"; import { registry } from "@web/core/registry"; import { symmetricalDifference } from "../core/utils/arrays"; import { session } from "@web/session"; +import { UPDATE_METHODS } from "@web/core/orm_service"; function parseCompanyIds(cidsFromHash) { const cids = []; @@ -30,8 +31,8 @@ function computeAllowedCompanyIds(cids) { } export const companyService = { - dependencies: ["user", "router", "cookie"], - start(env, { user, router, cookie }) { + dependencies: ["user", "router", "cookie", "action"], + start(env, { user, router, cookie, action }) { let cids; if ("cids" in router.current.hash) { cids = parseCompanyIds(router.current.hash.cids); @@ -47,6 +48,14 @@ export const companyService = { user.updateContext({ allowed_company_ids: allowedCompanyIds }); const availableCompanies = session.user_companies.allowed_companies; + env.bus.addEventListener("RPC:RESPONSE", (ev) => { + const { model, method } = ev.detail.data.params; + if (model === "res.company" && UPDATE_METHODS.includes(method)) { + if (!browser.localStorage.getItem("running_tour")) { + action.doAction("reload_context"); + } + } + }); return { availableCompanies, get allowedCompanyIds() { diff --git a/addons/web/static/src/webclient/currency_service.js b/addons/web/static/src/webclient/currency_service.js new file mode 100644 index 00000000000..301bf1f31c8 --- /dev/null +++ b/addons/web/static/src/webclient/currency_service.js @@ -0,0 +1,29 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { currencies } from "@web/core/currency"; +import { UPDATE_METHODS } from "@web/core/orm_service"; + +export const currencyService = { + dependencies: ["rpc"], + start(env, { rpc }) { + /** + * Reload the currencies (initially given in session_info) + */ + async function reloadCurrencies() { + const result = await rpc("/web/session/get_session_info"); + for (const k in currencies) { + delete currencies[k]; + } + Object.assign(currencies, result); + } + env.bus.addEventListener("RPC:RESPONSE", (ev) => { + const { model, method } = ev.detail.data.params; + if (model === "res.currency" && UPDATE_METHODS.includes(method)) { + reloadCurrencies(); + } + }); + }, +}; + +registry.category("services").add("currency", currencyService); diff --git a/addons/web/static/src/webclient/loading_indicator/loading_indicator.js b/addons/web/static/src/webclient/loading_indicator/loading_indicator.js index eebe2707d06..fdd63fa9d9f 100644 --- a/addons/web/static/src/webclient/loading_indicator/loading_indicator.js +++ b/addons/web/static/src/webclient/loading_indicator/loading_indicator.js @@ -36,7 +36,10 @@ export class LoadingIndicator extends Component { }); } - requestCall({ detail: rpcId }) { + requestCall({ detail }) { + if (detail.settings.silent) { + return; + } if (this.state.count === 0) { browser.clearTimeout(this.startShowTimer); this.startShowTimer = browser.setTimeout(() => { @@ -49,12 +52,15 @@ export class LoadingIndicator extends Component { } }, 250); } - this.rpcIds.add(rpcId); + this.rpcIds.add(detail.data.id); this.state.count++; } - responseCall({ detail: rpcId }) { - this.rpcIds.delete(rpcId); + responseCall({ detail }) { + if (detail.settings.silent) { + return; + } + this.rpcIds.delete(detail.data.id); this.state.count = this.rpcIds.size; if (this.state.count === 0) { browser.clearTimeout(this.startShowTimer); diff --git a/addons/web/static/tests/core/network/rpc_service_tests.js b/addons/web/static/tests/core/network/rpc_service_tests.js index 4f68df4a617..afb957cce40 100644 --- a/addons/web/static/tests/core/network/rpc_service_tests.js +++ b/addons/web/static/tests/core/network/rpc_service_tests.js @@ -190,20 +190,22 @@ QUnit.test("check trigger RPC:REQUEST and RPC:RESPONSE for a simple rpc", async }); const rpcIdsRequest = []; const rpcIdsResponse = []; - env.bus.addEventListener("RPC:REQUEST", (rpcId) => { - rpcIdsRequest.push(rpcId); - assert.step("RPC:REQUEST"); + env.bus.addEventListener("RPC:REQUEST", (ev) => { + rpcIdsRequest.push(ev.detail.data.id); + const silent = ev.detail.settings.silent; + assert.step("RPC:REQUEST" + (silent ? "(silent)" : "")); }); - env.bus.addEventListener("RPC:RESPONSE", (rpcId) => { - rpcIdsResponse.push(rpcId); - assert.step("RPC:RESPONSE"); + env.bus.addEventListener("RPC:RESPONSE", (ev) => { + rpcIdsResponse.push(ev.detail.data.id); + const silent = ev.detail.settings.silent; + assert.step("RPC:RESPONSE" + (silent ? "(silent)" : "")); }); await env.services.rpc("/test/"); assert.strictEqual(rpcIdsRequest.toString(), rpcIdsResponse.toString()); assert.verifySteps(["RPC:REQUEST", "RPC:RESPONSE"]); await env.services.rpc("/test/", {}, { silent: true }); - assert.verifySteps([]); + assert.verifySteps(["RPC:REQUEST(silent)", "RPC:RESPONSE(silent)"]); unpatch(browser, "mock.xhr"); }); diff --git a/addons/web/static/tests/helpers/mock_services.js b/addons/web/static/tests/helpers/mock_services.js index f3b86ac953e..4ff1e15df6c 100644 --- a/addons/web/static/tests/helpers/mock_services.js +++ b/addons/web/static/tests/helpers/mock_services.js @@ -14,6 +14,7 @@ import { uiService } from "@web/core/ui/ui_service"; import { ConnectionAbortedError } from "../../src/core/network/rpc_service"; import { Component, status } from "@odoo/owl"; +import { ormService } from "@web/core/orm_service"; // ----------------------------------------------------------------------------- // Mock Services @@ -59,14 +60,25 @@ function buildMockRPC(mockRPC) { export function makeFakeRPCService(mockRPC) { return { name: "rpc", - start() { + start(env) { const rpcService = buildMockRPC(mockRPC); - return function () { + let nextId = 1; + return function (route, params = {}, settings = {}) { let rejectFn; + const data = { + id: nextId++, + jsonrpc: "2.0", + method: "call", + params: params, + }; + env.bus.trigger("RPC:REQUEST", { data, settings }); const rpcProm = new Promise((resolve, reject) => { rejectFn = reject; rpcService(...arguments) - .then(resolve) + .then((result) => { + env.bus.trigger("RPC:RESPONSE", { data, settings }); + resolve(result); + }) .catch(reject); }); rpcProm.abort = (rejectError = true) => { @@ -307,6 +319,16 @@ export function makeFakeHTTPService(getResponse, postResponse) { }; } +function makeFakeActionService() { + return { + start() { + return { + doAction() {}, + }; + }, + }; +} + export const mocks = { color_scheme: () => fakeColorSchemeService, company: () => fakeCompanyService, @@ -321,4 +343,6 @@ export const mocks = { ui: () => uiService, user: () => userService, dialog: makeFakeDialogService, + orm: () => ormService, + action: makeFakeActionService, }; diff --git a/addons/web/static/tests/setup.js b/addons/web/static/tests/setup.js index 6c18634c9eb..472557461ed 100644 --- a/addons/web/static/tests/setup.js +++ b/addons/web/static/tests/setup.js @@ -19,6 +19,7 @@ transitionConfig.disabled = true; import { patch } from "@web/core/utils/patch"; import { App, whenReady } from "@odoo/owl"; +import { currencies } from "@web/core/currency"; const { prepareRegistriesWithCleanup } = utils; @@ -247,10 +248,6 @@ function patchSessionInfo() { load_menus: "161803", translations: "314159", }, - currencies: { - 1: { name: "USD", digits: [69, 2], position: "before", symbol: "$" }, - 2: { name: "EUR", digits: [69, 2], position: "after", symbol: "€" }, - }, user_context: { lang: "en", uid: 7, @@ -271,7 +268,11 @@ function patchSessionInfo() { }, db: "test", server_version: "1.0", - server_version_info: [1, 0, 0, "final", 0, ''], + server_version_info: [1, 0, 0, "final", 0, ""], + }); + patchWithCleanup(currencies, { + 1: { name: "USD", digits: [69, 2], position: "before", symbol: "$" }, + 2: { name: "EUR", digits: [69, 2], position: "after", symbol: "€" }, }); } diff --git a/addons/web/static/tests/views/fields/formatters_tests.js b/addons/web/static/tests/views/fields/formatters_tests.js index 2d930c471b6..97fcfc5a87e 100644 --- a/addons/web/static/tests/views/fields/formatters_tests.js +++ b/addons/web/static/tests/views/fields/formatters_tests.js @@ -2,6 +2,7 @@ import { defaultLocalization } from "@web/../tests/helpers/mock_services"; import { patchWithCleanup } from "@web/../tests/helpers/utils"; +import { currencies } from "@web/core/currency"; import { localization } from "@web/core/l10n/localization"; import { session } from "@web/session"; import { @@ -246,7 +247,7 @@ QUnit.module("Fields", (hooks) => { }); QUnit.test("formatMonetary", function (assert) { - patchWithCleanup(session.currencies, { + patchWithCleanup(currencies, { 10: { digits: [69, 2], position: "after", diff --git a/addons/web/static/tests/views/fields/monetary_field_tests.js b/addons/web/static/tests/views/fields/monetary_field_tests.js index 610f7dded3f..32a0d12fe81 100644 --- a/addons/web/static/tests/views/fields/monetary_field_tests.js +++ b/addons/web/static/tests/views/fields/monetary_field_tests.js @@ -10,7 +10,7 @@ import { patchWithCleanup, triggerEvent, } from "@web/../tests/helpers/utils"; -import { session } from "@web/session"; +import { currencies } from "@web/core/currency"; let serverData; let target; @@ -252,15 +252,12 @@ QUnit.module("Fields", (hooks) => { QUnit.test("with currency digits != 2 - float field", async function (assert) { // need to also add it to the session (as currencies are loaded there) - patchWithCleanup(session, { - currencies: { - ...session.currencies, - 3: { - name: "VEF", - symbol: "Bs.F", - position: "after", - digits: [0, 4], - }, + patchWithCleanup(currencies, { + 3: { + name: "VEF", + symbol: "Bs.F", + position: "after", + digits: [0, 4], }, }); @@ -313,15 +310,12 @@ QUnit.module("Fields", (hooks) => { QUnit.test("with currency digits != 2 - monetary field", async function (assert) { // need to also add it to the session (as currencies are loaded there) - patchWithCleanup(session, { - currencies: { - ...session.currencies, - 3: { - name: "VEF", - symbol: "Bs.F", - position: "after", - digits: [0, 4], - }, + patchWithCleanup(currencies, { + 3: { + name: "VEF", + symbol: "Bs.F", + position: "after", + digits: [0, 4], }, }); @@ -865,15 +859,12 @@ QUnit.module("Fields", (hooks) => { }, ]; - patchWithCleanup(session, { - currencies: { - ...session.currencies, - 1: { - name: "USD", - symbol: "$", - position: "before", - digits: [0, 4], - }, + patchWithCleanup(currencies, { + 1: { + name: "USD", + symbol: "$", + position: "before", + digits: [0, 4], }, }); diff --git a/addons/web/static/tests/views/form/form_view_tests.js b/addons/web/static/tests/views/form/form_view_tests.js index 66ae6beb7e2..d5cb0097082 100644 --- a/addons/web/static/tests/views/form/form_view_tests.js +++ b/addons/web/static/tests/views/form/form_view_tests.js @@ -29,9 +29,7 @@ import { tooltipService } from "@web/core/tooltip/tooltip_service"; import { CharField } from "@web/views/fields/char/char_field"; import { FormController } from "@web/views/form/form_controller"; import { session } from "@web/session"; -import legacySession from "web.session"; import { scrollerService } from "@web/core/scroller_service"; -import BasicModel from "web.BasicModel"; import { localization } from "@web/core/l10n/localization"; import { SIZES } from "@web/core/ui/ui_service"; import { errorService } from "@web/core/errors/error_service"; @@ -43,6 +41,7 @@ const serviceRegistry = registry.category("services"); const widgetRegistry = registry.category("view_widgets"); import { Component, xml, EventBus } from "@odoo/owl"; +import { companyService } from "@web/webclient/company_service"; let target; let serverData; @@ -10385,116 +10384,6 @@ QUnit.module("Views", (hooks) => { ]); }); - QUnit.test('edition in form view on a "noCache" model', async function (assert) { - patchWithCleanup(BasicModel.prototype, { - noCacheModels: BasicModel.prototype.noCacheModels.concat(["partner"]), - }); - - const form = await makeView({ - type: "form", - resModel: "partner", - serverData, - arch: `
`, - resId: 1, - mockRPC(route, args) { - if (args.method === "write") { - assert.step("write"); - } - }, - }); - - form.env.bus.on("CLEAR-CACHES", target, assert.step.bind(assert, "clear_cache")); - - await editInput(target, "[name=display_name] input", "new value"); - await clickSave(target); - - assert.verifySteps(["write", "clear_cache"]); - }); - - QUnit.test('creation in form view on a "noCache" model', async function (assert) { - patchWithCleanup(BasicModel.prototype, { - noCacheModels: BasicModel.prototype.noCacheModels.concat(["partner"]), - }); - - const form = await makeView({ - type: "form", - resModel: "partner", - serverData, - arch: `
`, - resId: 1, - mockRPC(route, args) { - if (args.method === "write") { - assert.step("write"); - } - }, - }); - - form.env.bus.on("CLEAR-CACHES", target, assert.step.bind(assert, "clear_cache")); - - await editInput(target, "[name=display_name] input", "new value"); - await clickSave(target); - - assert.verifySteps(["write", "clear_cache"]); - }); - - QUnit.test('deletion in form view on a "noCache" model', async function (assert) { - patchWithCleanup(BasicModel.prototype, { - noCacheModels: BasicModel.prototype.noCacheModels.concat(["partner"]), - }); - - const form = await makeView({ - type: "form", - serverData, - resModel: "partner", - arch: `
`, - mockRPC(route, args) { - if (args.method === "unlink") { - assert.step("unlink"); - } - }, - resId: 1, - actionMenus: {}, - }); - form.env.bus.on("CLEAR-CACHES", target, assert.step.bind(assert, "clear_cache")); - - await toggleActionMenu(target); - await toggleMenuItem(target, "Delete"); - await click(target.querySelector(".modal-footer .btn-primary")); - - assert.verifySteps(["unlink", "clear_cache"]); - }); - - QUnit.test( - "reload currencies when writing on records of model res.currency", - async function (assert) { - serverData.models["res.currency"] = { - fields: {}, - records: [{ id: 1, display_name: "some currency" }], - }; - - patchWithCleanup(legacySession, { - reloadCurrencies: function () { - assert.step("reload currencies"); - }, - }); - - await makeView({ - type: "form", - resModel: "res.currency", - serverData, - arch: '
', - resId: 1, - mockRPC(route, args) { - assert.step(args.method); - }, - }); - - await editInput(target.querySelector("[name=display_name]"), "input", "new value"); - await clickSave(target); - assert.verifySteps(["get_views", "read", "write", "reload currencies", "read"]); - } - ); - QUnit.test("keep editing after call_button fail", async function (assert) { assert.expect(4); @@ -11233,6 +11122,7 @@ QUnit.module("Views", (hooks) => { }); }, }; + serviceRegistry.add("company", companyService, { force: true }); serviceRegistry.add("action", fakeActionService, { force: true }); await makeView({ @@ -11270,6 +11160,7 @@ QUnit.module("Views", (hooks) => { }); }, }; + serviceRegistry.add("company", companyService, { force: true }); serviceRegistry.add("action", fakeActionService, { force: true }); serverData.models["res.company"].records = [ diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js index 76c95e0db11..5281a38d022 100644 --- a/addons/web/static/tests/views/list_view_tests.js +++ b/addons/web/static/tests/views/list_view_tests.js @@ -12253,49 +12253,6 @@ QUnit.module("Views", (hooks) => { ); }); - QUnit.test('list view on a "noCache" model', async function (assert) { - const list = await makeView({ - type: "list", - resModel: "foo", - serverData, - arch: ``, - mockRPC(route, args) { - if (["name_create", "unlink", "write"].includes(args.method)) { - assert.step(args.method); - } - }, - actionMenus: {}, - }); - - list.model.noCacheModels = list.model.noCacheModels.concat(["foo"]); - list.env.bus.on("CLEAR-CACHES", target, assert.step.bind(assert, "clear_cache")); - - // create a new record - await click(target.querySelector(".o_list_button_add")); - await editInput(target, ".o_selected_row .o_field_widget input", "some value"); - await clickSave(target); - - // edit an existing record - await click(target.querySelector(".o_data_cell")); - await editInput(target, ".o_selected_row .o_field_widget input", "new value"); - await click(target.querySelector(".o_list_button_save")); - - // delete a record - await click(target.querySelector(".o_data_row .o_list_record_selector input")); - await toggleActionMenu(target); - await toggleMenuItem(target, "Delete"); - await click(target.querySelector(".modal-footer .btn-primary")); - - assert.verifySteps([ - "name_create", - "clear_cache", - "write", - "clear_cache", - "unlink", - "clear_cache", - ]); - }); - QUnit.test( "list view move to previous page when all records from last page deleted", async function (assert) { diff --git a/addons/web/static/tests/views/view_service_tests.js b/addons/web/static/tests/views/view_service_tests.js index d9a9a4182e5..ca3c91c9f91 100644 --- a/addons/web/static/tests/views/view_service_tests.js +++ b/addons/web/static/tests/views/view_service_tests.js @@ -18,6 +18,9 @@ QUnit.module("View service", (hooks) => { fields: {}, records: [], }, + "ir.ui.view": { + fields: {}, records: [] + }, }; const fakeUiService = { @@ -109,6 +112,37 @@ QUnit.module("View service", (hooks) => { assert.verifySteps(["get_views", "get_views"]); }); + QUnit.test("clear cache when updating ir.ui.view", async (assert) => { + const mockRPC = (route, args) => { + if (route.includes("get_views")) { + assert.step("get_views"); + } + }; + const loadView = () => + env.services.views.loadViews( + { + resModel: "take.five", + views: [[99, "list"]], + context: { default_field_value: 1 }, + }, + {} + ); + + await makeMockServer(serverData, mockRPC); + const env = await makeTestEnv(); + + await loadView(); + assert.verifySteps(["get_views"]); + await loadView(); + assert.verifySteps([]); // cache works => no actual rpc + await env.services.orm.unlink("ir.ui.view", [3]); + await loadView(); + assert.verifySteps(["get_views"]); // cache was invalidated + await env.services.orm.unlink("take.five", [3]); + await loadView(); + assert.verifySteps([]); // cache was not invalidated + }); + QUnit.test("loadViews stores fields in cache", async (assert) => { assert.expect(2); diff --git a/addons/web/static/tests/webclient/actions/window_action_tests.js b/addons/web/static/tests/webclient/actions/window_action_tests.js index 7a58dd3df68..71d05edee1a 100644 --- a/addons/web/static/tests/webclient/actions/window_action_tests.js +++ b/addons/web/static/tests/webclient/actions/window_action_tests.js @@ -2427,6 +2427,7 @@ QUnit.module("ActionManager", (hooks) => { const mockRPC = async (route, args) => { assert.step(args.method || route); }; + serverData.models["ir.actions.act_window"] = { fields: {}, records: [] }; const webClient = await createWebClient({ serverData, mockRPC }); assert.verifySteps(["/web/webclient/load_menus"]); @@ -2438,6 +2439,12 @@ QUnit.module("ActionManager", (hooks) => { assert.containsOnce(target, ".o_kanban_view"); assert.verifySteps(["web_search_read"]); + + await webClient.env.services.orm.unlink("ir.actions.act_window", [333]); + assert.verifySteps(["unlink"]); + await doAction(webClient, 1); + // cache was cleared => reload the action + assert.verifySteps(["/web/action/load", "web_search_read"]); }); QUnit.test("pushState also changes the title of the tab", async (assert) => { diff --git a/addons/web/static/tests/webclient/company_service_tests.js b/addons/web/static/tests/webclient/company_service_tests.js new file mode 100644 index 00000000000..fdb23b0d65c --- /dev/null +++ b/addons/web/static/tests/webclient/company_service_tests.js @@ -0,0 +1,32 @@ +/** @odoo-module **/ + +import { ormService } from "@web/core/orm_service"; +import { registry } from "@web/core/registry"; +import { companyService } from "@web/webclient/company_service"; +import { makeTestEnv } from "../helpers/mock_env"; + +const serviceRegistry = registry.category("services"); + +QUnit.module("company service"); + +QUnit.test("reload webclient when updating a res.company", async (assert) => { + serviceRegistry.add("company", companyService); + serviceRegistry.add("orm", ormService); + serviceRegistry.add("action", { + start(env) { + return { + doAction(action) { + assert.step(action); + }, + }; + }, + }); + const env = await makeTestEnv(); + assert.verifySteps([]); + await env.services.orm.read("res.company", [32]); + assert.verifySteps([]); + await env.services.orm.unlink("res.company", [32]); + assert.verifySteps(["reload_context"]); + await env.services.orm.unlink("notacompany", [32]); + assert.verifySteps([]); +}); diff --git a/addons/web/static/tests/webclient/currency_service_tests.js b/addons/web/static/tests/webclient/currency_service_tests.js new file mode 100644 index 00000000000..0b681ab2fc4 --- /dev/null +++ b/addons/web/static/tests/webclient/currency_service_tests.js @@ -0,0 +1,31 @@ +/** @odoo-module **/ + +import { ormService } from "@web/core/orm_service"; +import { registry } from "@web/core/registry"; +import { currencyService } from "@web/webclient/currency_service"; +import { makeTestEnv } from "../helpers/mock_env"; +import { makeFakeRPCService } from "../helpers/mock_services"; + +const serviceRegistry = registry.category("services"); + +QUnit.module("currency service"); + +QUnit.test("reload currencies when updating a res.currency", async (assert) => { + serviceRegistry.add("currency", currencyService); + serviceRegistry.add("orm", ormService); + const fakeRpc = makeFakeRPCService((route) => { + assert.step(route); + }); + serviceRegistry.add("rpc", fakeRpc); + const env = await makeTestEnv(); + assert.verifySteps([]); + await env.services.orm.read("res.currency", [32]); + assert.verifySteps(["/web/dataset/call_kw/res.currency/read"]); + await env.services.orm.unlink("res.currency", [32]); + assert.verifySteps([ + "/web/dataset/call_kw/res.currency/unlink", + "/web/session/get_session_info", + ]); + await env.services.orm.unlink("notcurrency", [32]); + assert.verifySteps(["/web/dataset/call_kw/notcurrency/unlink"]); +}); diff --git a/addons/web/static/tests/webclient/loading_indicator_tests.js b/addons/web/static/tests/webclient/loading_indicator_tests.js index 195ced9b5a6..1dba387afc5 100644 --- a/addons/web/static/tests/webclient/loading_indicator_tests.js +++ b/addons/web/static/tests/webclient/loading_indicator_tests.js @@ -31,12 +31,14 @@ QUnit.module("LoadingIndicator", { }, }); +const payload = (id) => ({ data: { id }, settings: {} }); + QUnit.test("displays the loading indicator in non debug mode", async (assert) => { const env = await makeTestEnv(); await mount(LoadingIndicator, target, { env }); let loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual(loadingIndicator, null, "the loading indicator should not be displayed"); - env.bus.trigger("RPC:REQUEST", 1); + env.bus.trigger("RPC:REQUEST", payload(1)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.notStrictEqual(loadingIndicator, null, "the loading indicator should be displayed"); @@ -45,7 +47,7 @@ QUnit.test("displays the loading indicator in non debug mode", async (assert) => "Loading", "the loading indicator should display 'Loading'" ); - env.bus.trigger("RPC:RESPONSE", 1); + env.bus.trigger("RPC:RESPONSE", payload(1)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual(loadingIndicator, null, "the loading indicator should not be displayed"); @@ -57,7 +59,7 @@ QUnit.test("displays the loading indicator for one rpc in debug mode", async (as await mount(LoadingIndicator, target, { env }); let loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual(loadingIndicator, null, "the loading indicator should not be displayed"); - env.bus.trigger("RPC:REQUEST", 1); + env.bus.trigger("RPC:REQUEST", payload(1)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.notStrictEqual(loadingIndicator, null, "the loading indicator should be displayed"); @@ -66,7 +68,7 @@ QUnit.test("displays the loading indicator for one rpc in debug mode", async (as "Loading (1)", "the loading indicator should indicate 1 request in progress" ); - env.bus.trigger("RPC:RESPONSE", 1); + env.bus.trigger("RPC:RESPONSE", payload(1)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual(loadingIndicator, null, "the loading indicator should not be displayed"); @@ -78,8 +80,8 @@ QUnit.test("displays the loading indicator for multi rpc in debug mode", async ( await mount(LoadingIndicator, target, { env }); let loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual(loadingIndicator, null, "the loading indicator should not be displayed"); - env.bus.trigger("RPC:REQUEST", 1); - env.bus.trigger("RPC:REQUEST", 2); + env.bus.trigger("RPC:REQUEST", payload(1)); + env.bus.trigger("RPC:REQUEST", payload(2)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.notStrictEqual(loadingIndicator, null, "the loading indicator should be displayed"); @@ -88,7 +90,7 @@ QUnit.test("displays the loading indicator for multi rpc in debug mode", async ( "Loading (2)", "the loading indicator should indicate 2 requests in progress." ); - env.bus.trigger("RPC:REQUEST", 3); + env.bus.trigger("RPC:REQUEST", payload(3)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual( @@ -96,7 +98,7 @@ QUnit.test("displays the loading indicator for multi rpc in debug mode", async ( "Loading (3)", "the loading indicator should indicate 3 requests in progress." ); - env.bus.trigger("RPC:RESPONSE", 1); + env.bus.trigger("RPC:RESPONSE", payload(1)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual( @@ -104,7 +106,7 @@ QUnit.test("displays the loading indicator for multi rpc in debug mode", async ( "Loading (2)", "the loading indicator should indicate 2 requests in progress." ); - env.bus.trigger("RPC:REQUEST", 4); + env.bus.trigger("RPC:REQUEST", payload(4)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual( @@ -112,8 +114,8 @@ QUnit.test("displays the loading indicator for multi rpc in debug mode", async ( "Loading (3)", "the loading indicator should indicate 3 requests in progress." ); - env.bus.trigger("RPC:RESPONSE", 2); - env.bus.trigger("RPC:RESPONSE", 3); + env.bus.trigger("RPC:RESPONSE", payload(2)); + env.bus.trigger("RPC:RESPONSE", payload(3)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual( @@ -121,7 +123,7 @@ QUnit.test("displays the loading indicator for multi rpc in debug mode", async ( "Loading (1)", "the loading indicator should indicate 1 request in progress." ); - env.bus.trigger("RPC:RESPONSE", 4); + env.bus.trigger("RPC:RESPONSE", payload(4)); await nextTick(); loadingIndicator = target.querySelector(".o_loading_indicator"); assert.strictEqual(loadingIndicator, null, "the loading indicator should not be displayed"); @@ -144,9 +146,9 @@ QUnit.test("loading indicator blocks UI", async (assert) => { assert.step("unblock"); }); await mount(LoadingIndicator, target, { env }); - env.bus.trigger("RPC:REQUEST", 1); + env.bus.trigger("RPC:REQUEST", payload(1)); await nextTick(); - env.bus.trigger("RPC:RESPONSE", 1); + env.bus.trigger("RPC:RESPONSE", payload(1)); await nextTick(); assert.verifySteps(["set timeout 250", "set timeout 3000", "block", "unblock"]); unpatch(originalBrowser, "mock.settimeout"); @@ -163,12 +165,12 @@ QUnit.test("loading indicator doesn't unblock ui if it didn't block it", async ( assert.step("unblock"); }); await mount(LoadingIndicator, target, { env }); - env.bus.trigger("RPC:REQUEST", 1); + env.bus.trigger("RPC:REQUEST", payload(1)); execRegisteredTimeouts(); - env.bus.trigger("RPC:RESPONSE", 1); + env.bus.trigger("RPC:RESPONSE", payload(1)); assert.verifySteps(["block", "unblock"]); - env.bus.trigger("RPC:REQUEST", 2); - env.bus.trigger("RPC:RESPONSE", 2); + env.bus.trigger("RPC:REQUEST", payload(2)); + env.bus.trigger("RPC:RESPONSE", payload(2)); execRegisteredTimeouts(); assert.verifySteps([]); }); @@ -185,14 +187,14 @@ QUnit.test("loading indicator is not displayed immediately", async (assert) => { assert.step("unblock"); }); await mount(LoadingIndicator, target, { env }); - env.bus.trigger("RPC:REQUEST", 1); + env.bus.trigger("RPC:REQUEST", payload(1)); await nextTick(); assert.containsNone(target, ".o_loading_indicator"); await advanceTime(400); await nextTick(); assert.containsOnce(target, ".o_loading_indicator"); - env.bus.trigger("RPC:RESPONSE", 1); + env.bus.trigger("RPC:RESPONSE", payload(1)); await nextTick(); assert.containsNone(target, ".o_loading_indicator"); });