[ADD] web: add owl 1.0.0-beta1 to odoo
With this commit, we properly introduce the Owl library into our beloved codebase. More information on the Owl library can be found in the project repository: https://github.com/odoo/owl/ Co-authored-by: Aaron Bohy <aab@odoo.com> Co-authored-by: Julien Mougenot <jum@odoo.com> Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
This commit is contained in:
committed by
Vincent Schippefilt
co-authored by
Aaron Bohy
Julien Mougenot
Mathieu Duckerts-Antoine
parent
3b4592acfc
commit
29726e8fd0
File diff suppressed because it is too large
Load Diff
@@ -1247,7 +1247,7 @@
|
||||
|
||||
var LISTENERS = Object.create(null);
|
||||
var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"];
|
||||
SUPPORTED_EVENTS.push("OdooAfterTestHook"); // Odoo customization
|
||||
SUPPORTED_EVENTS.push("OdooBeforeTestHook", "OdooAfterTestHook"); // Odoo customization
|
||||
|
||||
/**
|
||||
* Emits an event with the specified data to all currently registered listeners.
|
||||
@@ -3001,7 +3001,8 @@
|
||||
_this.testEnvironment = extend({}, module.testEnvironment);
|
||||
|
||||
_this.started = now();
|
||||
emit("testStart", _this.testReport.start(true));
|
||||
emit("testStart", _this.testReport.start(true));
|
||||
emit("OdooBeforeTestHook"); // Odoo customization
|
||||
return runLoggingCallbacks("testStart", {
|
||||
name: _this.testName,
|
||||
module: module.name,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Add a new method to owl Components to ensure that no performed RPC is
|
||||
* resolved/rejected when the component is destroyed.
|
||||
*/
|
||||
(function () {
|
||||
owl.Component.prototype.rpc = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
return this.env.services.rpc(...arguments)
|
||||
.then(result => {
|
||||
if (!this.__owl__.isDestroyed) {
|
||||
resolve(result);
|
||||
}
|
||||
})
|
||||
.catch(reason => {
|
||||
if (!this.__owl__.isDestroyed) {
|
||||
reject(reason);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
})();
|
||||
@@ -4,7 +4,6 @@ odoo.define('web.Session', function (require) {
|
||||
var ajax = require('web.ajax');
|
||||
var concurrency = require('web.concurrency');
|
||||
var core = require('web.core');
|
||||
var local_storage = require('web.local_storage');
|
||||
var mixins = require('web.mixins');
|
||||
var utils = require('web.utils');
|
||||
|
||||
@@ -239,7 +238,14 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
|
||||
var route = '/web/webclient/qweb/' + (cacheId ? cacheId : Date.now()) + '?mods=' + mods;
|
||||
return $.get(route).then(function (doc) {
|
||||
if (!doc) { return; }
|
||||
const owlTemplates = [];
|
||||
for (let child of doc.querySelectorAll("templates > [owl]")) {
|
||||
child.removeAttribute('owl');
|
||||
owlTemplates.push(child.outerHTML);
|
||||
child.remove();
|
||||
}
|
||||
qweb.add_template(doc);
|
||||
self.owlTemplates = `<templates> ${owlTemplates.join('\n')} </templates>`;
|
||||
});
|
||||
});
|
||||
return lock;
|
||||
|
||||
@@ -203,7 +203,10 @@ var diacriticsMap = {
|
||||
'\u0225': 'z','\u0240': 'z','\u2C6C': 'z','\uA763': 'z',
|
||||
};
|
||||
|
||||
const patchMap = new WeakMap();
|
||||
|
||||
var utils = {
|
||||
|
||||
/**
|
||||
* Throws an error if the given condition is not true
|
||||
*
|
||||
@@ -510,6 +513,51 @@ var utils = {
|
||||
str = "" + str;
|
||||
return new Array(size - str.length + 1).join('0') + str;
|
||||
},
|
||||
/**
|
||||
* Patch a class and return a function that remove the patch
|
||||
* when called.
|
||||
*
|
||||
* @param {Class} C Class to patch
|
||||
* @param {string} patchName
|
||||
* @param {Object} patch
|
||||
* @returns {Function}
|
||||
*/
|
||||
patch: function(C, patchName, patch) {
|
||||
let metadata = patchMap.get(C.prototype);
|
||||
if (!metadata) {
|
||||
metadata = {
|
||||
origMethods: {},
|
||||
patches: {},
|
||||
current: []
|
||||
};
|
||||
patchMap.set(C.prototype, metadata);
|
||||
}
|
||||
const proto = C.prototype;
|
||||
if (metadata.patches[patchName]) {
|
||||
throw new Error(`Patch [${patchName}] already exists`);
|
||||
}
|
||||
metadata.patches[patchName] = patch;
|
||||
applyPatch(proto, patch);
|
||||
metadata.current.push(patchName);
|
||||
|
||||
function applyPatch(proto, patch) {
|
||||
Object.keys(patch).forEach(function (methodName) {
|
||||
const method = patch[methodName];
|
||||
if (typeof method === "function") {
|
||||
const original = proto[methodName];
|
||||
if (!(methodName in metadata.origMethods)) {
|
||||
metadata.origMethods[methodName] = original;
|
||||
}
|
||||
proto[methodName] = function (...args) {
|
||||
this._super = original;
|
||||
return method.call(this, ...args);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this.unpatch.bind(null, C, patchName);
|
||||
},
|
||||
/**
|
||||
* performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision
|
||||
* See the corresponding float_round() in server/tools/float_utils.py for more info
|
||||
@@ -661,6 +709,33 @@ var utils = {
|
||||
});
|
||||
return casesensetive ? str : str.toLowerCase();
|
||||
},
|
||||
/**
|
||||
* We define here an unpatch function. This is mostly useful if we want to
|
||||
* remove a patch. For example, for testing purposes
|
||||
*
|
||||
* @param {Class} C
|
||||
* @param {string} patchName
|
||||
*/
|
||||
unpatch: function(C, patchName) {
|
||||
const proto = C.prototype;
|
||||
let metadata = patchMap.get(proto);
|
||||
if (!metdata) {
|
||||
return;
|
||||
}
|
||||
patchMap.delete(proto);
|
||||
|
||||
// reset to original
|
||||
for (let k in metadata.origMethods) {
|
||||
proto[k] = metadata.origMethods[k];
|
||||
}
|
||||
|
||||
// apply other patches
|
||||
for (let name of metadata.current) {
|
||||
if (name !== patchName) {
|
||||
this.patch(C, name, metadata.patches[name]);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {any} node
|
||||
* @param {any} strip_whitespace
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
odoo.define("web.env", function (require) {
|
||||
"use strict";
|
||||
|
||||
const { _lt, _t, bus } = require("web.core");
|
||||
const { blockUI, unblockUI } = require("web.framework");
|
||||
const { device, isDebug } = require("web.config");
|
||||
const { jsonRpc } = require('web.ajax');
|
||||
const rpc = require("web.rpc");
|
||||
const session = require("web.session");
|
||||
const utils = require("web.utils");
|
||||
|
||||
|
||||
const qweb = new owl.QWeb({ translateFn: _t });
|
||||
|
||||
function ajaxJsonRPC() {
|
||||
return jsonRpc(...arguments);
|
||||
}
|
||||
|
||||
function getCookie() {
|
||||
return utils.get_cookie(...arguments);
|
||||
}
|
||||
|
||||
function httpRequest(route, params = {}, readMethod = 'json') {
|
||||
const formData = new FormData();
|
||||
for (const key in params) {
|
||||
if (key === 'method') {
|
||||
continue;
|
||||
}
|
||||
const value = params[key];
|
||||
if (Array.isArray(value) && value.length) {
|
||||
for (const val of value) {
|
||||
formData.append(key, val);
|
||||
}
|
||||
} else {
|
||||
formData.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(route, {
|
||||
method: params.method || 'POST',
|
||||
body: formData,
|
||||
}).then(response => response[readMethod]());
|
||||
}
|
||||
|
||||
function navigate(url, params) {
|
||||
window.location = $.param.querystring(url, params);
|
||||
}
|
||||
|
||||
function performRPC(params, options) {
|
||||
const query = rpc.buildQuery(params);
|
||||
return session.rpc(query.route, query.params, options);
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function setCookie() {
|
||||
utils.set_cookie(...arguments);
|
||||
}
|
||||
|
||||
// There should be as much dependencies as possible in the env object.
|
||||
// This will allow an easier testing of components.
|
||||
// See https://github.com/odoo/owl/blob/master/doc/reference/environment.md#content-of-an-environment
|
||||
// for more information on environments.
|
||||
return {
|
||||
_lt,
|
||||
_t,
|
||||
bus,
|
||||
device,
|
||||
isDebug,
|
||||
qweb,
|
||||
services: {
|
||||
ajaxJsonRPC,
|
||||
blockUI,
|
||||
getCookie,
|
||||
httpRequest,
|
||||
navigate,
|
||||
reloadPage,
|
||||
rpc: performRPC,
|
||||
setCookie,
|
||||
unblockUI,
|
||||
},
|
||||
session,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
odoo.define('web.web_client', function (require) {
|
||||
|
||||
const env = require('web.env');
|
||||
const session = require("web.session");
|
||||
const WebClient = require('web.WebClient');
|
||||
|
||||
owl.config.mode = env.isDebug() ? "dev" : "prod";
|
||||
owl.Component.env = env;
|
||||
|
||||
const webClient = new WebClient();
|
||||
|
||||
/**
|
||||
* Add the owl templates to the environment and start the web client.
|
||||
*/
|
||||
async function startWebClient() {
|
||||
await session.is_bound;
|
||||
env.qweb.addTemplates(session.owlTemplates);
|
||||
|
||||
await owl.utils.whenReady();
|
||||
webClient.setElement($(document.body));
|
||||
webClient.start();
|
||||
}
|
||||
|
||||
startWebClient();
|
||||
|
||||
return webClient;
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
odoo.define("web.public_env", function (require) {
|
||||
"use strict";
|
||||
|
||||
const { _t } = require("web.core");
|
||||
const rpc = require("web.rpc");
|
||||
|
||||
const qweb = new owl.QWeb({ translateFn: _t });
|
||||
|
||||
function performRPC(params, options) {
|
||||
const query = rpc.buildQuery(params);
|
||||
return session.rpc(query.route, query.params, options);
|
||||
}
|
||||
|
||||
// There should be as much dependencies as possible in the env object.
|
||||
// This will allow an easier testing of components.
|
||||
// See https://github.com/odoo/owl/blob/master/doc/reference/environment.md#content-of-an-environment
|
||||
// for more information on environments.
|
||||
return {
|
||||
qweb,
|
||||
services: {
|
||||
rpc: performRPC,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
odoo.define('web.component_extension_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const makeTestEnvironment = require("web.test_env");
|
||||
const testUtils = require("web.test_utils");
|
||||
|
||||
const { Component, tags } = owl;
|
||||
const { xml } = tags;
|
||||
|
||||
QUnit.module("web", function () {
|
||||
QUnit.module("Component Extension");
|
||||
|
||||
QUnit.test("Component destroyed while performing successful RPC", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class Parent extends Component {}
|
||||
Parent.env = makeTestEnvironment({}, () => Promise.resolve());
|
||||
Parent.template = xml`<div/>`;
|
||||
|
||||
const parent = new Parent();
|
||||
|
||||
parent.rpc({}).then(() => { throw new Error(); });
|
||||
parent.destroy();
|
||||
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.ok(true, "Promise should still be pending");
|
||||
});
|
||||
|
||||
QUnit.test("Component destroyed while performing failed RPC", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class Parent extends Component {}
|
||||
Parent.env = makeTestEnvironment({}, () => Promise.reject());
|
||||
Parent.template = xml`<div/>`;
|
||||
|
||||
const parent = new Parent();
|
||||
|
||||
parent.rpc({}).catch(() => { throw new Error(); });
|
||||
parent.destroy();
|
||||
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.ok(true, "Promise should still be pending");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
odoo.define('web.test_env', async function (require) {
|
||||
"use strict";
|
||||
|
||||
const { buildQuery } = require("web.rpc");
|
||||
const Bus = require("web.Bus");
|
||||
const session = require('web.session');
|
||||
|
||||
/**
|
||||
* Wrap the target object in a Proxy, giving it a generic getter that will
|
||||
* throw an error instead of returning `undefined` in case the property has
|
||||
* not been set.
|
||||
*
|
||||
* @param {Object} target
|
||||
* @param {string} name
|
||||
* @returns {Proxy}
|
||||
*/
|
||||
function _proxify(target, name) {
|
||||
for (const prop in target) {
|
||||
if (
|
||||
target.hasOwnProperty(prop) &&
|
||||
typeof target[prop] === 'object' &&
|
||||
target[prop] !== null
|
||||
) {
|
||||
target[prop] = _proxify(target[prop], `${name}.${prop}`);
|
||||
}
|
||||
}
|
||||
return new Proxy(target, {
|
||||
get(object, property) {
|
||||
if (typeof property === 'string' && !(property in object)) {
|
||||
throw new Error(`Property "${property}" not implemented in "${name}".`);
|
||||
}
|
||||
return object[property];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test environment with the given environment object.
|
||||
* Any access to a key that has not been explicitly defined in the given environment object
|
||||
* will result in an error.
|
||||
*
|
||||
* @param {Object} [env={}]
|
||||
* @param {Function} [providedRPC=null]
|
||||
* @returns {Proxy}
|
||||
*/
|
||||
function makeTestEnvironment(env = {}, providedRPC = null) {
|
||||
const proxiedEnv = _proxify(env, 'env');
|
||||
const defaultEnv = {
|
||||
_t: env._t || (s => s),
|
||||
bus: new Bus(),
|
||||
qweb: new owl.QWeb({ templates: session.owlTemplates }),
|
||||
services: Object.assign({
|
||||
rpc(params, options) {
|
||||
const query = buildQuery(params);
|
||||
return testEnv.session.rpc(query.route, query.params, options);
|
||||
},
|
||||
}, env.services),
|
||||
session: Object.assign({
|
||||
rpc(route, params, options) {
|
||||
if (providedRPC) {
|
||||
return providedRPC(route, params, options);
|
||||
}
|
||||
throw new Error(`No method to perform RPC`);
|
||||
},
|
||||
}, env.session),
|
||||
device: Object.assign({
|
||||
isMobile: false,
|
||||
}, env.device),
|
||||
};
|
||||
const testEnv = Object.assign(proxiedEnv, defaultEnv);
|
||||
return testEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Before each test, we want owl.Component.env to be a fresh test environment.
|
||||
*/
|
||||
QUnit.on('OdooBeforeTestHook', function () {
|
||||
owl.Component.env = makeTestEnvironment();
|
||||
});
|
||||
|
||||
return makeTestEnvironment;
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// define the 'web.web_client' module because some other modules require it
|
||||
odoo.define('web.web_client', async function (require) {
|
||||
"use strict";
|
||||
|
||||
const session = require("web.session");
|
||||
const WebClient = require('web.WebClient');
|
||||
|
||||
const webClient = new WebClient();
|
||||
|
||||
await session.is_bound;
|
||||
session.owlTemplates = session.owlTemplates.replace(/t-transition/g, 'transition');
|
||||
|
||||
return webClient;
|
||||
});
|
||||
@@ -86,6 +86,8 @@
|
||||
<script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/moment/moment.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/owl/owl.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/component_extension.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/jquery.ui/jquery-ui.js"></script>
|
||||
@@ -263,6 +265,7 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/tools/test_menus_loader.js"/>
|
||||
<script type="text/javascript" src="/web/static/src/js/tools/debug_manager_backend.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/tools/tools.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/env.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/widgets/data_export.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/widgets/date_picker.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/widgets/domain_selector_dialog.js"></script>
|
||||
@@ -398,6 +401,7 @@
|
||||
<t t-call="web._assets_frontend_minimal_js"/>
|
||||
|
||||
<script type="text/javascript" src="/web/static/src/js/services/session.js"/>
|
||||
<script type="text/javascript" src="/web/static/src/js/public/public_env.js"/>
|
||||
<script type="text/javascript" src="/web/static/src/js/public/public_crash_manager.js"/>
|
||||
<script type="text/javascript" src="/web/static/src/js/public/public_notification.js"/>
|
||||
<script type="text/javascript" src="/web/static/src/js/public/public_root.js"/>
|
||||
@@ -602,14 +606,7 @@
|
||||
<script type="text/javascript" src="/web/static/lib/ace/mode-xml.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/ace/mode-js.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
// define the 'web.web_client' module because some other modules require it
|
||||
odoo.define('web.web_client', function (require) {
|
||||
var WebClient = require('web.WebClient');
|
||||
var web_client = new WebClient();
|
||||
return web_client;
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" src="/web/static/tests/main_tests.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
@@ -640,7 +637,9 @@
|
||||
<script type="text/javascript" src="/web/static/tests/helpers/test_utils_pivot.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/helpers/test_utils.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/helpers/mock_server.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/helpers/test_env.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/web/static/tests/component_extension_tests.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/boot_tests.js"></script>
|
||||
</template>
|
||||
|
||||
@@ -864,17 +863,7 @@
|
||||
<![endif]-->
|
||||
]]>
|
||||
|
||||
<script type="text/javascript">
|
||||
odoo.define('web.web_client', function (require) {
|
||||
var WebClient = require('web.WebClient');
|
||||
var web_client = new WebClient();
|
||||
$(function() {
|
||||
web_client.setElement($(document.body));
|
||||
web_client.start();
|
||||
});
|
||||
return web_client;
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" src="/web/static/src/js/main.js"></script>
|
||||
</t>
|
||||
<t t-set="head" t-value="head_web + (head or '')"/>
|
||||
<t t-set="body_classname" t-value="'o_web_client'"/>
|
||||
|
||||
Reference in New Issue
Block a user