From 377705aada34fd98ae92c39cb0a1f0151ee3f720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 7 Dec 2017 14:06:29 +0100 Subject: [PATCH] [IMP] web: tests: add a way to properly mock Widgets This rev. introduces two helper functions for the tests: patch and unpatch. Those function allow patching an Odoo Widget (e.g. AbstractField), and unpatch it at the end of the test with the guarantee that the Widget's prototype is exactly the same as it was before the test. This was necessary because the way it was done before was a little bit too naive, and could lead to unexpected behavior. Indeed, doing var someFunction = AbstractField.prototype.someFunction; // test content AbstractField.prototype.someFunction = someFunction; is correct if someFunction is actually define (or overriden) in AbstractField, as in that case, it really exists on its prototype. Otherwise, it isn't correct if it is not directly defined on the prototype before the test, as it will be after.. --- .../tests/fields/relational_fields_tests.js | 23 ++++---- addons/web/static/tests/helpers/test_utils.js | 59 +++++++++++++++++-- addons/web/static/tests/views/list_tests.js | 13 ++-- 3 files changed, 71 insertions(+), 24 deletions(-) diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 6dbe709a324..f7a63a18f9b 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -4040,16 +4040,16 @@ QUnit.module('relational_fields', { assert.expect(5); var delta = 0; - var oldInit = AbstractField.prototype.init; - var oldDestroy = AbstractField.prototype.destroy; - AbstractField.prototype.init = function () { - delta++; - oldInit.apply(this, arguments); - }; - AbstractField.prototype.destroy = function () { - delta--; - oldDestroy.apply(this, arguments); - }; + testUtils.patch(AbstractField, { + init: function () { + delta++; + this._super.apply(this, arguments); + }, + destroy: function () { + delta--; + this._super.apply(this, arguments); + }, + }); var deactiveOnchange = true; @@ -4136,8 +4136,7 @@ QUnit.module('relational_fields', { "should correctly refresh the records after save"); form.destroy(); - AbstractField.prototype.init = oldInit; - AbstractField.prototype.destroy = oldDestroy; + testUtils.unpatch(AbstractField); }); QUnit.test('one2many editable list with onchange keeps the order', function (assert) { diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index 37a98de1746..f7b1221f6f8 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -551,6 +551,51 @@ function removeSrcAttribute($el, widget) { }); } +var patches = {}; +/** + * Patches a given Class with the given properties. + * + * @param {Class} Klass + * @param {Object} props + */ +function patch (Klass, props) { + var patchID = _.uniqueId('patch_'); + Klass.__patchID = patchID; + patches[patchID] = { + Klass: Klass, + otherPatchedProps: [], + ownPatchedProps: [], + }; + _.each(props, function (value, key) { + if (Klass.prototype.hasOwnProperty(key)) { + patches[patchID].ownPatchedProps.push({ + key: key, + initialValue: Klass.prototype[key], + }); + } else { + patches[patchID].otherPatchedProps.push(key); + } + }); + Klass.include(props); +} +/** + * Unpatches a given Class. + * + * @param {Class} Klass + */ +function unpatch(Klass) { + var patchID = Klass.__patchID; + var patch = patches[patchID]; + _.each(patch.ownPatchedProps, function (p) { + Klass[p.key] = p.initialValue; + }); + _.each(patch.otherPatchedProps, function (key) { + delete Klass.prototype[key]; + }); + delete patches[patchID]; + delete Klass.__patchID; +} + // Loading static files cannot be properly simulated when their real content is // really needed. This is the case for static XML files so we load them here, // before starting the qunit test suite. @@ -566,18 +611,20 @@ return $.when( QUnit.start(); }, 0); return { - intercept: intercept, - observe: observe, - createView: createView, + addMockEnvironment: addMockEnvironment, createAsyncView: createAsyncView, createModel: createModel, createParent: createParent, - addMockEnvironment: addMockEnvironment, + createView: createView, dragAndDrop: dragAndDrop, + intercept: intercept, + observe: observe, + patch: patch, + removeSrcAttribute: removeSrcAttribute, + triggerKeypressEvent: triggerKeypressEvent, triggerMouseEvent: triggerMouseEvent, triggerPositionalMouseEvent: triggerPositionalMouseEvent, - triggerKeypressEvent: triggerKeypressEvent, - removeSrcAttribute: removeSrcAttribute, + unpatch: unpatch, }; }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 9180015685e..44c76d41c34 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -415,11 +415,12 @@ QUnit.module('Views', { QUnit.test('editable list: add a line and discard', function (assert) { assert.expect(11); - var oldDestroy = basicFields.FieldChar.prototype.destroy; - basicFields.FieldChar.prototype.destroy = function () { - assert.step('destroy'); - oldDestroy.apply(this, arguments); - }; + testUtils.patch(basicFields.FieldChar, { + destroy: function () { + assert.step('destroy'); + this._super.apply(this, arguments); + }, + }); var list = createView({ View: ListView, @@ -456,7 +457,7 @@ QUnit.module('Views', { assert.verifySteps(['destroy'], "should have destroyed the widget of the removed line"); - basicFields.FieldChar.prototype.destroy = oldDestroy; + testUtils.unpatch(basicFields.FieldChar); list.destroy(); });