[IMP] web: add es6 class patcher
This commit implements a new way to monkey-patch ES6
classes (for instance, Owl Components).
Usage
-----
odoo.module('module.BaseClass', function (require) {
const patchMixin = require('web.patchMixin');
// Define your class as usual
class BaseClass {
constructor() {
this.foo = 'foo';
}
bar() {
console.log(this.foo);
}
}
// but export it like this to have a "patchable" version
return patchMixin(BaseClass);
});
odoo.module('module.Patch', function (require) {
// imports the "patchable" version
const BaseClass = require('module.BaseClass');
BaseClass.patch('key', T =>
class extends T {
constructor() {
super();
this.foo = 'patched foo';
}
bar() {
super.bar();
console.log('patched bar()');
}
}
);
});
odoo.module('module.instance', function (require) {
// imports the "patchable" version
const BaseClass = require('module.BaseClass');
const obj = new BaseClass();
obj.bar();
// if module.patch is loaded before this module the patch
// will be apply and calling obj.bar() will log:
// patched foo
// patched bar()
// You can unpatch a patchable class
BaseClass.unpatch('key');
});
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
odoo.define("web.patchMixin", function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This module defines and exports the 'patchMixin' function. This function
|
||||
* returns a 'monkey-patchable' version of the ES6 Class given in arguments.
|
||||
*
|
||||
* const patchMixin = require('web.patchMixin');
|
||||
* class MyClass {
|
||||
* print() {
|
||||
* console.log('MyClass');
|
||||
* }
|
||||
* }
|
||||
* const MyPatchedClass = patchMixin(MyClass);
|
||||
*
|
||||
*
|
||||
* A patchable class has a 'patch' function, allowing to define a patch:
|
||||
*
|
||||
* MyPatchedClass.patch("module_name.key", T =>
|
||||
* class extends T {
|
||||
* print() {
|
||||
* console.log('MyPatchedClass');
|
||||
* super.print();
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* const myPatchedClass = new MyPatchedClass();
|
||||
* myPatchedClass.print(); // displays "MyPatchedClass" and "MyClass"
|
||||
*
|
||||
*
|
||||
* The 'unpatch' function can be used to remove a patch, given its key:
|
||||
*
|
||||
* MyPatchedClass.unpatch("module_name.key");
|
||||
*/
|
||||
function patchMixin(OriginalClass) {
|
||||
let unpatchList = [];
|
||||
class PatchableClass extends OriginalClass {}
|
||||
|
||||
PatchableClass.patch = function (name, patch) {
|
||||
if (unpatchList.find(x => x.name === name)) {
|
||||
throw new Error(`Class ${OriginalClass.name} already has a patch ${name}`);
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(this, 'patch')) {
|
||||
throw new Error(`Class ${this.name} is not patchable`);
|
||||
}
|
||||
const SubClass = patch(Object.getPrototypeOf(this));
|
||||
unpatchList.push({
|
||||
name: name,
|
||||
elem: this,
|
||||
prototype: this.prototype,
|
||||
origProto: Object.getPrototypeOf(this),
|
||||
origPrototype: Object.getPrototypeOf(this.prototype),
|
||||
patch: patch,
|
||||
});
|
||||
Object.setPrototypeOf(this, SubClass);
|
||||
Object.setPrototypeOf(this.prototype, SubClass.prototype);
|
||||
};
|
||||
|
||||
PatchableClass.unpatch = function (name) {
|
||||
if (!unpatchList.find(x => x.name === name)) {
|
||||
throw new Error(`Class ${OriginalClass.name} does not have any patch ${name}`);
|
||||
}
|
||||
const toUnpatch = unpatchList.reverse();
|
||||
unpatchList = [];
|
||||
for (let unpatch of toUnpatch) {
|
||||
Object.setPrototypeOf(unpatch.elem, unpatch.origProto);
|
||||
Object.setPrototypeOf(unpatch.prototype, unpatch.origPrototype);
|
||||
}
|
||||
for (let u of toUnpatch.reverse()) {
|
||||
if (u.name !== name) {
|
||||
PatchableClass.patch(u.name, u.patch);
|
||||
}
|
||||
}
|
||||
};
|
||||
return PatchableClass;
|
||||
}
|
||||
|
||||
return patchMixin;
|
||||
});
|
||||
@@ -527,7 +527,12 @@ var utils = {
|
||||
/**
|
||||
* Patch a class and return a function that remove the patch
|
||||
* when called.
|
||||
*
|
||||
*
|
||||
* This function is the last resort solution for monkey-patching an
|
||||
* ES6 Class, for people that do not control the code defining the Class
|
||||
* to patch (e.g. partners), and when that Class isn't patchable already
|
||||
* (i.e. when it doesn't have a 'patch' function, defined by the 'web.PatchMixin').
|
||||
*
|
||||
* @param {Class} C Class to patch
|
||||
* @param {string} patchName
|
||||
* @param {Object} patch
|
||||
|
||||
@@ -0,0 +1,994 @@
|
||||
odoo.define("web.patchMixin_tests", function (require) {
|
||||
"use strict";
|
||||
|
||||
const patchMixin = require('web.patchMixin');
|
||||
|
||||
QUnit.module('core', {}, function () {
|
||||
|
||||
QUnit.module('patchMixin', {}, function () {
|
||||
|
||||
QUnit.test('basic use', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const a = new A();
|
||||
a.f();
|
||||
|
||||
assert.ok(a instanceof A);
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'A.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('simple patch', function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'patch.constructor',
|
||||
'A.f',
|
||||
'patch.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('two patches on same base class', function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch1', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('patch1.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch1.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch2', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('patch2.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch2.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'patch1.constructor',
|
||||
'patch2.constructor',
|
||||
'A.f',
|
||||
'patch1.f',
|
||||
'patch2.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('two patches with same name on same base class', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const A = patchMixin(class {});
|
||||
|
||||
A.patch('patch', T => class extends T {});
|
||||
|
||||
// keys should be unique
|
||||
assert.throws(() => {
|
||||
A.patch('patch', T => class extends T {});
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('unpatch', function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'patch.constructor',
|
||||
'A.f',
|
||||
'patch.f',
|
||||
]);
|
||||
|
||||
A.unpatch('patch');
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'A.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('unpatch 2', function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch1', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('patch1.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch1.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch2', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('patch2.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch2.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'patch1.constructor',
|
||||
'patch2.constructor',
|
||||
'A.f',
|
||||
'patch1.f',
|
||||
'patch2.f',
|
||||
]);
|
||||
|
||||
A.unpatch('patch1');
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'patch2.constructor',
|
||||
'A.f',
|
||||
'patch2.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('unpatch inexistent', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const A = patchMixin(class {});
|
||||
A.patch('patch', T => class extends T {});
|
||||
|
||||
A.unpatch('patch');
|
||||
assert.throws(() => {
|
||||
A.unpatch('inexistent-patch');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('patch for specialization', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
let args = [];
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
args = ['A', ...arguments];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super('patch', ...arguments);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
new A('instantiation');
|
||||
|
||||
assert.deepEqual(args, ['A', 'patch', 'instantiation']);
|
||||
});
|
||||
|
||||
QUnit.test('instance fields', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
this.x = ['A'];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
this.x.push('patch');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const a = new A();
|
||||
assert.deepEqual(a.x, ['A', 'patch']);
|
||||
});
|
||||
|
||||
QUnit.test('call instance method defined in patch', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const A = patchMixin(
|
||||
class {}
|
||||
);
|
||||
|
||||
assert.notOk((new A()).f);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
f() {
|
||||
assert.step('patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
(new A()).f();
|
||||
assert.verifySteps(['patch.f']);
|
||||
});
|
||||
|
||||
QUnit.test('class methods', function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
static f() {
|
||||
assert.step('A');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.f();
|
||||
assert.verifySteps(['A']);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
static f() {
|
||||
super.f();
|
||||
assert.step('patch');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.f();
|
||||
assert.verifySteps(['A', 'patch']);
|
||||
|
||||
A.unpatch('patch');
|
||||
|
||||
A.f();
|
||||
assert.verifySteps(['A']);
|
||||
});
|
||||
|
||||
QUnit.test('class fields', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
class A {}
|
||||
A.foo = ['A'];
|
||||
A.bar = 'A';
|
||||
|
||||
const PatchableA = patchMixin(A);
|
||||
|
||||
PatchableA.patch('patch', T => {
|
||||
class Patch extends T {}
|
||||
|
||||
Patch.foo = [...T.foo, 'patched A'];
|
||||
Patch.bar = 'patched A';
|
||||
|
||||
return Patch;
|
||||
});
|
||||
|
||||
assert.deepEqual(PatchableA.foo, ['A', 'patched A']);
|
||||
assert.strictEqual(PatchableA.bar, 'patched A');
|
||||
|
||||
PatchableA.unpatch('patch');
|
||||
|
||||
assert.deepEqual(PatchableA.foo, ['A']);
|
||||
assert.strictEqual(PatchableA.bar, 'A');
|
||||
});
|
||||
|
||||
QUnit.test('lazy patch', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const a = new A();
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
// will not be called
|
||||
assert.step('patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
a.f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'A.f',
|
||||
'patch.f',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
QUnit.module('inheritance');
|
||||
|
||||
QUnit.test('inheriting a patchable class', function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
class B extends A {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.f');
|
||||
}
|
||||
}
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'A.f',
|
||||
]);
|
||||
|
||||
(new B()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'B.constructor',
|
||||
'A.f',
|
||||
'B.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('inheriting a patchable class that has patch', function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
class B extends A {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.f');
|
||||
}
|
||||
}
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'patch.constructor',
|
||||
'A.f',
|
||||
'patch.f',
|
||||
]);
|
||||
|
||||
(new B()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'patch.constructor',
|
||||
'B.constructor',
|
||||
'A.f',
|
||||
'patch.f',
|
||||
'B.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('patch inherited patchable class', function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const B = patchMixin(
|
||||
class extends A {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
B.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'A.f',
|
||||
]);
|
||||
|
||||
(new B()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'B.constructor',
|
||||
'patch.constructor',
|
||||
'A.f',
|
||||
'B.f',
|
||||
'patch.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('patch inherited patched class', function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A.patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('A.patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* /!\ WARNING /!\
|
||||
*
|
||||
* If you want to patch class B, make it patchable
|
||||
* otherwise it will patch class A!
|
||||
*/
|
||||
const B = patchMixin(
|
||||
class extends A {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
B.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const a = new A();
|
||||
a.f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'A.patch.constructor',
|
||||
'A.f',
|
||||
'A.patch.f',
|
||||
]);
|
||||
|
||||
const b = new B();
|
||||
b.f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'A.patch.constructor',
|
||||
'B.constructor',
|
||||
'B.patch.constructor',
|
||||
'A.f',
|
||||
'A.patch.f',
|
||||
'B.f',
|
||||
'B.patch.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('unpatch inherited patched class', function (assert) {
|
||||
assert.expect(15);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A.patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('A.patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const B = patchMixin(
|
||||
class extends A {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
B.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.unpatch('patch');
|
||||
|
||||
(new A()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'A.f',
|
||||
]);
|
||||
|
||||
(new B()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'B.constructor',
|
||||
'B.patch.constructor',
|
||||
'A.f',
|
||||
'B.f',
|
||||
'B.patch.f',
|
||||
]);
|
||||
|
||||
B.unpatch('patch');
|
||||
|
||||
(new B()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'B.constructor',
|
||||
'A.f',
|
||||
'B.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('unpatch inherited patched class 2', function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A.patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('A.patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const B = patchMixin(
|
||||
class extends A {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
B.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
B.unpatch('patch');
|
||||
|
||||
(new B()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'A.patch.constructor',
|
||||
'B.constructor',
|
||||
'A.f',
|
||||
'A.patch.f',
|
||||
'B.f',
|
||||
]);
|
||||
|
||||
A.unpatch('patch');
|
||||
|
||||
(new B()).f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'B.constructor',
|
||||
'A.f',
|
||||
'B.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('class methods', function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
static f() {
|
||||
assert.step('A');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const B = patchMixin(
|
||||
class extends A {
|
||||
static f() {
|
||||
super.f();
|
||||
assert.step('B');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
static f() {
|
||||
super.f();
|
||||
assert.step('A.patch');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
B.patch('patch', T =>
|
||||
class extends T {
|
||||
static f() {
|
||||
super.f();
|
||||
assert.step('B.patch');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
B.f();
|
||||
assert.verifySteps(['A', 'A.patch', 'B', 'B.patch']);
|
||||
|
||||
A.unpatch('patch');
|
||||
|
||||
B.f();
|
||||
assert.verifySteps(['A', 'B', 'B.patch']);
|
||||
|
||||
B.unpatch('patch');
|
||||
|
||||
B.f();
|
||||
assert.verifySteps(['A', 'B']);
|
||||
});
|
||||
|
||||
QUnit.test('class fields', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
class A {}
|
||||
A.foo = ['A'];
|
||||
A.bar = 'A';
|
||||
|
||||
const PatchableA = patchMixin(A);
|
||||
|
||||
class B extends PatchableA {}
|
||||
// /!\ This is not dynamic
|
||||
// so if A.foo is patched after this assignment
|
||||
// B.foo won't have the patches of A.foo
|
||||
B.foo = [...PatchableA.foo, 'B'];
|
||||
B.bar = 'B';
|
||||
|
||||
const PatchableB = patchMixin(B);
|
||||
|
||||
PatchableA.patch('patch', T => {
|
||||
class Patch extends T {}
|
||||
|
||||
Patch.foo = [...T.foo, 'patched A'];
|
||||
Patch.bar = 'patched A';
|
||||
|
||||
return Patch;
|
||||
});
|
||||
|
||||
PatchableB.patch('patch', T => {
|
||||
class Patch extends T {}
|
||||
|
||||
Patch.foo = [...T.foo, 'patched B'];
|
||||
Patch.bar = 'patched B';
|
||||
|
||||
return Patch;
|
||||
});
|
||||
|
||||
assert.deepEqual(PatchableB.foo, [ 'A', /* 'patched A', */ 'B', 'patched B' ]);
|
||||
assert.deepEqual(PatchableA.foo, [ 'A', 'patched A' ]);
|
||||
assert.strictEqual(PatchableB.bar, 'patched B');
|
||||
});
|
||||
|
||||
QUnit.test('inheritance and lazy patch', function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const A = patchMixin(
|
||||
class {
|
||||
constructor() {
|
||||
assert.step('A.constructor');
|
||||
}
|
||||
f() {
|
||||
assert.step('A.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
class B extends A {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('B.f');
|
||||
}
|
||||
}
|
||||
|
||||
const b = new B();
|
||||
|
||||
A.patch('patch', T =>
|
||||
class extends T {
|
||||
constructor() {
|
||||
super();
|
||||
// will not be called
|
||||
assert.step('patch.constructor');
|
||||
}
|
||||
f() {
|
||||
super.f();
|
||||
assert.step('patch.f');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
b.f();
|
||||
|
||||
assert.verifySteps([
|
||||
'A.constructor',
|
||||
'B.constructor',
|
||||
'A.f',
|
||||
'patch.f',
|
||||
'B.f',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('patch not patchable class that inherits patchable class', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const A = patchMixin(class {});
|
||||
class B extends A {}
|
||||
|
||||
// class B is not patchable
|
||||
assert.throws(() => {
|
||||
B.patch('patch', T => class extends T {});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -144,6 +144,7 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/core/dom.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/local_storage.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/mixins.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/patch_mixin.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/qweb.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/ram_storage.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/registry.js"></script>
|
||||
@@ -721,6 +722,7 @@
|
||||
<script type="text/javascript" src="/web/static/tests/core/data_comparison_utils_tests.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/core/math_utils_tests.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/core/mixins_tests.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/core/patch_mixin_tests.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/core/time_tests.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/core/concurrency_tests.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/core/util_tests.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user