From 0ba11283c63a878271b85c282f75190758101e63 Mon Sep 17 00:00:00 2001 From: Tanner Nielsen Date: Wed, 22 Apr 2020 23:19:51 -0500 Subject: [PATCH] fix: wrong this in init functions for Mixin(A, Mixin(B, C)) scenario --- src/mixins.ts | 21 ++++++++--- test/integration/init-function.test.ts | 51 ++++++++------------------ 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/src/mixins.ts b/src/mixins.ts index bc3ee4f..884efaf 100644 --- a/src/mixins.ts +++ b/src/mixins.ts @@ -197,17 +197,28 @@ function Mixin< function Mixin(...constructors: Class[]) { const prototypes = constructors.map(constructor => constructor.prototype); - // NOTE: we save the init function name here because MixedClass could be called with different settings than Mixin const initFunctionName = settings.initFunction; + if (initFunctionName !== null) { + const initFunctions: Function[] = prototypes + .map(proto => proto[initFunctionName]) + .filter(func => typeof func === 'function'); + + const combinedInitFunction = function(...args) { + for (let initFunction of initFunctions) + initFunction.apply(this, args); + }; + + const extraProto = { [initFunctionName]: combinedInitFunction }; + + prototypes.push(extraProto); + } function MixedClass(...args) { for (const constructor of constructors) copyProps(this, new constructor(...args)); - if (initFunctionName !== null) - for (const constructor of constructors) - if (typeof constructor.prototype[initFunctionName] === 'function') - constructor.prototype[initFunctionName].apply(this, args); + if (initFunctionName !== null && typeof this[initFunctionName] === 'function') + this[initFunctionName].apply(this, args); } MixedClass.prototype = settings.prototypeStrategy === 'copy' diff --git a/test/integration/init-function.test.ts b/test/integration/init-function.test.ts index 0fc39a2..b8b5089 100644 --- a/test/integration/init-function.test.ts +++ b/test/integration/init-function.test.ts @@ -83,58 +83,37 @@ describe('Using an init function', () => { } }); - it('should work with multiple layers of inheritance', () => { + it('should receive proper `this` in the Mixin(A, Mixin(B, C)) scenario', () => { settings.initFunction = 'init'; - abstract class ClassA { - public name; + class ClassA { + public initContextA = null; protected init() { - this.name = this.constructor.name; + this.initContextA = this; } } - class ClassB extends ClassA { - public name1; + class ClassB { + public initContextB = null; protected init() { - super.init(); - this.name1 = this.name + 1; + this.initContextB = this; } } - class ClassC extends ClassA { - public name2; + class ClassC { + public initContextC = null; protected init() { - super.init(); - this.name2 = this.name + 2; + this.initContextC = this; } } - class ClassD extends ClassA { - public name3; - protected init() { - super.init(); - this.name3 = this.name + 3; - } - } - - class ClassE extends Mixin(ClassB, ClassC) {} - class ClassF extends Mixin(ClassD, ClassE) {} - - const e = new ClassE(); - const f = new ClassF(); + class ClassD extends Mixin(ClassA, Mixin(ClassB, ClassC)) {} - expect(e).to.deep.equal({ - name: 'ClassE', - name1: 'ClassE1', - name2: 'ClassE2', - }); + const d = new ClassD(); - expect(f).to.deep.equal({ - name: 'ClassF', - name1: 'ClassF1', - name2: 'ClassF2', - name3: 'ClassF3', - }); + expect(d.initContextA).to.equal(d); + expect(d.initContextB).to.equal(d); + expect(d.initContextC).to.equal(d); }); }); });