Skip to content

Commit 660c6ad

Browse files
authored
Merge pull request #12 from Exabyte-io/feature/SOF-6165
SOF 6165 - extendThis
2 parents 0b22655 + ff7c468 commit 660c6ad

File tree

5 files changed

+178
-3
lines changed

5 files changed

+178
-3
lines changed

src/entity/in_memory.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,12 @@ export class InMemoryEntity {
9595

9696
if (!ctx.isValid()) {
9797
console.log(JSON.stringify(this.toJSON()));
98-
console.log(ctx.getErrorObject());
98+
if (ctx.getErrorObject) {
99+
console.log(ctx.getErrorObject());
100+
}
101+
if (ctx.validationErrors) {
102+
console.log(ctx.validationErrors());
103+
}
99104
}
100105

101106
return ctx.isValid();
@@ -135,7 +140,8 @@ export class InMemoryEntity {
135140
}
136141

137142
/**
138-
* @summary Pluck an entity from a collection by name
143+
* @summary Pluck an entity from a collection by name.
144+
* If no name is provided and no entity has prop isDefault, return the first entity
139145
* @param entities {Array} the entities
140146
* @param entity {string} the kind of entities
141147
* @param name {string} the name of the entity to choose
@@ -145,6 +151,7 @@ export class InMemoryEntity {
145151
let filtered;
146152
if (!name) {
147153
filtered = entities.filter(entity => entity.prop("isDefault") === true);
154+
if (!filtered.length) filtered = [entities[0]];
148155
} else {
149156
filtered = entities.filter(entity => entity.prop("name") === name);
150157
}

src/entity/mixins/runtime_items.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ export const RuntimeItemsMixin = (superclass) => {
4747

4848
export const RuntimeItemsUILogicMixin = (superclass) => {
4949
return class extends RuntimeItemsMixin(superclass) {
50+
constructor(config) {
51+
super(config);
52+
this._initRuntimeItems(
53+
["results", "monitors", "preProcessors", "postProcessors"],
54+
config,
55+
);
56+
}
5057

5158
setRuntimeItemsToDefaultValues() {
5259
["results", "monitors", "preProcessors", "postProcessors"].map((name) =>

src/utils/class.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,30 @@ export function extendClassStaticProps(childClass, parentClass, excludedProps =
3535
childClass[prop] = parentClass[prop]
3636
});
3737
}
38+
39+
/**
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.
43+
* See extendClass.
44+
*/
45+
export function extendThis(childClass, parentClass, config) {
46+
let props, protos;
47+
let obj = new parentClass.prototype.constructor(config);
48+
const exclude = ["constructor", ...Object.getOwnPropertyNames(childClass.prototype)];
49+
const seen = []; // remember most recent occurrence of prop name (like inheritance)
50+
while (Object.getPrototypeOf(obj)) {
51+
protos = Object.getPrototypeOf(obj);
52+
props = Object.getOwnPropertyNames(protos);
53+
props.filter(p => !exclude.includes(p)).map((prop) => {
54+
if (seen.includes(prop)) return;
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];
60+
seen.push(prop);
61+
})
62+
obj = protos;
63+
}
64+
}

src/utils/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { compareEntitiesInOrderedSetForSorting } from "../entity/set/ordered/utils";
22
import { safeMakeArray, convertToCompactCSVArrayOfObjects } from "./array";
3-
import { extendClass, extendClassStaticProps, cloneClass } from "./class";
3+
import { extendThis, extendClass, extendClassStaticProps, cloneClass } from "./class";
44
import { deepClone } from "./clone";
55
import { refreshCodeMirror } from "./codemirror";
66
import { getProgrammingLanguageFromFileExtension, formatFileSize } from "./file";
@@ -32,6 +32,7 @@ export {
3232
convertToCompactCSVArrayOfObjects,
3333

3434
cloneClass,
35+
extendThis,
3536
extendClass,
3637
extendClassStaticProps,
3738

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)