odoo.define('web.data_manager_tests', function (require) { "use strict"; const config = require('web.config'); const DataManager = require('web.DataManager'); const MockServer = require('web.MockServer'); const rpc = require('web.rpc'); const testUtils = require('web.test_utils'); /** * Create a simple data manager with mocked functions: * - mockRPC -> rpc.query * - isDebug -> config.isDebug * @param {Object} params * @param {Object} params.archs * @param {Object} params.data * @param {Function} params.isDebug * @param {Function} params.mockRPC * @returns {DataManager} */ function createDataManager({ archs, data, isDebug, mockRPC }) { const dataManager = new DataManager(); const server = new MockServer(data, { archs }); const serverMethods = { async get_views({ kwargs, model }) { const models = {}; models[model] = server.fieldsGet(model); const views = {}; for (const [viewId, viewType] of kwargs.views) { const arch = archs[[model, viewId || false, viewType].join()]; views[viewType] = server.getView({ arch, model, viewId }); for (const modelName of views[viewType].models) { models[modelName] = server.fieldsGet(modelName); } } const result = { models, views }; if (kwargs.options.load_filters && views.search) { views.search.filters = data['ir.filters'].records.filter(r => r.model_id === model); } return result; }, async get_filters({ args, model }) { return data[model].records.filter(r => r.model_id === args[0]); }, async create_or_replace({ args }) { const id = data['ir.filters'].records.reduce((i, r) => Math.max(i, r.id), 0) + 1; const filter = Object.assign(args[0], { id }); data['ir.filters'].records.push(filter); return id; }, async unlink({ args }) { data['ir.filters'].records = data['ir.filters'].records.filter( r => r.id !== args[0] ); return true; }, }; testUtils.mock.patch(rpc, { async query({ method }) { this._super = serverMethods[method].bind(this, ...arguments); return mockRPC.apply(this, arguments); }, }); testUtils.mock.patch(config, { isDebug }); return dataManager; } QUnit.module("Services", { beforeEach() { this.archs = { 'oui,10,kanban': '', 'oui,20,search': '', }; this.data = { oui: { fields: {}, records: [] }, 'ir.filters': { fields: { context: { type: "Text", string: "Context" }, domain: { type: "Text", string: "Domain" }, model_id: { type: "Selection", string: "Model" }, name: { type: "Char", string: "Name" }, }, records: [{ id: 2, context: '{}', domain: '[]', model_id: 'oui', name: "Favorite", }] } }; this.loadViewsParams = { model: "oui", context: {}, views_descr: [ [10, 'kanban'], [20, 'search'], ], }; }, afterEach() { testUtils.mock.unpatch(rpc); testUtils.mock.unpatch(config); }, }, function () { QUnit.module("Data manager"); QUnit.test("Load views with filters (non-debug mode)", async function (assert) { assert.expect(4); const dataManager = createDataManager({ archs: this.archs, data: this.data, isDebug() { return false; }, async mockRPC({ method, model }) { assert.step([model, method].join('.')); return this._super(...arguments); }, }); const firstLoad = await dataManager.load_views(this.loadViewsParams, { load_filters: true, }); const secondLoad = await dataManager.load_views(this.loadViewsParams, { load_filters: true, }); const filters = await dataManager.load_filters({ modelName: 'oui' }); assert.deepEqual(firstLoad, secondLoad, "query with same params and options should yield the same results"); assert.deepEqual(firstLoad.search.favoriteFilters, filters, "load filters should yield the same result as the first load_views' filters"); assert.verifySteps(['oui.get_views'], "only load once when not in assets debugging"); }); QUnit.test("Load views with filters (debug mode)", async function (assert) { assert.expect(6); const dataManager = createDataManager({ archs: this.archs, data: this.data, isDebug() { return true; // assets }, async mockRPC({ method, model }) { assert.step([model, method].join('.')); return this._super(...arguments); }, }); const firstLoad = await dataManager.load_views(this.loadViewsParams, { load_filters: true, }); const secondLoad = await dataManager.load_views(this.loadViewsParams, { load_filters: true, }); const filters = await dataManager.load_filters({ modelName: 'oui' }); assert.deepEqual(firstLoad, secondLoad, "query with same params and options should yield the same results"); assert.deepEqual(firstLoad.search.favoriteFilters, filters, "load filters should yield the same result as the first load_views' filters"); assert.verifySteps([ 'oui.get_views', 'oui.get_views', 'ir.filters.get_filters', ], "reload each time when in assets debugging"); }); QUnit.test("Cache invalidation and filters addition/deletion", async function (assert) { assert.expect(10); const dataManager = createDataManager({ archs: this.archs, data: this.data, isDebug() { return false; // Cache only works if 'debug !== assets' }, async mockRPC({ method, model }) { assert.step([model, method].join('.')); return this._super(...arguments); }, }); // A few unnecessary 'load_filters' are done in this test to assert // that the cache invalidation mechanics are working. let filters; const firstLoad = await dataManager.load_views(this.loadViewsParams, { load_filters: true, }); // Cache is valid -> should not trigger an RPC filters = await dataManager.load_filters({ modelName: 'oui' }); assert.deepEqual(firstLoad.search.favoriteFilters, filters, "load_filters and load_views.search should return the same filters"); const filterId = await dataManager.create_filter({ context: "{}", domain: "[]", model_id: 'oui', name: "Temp", }); // Cache is not valid anymore -> triggers a 'get_filters' filters = await dataManager.load_filters({ modelName: 'oui' }); // Cache is valid -> should not trigger an RPC filters = await dataManager.load_filters({ modelName: 'oui' }); assert.strictEqual(filters.length, 2, "A new filter should have been added"); assert.ok(filters.find(f => f.id === filterId) === filters[filters.length - 1], "Create filter should return the id of the last created filter"); await dataManager.delete_filter(filterId); // Views cache is valid but filters cache is not -> triggers a 'get_filters' const secondLoad = await dataManager.load_views(this.loadViewsParams, { load_filters: true, }); filters = secondLoad.search.favoriteFilters; // Filters cache is once again valid -> no RPC const expectedFilters = await dataManager.load_filters({ modelName: 'oui' }); assert.deepEqual(filters, expectedFilters, "Filters loaded by the load_views should be equal to the result of a load_filters"); assert.verifySteps([ 'oui.get_views', 'ir.filters.create_or_replace', 'ir.filters.get_filters', 'ir.filters.unlink', 'ir.filters.get_filters', ], "server should have been called only when needed"); }); }); });