summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Caburnay <jcb@odoo.com>2020-03-06 08:11:36 +0000
committerJoseph Caburnay <jcb@odoo.com>2020-03-06 09:04:20 +0000
commitd6b854306cae245343a7acb9edc4178d245a49fe (patch)
treec339d3778c291cea01339f36d9db1c1ad7ed4a43
parented1a39c61826d740e2f34f2d2d9072603021503b (diff)
[IMP] web: allow patching getters
Previous implementation of patch (web.utils) only allows patching methods. This commit allows patching getters as well. closes odoo/odoo#47047 Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
-rw-r--r--addons/web/static/src/js/core/utils.js52
-rw-r--r--addons/web/static/tests/core/util_tests.js80
2 files changed, 124 insertions, 8 deletions
diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js
index 243f3f51cd0..9bc1a287512 100644
--- a/addons/web/static/src/js/core/utils.js
+++ b/addons/web/static/src/js/core/utils.js
@@ -520,6 +520,10 @@ var utils = {
* Patch a class and return a function that remove the patch
* when called.
*
+ * In the patch object, super methods and getters are accessible thru:
+ * - this._super() - for method
+ * - this._super - for getter
+ *
* @param {Class} C Class to patch
* @param {string} patchName
* @param {Object} patch
@@ -531,7 +535,8 @@ var utils = {
metadata = {
origMethods: {},
patches: {},
- current: []
+ current: [],
+ originalDescriptors: {},
};
patchMap.set(C.prototype, metadata);
}
@@ -544,20 +549,41 @@ var utils = {
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;
+ Object.keys(patch).forEach(function(property) {
+ const protoDescriptor = Object.getOwnPropertyDescriptor(proto, property);
+ const patchDescriptor = Object.getOwnPropertyDescriptor(patch, property);
+ if (!protoDescriptor && patchDescriptor) {
+ if (!(property in metadata.originalDescriptors)) {
+ metadata.originalDescriptors[property] = undefined;
+ }
+ Object.defineProperty(proto, property, patchDescriptor);
+ return;
+ }
+ if (protoDescriptor.value) {
+ const method = patch[property];
+ if (typeof method !== 'function') return;
+ const original = proto[property];
+ if (!(property in metadata.origMethods)) {
+ metadata.origMethods[property] = original;
}
- proto[methodName] = function (...args) {
+ proto[property] = function(...args) {
const previousSuper = this._super;
this._super = original;
const res = method.call(this, ...args);
this._super = previousSuper;
return res;
};
+ } else if (protoDescriptor.get) {
+ if (!(property in metadata.originalDescriptors)) {
+ metadata.originalDescriptors[property] = protoDescriptor;
+ }
+ const patchDescriptor = Object.getOwnPropertyDescriptor(patch, property);
+ Object.defineProperty(proto, property, {
+ get() {
+ this._super = protoDescriptor.get.apply(this);
+ return patchDescriptor.get.apply(this);
+ },
+ });
}
});
}
@@ -735,6 +761,16 @@ var utils = {
proto[k] = metadata.origMethods[k];
}
+ // reset the descriptors
+ for (let k in metadata.originalDescriptors) {
+ const descriptor = metadata.originalDescriptors[k];
+ if (descriptor === undefined) {
+ delete proto[k];
+ } else {
+ Object.defineProperty(proto, k, descriptor);
+ }
+ }
+
// apply other patches
for (let name of metadata.current) {
if (name !== patchName) {
diff --git a/addons/web/static/tests/core/util_tests.js b/addons/web/static/tests/core/util_tests.js
index 91a9f6b271b..f33a76d87c1 100644
--- a/addons/web/static/tests/core/util_tests.js
+++ b/addons/web/static/tests/core/util_tests.js
@@ -101,6 +101,86 @@ QUnit.module('core', {}, function () {
assert.strictEqual(human_number(-1.012e+43, 2, 2), '-1.01e+43');
});
+ QUnit.test('patch a class with getter', function (assert) {
+ assert.expect(13);
+
+ const { patch } = utils;
+
+ class TestClass {
+ get val() {
+ return 'aaa';
+ }
+ }
+
+ const testInstance = new TestClass();
+ assert.strictEqual(testInstance.val, 'aaa');
+
+ // Extend `val`
+
+ const removePatch1 = patch(TestClass, 'patch1', {
+ get val() {
+ return this._super + 'bbb';
+ },
+ });
+ assert.strictEqual(testInstance.val, 'aaabbb');
+
+ const removePatch2 = patch(TestClass, 'patch2', {
+ get val() {
+ return this._super + 'ccc';
+ },
+ });
+ assert.strictEqual(testInstance.val, 'aaabbbccc');
+
+ removePatch1();
+ assert.strictEqual(testInstance.val, 'aaaccc');
+
+ removePatch2();
+ assert.strictEqual(testInstance.val, 'aaa');
+
+ // Add getters
+
+ const removeMorePatch1 = patch(TestClass, 'more-patch-1', {
+ get realVal() {
+ return `${this.wrapper.start}${this.val}${this.wrapper.end}`
+ },
+ get wrapper() {
+ return {
+ start: '(',
+ end: ')',
+ }
+ }
+ })
+ assert.strictEqual(testInstance.realVal, '(aaa)');
+
+ const removeMorePatch2 = patch(TestClass, 'more-patch-2', {
+ get wrapper() {
+ return {
+ start: `[${this._super.start}`,
+ end: `${this._super.end}]`,
+ }
+ }
+ })
+ assert.strictEqual(testInstance.realVal, '[(aaa)]');
+
+ const removeMorePatch3 = patch(TestClass, 'more-patch-3', {
+ get realVal() {
+ return `${this._super} - augmented`;
+ }
+ })
+ assert.strictEqual(testInstance.realVal, '[(aaa)] - augmented');
+
+ removeMorePatch2();
+ assert.strictEqual(testInstance.realVal, '(aaa) - augmented');
+
+ removeMorePatch3();
+ assert.strictEqual(testInstance.realVal, '(aaa)');
+
+ removeMorePatch1();
+ assert.strictEqual(testInstance.realVal, undefined);
+ assert.strictEqual(testInstance.wrapper, undefined);
+ assert.strictEqual(testInstance.val, 'aaa');
+ });
+
});
});