Skip to content

Commit 83757d4

Browse files
authored
Merge pull request #13 from Exabyte-io/feature/SOF-6165-1
feat: update extendThis and add test
2 parents 69ace7f + 42c1035 commit 83757d4

File tree

2 files changed

+148
-15
lines changed

2 files changed

+148
-15
lines changed

src/utils/class.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,28 @@ export function extendClassStaticProps(childClass, parentClass, excludedProps =
3737
}
3838

3939
/**
40-
* Extends an object with a parent class namespace.
40+
* Slightly different implementation of extendClass assuming excludedProps
41+
* is contained within the child-most class definition and assigning only
42+
* the most recent props rather than the most distant props.
4143
* See extendClass.
42-
* TODO : match call signature with extendClass and
43-
* make use of getPrototypeOf rather than __proto__
44-
* but verify correctness in additional use cases first
4544
*/
46-
export function extendThis(child, childClass, parentClass, excludedProps = [], ...args) {
47-
let props;
48-
let obj = new parentClass.prototype.constructor(...args);
45+
export function extendThis(childClass, parentClass, config) {
46+
let props, protos;
47+
let obj = new parentClass.prototype.constructor(config);
4948
const exclude = ["constructor", ...Object.getOwnPropertyNames(childClass.prototype)];
5049
const seen = []; // remember most recent occurrence of prop name (like inheritance)
51-
while (obj.__proto__) {
52-
props = Object.getOwnPropertyNames(obj.__proto__);
50+
while (Object.getPrototypeOf(obj)) {
51+
protos = Object.getPrototypeOf(obj);
52+
props = Object.getOwnPropertyNames(protos);
5353
props.filter(p => !exclude.includes(p)).map((prop) => {
5454
if (seen.includes(prop)) return;
55-
const getter = obj.__lookupGetter__(prop);
56-
const setter = obj.__lookupSetter__(prop);
57-
if (getter) child.__defineGetter__(prop, getter);
58-
if (setter) child.__defineSetter__(prop, setter);
59-
if (!(getter || setter)) child[prop] = obj[prop];
55+
const getter = protos.__lookupGetter__(prop);
56+
const setter = protos.__lookupSetter__(prop);
57+
if (getter) childClass.prototype.__defineGetter__(prop, getter);
58+
if (setter) childClass.prototype.__defineSetter__(prop, setter);
59+
if (!(getter || setter)) childClass.prototype[prop] = protos[prop];
6060
seen.push(prop);
6161
})
62-
obj = obj.__proto__;
62+
obj = protos;
6363
}
6464
}

tests/utils.class.js

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { mix } from "mixwith";
2+
import { expect } from "chai";
3+
import { InMemoryEntity, NamedInMemoryEntity, DefaultableMixin, RuntimeItemsMixin } from "../src/entity";
4+
import { deepClone } from "../src/utils/clone";
5+
import { extendClass, extendThis } from "../src/utils/class";
6+
7+
8+
class BaseEntity extends mix(InMemoryEntity).with(RuntimeItemsMixin) {
9+
10+
constructor(config) {
11+
super(config);
12+
}
13+
14+
baseMethod() {
15+
return "base";
16+
}
17+
18+
}
19+
20+
21+
class ExtendClassEntity extends mix(NamedInMemoryEntity).with(DefaultableMixin) {
22+
23+
constructor(config, excluded = []) {
24+
super(config);
25+
extendClass(ExtendClassEntity, BaseEntity, excluded, [config]);
26+
27+
}
28+
29+
baseMethod() {
30+
return "derived";
31+
}
32+
33+
}
34+
35+
36+
class BaseBetweenEntity extends NamedInMemoryEntity {
37+
38+
static staticAttr = "base";
39+
40+
constructor(config) {
41+
super(config);
42+
this.instanceAttr = "base";
43+
}
44+
45+
betweenMethod() {
46+
return "base";
47+
}
48+
49+
}
50+
51+
52+
class BetweenEntity extends BaseBetweenEntity {
53+
54+
static staticAttr = "between";
55+
56+
constructor(config) {
57+
super(config);
58+
this.instanceAttr = "between";
59+
}
60+
61+
betweenMethod() {
62+
return "between";
63+
}
64+
}
65+
66+
67+
class ExtendThisEntity extends mix(BetweenEntity).with(DefaultableMixin) {
68+
69+
constructor(config, excluded = []) {
70+
super(config);
71+
extendThis(ExtendThisEntity, BaseEntity, config);
72+
73+
}
74+
75+
baseMethod() {
76+
return "derived";
77+
}
78+
79+
}
80+
81+
82+
describe("extendClass", () => {
83+
84+
it("extends classes no excluded props", () => {
85+
const obj = new ExtendClassEntity({});
86+
expect(obj.baseMethod()).to.be.equal("base");
87+
});
88+
89+
it("should support excluded props but doesnt", () => {
90+
const obj = new ExtendClassEntity({});
91+
expect(obj.baseMethod()).not.to.be.equal("derived");
92+
});
93+
94+
it("should have results but doesnt", () => {
95+
const obj = new ExtendClassEntity({"results": ["test"]});
96+
expect(JSON.stringify(obj.results)).not.to.be.equal(JSON.stringify([{"name": "test"}]));
97+
});
98+
99+
});
100+
101+
102+
describe("extendThis", () => {
103+
104+
it("extends this prefer child method", () => {
105+
const obj = new ExtendThisEntity({});
106+
expect(obj.baseMethod()).to.be.equal("derived");
107+
});
108+
109+
it("extends this support base mixins", () => {
110+
const obj = new ExtendThisEntity({"results": ["test"]});
111+
expect(JSON.stringify(obj.results)).to.be.equal(JSON.stringify([{"name": "test"}]));
112+
});
113+
114+
it("remembers intermediate methods", () => {
115+
const base = new BaseBetweenEntity();
116+
expect(base.betweenMethod()).to.be.equal("base");
117+
const obj = new ExtendThisEntity({});
118+
expect(obj.betweenMethod()).to.be.equal("between");
119+
});
120+
121+
it("propagates instance attributes", () => {
122+
const base = new BaseBetweenEntity({});
123+
expect(base.instanceAttr).to.be.equal("base");
124+
const obj = new ExtendThisEntity({});
125+
expect(obj.instanceAttr).to.be.equal("between");
126+
});
127+
128+
it("propagates static attributes", () => {
129+
expect(BaseBetweenEntity.staticAttr).to.be.equal("base");
130+
expect(ExtendThisEntity.staticAttr).to.be.equal("between");
131+
});
132+
133+
});

0 commit comments

Comments
 (0)