From 2524c7aaace5ce314eace5517285bf13586fb176 Mon Sep 17 00:00:00 2001 From: Julien Mougenot Date: Fri, 31 Jul 2020 11:52:25 +0000 Subject: [PATCH] [IMP] web: Do not store search panel categories This commit handles two things: 1) Move the local storage logic of the search panel into the documents module. 2) This allows for a variety of optimisations now that we do not have to constantly wait for a potential default value hidden in the storage. This is why this commit also introduces a way for the search panel model extension to sometimes return its query informations sooner than after being completely loaded. Task 2284207 --- .../js/chrome/action_manager_act_window.js | 1 - .../web/static/src/js/views/abstract_view.js | 9 - .../js/views/search_panel_model_extension.js | 317 ++++++++------ .../tests/chrome/action_manager_tests.js | 3 - .../static/tests/views/search_panel_tests.js | 412 ++++++++---------- 5 files changed, 357 insertions(+), 385 deletions(-) diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js index 749feacb3d6..ee39d7cb7a7 100644 --- a/addons/web/static/src/js/chrome/action_manager_act_window.js +++ b/addons/web/static/src/js/chrome/action_manager_act_window.js @@ -693,7 +693,6 @@ ActionManager.include({ if (config.device.isMobile && actionData.mobile) { options = Object.assign({}, options, actionData.mobile); } - action.flags = _.extend({}, action.flags, {searchPanelDefaultNoFilter: true}); return self.doAction(action, options).then(ev.data.on_success, ev.data.on_fail); }); }, diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index 129cb6817b3..127b2a10c45 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -260,17 +260,8 @@ var AbstractView = Factory.extend({ if (this.withSearchPanel) { // Search panel (Model) const SearchPanelComponent = this.config.SearchPanel; - const defaultValues = {}; - Object.keys(this.loadParams.context).forEach((key) => { - let match = /^searchpanel_default_(.*)$/.exec(key); - if (match) { - defaultValues[match[1]] = this.loadParams.context[key]; - } - }); extensions[SearchPanelComponent.modelExtension] = { archNodes: searchPanelInfo.children, - defaultNoFilter: params.searchPanelDefaultNoFilter, - defaultValues, }; this.controllerParams.withSearchPanel = true; this.rendererParams.withSearchPanel = true; diff --git a/addons/web/static/src/js/views/search_panel_model_extension.js b/addons/web/static/src/js/views/search_panel_model_extension.js index 3641c425ba7..3aca92029f3 100644 --- a/addons/web/static/src/js/views/search_panel_model_extension.js +++ b/addons/web/static/src/js/views/search_panel_model_extension.js @@ -13,6 +13,14 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function const DEFAULT_LIMIT = 200; let nextSectionId = 1; + /** + * @param {Filter} filter + * @returns {boolean} + */ + function hasDomain(filter) { + return filter.domain !== "[]"; + } + /** * @param {Section} section * @returns {boolean} @@ -68,14 +76,44 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function constructor() { super(...arguments); - this.searchDomain = null; - this.DEFAULT_VALUE_INDEX = 0; + this.categoriesToLoad = []; + this.defaultValues = {}; + this.filtersToLoad = []; + this.initialStateImport = false; + this.searchDomain = []; + for (const key in this.config.context) { + const match = /^searchpanel_default_(.*)$/.exec(key); + if (match) { + this.defaultValues[match[1]] = this.config.context[key]; + } + } } //--------------------------------------------------------------------- // Public //--------------------------------------------------------------------- + /** + * @override + */ + async callLoad(params) { + const searchDomain = this._getExternalDomain(); + params.searchDomainChanged = ( + JSON.stringify(this.searchDomain) !== JSON.stringify(searchDomain) + ); + if (!this.shouldLoad && !this.initialStateImport) { + const isFetchable = (section) => section.enableCounters || + (params.searchDomainChanged && !section.expand); + this.categoriesToLoad = this.categories.filter(isFetchable); + this.filtersToLoad = this.filters.filter(isFetchable); + this.shouldLoad = params.searchDomainChanged || + Boolean(this.categoriesToLoad.length + this.filtersToLoad.length); + } + this.searchDomain = searchDomain; + this.initialStateImport = false; + await super.callLoad(params); + } + /** * @override */ @@ -109,6 +147,7 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function * @override */ importState(importedState) { + this.initialStateImport = Boolean(importedState && !this.state.sections); super.importState(...arguments); if (importedState) { this.state.sections = new Map(this.state.sections); @@ -127,8 +166,18 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function /** * @override */ - async load() { - await this._fetchSections({ initialLoad: true }); + async isReady() { + await this.sectionsPromise; + } + + /** + * @override + */ + async load(params) { + this.sectionsPromise = this._fetchSections(params.isInitialLoad); + if (this._shouldWaitForData(params)) { + await this.sectionsPromise; + } } /** @@ -139,13 +188,6 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function this._createSectionsFromArch(); } - /** - * @override - */ - async reloadAfterDispatch() { - await this._fetchSections(); - } - //--------------------------------------------------------------------- // Actions / Getters //--------------------------------------------------------------------- @@ -180,18 +222,13 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function } /** - * Sets the active value id of a given category. This will also update - * the local storage value. + * Sets the active value id of a given category. * @param {number} sectionId * @param {number} valueId */ toggleCategoryValue(sectionId, valueId) { const category = this.state.sections.get(sectionId); category.activeValueId = valueId; - const storageKey = `searchpanel_${this.config.modelName}_${category.fieldName}`; - this.env.services.local_storage.setItem(storageKey, valueId); - // FIXME this should be removed (in another commit): should no longer (re)store - // active category in localStorage } /** @@ -240,7 +277,7 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function */ _applyDefaultFilterValues() { for (const { fieldName, values } of this.filters) { - const defaultValues = this.config.defaultValues[fieldName] || []; + const defaultValues = this.defaultValues[fieldName] || []; for (const valueId of defaultValues) { const value = values.get(valueId); if (value) { @@ -266,15 +303,6 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function if (category.hierarchize) { category.parentField = parentField; } - - const allItem = { - childrenIds: [], - display_name: this.env._t("All"), - id: false, - bold: true, - parentId: false, - }; - category.values = new Map([[false, allItem]]); for (const value of values) { category.values.set( value.id, @@ -299,7 +327,8 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function } } // Set active value from context - category.activeValueId = this._getCategoryDefaultValue(values, category); + const valueIds = [false, ...values.map((val) => val.id)]; + this._ensureCategoryValue(category, valueIds); } /** @@ -318,8 +347,8 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function // restore checked property values.forEach((value) => { - const oldValue = filter.values && filter.values.get(value.id); - value.checked = (oldValue && oldValue.checked) || false; + const oldValue = filter.values.get(value.id); + value.checked = oldValue ? oldValue.checked : false; }); filter.values = new Map(); @@ -390,12 +419,21 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function index, limit: pyUtils.py_eval(attrs.limit || String(DEFAULT_LIMIT)), type, + values: new Map(), }; if (type === "category") { + section.activeValueId = this.defaultValues[attrs.name]; section.icon = section.icon || "fa-folder"; section.hierarchize = !!pyUtils.py_eval( attrs.hierarchize || "1" ); + section.values.set(false, { + childrenIds: [], + display_name: this.env._t("All"), + id: false, + bold: true, + parentId: false, + }); } else { section.domain = attrs.domain || "[]"; section.groupBy = attrs.groupby; @@ -405,104 +443,97 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function }); } + /** + * Ensures that the active value of a category is one of its own + * existing values. + * @private + * @param {Category} category + * @param {number[]} valueIds + */ + _ensureCategoryValue(category, valueIds) { + if (!valueIds.includes(category.activeValueId)) { + category.activeValueId = valueIds[0]; + } + } + /** * Fetches values for each category at startup. At reload a category is * only fetched if needed. * @private - * @param {{ - * initialLoad: boolean, - * searchDomainHasChanged: boolean - * }} options + * @param {Category[]} categories * @returns {Promise} resolved when all categories have been fetched */ - async _fetchCategories({ initialLoad, searchDomainHasChanged }) { - const proms = []; - for (const category of this.categories) { - const { enableCounters, expand, hierarchize, limit } = category; - if (initialLoad || enableCounters || (searchDomainHasChanged && !expand)) { - const prom = this.env.services.rpc({ - method: "search_panel_select_range", - model: this.config.modelName, - args: [category.fieldName], - kwargs: { - category_domain: this._getCategoryDomain( - category.id - ), - enable_counters: enableCounters, - expand, - filter_domain: this._getFilterDomain(), - hierarchize, - limit, - search_domain: this.searchDomain, - }, - }).then(result => this._createCategoryTree(category.id, result)); - proms.push(prom); - } - } - await Promise.all(proms); + async _fetchCategories(categories) { + const filterDomain = this._getFilterDomain(); + await Promise.all(categories.map(async (category) => { + const result = await this.env.services.rpc({ + method: "search_panel_select_range", + model: this.config.modelName, + args: [category.fieldName], + kwargs: { + category_domain: this._getCategoryDomain(category.id), + enable_counters: category.enableCounters, + expand: category.expand, + filter_domain: filterDomain, + hierarchize: category.hierarchize, + limit: category.limit, + search_domain: this.searchDomain, + }, + }); + this._createCategoryTree(category.id, result); + })); } /** * Fetches values for each filter. This is done at startup and at each * reload if needed. * @private - * @param {{ - * initialLoad: boolean, - * searchDomainHasChanged: boolean - * }} options + * @param {Filter[]} filters * @returns {Promise} resolved when all filters have been fetched */ - async _fetchFilters({ initialLoad, searchDomainHasChanged }) { + async _fetchFilters(filters) { const evalContext = {}; for (const category of this.categories) { evalContext[category.fieldName] = category.activeValueId; } const categoryDomain = this._getCategoryDomain(); - const proms = []; - for (const filter of this.filters) { - const { enableCounters, expand, groupBy, limit } = filter; - if (initialLoad || enableCounters || (searchDomainHasChanged && !expand)) { - const prom = this.env.services.rpc({ - method: "search_panel_select_multi_range", - model: this.config.modelName, - args: [filter.fieldName], - kwargs: { - category_domain: categoryDomain, - comodel_domain: Domain.prototype.stringToArray( - filter.domain, - evalContext - ), - enable_counters: enableCounters, - filter_domain: this._getFilterDomain(filter.id), - expand, - group_by: groupBy || false, - group_domain: this._getGroupDomain(filter), - limit, - search_domain: this.searchDomain, - }, - }).then(result => this._createFilterTree(filter.id, result)); - proms.push(prom); - } - } - await Promise.all(proms); + await Promise.all(filters.map(async (filter) => { + const result = await this.env.services.rpc({ + method: "search_panel_select_multi_range", + model: this.config.modelName, + args: [filter.fieldName], + kwargs: { + category_domain: categoryDomain, + comodel_domain: Domain.prototype.stringToArray( + filter.domain, + evalContext + ), + enable_counters: filter.enableCounters, + filter_domain: this._getFilterDomain(filter.id), + expand: filter.expand, + group_by: filter.groupBy || false, + group_domain: this._getGroupDomain(filter), + limit: filter.limit, + search_domain: this.searchDomain, + }, + }); + this._createFilterTree(filter.id, result); + })); } /** - * Fetches categories and filters at startup or if needed. * @private - * @param {Object} [options={}] - * @param {boolean} [options.initialLoad=false] + * @param {boolean} isInitialLoad + * @returns {Promise} */ - async _fetchSections(options = {}) { - const searchDomain = this._getExternalDomain(); - if (!options.initialLoad) { - options.searchDomainHasChanged = - JSON.stringify(this.searchDomain) !== JSON.stringify(searchDomain); - } - this.searchDomain = searchDomain; - await this._fetchCategories(options); - await this._fetchFilters(options); - if (options.initialLoad) { + async _fetchSections(isInitialLoad) { + await this._fetchCategories( + isInitialLoad ? this.categories : this.categoriesToLoad + ); + await this._fetchFilters( + isInitialLoad ? this.filters : this.filtersToLoad + ); + if (isInitialLoad) { this._applyDefaultFilterValues(); } } @@ -520,53 +551,22 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function for (const category of this.categories) { if ( category.id === excludedCategoryId || - category.activeValueId === false + !category.activeValueId ) { continue; } - if (category.activeValueId) { - const field = this.config.fields[category.fieldName]; - const operator = - field.type === "many2one" && category.parentField ? "child_of" : "="; - domain.push([ - category.fieldName, - operator, - category.activeValueId, - ]); - } + const field = this.config.fields[category.fieldName]; + const operator = + field.type === "many2one" && category.parentField ? "child_of" : "="; + domain.push([ + category.fieldName, + operator, + category.activeValueId, + ]); } return domain; } - /** - * @private - * @param {Object[]} values - * @param {Category} category - * @returns {number | false} - */ - _getCategoryDefaultValue(values, { fieldName }) { - const validValues = [false, ...values.map((val) => val.id)]; - const defaultValue = this.config.defaultValues[fieldName]; - if (validValues.includes(defaultValue)) { - return defaultValue; - } - if (this.config.defaultNoFilter) { - return false; - } - // If not set in context, or set to an unknown value, set active value - // from localStorage - const storageKey = `searchpanel_${this.config.modelName}_${fieldName}`; - const storageValue = this.env.services.local_storage.getItem(storageKey); - if (validValues.includes(storageValue)) { - return storageValue; - } - // If still not a valid value, get the search panel default value - // from the given valid values. - return validValues[ - Math.min(validValues.length - 1, this.DEFAULT_VALUE_INDEX) - ]; - } - /** * Returns the domain retrieved from the other model extensions. * @private @@ -617,7 +617,7 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function for (const group of groups.values()) { addCondition(fieldName, group.values); } - } else if (values) { + } else { addCondition(fieldName, values); } } @@ -694,6 +694,33 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function return groupDomain; } + /** + * Returns whether the query informations should be considered as ready + * before or after having (re-)fetched the sections data. + * @private + * @param {Object} params + * @param {boolean} params.isInitialLoad + * @param {boolean} params.searchDomainChanged + * @returns {boolean} + */ + _shouldWaitForData({ isInitialLoad, searchDomainChanged }) { + if (isInitialLoad && Object.keys(this.defaultValues).length) { + // Default values need to be checked on initial load + return true; + } + if (this.categories.length && this.filters.some(hasDomain)) { + // Selected category value might affect the filter values + return true; + } + if (!this.searchDomain.length) { + // No search domain -> no need to check for expand + return false; + } + return [...this.state.sections.values()].some( + (section) => !section.expand && searchDomainChanged + ); + } + //--------------------------------------------------------------------- // Static //--------------------------------------------------------------------- diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index 971c5effb98..02c6fbda8e4 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -3591,9 +3591,6 @@ QUnit.module('ActionManager', { active_id: 1, active_ids: [1], }, - flags: { - searchPanelDefaultNoFilter: true, - }, }); var checkSessionStorage = false; var actionManager = await createActionManager({ diff --git a/addons/web/static/tests/views/search_panel_tests.js b/addons/web/static/tests/views/search_panel_tests.js index f9004d562c4..387cc8dc0e5 100644 --- a/addons/web/static/tests/views/search_panel_tests.js +++ b/addons/web/static/tests/views/search_panel_tests.js @@ -1,10 +1,8 @@ odoo.define("web/static/tests/views/search_panel_tests.js", function (require) { "use strict"; -const AbstractStorageService = require('web.AbstractStorageService'); const FormView = require('web.FormView'); const KanbanView = require('web.KanbanView'); -const RamStorage = require('web.RamStorage'); const testUtils = require('web.test_utils'); const SearchPanel = require("web/static/src/js/views/search_panel.js"); @@ -115,13 +113,6 @@ QUnit.module('Views', { `, }; - - var RamStorageService = AbstractStorageService.extend({ - storage: new RamStorage(), - }); - this.services = { - local_storage: RamStorageService, - }; }, }, function () { @@ -138,7 +129,6 @@ QUnit.module('Views', { assert.step((args.method || route) + (args.model ? (' on ' + args.model) : '')); return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -198,7 +188,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -237,7 +226,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -279,7 +267,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -320,7 +307,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -368,7 +354,6 @@ QUnit.module('Views', { } return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -434,7 +419,6 @@ QUnit.module('Views', { } return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -486,132 +470,6 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('store and retrieve active category value', async function (assert) { - assert.expect(9); - - var Storage = RamStorage.extend({ - getItem: function (key) { - assert.step(`getItem ${key}`); - const item = this._super(...arguments); - return item || 3; // default: 'asustek' - }, - setItem: function (key, value) { - assert.step(`setItem ${key} to ${value}`); - this._super(...arguments); - }, - }); - var RamStorageService = AbstractStorageService.extend({ - storage: new Storage(), - }); - - var expectedActiveId = 3; - var kanban = await createView({ - View: KanbanView, - model: 'partner', - data: this.data, - services: { - local_storage: RamStorageService, - }, - arch: ` - - - -
- -
-
-
-
`, - archs: { - 'partner,false,search': ` - - - - - `, - }, - mockRPC: function (route, args) { - if (route === '/web/dataset/search_read') { - assert.deepEqual(args.domain, [['company_id', 'child_of', expectedActiveId]]); - } - return this._super.apply(this, arguments); - }, - }); - - // 'asustek' should be selected by default - assert.containsOnce(kanban, '.o_search_panel_category_value .active'); - assert.containsOnce(kanban, '.o_search_panel_category_value:nth(1) .active'); - assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 2); - - // select 'agrolait' - expectedActiveId = 5; - await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header')); - - assert.verifySteps([ - 'getItem searchpanel_partner_company_id', // get on start - 'setItem searchpanel_partner_company_id to 5', - 'getItem searchpanel_partner_company_id', // get on reload - ]); - - kanban.destroy(); - }); - - QUnit.test('retrieved category value does not exist', async function (assert) { - assert.expect(6); - - var Storage = RamStorage.extend({ - getItem: function (key) { - assert.step('getItem ' + key); - return 343; // this value doesn't exist - }, - }); - var RamStorageService = AbstractStorageService.extend({ - storage: new Storage(), - }); - - var kanban = await createView({ - View: KanbanView, - model: 'partner', - data: this.data, - services: { - local_storage: RamStorageService, - }, - arch: ` - - - -
- -
-
-
-
`, - archs: { - 'partner,false,search': ` - - - - - `, - }, - mockRPC: function (route, args) { - if (route === '/web/dataset/search_read') { - assert.deepEqual(args.domain, []); - } - return this._super.apply(this, arguments); - }, - }); - - // 'All' should be selected by default as the retrieved value does not exist - assert.containsOnce(kanban, '.o_search_panel_category_value .active'); - assert.containsOnce(kanban, '.o_search_panel_category_value:first .active'); - assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4); - - assert.verifySteps(['getItem searchpanel_partner_company_id']); - - kanban.destroy(); - }); - QUnit.test('category has been archived', async function (assert) { assert.expect(2); @@ -688,7 +546,6 @@ QUnit.module('Views', { } return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -778,7 +635,6 @@ QUnit.module('Views', { } return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -876,7 +732,6 @@ QUnit.module('Views', { } return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -929,7 +784,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -982,7 +836,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -1034,7 +887,6 @@ QUnit.module('Views', { } return result; }, - services: this.services, arch: ` @@ -1104,6 +956,187 @@ QUnit.module('Views', { kanban.destroy(); }); + QUnit.test("concurrency: single category", async function (assert) { + assert.expect(12); + + let prom = testUtils.makeTestPromise(); + const kanbanPromise = createView({ + arch: ` + + +
+ +
+
+
`, + archs: { + "partner,false,search": ` + + + + + + `, + }, + context: { + searchpanel_default_company_id: [5], + }, + data: this.data, + async mockRPC(route, args) { + const _super = this._super.bind(this); + if (route !== "/web/dataset/search_read") { + await prom; + } + assert.step(args.method || route); + return _super(...arguments); + }, + model: 'partner', + View: KanbanView, + }); + + // Case 1: search panel is awaited to build the query with search defaults + await testUtils.nextTick(); + assert.verifySteps([]); + + prom.resolve(); + const kanban = await kanbanPromise; + + assert.verifySteps([ + "search_panel_select_range", + "/web/dataset/search_read", + ]); + + // Case 2: search domain changed so we wait for the search panel once again + prom = testUtils.makeTestPromise(); + + await testUtils.controlPanel.toggleFilterMenu(kanban); + await testUtils.controlPanel.toggleMenuItem(kanban, 0); + + assert.verifySteps([]); + + prom.resolve(); + await testUtils.nextTick(); + + assert.verifySteps([ + "search_panel_select_range", + "/web/dataset/search_read", + ]); + + // Case 3: search domain is the same and default values do not matter anymore + prom = testUtils.makeTestPromise(); + + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); + + // The search read is executed right away in this case + assert.verifySteps(["/web/dataset/search_read"]); + + prom.resolve(); + await testUtils.nextTick(); + + assert.verifySteps(["search_panel_select_range"]); + + kanban.destroy(); + }); + + QUnit.test("concurrency: category and filter", async function (assert) { + assert.expect(5); + + let prom = testUtils.makeTestPromise(); + const kanbanPromise = createView({ + arch: ` + + +
+ +
+
+
`, + archs: { + "partner,false,search": ` + + + + + + `, + }, + data: this.data, + async mockRPC(route, args) { + const _super = this._super.bind(this); + if (route !== "/web/dataset/search_read") { + await prom; + } + assert.step(args.method || route); + return _super(...arguments); + }, + model: 'partner', + View: KanbanView, + }); + + await testUtils.nextTick(); + assert.verifySteps(["/web/dataset/search_read"]); + + prom.resolve(); + const kanban = await kanbanPromise; + + assert.verifySteps([ + "search_panel_select_range", + "search_panel_select_multi_range", + ]); + + kanban.destroy(); + }); + + QUnit.test("concurrency: category and filter with a domain", async function (assert) { + assert.expect(5); + + let prom = testUtils.makeTestPromise(); + const kanbanPromise = createView({ + arch: ` + + +
+ +
+
+
`, + archs: { + "partner,false,search": ` + + + + + + `, + }, + data: this.data, + async mockRPC(route, args) { + const _super = this._super.bind(this); + if (route !== "/web/dataset/search_read") { + await prom; + } + assert.step(args.method || route); + return _super(...arguments); + }, + model: 'partner', + View: KanbanView, + }); + + await testUtils.nextTick(); + assert.verifySteps([]); + + prom.resolve(); + const kanban = await kanbanPromise; + + assert.verifySteps([ + "search_panel_select_range", + "search_panel_select_multi_range", + "/web/dataset/search_read", + ]); + + kanban.destroy(); + }); + QUnit.test('concurrency: misordered get_filters', async function (assert) { assert.expect(15); @@ -1119,7 +1152,6 @@ QUnit.module('Views', { } return result; }, - services: this.services, arch: ` @@ -1154,7 +1186,7 @@ QUnit.module('Views', { // 'All' should still be selected, and there should still be 4 records assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(1) .active'); - assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4); + assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 1); // select 'ghi' (delay the reload) def = testUtils.makeTestPromise(); @@ -1164,7 +1196,7 @@ QUnit.module('Views', { // 'All' should still be selected, and there should still be 4 records assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(3) .active'); - assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4); + assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 2); // unlock ghi search ghiDef.resolve(); @@ -1198,7 +1230,6 @@ QUnit.module('Views', { } return result; }, - services: this.services, arch: ` @@ -1264,7 +1295,6 @@ QUnit.module('Views', { } return result; }, - services: this.services, arch: ` @@ -1371,7 +1401,6 @@ QUnit.module('Views', { } return result; }, - services: this.services, arch: ` @@ -1463,7 +1492,6 @@ QUnit.module('Views', { assert.step(args.method || route); return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -1533,7 +1561,6 @@ QUnit.module('Views', { assert.step(args.method || route); return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -1606,7 +1633,6 @@ QUnit.module('Views', { } return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -1711,7 +1737,6 @@ QUnit.module('Views', { } return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -1823,7 +1848,6 @@ QUnit.module('Views', { } return result; }, - services: this.services, arch: ` @@ -1952,7 +1976,6 @@ QUnit.module('Views', { } return result; }, - services: this.services, arch: ` @@ -2002,7 +2025,6 @@ QUnit.module('Views', { } return result; }, - services: this.services, arch: ` @@ -2073,7 +2095,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -2123,7 +2144,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -2165,7 +2185,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -2215,7 +2234,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: `
@@ -2261,7 +2279,6 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - services: this.services, arch: ` @@ -2358,7 +2375,6 @@ QUnit.module('Views', { actions: this.actions, archs: this.archs, data: this.data, - services: this.services, mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { assert.step(JSON.stringify(args.domain)); @@ -2407,7 +2423,6 @@ QUnit.module('Views', { actions: this.actions, archs: this.archs, data: this.data, - services: this.services, mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { assert.step(JSON.stringify(args.domain)); @@ -2488,26 +2503,10 @@ QUnit.module('Views', { QUnit.test('after onExecuteAction, selects "All" as default category value', async function (assert) { assert.expect(4); - var Storage = RamStorage.extend({ - getItem: function (key) { - assert.step('getItem ' + key); - return 3; // 'asustek' - }, - setItem: function (key, value) { - assert.step('setItem ' + key + ' to ' + value); - }, - }); - var RamStorageService = AbstractStorageService.extend({ - storage: new Storage(), - }); - var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, - services: { - local_storage: RamStorageService, - }, }); await actionManager.doAction(2); @@ -2530,7 +2529,6 @@ QUnit.module('Views', { actions: this.actions, archs: this.archs, data: this.data, - services: this.services, }); await actionManager.doAction(2); @@ -2549,7 +2547,6 @@ QUnit.module('Views', { actions: this.actions, archs: this.archs, data: this.data, - services: this.services, mockRPC: function (route, args) { assert.step(args.method || route); return this._super.apply(this, arguments); @@ -2587,7 +2584,6 @@ QUnit.module('Views', { actions: this.actions, archs: this.archs, data: this.data, - services: this.services, }); actionManager.$el.css('max-height', 300); @@ -2669,7 +2665,6 @@ QUnit.module('Views', { assert.step(args.method || route); return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -2694,7 +2689,7 @@ QUnit.module('Views', { assert.verifySteps([ 'search_panel_select_range', "search_panel_select_multi_range", - '/web/dataset/search_read' + '/web/dataset/search_read', ]); assert.deepEqual(getCounters(kanban), [ @@ -2712,7 +2707,7 @@ QUnit.module('Views', { assert.verifySteps([ 'search_panel_select_range', "search_panel_select_multi_range", - '/web/dataset/search_read' + '/web/dataset/search_read', ]); kanban.destroy(); @@ -2745,7 +2740,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -2788,7 +2782,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -2831,7 +2824,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -2869,7 +2861,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -2907,7 +2898,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -2950,7 +2940,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -2993,7 +2982,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3031,7 +3019,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3067,7 +3054,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3103,7 +3089,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3139,7 +3124,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3175,7 +3159,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3211,7 +3194,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3247,7 +3229,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3283,7 +3264,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3319,7 +3299,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3355,7 +3334,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3391,7 +3369,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3427,7 +3404,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3463,7 +3439,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3499,7 +3474,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3535,7 +3509,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3571,7 +3544,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3607,7 +3579,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3643,7 +3614,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3679,7 +3649,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3715,7 +3684,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3751,7 +3719,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3787,7 +3754,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3823,7 +3789,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3859,7 +3824,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3895,7 +3859,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3936,7 +3899,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -3980,7 +3942,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -4018,7 +3979,6 @@ QUnit.module('Views', { }, data: this.data, model: 'partner', - services: this.services, View: KanbanView, }); @@ -4043,7 +4003,6 @@ QUnit.module('Views', { assert.step(args.method || route); return this._super.apply(this, arguments); }, - services: this.services, arch: ` @@ -4115,7 +4074,6 @@ QUnit.module('Views', { assert.step(args.method || route); return this._super.apply(this, arguments); }, - services: this.services, arch: `