diff --git a/addons/web/static/src/start.js b/addons/web/static/src/start.js
index 89e04c8bbec..59201815592 100644
--- a/addons/web/static/src/start.js
+++ b/addons/web/static/src/start.js
@@ -3,10 +3,10 @@
import { makeEnv, startServices } from "./env";
import { legacySetupProm } from "./legacy/legacy_setup";
import { mapLegacyEnvToWowlEnv } from "./legacy/utils";
-import { processTemplates, loadBundle } from "./core/assets";
import { localization } from "@web/core/l10n/localization";
import { session } from "@web/session";
import { renderToString } from "./core/utils/render";
+import { setLoadXmlDefaultApp, templates } from "@web/core/assets";
const { App, whenReady } = owl;
@@ -29,25 +29,21 @@ export async function startWebClient(Webclient) {
// setup environment
const env = makeEnv();
- const [, templates] = await Promise.all([
- startServices(env),
- odoo.loadTemplatesPromise.then(processTemplates),
- ]);
+ await startServices(env);
// start web client
await whenReady();
- window.__OWL_TEMPLATES__ = templates;
const legacyEnv = await legacySetupProm;
mapLegacyEnvToWowlEnv(legacyEnv, env);
const app = new App(Webclient, {
env,
+ templates,
dev: env.debug,
- templates: window.__OWL_TEMPLATES__,
translatableAttributes: ["data-tooltip"],
translateFn: env._t,
});
renderToString.app = app;
- loadBundle.app = app;
+ setLoadXmlDefaultApp(app);
const root = await app.mount(document.body);
const classList = document.body.classList;
if (localization.direction === "rtl") {
diff --git a/addons/web/static/src/views/fields/journal_dashboard_graph/journal_dashboard_graph_field.js b/addons/web/static/src/views/fields/journal_dashboard_graph/journal_dashboard_graph_field.js
index 2bc13322efe..711fdaf743a 100644
--- a/addons/web/static/src/views/fields/journal_dashboard_graph/journal_dashboard_graph_field.js
+++ b/addons/web/static/src/views/fields/journal_dashboard_graph/journal_dashboard_graph_field.js
@@ -12,7 +12,7 @@ export class JournalDashboardGraphField extends Component {
this.canvasRef = useRef("canvas");
this.data = JSON.parse(this.props.value);
- onWillStart(() => loadJS(["/web/static/lib/Chart/Chart.js"]));
+ onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js"));
useEffect(() => {
this.renderChart();
diff --git a/addons/web/static/src/views/graph/graph_renderer.js b/addons/web/static/src/views/graph/graph_renderer.js
index 01d596396d9..6b26d84b887 100644
--- a/addons/web/static/src/views/graph/graph_renderer.js
+++ b/addons/web/static/src/views/graph/graph_renderer.js
@@ -50,7 +50,7 @@ export class GraphRenderer extends Component {
this.tooltip = null;
this.legendTooltip = null;
- onWillStart(() => loadJS(["/web/static/lib/Chart/Chart.js"]));
+ onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js"));
useEffect(() => this.renderChart());
onWillUnmount(this.onWillUnmount);
diff --git a/addons/web/static/tests/core/dropdown_tests.js b/addons/web/static/tests/core/dropdown_tests.js
index 8ccd579f9c6..1a05206d297 100644
--- a/addons/web/static/tests/core/dropdown_tests.js
+++ b/addons/web/static/tests/core/dropdown_tests.js
@@ -21,6 +21,7 @@ import {
triggerHotkey,
} from "../helpers/utils";
import { makeParent } from "./tooltip/tooltip_service_tests";
+import { templates } from "@web/core/assets";
const { App, Component, xml } = owl;
const serviceRegistry = registry.category("services");
@@ -392,7 +393,7 @@ QUnit.module("Components", ({ beforeEach }) => {
env = await makeTestEnv();
const app = new App(Parent, {
env,
- templates: window.__OWL_TEMPLATES__,
+ templates,
test: true,
});
registerCleanup(() => app.destroy());
diff --git a/addons/web/static/tests/core/tooltip/tooltip_service_tests.js b/addons/web/static/tests/core/tooltip/tooltip_service_tests.js
index b2b1f7a0788..646dc07e6ce 100644
--- a/addons/web/static/tests/core/tooltip/tooltip_service_tests.js
+++ b/addons/web/static/tests/core/tooltip/tooltip_service_tests.js
@@ -9,6 +9,7 @@ import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env";
import { getFixture, nextTick, patchWithCleanup, triggerEvent } from "../../helpers/utils";
import { registerCleanup } from "../../helpers/cleanup";
import { makeFakeLocalizationService } from "../../helpers/mock_services";
+import { templates } from "@web/core/assets";
const { App, Component, useState, xml } = owl;
@@ -82,7 +83,7 @@ export async function makeParent(Child, options = {}) {
const app = new App(Parent, {
env,
target,
- templates: window.__OWL_TEMPLATES__,
+ templates,
test: true,
});
registerCleanup(() => app.destroy());
diff --git a/addons/web/static/tests/core/utils/assets_tests.js b/addons/web/static/tests/core/utils/assets_tests.js
index b0ae0f682f6..e656e2dbb92 100644
--- a/addons/web/static/tests/core/utils/assets_tests.js
+++ b/addons/web/static/tests/core/utils/assets_tests.js
@@ -1,8 +1,6 @@
/** @odoo-module **/
-import { loadJS, loadCSS, fetchAndProcessTemplates } from "@web/core/assets";
-import { patchWithCleanup } from "@web/../tests/helpers/utils";
-import { browser } from "@web/core/browser/browser";
+import { loadJS, loadCSS } from "@web/core/assets";
QUnit.module("utils", () => {
QUnit.module("Assets");
@@ -30,22 +28,4 @@ QUnit.module("utils", () => {
"Document contains a link with the href we asked to load"
);
});
-
- QUnit.test("fetchAndProcessTemplates: load invalid bundle", function (assert) {
- let lastFetchedURL;
- patchWithCleanup(browser, {
- fetch: function (url) {
- lastFetchedURL = url;
- return Promise.reject(`Failed to load ressource at "${url}"`);
- },
- });
- assert.rejects(
- fetchAndProcessTemplates("web.some_invalid_bundle"),
- "Trying to load an invalid bundle rejects the promise"
- );
- assert.ok(
- /\/web\/webclient\/qweb\/.*\?bundle=web\.some_invalid_bundle$/.test(lastFetchedURL),
- "Loading a bundle calls the /web/webclient/qweb route with the corresponding bundle query parameter"
- );
- });
});
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 48049b20d69..c2ff44a1396 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -15,6 +15,7 @@ import {
serializeDate,
serializeDateTime,
} from "@web/core/l10n/dates";
+import { assets } from "@web/core/assets";
const serviceRegistry = registry.category("services");
@@ -2200,12 +2201,11 @@ export async function makeMockServer(serverData, mockRPC) {
// simulates that we serialized the call to be passed in a real request
args = JSON.parse(JSON.stringify(args));
}
- const performRPC = (route, args) => mockServer.performRPC(route, args);
if (mockRPC) {
- res = await mockRPC(route, args, performRPC);
+ res = await mockRPC(route, args, mockServer.performRPC.bind(mockServer));
}
if (res === undefined) {
- res = await performRPC(route, args);
+ res = await mockServer.performRPC(route, args);
}
return res;
};
@@ -2213,6 +2213,29 @@ export async function makeMockServer(serverData, mockRPC) {
patchWithCleanup(browser, {
fetch: makeMockFetch(_mockRPC),
});
+ if (mockRPC) {
+ const { loadJS, loadCSS } = assets;
+ patchWithCleanup(assets, {
+ loadJS: async function (ressource) {
+ let res = await mockRPC(ressource, {});
+ if (res === undefined) {
+ res = await loadJS(ressource);
+ } else {
+ console.log("%c[assets] fetch (mock) JS ressource " + ressource, "color: #66e; font-weight: bold;");
+ }
+ return res;
+ },
+ loadCSS: async function (ressource) {
+ let res = await mockRPC(ressource, {});
+ if (res === undefined) {
+ res = await loadCSS(ressource);
+ } else {
+ console.log("%c[assets] fetch (mock) CSS ressource " + ressource, "color: #66e; font-weight: bold;");
+ }
+ return res;
+ },
+ });
+ }
// Replace RPC service
serviceRegistry.add("rpc", rpcService, { force: true });
return mockServer;
diff --git a/addons/web/static/tests/helpers/utils.js b/addons/web/static/tests/helpers/utils.js
index e098ead114f..628847b3817 100644
--- a/addons/web/static/tests/helpers/utils.js
+++ b/addons/web/static/tests/helpers/utils.js
@@ -7,6 +7,7 @@ import { Deferred } from "@web/core/utils/concurrency";
import { patch, unpatch } from "@web/core/utils/patch";
import { isVisible } from "@web/core/utils/ui";
import { registerCleanup } from "./cleanup";
+import { templates } from "@web/core/assets";
const { App, onMounted, onPatched, useComponent } = owl;
@@ -587,7 +588,7 @@ export async function mount(Comp, target, config = {}) {
env = env || {};
const configuration = {
env,
- templates: window.__OWL_TEMPLATES__,
+ templates,
test: true,
props,
};
diff --git a/addons/web/static/tests/legacy/core/ajax_tests.js b/addons/web/static/tests/legacy/core/ajax_tests.js
deleted file mode 100644
index f58d7368d45..00000000000
--- a/addons/web/static/tests/legacy/core/ajax_tests.js
+++ /dev/null
@@ -1,35 +0,0 @@
-odoo.define('web.ajax_tests', function (require) {
-"use strict";
-
-var ajax = require('web.ajax');
-
-QUnit.module('core', function () {
-
- var test_css_url = '/test_assetsbundle/static/src/css/test_cssfile1.css';
- var test_link_selector = 'link[href="' + test_css_url + '"]';
-
- QUnit.module('ajax', {
- beforeEach: function () {
- $(test_link_selector).remove();
- },
- afterEach: function () {
- $(test_link_selector).remove();
- }
- });
-
- QUnit.test('loadCSS', function (assert) {
- var done = assert.async();
- assert.expect(2);
- ajax.loadCSS(test_css_url).then(function () {
- var $links = $(test_link_selector);
- assert.strictEqual($links.length, 1, "The css should be added to the dom.");
- ajax.loadCSS(test_css_url).then(function () {
- var $links = $(test_link_selector);
- assert.strictEqual($links.length, 1, "The css should have been added only once.");
- done();
- });
- });
- });
-});
-
-});
diff --git a/addons/web/static/tests/legacy/core/owl_dialog_tests.js b/addons/web/static/tests/legacy/core/owl_dialog_tests.js
index f2a8c218b2d..2e171a5bd23 100644
--- a/addons/web/static/tests/legacy/core/owl_dialog_tests.js
+++ b/addons/web/static/tests/legacy/core/owl_dialog_tests.js
@@ -469,6 +469,7 @@ odoo.define('web.owl_dialog_tests', function (require) {
`,
"event,2,form": `
`,
},
+ activateMockServer: true,
};
const target = getFixture();
diff --git a/addons/web/static/tests/legacy/fields/basic_fields_tests.js b/addons/web/static/tests/legacy/fields/basic_fields_tests.js
index 3fd0d843acc..17b41385726 100644
--- a/addons/web/static/tests/legacy/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/legacy/fields/basic_fields_tests.js
@@ -1,7 +1,6 @@
odoo.define('web.basic_fields_tests', function (require) {
"use strict";
-var ajax = require('web.ajax');
var basicFields = require('web.basic_fields');
var concurrency = require('web.concurrency');
var config = require('web.config');
@@ -9071,12 +9070,6 @@ QUnit.module('Legacy basic_fields', {
form.destroy();
});
- QUnit.module('FieldColor', {
- before: function () {
- return ajax.loadXML('/web/static/src/legacy/xml/colorpicker.xml', core.qweb);
- },
- });
-
QUnit.test('Field Color: default widget state', async function (assert) {
assert.expect(4);
diff --git a/addons/web/static/tests/legacy/fields/signature_tests.js b/addons/web/static/tests/legacy/fields/signature_tests.js
index ad4375c475b..c2c1e23ec6c 100644
--- a/addons/web/static/tests/legacy/fields/signature_tests.js
+++ b/addons/web/static/tests/legacy/fields/signature_tests.js
@@ -1,8 +1,6 @@
odoo.define('web.signature_field_tests', function (require) {
"use strict";
-var ajax = require('web.ajax');
-var core = require('web.core');
var FormView = require('web.FormView');
var testUtils = require('web.test_utils');
@@ -38,13 +36,6 @@ QUnit.module('signature', {
};
}
}, function () {
-
- QUnit.module('Signature Field', {
- before: function () {
- return ajax.loadXML('/web/static/src/legacy/xml/name_and_signature.xml', core.qweb);
- },
- });
-
QUnit.test('Set simple field in "full_name" node option', async function (assert) {
assert.expect(3);
diff --git a/addons/web/static/tests/legacy/helpers/mock_server.js b/addons/web/static/tests/legacy/helpers/mock_server.js
index 958952bbfa1..b3737fe7e8d 100644
--- a/addons/web/static/tests/legacy/helpers/mock_server.js
+++ b/addons/web/static/tests/legacy/helpers/mock_server.js
@@ -1955,6 +1955,14 @@ var MockServer = Class.extend({
* @returns {any}
*/
_performFetch(resource, init) {
+ if (resource.match(/\/static(\/\S+\/|\/)libs?/)) {
+ // every lib must be includes into the test bundle.
+ return true;
+ }
+ if (resource.match(/\/web\/bundle\/[^.]+\.[^.]+/)) {
+ // every asset must be includes into the test bundle.
+ return true;
+ }
throw new Error("Unimplemented resource: " + resource);
},
/**
diff --git a/addons/web/static/tests/legacy/helpers/test_env.js b/addons/web/static/tests/legacy/helpers/test_env.js
index 5bf0d0d5400..cededce739e 100644
--- a/addons/web/static/tests/legacy/helpers/test_env.js
+++ b/addons/web/static/tests/legacy/helpers/test_env.js
@@ -4,7 +4,7 @@ odoo.define('web.test_env', async function (require) {
const Bus = require('web.Bus');
const session = require('web.session');
const { makeTestEnvServices } = require('@web/../tests/legacy/helpers/test_services');
-
+ const { templates, setLoadXmlDefaultApp } = require("@web/core/assets");
const { renderToString } = require('@web/core/utils/render');
const { App, Component } = owl;
@@ -21,8 +21,9 @@ odoo.define('web.test_env', async function (require) {
*/
function makeTestEnvironment(env = {}, providedRPC = null) {
if (!app) {
- app = new App(null, { templates: window.__OWL_TEMPLATES__, test: true });
+ app = new App(null, { templates, test: true });
renderToString.app = app;
+ setLoadXmlDefaultApp(app);
}
const defaultTranslationParamters = {
diff --git a/addons/web/static/tests/legacy/helpers/test_services.js b/addons/web/static/tests/legacy/helpers/test_services.js
index e180c0c27bf..5bcafb7cfdd 100644
--- a/addons/web/static/tests/legacy/helpers/test_services.js
+++ b/addons/web/static/tests/legacy/helpers/test_services.js
@@ -23,7 +23,6 @@ function makeTestEnvServices(env) {
rpc() {
return env.session.rpc(...arguments); // Compatibility Legacy Widgets
},
- loadLibs() {}
},
rpc(params, options) {
const query = buildQuery(params);
diff --git a/addons/web/static/tests/legacy/helpers/test_utils.js b/addons/web/static/tests/legacy/helpers/test_utils.js
index cd021609026..7a3e9ac9161 100644
--- a/addons/web/static/tests/legacy/helpers/test_utils.js
+++ b/addons/web/static/tests/legacy/helpers/test_utils.js
@@ -10,8 +10,6 @@ odoo.define('web.test_utils', async function (require) {
* instance of a view, appended in the dom, ready to be tested.
*/
- const ajax = require('web.ajax');
- const core = require('web.core');
const relationalFields = require('web.relational_fields');
const session = require('web.session');
const testUtilsCreate = require('web.test_utils_create');
@@ -121,12 +119,9 @@ odoo.define('web.test_utils', async function (require) {
// before starting the qunit test suite.
// (session.js is in charge of loading the static xml bundle and we also have
// to load xml files that are normally lazy loaded by specific widgets).
- await Promise.all([
- session.is_bound,
- ajax.loadXML('/web/static/src/legacy/frontend/debug_manager/debug.xml', core.qweb),
- ajax.loadXML('/web/static/src/legacy/xml/dialog.xml', core.qweb),
- ajax.loadXML('/web/static/src/legacy/xml/translation_dialog.xml', core.qweb),
- ]);
+ // Assets can also contain static xml files. They are loaded when the session
+ // is launched.
+ await session.is_bound;
setTimeout(function () {
// jquery autocomplete refines the search in a setTimeout() parameterized
// with a delay, so we force this delay to 0 s.t. the dropdown is filtered
diff --git a/addons/web/static/tests/legacy/helpers/test_utils_mock.js b/addons/web/static/tests/legacy/helpers/test_utils_mock.js
index d866c8e3e9d..6ef9bf9fcac 100644
--- a/addons/web/static/tests/legacy/helpers/test_utils_mock.js
+++ b/addons/web/static/tests/legacy/helpers/test_utils_mock.js
@@ -22,7 +22,9 @@ const makeTestEnvironment = require('web.test_env');
const MockServer = require('web.MockServer');
const RamStorage = require('web.RamStorage');
const session = require('web.session');
-const { patchDate } = require("@web/../tests/helpers/utils");
+const { patchWithCleanup, patchDate } = require("@web/../tests/helpers/utils");
+const { browser } = require("@web/core/browser/browser");
+const { assets } = require("@web/core/assets");
const { processArch } = require("@web/legacy/legacy_load_views");
const { Component } = owl;
@@ -333,7 +335,7 @@ async function addMockEnvironmentOwl(Component, params, mockServer) {
if (!mockServer) {
let Server = MockServer;
if (params.mockFetch) {
- Server = MockServer.extend({ _performFetch: params.mockFetch });
+ Server = Server.extend({ _performFetch: params.mockFetch });
}
if (params.mockRPC) {
Server = Server.extend({ _performRpc: params.mockRPC });
@@ -346,6 +348,40 @@ async function addMockEnvironmentOwl(Component, params, mockServer) {
});
}
+ patchWithCleanup(browser, {
+ fetch: async (url, args) => {
+ const result = await mockServer.performFetch(url, args || {});
+ return {
+ json: () => result,
+ text: () => result,
+ };
+ },
+ });
+
+ if (params.mockFetch) {
+ const { loadJS, loadCSS } = assets;
+ patchWithCleanup(assets, {
+ loadJS: async function (ressource) {
+ let res = await params.mockFetch(ressource, {});
+ if (res === undefined) {
+ res = await loadJS(ressource);
+ } else {
+ console.log("%c[assets] fetch (mock) JS ressource " + ressource, "color: #66e; font-weight: bold;");
+ }
+ return res;
+ },
+ loadCSS: async function (ressource) {
+ let res = await params.mockFetch(ressource, {});
+ if (res === undefined) {
+ res = await loadCSS(ressource);
+ } else {
+ console.log("%c[assets] fetch (mock) CSS ressource " + ressource, "color: #66e; font-weight: bold;");
+ }
+ return res;
+ },
+ });
+ }
+
// remove the multi-click delay for the quick edit in form view
const initialQuickEditDelay = FormController.prototype.multiClickTime;
FormController.prototype.multiClickTime = params.formMultiClickTime || 0;
diff --git a/addons/web/static/tests/legacy/views/abstract_view_tests.js b/addons/web/static/tests/legacy/views/abstract_view_tests.js
index 4a5bc84cd35..a5ec9923362 100644
--- a/addons/web/static/tests/legacy/views/abstract_view_tests.js
+++ b/addons/web/static/tests/legacy/views/abstract_view_tests.js
@@ -1,15 +1,11 @@
odoo.define('web.abstract_view_tests', function (require) {
"use strict";
-var AbstractView = require('web.AbstractView');
-var ajax = require('web.ajax');
-var testUtils = require('web.test_utils');
const { registry } = require('@web/core/registry');
const legacyViewRegistry = require('web.view_registry');
var ListView = require('web.ListView');
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
-var createView = testUtils.createView;
QUnit.module('LegacyViews', {
beforeEach: function () {
@@ -34,89 +30,6 @@ QUnit.module('LegacyViews', {
QUnit.module('AbstractView');
- QUnit.test('lazy loading of js libs (in parallel)', async function (assert) {
- var done = assert.async();
- assert.expect(6);
-
- var prom = testUtils.makeTestPromise();
- var loadJS = ajax.loadJS;
- const libraryLoadingStartedPromise = testUtils.makeTestPromise();
- ajax.loadJS = function (url) {
- libraryLoadingStartedPromise.resolve();
- assert.step(url);
- return prom.then(function () {
- assert.step(url + ' loaded');
- });
- };
-
- var View = AbstractView.extend({
- jsLibs: [['a', 'b']],
- });
- createView({
- View: View,
- arch: '
',
- data: this.data,
- model: 'fake_model',
- }).then(function (view) {
- assert.verifySteps(['a loaded', 'b loaded'],
- "should wait for both libs to be loaded");
- ajax.loadJS = loadJS;
- view.destroy();
- done();
- });
-
- await libraryLoadingStartedPromise;
- assert.verifySteps(['a', 'b'], "both libs should be loaded in parallel");
- prom.resolve();
- });
-
- QUnit.test('lazy loading of js libs (sequentially)', async function (assert) {
- var done = assert.async();
- assert.expect(10);
-
- var proms = {
- a: testUtils.makeTestPromise(),
- b: testUtils.makeTestPromise(),
- c: testUtils.makeTestPromise(),
- };
- var loadJS = ajax.loadJS;
- const libraryLoadingStartedPromise = testUtils.makeTestPromise();
- ajax.loadJS = function (url) {
- libraryLoadingStartedPromise.resolve();
- assert.step(url);
- return proms[url].then(function () {
- assert.step(url + ' loaded');
- });
- };
-
- var View = AbstractView.extend({
- jsLibs: [
- ['a', 'b'],
- 'c',
- ],
- });
- createView({
- View: View,
- arch: '
',
- data: this.data,
- model: 'fake_model',
- }).then(function (view) {
- assert.verifySteps(['c loaded'], "should wait for all libs to be loaded");
- ajax.loadJS = loadJS;
- view.destroy();
- done();
- });
- await libraryLoadingStartedPromise;
- assert.verifySteps(['a', 'b'], "libs 'a' and 'b' should be loaded in parallel");
- await proms.b.resolve();
- await testUtils.nextTick();
- assert.verifySteps(['b loaded'], "should wait for 'a' and 'b' to be loaded before loading 'c'");
- await proms.a.resolve();
- await testUtils.nextTick();
- assert.verifySteps(['a loaded', 'c'], "should load 'c' when 'a' and 'b' are loaded");
- await proms.c.resolve();
- });
-
QUnit.test('group_by from context can be a string, instead of a list of strings', async function (assert) {
assert.expect(1);
diff --git a/addons/web/static/tests/legacy/views/form_tests.js b/addons/web/static/tests/legacy/views/form_tests.js
index efb1a21f22c..014beb91768 100644
--- a/addons/web/static/tests/legacy/views/form_tests.js
+++ b/addons/web/static/tests/legacy/views/form_tests.js
@@ -10277,12 +10277,11 @@ QUnit.module('LegacyViews', {
arch: `
`,
- mockRPC(route, args) {
- if (args.method === "render_public_asset") {
- assert.deepEqual(args.args, ["web.assets_backend_legacy_lazy"]);
+ mockFetch(route) {
+ if (route.includes("/web/bundle/")) {
+ assert.deepEqual(route, location.origin + "/web/bundle/web.assets_backend_legacy_lazy");
return Promise.resolve(true);
}
- return this._super(...arguments);
}
});
diff --git a/addons/web/static/tests/setup.js b/addons/web/static/tests/setup.js
index 61a24a5b137..5bf504f6af6 100644
--- a/addons/web/static/tests/setup.js
+++ b/addons/web/static/tests/setup.js
@@ -2,8 +2,10 @@
import core, { _t } from "web.core";
import session from "web.session";
+import { assets, templates } from "@web/core/assets";
import { browser, makeRAMLocalStorage } from "@web/core/browser/browser";
-import { patchTimeZone, patchWithCleanup } from "@web/../tests/helpers/utils";
+import { nextTick, patchTimeZone, patchWithCleanup } from "@web/../tests/helpers/utils";
+import { memoize } from "@web/core/utils/functions";
import { legacyProm } from "web.test_legacy";
import { registerCleanup } from "./helpers/cleanup";
import { utils } from "./helpers/mock_env";
@@ -15,8 +17,7 @@ import { loadLanguages } from "@web/core/l10n/translation";
transitionConfig.disabled = true;
import { patch } from "@web/core/utils/patch";
-import { processTemplates } from "@web/core/assets";
-const { App, whenReady, loadFile } = owl;
+const { App, whenReady } = owl;
const { prepareRegistriesWithCleanup } = utils;
@@ -263,7 +264,7 @@ function patchSessionInfo() {
* @param {Document} templates Document containing the templates to
* process.
*/
-function removeUnwantedAttrsFromTemplates(templates, attrs) {
+function removeUnwantedAttrsFromTemplates(attrs) {
function replaceAttr(attrName, prefix, element) {
const attrKey = `${prefix}${attrName}`;
const attrValue = element.getAttribute(attrKey);
@@ -280,6 +281,45 @@ function removeUnwantedAttrsFromTemplates(templates, attrs) {
}
}
+// alt attribute causes issues with scroll tests. Indeed, alt is
+// displayed between the time we scroll programatically and the time
+// we assert for the scroll position. The src attribute is removed
+// as well to make sure images won't trigger a GET request on the
+// server.
+
+// Clean up templates that have already been added.
+removeUnwantedAttrsFromTemplates(['alt', 'src']);
+
+const { loadXML, getBundle, loadJS, loadCSS } = assets;
+patch(assets, 'TestAssetsLoadXML', {
+ loadXML: function (templates) {
+ console.log("%c[assets] fetch XML ressource", "color: #66e; font-weight: bold;");
+ // Clean up new templates that might be added later.
+ loadXML(templates);
+ removeUnwantedAttrsFromTemplates(['alt', 'src']);
+ },
+ getBundle: memoize(async function (xmlID) {
+ console.log("%c[assets] fetch libs from xmlID: " + xmlID, "color: #66e; font-weight: bold;");
+ return getBundle(xmlID);
+ }),
+ loadJS: memoize(async function (ressource) {
+ if (ressource.match(/\/static(\/\S+\/|\/)libs?/)) {
+ console.log("%c[assets] fetch (mock) JS ressource: " + ressource, "color: #66e; font-weight: bold;");
+ return nextTick();
+ }
+ console.log("%c[assets] fetch JS ressource: " + ressource, "color: #66e; font-weight: bold;");
+ return loadJS(ressource);
+ }),
+ loadCSS: memoize(async function (ressource) {
+ if (ressource.match(/\/static(\/\S+\/|\/)libs?/)) {
+ console.log("%c[assets] fetch (mock) CSS ressource: " + ressource, "color: #66e; font-weight: bold;");
+ return nextTick();
+ }
+ console.log("%c[assets] fetch CSS ressource: " + ressource, "color: #66e; font-weight: bold;");
+ return loadCSS(ressource);
+ }),
+});
+
export async function setupTests() {
QUnit.testStart(() => {
checkGlobalObjectsIntegrity();
@@ -294,16 +334,6 @@ export async function setupTests() {
patchSessionInfo();
});
- const templatesUrl = `/web/webclient/qweb/${new Date().getTime()}?bundle=web.assets_qweb`;
- const templates = await loadFile(templatesUrl);
- window.__OWL_TEMPLATES__ = processTemplates(templates);
- // alt attribute causes issues with scroll tests. Indeed, alt is
- // displayed between the time we scroll programatically and the time
- // we assert for the scroll position. The src attribute is removed
- // as well to make sure images won't trigger a GET request on the
- // server.
- removeUnwantedAttrsFromTemplates(window.__OWL_TEMPLATES__, ["alt", "src"]);
- session.owlTemplates = window.__OWL_TEMPLATES__;
await Promise.all([whenReady(), legacyProm]);
// make sure images do not trigger a GET on the server
diff --git a/addons/web/tests/__init__.py b/addons/web/tests/__init__.py
index 35229ab1a12..4995f496933 100644
--- a/addons/web/tests/__init__.py
+++ b/addons/web/tests/__init__.py
@@ -6,7 +6,6 @@ from . import test_image
from . import test_ir_model
from . import test_js
from . import test_menu
-from . import test_serving_base
from . import test_click_everywhere
from . import test_base_document_layout
from . import test_load_menus
@@ -14,4 +13,5 @@ from . import test_profiler
from . import test_session_info
from . import test_read_progress_bar
from . import test_assets
+from . import test_assets_xml
from . import test_login
diff --git a/addons/web/tests/test_assets.py b/addons/web/tests/test_assets.py
index b277f82fb04..9156ad9f150 100644
--- a/addons/web/tests/test_assets.py
+++ b/addons/web/tests/test_assets.py
@@ -39,20 +39,19 @@ class TestAssetsGenerateTimeCommon(odoo.tests.TransactionCase):
_logger.info('Error detected while generating bundle %r %s', bundle, assets_type)
-@odoo.tests.tagged('post_install', '-at_install')
+@odoo.tests.tagged('post_install', '-at_install', 'assets_bundle')
class TestLogsAssetsGenerateTime(TestAssetsGenerateTimeCommon):
def test_logs_assets_generate_time(self):
"""
The purpose of this test is to monitor the time of assets bundle generation.
This is not meant to test the generation failure, hence the try/except and the mute logger.
- For example, 'web.assets_qweb' is contains only static xml.
"""
for bundle, duration in self.generate_bundles():
_logger.info('Bundle %r generated in %.2fs', bundle, duration)
-@odoo.tests.tagged('post_install', '-at_install', '-standard', 'bundle_generation')
+@odoo.tests.tagged('post_install', '-at_install', '-standard', 'assets_bundle')
class TestAssetsGenerateTime(TestAssetsGenerateTimeCommon):
"""
This test is meant to be run nightly to ensure bundle generation does not exceed
diff --git a/addons/web/tests/test_serving_base.py b/addons/web/tests/test_assets_xml.py
similarity index 73%
rename from addons/web/tests/test_serving_base.py
rename to addons/web/tests/test_assets_xml.py
index 39852e708fd..cf2c513f5c7 100644
--- a/addons/web/tests/test_serving_base.py
+++ b/addons/web/tests/test_assets_xml.py
@@ -8,57 +8,23 @@ from datetime import datetime
from lxml import etree
import logging
+import odoo
from odoo.tests.common import BaseCase, HttpCase, tagged
from odoo.tools import topological_sort
-from odoo.addons.web.controllers.utils import HomeStaticTemplateHelpers
+from odoo.addons.base.models.assetsbundle import AssetsBundle, WebAsset
+
_logger = logging.getLogger(__name__)
-def sample(population):
- return random.sample(
- population,
- random.randint(0, min(len(population), 5)))
-
-
-class TestModulesLoading(BaseCase):
- def setUp(self):
- self.mods = [str(i) for i in range(1000)]
-
- def test_topological_sort(self):
- random.shuffle(self.mods)
- modules = [
- (k, sample(self.mods[:i]))
- for i, k in enumerate(self.mods)]
- random.shuffle(modules)
- ms = dict(modules)
-
- seen = set()
- sorted_modules = topological_sort(ms)
- for module in sorted_modules:
- deps = ms[module]
- self.assertGreaterEqual(
- seen, set(deps),
- 'Module %s (index %d), ' \
- 'missing dependencies %s from loaded modules %s' % (
- module, sorted_modules.index(module), deps, seen
- ))
- seen.add(module)
-
-
-class TestStaticInheritanceCommon(BaseCase):
-
+class TestStaticInheritanceCommon(odoo.tests.TransactionCase):
def setUp(self):
super().setUp()
- # output is "manifest_glob" return
- self.asset_paths = [
- ('module_1_file_1', 'module_1', 'bundle_1'),
- ('module_2_file_1', 'module_2', 'bundle_1'),
- ]
self.template_files = {
- 'module_1_file_1': b"""
+ '/module_1/static/xml/file_1.xml': """
@@ -66,14 +32,16 @@ class TestStaticInheritanceCommon(BaseCase):
And I grew strong
- """,
-
- 'module_2_file_1': b"""
+ """,
+ '/module_2/static/xml/file_1.xml': """
- """,
+ """,
}
- self._set_patchers()
- self._toggle_patchers('start')
- self._reg_replace_ws = r"\s|\t"
+ self._patch = patch.object(WebAsset, '_fetch_content', lambda asset: self.template_files[asset.url])
+ self._patch.start()
def tearDown(self):
super().tearDown()
- self._toggle_patchers('stop')
+ self._patch.stop()
+
+ def renderBundle(self, debug=False):
+ files = []
+ for url in self.template_files:
+ atype = 'text/xml'
+ if '.js' in url:
+ atype = 'text/javascript'
+ files.append({
+ 'atype': atype,
+ 'url': url,
+ 'filename': url,
+ 'content': None,
+ 'media': None,
+ })
+ asset = AssetsBundle('web.test_bundle', files, env=self.env, css=False, js=True)
+ # to_node return the files descriptions and generate attachments.
+ asset.to_node(css=False, js=False, debug=debug and 'assets' or '')
+ content = asset.xml(show_inherit_info=debug)
+ return f'
\n{content}\n'
# Custom Assert
def assertXMLEqual(self, output, expected):
self.assertTrue(output)
self.assertTrue(expected)
- output = textwrap.dedent(output.decode('UTF-8')).strip()
- output = re.sub(self._reg_replace_ws, '', output)
+ self.assertEqual(etree.fromstring(output), etree.fromstring(expected))
- expected = textwrap.dedent(expected.decode('UTF-8')).strip()
- expected = re.sub(self._reg_replace_ws, '', expected)
- self.assertEqual(output, expected)
-
- # Private methods
- def _get_module_names(self):
- return ','.join([asset_path[1] for asset_path in self.asset_paths])
-
- def _set_patchers(self):
- def _patched_for_get_asset_paths(*args, **kwargs):
- # Ordered by module
- return self.asset_paths
-
- def _patch_for_read_addon_file(*args, **kwargs):
- return self.template_files[args[1]]
-
- self.patchers = [
- patch.object(HomeStaticTemplateHelpers, '_get_asset_paths', _patched_for_get_asset_paths),
- patch.object(HomeStaticTemplateHelpers, '_read_addon_file', _patch_for_read_addon_file),
- ]
-
- def _toggle_patchers(self, mode):
- self.assertTrue(mode in ('start', 'stop'))
- for p in self.patchers:
- getattr(p, mode)()
-
-
-@tagged('static_templates')
+@tagged('assets_bundle', 'static_templates')
class TestStaticInheritance(TestStaticInheritanceCommon):
# Actual test cases
- def test_static_inheritance_01(self):
- contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True)
- expected = b"""
-
+ def test_static_with_debug_mode(self):
+ expected = """
+
+
+
+
+
+
+ And I grew strong
+ And I learned how to get along
+
+
+
+
+
+
+
+
And I learned how to get along
+
+
+ """
+ self.assertXMLEqual(self.renderBundle(debug=True), expected)
+
+ def test_static_inheritance_01(self):
+ expected = """
+
+
@@ -147,6 +139,7 @@ class TestStaticInheritance(TestStaticInheritanceCommon):
And I learned how to get along
"""
-
- self.assertXMLEqual(contents, expected)
+ self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_static_inheritance_02(self):
self.template_files = {
- 'module_1_file_1': b'''
+ '/module_1/static/xml/file_1.xml': """
- '''
+ """,
}
- self.asset_paths = [
- ('module_1_file_1', 'module_1', 'bundle_1'),
- ]
- contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True)
- expected = b"""
-