[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..
This commit is contained in:
Géry Debongnie
2017-12-15 13:07:34 +01:00
committed by Aaron Bohy
parent 7f467d2346
commit 377705aada
3 changed files with 71 additions and 24 deletions
@@ -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) {
+53 -6
View File
@@ -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,
};
});
+7 -6
View File
@@ -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();
});