From b50aabe0accf9feaccebf0336ad70bb08ad79d7a Mon Sep 17 00:00:00 2001 From: Kostiantyn Dvornik Date: Fri, 8 Jul 2022 18:30:35 +0300 Subject: [PATCH 1/7] fix: validation error logs --- src/entity/in_memory.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/entity/in_memory.js b/src/entity/in_memory.js index c3a510be..71fdba61 100644 --- a/src/entity/in_memory.js +++ b/src/entity/in_memory.js @@ -95,7 +95,12 @@ export class InMemoryEntity { if (!ctx.isValid()) { console.log(JSON.stringify(this.toJSON())); - console.log(ctx.getErrorObject()); + if (ctx.getErrorObject) { + console.log(ctx.getErrorObject()); + } + if (ctx.validationErrors) { + console.log(ctx.validationErrors()); + } } return ctx.isValid(); From 42aa3274b28b2ff7ae18cca573dc3f4fdbdaeef1 Mon Sep 17 00:00:00 2001 From: tjduigna Date: Wed, 13 Jul 2022 13:26:32 -0400 Subject: [PATCH 2/7] fix: default isDefault --- src/entity/in_memory.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/entity/in_memory.js b/src/entity/in_memory.js index c3a510be..2f66dca8 100644 --- a/src/entity/in_memory.js +++ b/src/entity/in_memory.js @@ -145,6 +145,7 @@ export class InMemoryEntity { let filtered; if (!name) { filtered = entities.filter(entity => entity.prop("isDefault") === true); + if (!filtered.length) filtered = [entities[0]]; } else { filtered = entities.filter(entity => entity.prop("name") === name); } From 495440f23e41ee1b37fc20f867f591b6f7eee9df Mon Sep 17 00:00:00 2001 From: tjduigna Date: Fri, 15 Jul 2022 12:12:58 -0400 Subject: [PATCH 3/7] feat: add extendThis --- src/entity/in_memory.js | 3 ++- src/utils/class.js | 21 +++++++++++++++++++++ src/utils/index.js | 3 ++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/entity/in_memory.js b/src/entity/in_memory.js index 2f66dca8..ec07ac5c 100644 --- a/src/entity/in_memory.js +++ b/src/entity/in_memory.js @@ -135,7 +135,8 @@ export class InMemoryEntity { } /** - * @summary Pluck an entity from a collection by name + * @summary Pluck an entity from a collection by name. + * If no name is provided and no entity has prop isDefault, return the first entity * @param entities {Array} the entities * @param entity {string} the kind of entities * @param name {string} the name of the entity to choose diff --git a/src/utils/class.js b/src/utils/class.js index 233b9304..76598645 100644 --- a/src/utils/class.js +++ b/src/utils/class.js @@ -35,3 +35,24 @@ export function extendClassStaticProps(childClass, parentClass, excludedProps = childClass[prop] = parentClass[prop] }); } + +/** + * Extends an object with a parent class namespace. + * See extendClass + */ +export function extendThis(child, parentClass, excludedProps = [], ...args) { + let props; + let obj = new parentClass.prototype.constructor(...args); + const exclude = ["constructor", ...excludedProps]; + while (obj.__proto__) { + props = Object.getOwnPropertyNames(obj.__proto__); + props.filter(p => !exclude.includes(p)).map((prop) => { + const getter = obj.__lookupGetter__(prop); + const setter = obj.__lookupSetter__(prop); + if (getter) child.__defineGetter__(prop, getter); + if (setter) child.__defineSetter__(prop, setter); + if (!(getter || setter)) child[prop] = obj[prop]; + }) + obj = obj.__proto__; + } +} diff --git a/src/utils/index.js b/src/utils/index.js index e8edca59..32df15cd 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,6 +1,6 @@ import { compareEntitiesInOrderedSetForSorting } from "../entity/set/ordered/utils"; import { safeMakeArray, convertToCompactCSVArrayOfObjects } from "./array"; -import { extendClass, extendClassStaticProps, cloneClass } from "./class"; +import { extendThis, extendClass, extendClassStaticProps, cloneClass } from "./class"; import { deepClone } from "./clone"; import { refreshCodeMirror } from "./codemirror"; import { getProgrammingLanguageFromFileExtension, formatFileSize } from "./file"; @@ -32,6 +32,7 @@ export { convertToCompactCSVArrayOfObjects, cloneClass, + extendThis, extendClass, extendClassStaticProps, From da42ff748305152dfffdcf869fb782874adc8358 Mon Sep 17 00:00:00 2001 From: tjduigna Date: Fri, 15 Jul 2022 19:04:40 -0400 Subject: [PATCH 4/7] feat: enhance extendThis --- src/utils/class.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/utils/class.js b/src/utils/class.js index 76598645..290f36d1 100644 --- a/src/utils/class.js +++ b/src/utils/class.js @@ -38,20 +38,26 @@ export function extendClassStaticProps(childClass, parentClass, excludedProps = /** * Extends an object with a parent class namespace. - * See extendClass + * See extendClass. + * TODO : match call signature with extendClass and + * make use of getPrototypeOf rather than __proto__ + * but verify correctness in additional use cases first */ -export function extendThis(child, parentClass, excludedProps = [], ...args) { +export function extendThis(child, childClass, parentClass, excludedProps = [], ...args) { let props; let obj = new parentClass.prototype.constructor(...args); - const exclude = ["constructor", ...excludedProps]; + const exclude = ["constructor", ...Object.getOwnPropertyNames(childClass)]; + const seen = []; // remember most recent occurrence of prop name (like inheritance) while (obj.__proto__) { props = Object.getOwnPropertyNames(obj.__proto__); props.filter(p => !exclude.includes(p)).map((prop) => { + if (seen.includes(prop)) return; const getter = obj.__lookupGetter__(prop); const setter = obj.__lookupSetter__(prop); if (getter) child.__defineGetter__(prop, getter); if (setter) child.__defineSetter__(prop, setter); if (!(getter || setter)) child[prop] = obj[prop]; + seen.push(prop); }) obj = obj.__proto__; } From fcdceeb48f84bb71aa538b1cf22d8621ef4bd277 Mon Sep 17 00:00:00 2001 From: Alexander Zech Date: Fri, 15 Jul 2022 19:48:28 -0700 Subject: [PATCH 5/7] fix: get list of all functions in child class --- src/utils/class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/class.js b/src/utils/class.js index 290f36d1..6b062d90 100644 --- a/src/utils/class.js +++ b/src/utils/class.js @@ -46,7 +46,7 @@ export function extendClassStaticProps(childClass, parentClass, excludedProps = export function extendThis(child, childClass, parentClass, excludedProps = [], ...args) { let props; let obj = new parentClass.prototype.constructor(...args); - const exclude = ["constructor", ...Object.getOwnPropertyNames(childClass)]; + const exclude = ["constructor", ...Object.getOwnPropertyNames(childClass.prototype)]; const seen = []; // remember most recent occurrence of prop name (like inheritance) while (obj.__proto__) { props = Object.getOwnPropertyNames(obj.__proto__); From 52f87884601fee9e4d5c7571306a71b57f30f716 Mon Sep 17 00:00:00 2001 From: tjduigna Date: Mon, 18 Jul 2022 12:35:30 -0400 Subject: [PATCH 6/7] Revert "fix: remove circular logic in constructor" This reverts commit 8d1c8aef290348c65ed5ef07dea60326b6542d82. --- src/entity/mixins/runtime_items.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/entity/mixins/runtime_items.js b/src/entity/mixins/runtime_items.js index 5bc44bae..d90d8bb4 100644 --- a/src/entity/mixins/runtime_items.js +++ b/src/entity/mixins/runtime_items.js @@ -47,6 +47,13 @@ export const RuntimeItemsMixin = (superclass) => { export const RuntimeItemsUILogicMixin = (superclass) => { return class extends RuntimeItemsMixin(superclass) { + constructor(config) { + super(config); + this._initRuntimeItems( + ["results", "monitors", "preProcessors", "postProcessors"], + config, + ); + } setRuntimeItemsToDefaultValues() { ["results", "monitors", "preProcessors", "postProcessors"].map((name) => From 42c10359a8033d9a7ce14ce1fc7fb1578b17a23f Mon Sep 17 00:00:00 2001 From: tjduigna Date: Mon, 18 Jul 2022 15:33:41 -0400 Subject: [PATCH 7/7] feat: update extendThis and add test --- src/utils/class.js | 30 +++++----- tests/utils.class.js | 133 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 tests/utils.class.js diff --git a/src/utils/class.js b/src/utils/class.js index 6b062d90..ca1072d9 100644 --- a/src/utils/class.js +++ b/src/utils/class.js @@ -37,28 +37,28 @@ export function extendClassStaticProps(childClass, parentClass, excludedProps = } /** - * Extends an object with a parent class namespace. + * Slightly different implementation of extendClass assuming excludedProps + * is contained within the child-most class definition and assigning only + * the most recent props rather than the most distant props. * See extendClass. - * TODO : match call signature with extendClass and - * make use of getPrototypeOf rather than __proto__ - * but verify correctness in additional use cases first */ -export function extendThis(child, childClass, parentClass, excludedProps = [], ...args) { - let props; - let obj = new parentClass.prototype.constructor(...args); +export function extendThis(childClass, parentClass, config) { + let props, protos; + let obj = new parentClass.prototype.constructor(config); const exclude = ["constructor", ...Object.getOwnPropertyNames(childClass.prototype)]; const seen = []; // remember most recent occurrence of prop name (like inheritance) - while (obj.__proto__) { - props = Object.getOwnPropertyNames(obj.__proto__); + while (Object.getPrototypeOf(obj)) { + protos = Object.getPrototypeOf(obj); + props = Object.getOwnPropertyNames(protos); props.filter(p => !exclude.includes(p)).map((prop) => { if (seen.includes(prop)) return; - const getter = obj.__lookupGetter__(prop); - const setter = obj.__lookupSetter__(prop); - if (getter) child.__defineGetter__(prop, getter); - if (setter) child.__defineSetter__(prop, setter); - if (!(getter || setter)) child[prop] = obj[prop]; + const getter = protos.__lookupGetter__(prop); + const setter = protos.__lookupSetter__(prop); + if (getter) childClass.prototype.__defineGetter__(prop, getter); + if (setter) childClass.prototype.__defineSetter__(prop, setter); + if (!(getter || setter)) childClass.prototype[prop] = protos[prop]; seen.push(prop); }) - obj = obj.__proto__; + obj = protos; } } diff --git a/tests/utils.class.js b/tests/utils.class.js new file mode 100644 index 00000000..0616d282 --- /dev/null +++ b/tests/utils.class.js @@ -0,0 +1,133 @@ +import { mix } from "mixwith"; +import { expect } from "chai"; +import { InMemoryEntity, NamedInMemoryEntity, DefaultableMixin, RuntimeItemsMixin } from "../src/entity"; +import { deepClone } from "../src/utils/clone"; +import { extendClass, extendThis } from "../src/utils/class"; + + +class BaseEntity extends mix(InMemoryEntity).with(RuntimeItemsMixin) { + + constructor(config) { + super(config); + } + + baseMethod() { + return "base"; + } + +} + + +class ExtendClassEntity extends mix(NamedInMemoryEntity).with(DefaultableMixin) { + + constructor(config, excluded = []) { + super(config); + extendClass(ExtendClassEntity, BaseEntity, excluded, [config]); + + } + + baseMethod() { + return "derived"; + } + +} + + +class BaseBetweenEntity extends NamedInMemoryEntity { + + static staticAttr = "base"; + + constructor(config) { + super(config); + this.instanceAttr = "base"; + } + + betweenMethod() { + return "base"; + } + +} + + +class BetweenEntity extends BaseBetweenEntity { + + static staticAttr = "between"; + + constructor(config) { + super(config); + this.instanceAttr = "between"; + } + + betweenMethod() { + return "between"; + } +} + + +class ExtendThisEntity extends mix(BetweenEntity).with(DefaultableMixin) { + + constructor(config, excluded = []) { + super(config); + extendThis(ExtendThisEntity, BaseEntity, config); + + } + + baseMethod() { + return "derived"; + } + +} + + +describe("extendClass", () => { + + it("extends classes no excluded props", () => { + const obj = new ExtendClassEntity({}); + expect(obj.baseMethod()).to.be.equal("base"); + }); + + it("should support excluded props but doesnt", () => { + const obj = new ExtendClassEntity({}); + expect(obj.baseMethod()).not.to.be.equal("derived"); + }); + + it("should have results but doesnt", () => { + const obj = new ExtendClassEntity({"results": ["test"]}); + expect(JSON.stringify(obj.results)).not.to.be.equal(JSON.stringify([{"name": "test"}])); + }); + +}); + + +describe("extendThis", () => { + + it("extends this prefer child method", () => { + const obj = new ExtendThisEntity({}); + expect(obj.baseMethod()).to.be.equal("derived"); + }); + + it("extends this support base mixins", () => { + const obj = new ExtendThisEntity({"results": ["test"]}); + expect(JSON.stringify(obj.results)).to.be.equal(JSON.stringify([{"name": "test"}])); + }); + + it("remembers intermediate methods", () => { + const base = new BaseBetweenEntity(); + expect(base.betweenMethod()).to.be.equal("base"); + const obj = new ExtendThisEntity({}); + expect(obj.betweenMethod()).to.be.equal("between"); + }); + + it("propagates instance attributes", () => { + const base = new BaseBetweenEntity({}); + expect(base.instanceAttr).to.be.equal("base"); + const obj = new ExtendThisEntity({}); + expect(obj.instanceAttr).to.be.equal("between"); + }); + + it("propagates static attributes", () => { + expect(BaseBetweenEntity.staticAttr).to.be.equal("base"); + expect(ExtendThisEntity.staticAttr).to.be.equal("between"); + }); + +});