Create class declaration programmatically in javascript? - javascript

Eventually what I need is to build a class declaration at runtime providing dynamic parameters to a property annotation:
import {Type} from "external-module";
export default class TypeWrapper {
#Type(() => '{this part of the class declaration should be changable at runtime}')
data
}
I have a feeling that this should be possible to achieve but can't figure out a proper way yet.
As a proof of concept I was trying to do something like below:
let MyClass = eval('(class MyClass{})')
let myClass = new MyClass()
That works, however MyClass needs to define some imports:
let MyClass = eval('import {Type} from "external-module"' +
'(class MyClass{})')
That one fails with "Cannot use import statement outside a module" which is quite expected.
Another approach I tried is to load a module from string:
var moduleData = '' +
'import module from "./module/path/file.js"\n' +
'\n' +
'export default class MyClass {\n' +
'}\n' +
'\n';
var b64moduleData = "data:text/javascript;base64," + btoa(moduleData);
let MyClass = await import(b64moduleData)
But it fails with "Cannot find module", suggesting it assumes b64moduleData is a path rather than module data itself.
Anyone has any suggestions?

Normally, for this type of thing I would use a class factory:
import { Type } from 'some-module';
function createClass(parameters) {
return class {
....
}
}
const MyClass = createClass(...);
I'm unsure if this fits your particular use case based on the details provided.
EDIT: As a side-note, as far as I know, you cannot construct modules from a string, which is what your code is doing, and what the compiler is complaining about.

Related

Jest/Typescript: Mock class dependencies in Jest and Typescript

I have class B which is dependent on class A. I want to test a method of class B which is internally calling a method of class A. Now, I want to unit test my method of class B by mocking class A.
My code structure:
class A {
getSomething() {
return "Something";
}
}
class B {
constructor(objectOfClassA: A) {
this._objectOfClassA = objectOfClassA;
}
functionofClassBToTest() {
const returnValueFromClassA = this._objectOfClassA.getSomething();
return returnValueFromClassA;
}
}
What I have tried so far:
import ....
import { mocked } from 'jest-mock';
jest.mock("./A", () => {
return {
A: jest.fn().mockImplementation(() => {
return {
getSomething: getSomethingMock
}
})
};
});
const getSomethingMock = jest.fn().mockImplementation(() => {
return "Mock value";
});
const mockA = mocked(A, true);
test("test functionofClassBToTest", () => {
const classBToTest = new B(mockA);
expect(classBToTest.functionofClassBToTest.toStrictEqual("Mock value");
});
This is the error that I am getting:
TypeError: this._objectOfClassA.getSomething is not a function
Note: I don't want to initialize an object of class A inside my test function. I only want to mock the class.
I also found some previous posts on SO : Post1 and Post2 but nothing worked.
UPDATE: This problem got solved thanks to James's answer below. For mocking classes with private members, I have asked another SO Question. Please do read it.
Given that Typescript is structurally typed, it should be possible to simply construct an object literally that matches the interface of the A class and pass that into class B.
For example:
const mockA: jest.Mocked<A> = {
getSomething: jest.fn()
};
const b = new B(mockA);
expect(mockA.getSomething)
.toHaveBeenCalled();
This should not generate type errors given mockA exactly matches the interface of class A.
To mock the return value of A’s methods, refer to mocking individual functions with Jest.
This proves to be simpler and more succinct than attempting to mock the entire module. Since you’ve used IoC as a pattern, mocking the module is not necessary.
You also have a typo. Class B assigns to an instance variable with only one underscore but then calls the method on an instance variable with two underscores.

Why are my two javascript class instances not identical?

I have a custom "Enum" class TableSourceType (parent class Enum is given below):
import Enum from './../../components/enum.js';
export default class TableSourceType extends Enum {}
TableSourceType.csv = new TableSourceType('csv');
TableSourceType.sqLite = new TableSourceType('sqLite');
TableSourceType.mySql = new TableSourceType('mySql');
Furhtermore, I have "two" object instances foo and baa and would expect both instances to be identical and the same as TableSourceType.sqlite. However, following equality comparisons yield false:
foo === baa
foo.constructor === baa.constructor
If I compare the names of the instances I get true:
foo.name === baa.name
I already checked that I only have a single source code file that contains the class "TableSourceType". That ES6 class is imported with
import TableSourceType from '../notebooks/treez/src/data/table/tableSourceType.js'
=>Why do I get two different constructors for the "same" import?
Starting from my main html file, I have two <script type="module"> blocks.
The second script block is added dynamically at runtime in order to inject some user defined code and to save some stuff in a global variable.
The comparison takes place in the first ("static") script block. Maybe that somehow causes the instances not to be identical?
=> How can I ensure equality?
=> Where can I find more information to better understand that equality issue?
Actually I would like to use instances of my custom class in a switch statement:
switch (this.sourceType) {
case TableSourceType.csv:
this.__showAndHideCompontentsForCsv();
break;
case TableSourceType.sqLite:
this.__showAndHideCompontentsForSqLite();
break;
default:
var message = 'The TableSourceType "' + this.sourceType + '" is not yet implemented.';
throw new Error(message);
}
That switch statement fails. I would expect this.sourceType and TableSourceType.sqLite to be equal but they are not.
If it is not possible to ensure equality for instances in different script blocks (?) ... is it possible to implement something like custom "equals" and "hashcode" methods in JavaScript?
If so, I would try to tell TableSourceType to define its equality only based on the name property of the instances.
Here is my custom Enum class:
export default class Enum {
static get values(){
var keys = Object.keys(this).filter(key=>!key.startsWith('__'));
return keys.map(key=>this[key]);
}
static get names(){
return this.values.map((value)=>value.name);
}
static get importLocation(){
return this.__importLocation;
}
static forName(name){
for(var type of this.values){
if(type.name === name){
return type;
}
}
throw new Error('Unknown value "' + name + '"');
}
constructor(name){
this.name = name;
if(!this.constructor.__importLocation){
this.constructor.__importLocation = this.determineImportLocation();
}
}
toString(){
return this.name;
}
determineImportLocation(){
var stack = new Error().stack;
var lastLine = stack.split('\n').pop();
var startIndex = lastLine.indexOf('/src/');
var endIndex = lastLine.indexOf('.js:') + 3;
return lastLine.substring(startIndex, endIndex);
}
}
A work around would be to use the name property in the switch statement:
switch (this.sourceType.name) {
case TableSourceType.csv.name:
this.__showAndHideCompontentsForCsv();
break;
case TableSourceType.sqLite.name:
this.__showAndHideCompontentsForSqLite();
break;
default:
var message = 'The TableSourceType "' + this.sourceType + '" is not yet implemented.';
throw new Error(message);
}
However, I would prefer the original version of the switch statement.
I expected the content of my custom class file to be executed only once, because the import already has been resolved before. Thanks to Dani R I found out that the code is actually executed twice.
Following adapted code of TableSourceType works for me:
import Enum from './../../components/enum.js';
export default class TableSourceType extends Enum {}
if(window.TableSourceType){
TableSourceType = window.TableSourceType;
} else {
TableSourceType.csv = new TableSourceType('csv');
TableSourceType.sqLite = new TableSourceType('sqLite');
TableSourceType.mySql = new TableSourceType('mySql');
window.TableSourceType = TableSourceType;
}
If there is a more elegant way to ensure equality, please let me know.

How to call inherited symbol that computed!? as Function in extended class?

First of all I'm a newbie in Typescript so title may be inaccurate because I don't know how that code works properly.
I'm using the Klasa framework which is a Discord bot framework made top of Discord.js. They recently added plugin functionality and there is lots of examples written in regular ES6...
const { Client, util: { mergeDefault } } = require('klasa');
const DriverStore = require('../lib/structures/DriverStore');
const { OPTIONS } = require('../lib/util/constants');
class MusicClient extends Client {
constructor(config) {
super(config);
this.constructor[Client.plugin].call(this);
}
static [Client.plugin]() {
mergeDefault(OPTIONS, this.options);
this.drivers = new DriverStore(this);
this.registerStore(this.drivers);
}
}
module.exports = MusicClient;
Type of Client.plugin is Symbol. How this code work? And how can I achieve something similar to this with TypeScript or it is doable?
I tried doing it like this:
import { KlasaClientOptions, Client } from "klasa"
export class ExtendedClient extends Client {
public prop: string;
constructor(options: KlasaClientOptions) {
super(options);
// Element implicitly has an 'any' type 'typeof KlasaClient' has no index signature.
this.constructor[Client.plugin].call(this);
// Also trying to use ExtendedClient.plugin.call() gives me
// Property 'call' does not exist on type 'symbol'
}
static [Client.plugin]() {
// Property 'prop' does not exist of type 'typeof ExtendedClient'
this.prop = "somestring";
}
}
Edit: I fixed the error after I found out static [Client.plugin]() has the context of KlasaClient so I changed it as
import { KlasaClientOptions, Client } from "klasa"
export class ExtendedClient extends Client {
public prop: string;
constructor(options: KlasaClientOptions) {
super(options);
(this.constructor as any)[Client.plugin].call(this);
}
static [Client.plugin](this: ExtendedClient) {
this.prop = "somestring";
}
}
and the problems are solved...
The code is fairly odd, and unless there's a strong design constraint forcing it to be that way, it's not best practice.
It's defining a static method, but calling it as though it were an instance method. Which is part of why TypeScript is having issues with it.
The key parts are:
This:
static [Client.plugin]() {
this.registerStore(this.drivers);
}
...which defines a static method (a method on the constructor function) with the name from Client.plugin (which you've said is a Symbol, but it doesn't really matter whether it's a Symbol or a string).
And this in the constructor:
this.constructor[Client.plugin].call(this);
...which is what's calling it as though it were an instance method. this.constructor accesses the constructor function that created this (loosely speaking, that's not entirely accurate), and so this.constructor[Client.plugin] accesses the static method with the name from Client.plugin. Then .call(this) calls that method, but sets this as this during the call, a though it were an instance method. So within the call to Client.plugin, this is an instance of the class (whereas normally this would be the constructor function).
Which is very odd.
In terms of making TypeScript okay with it, I think I'd probably approach it like this:
import { KlasaClientOptions, Client } from "klasa";
export class ExtendedClient extends Client {
public prop: string;
constructor(options: KlasaClientOptions) {
super(options);
(this.constructor as any)[Client.plugin].call(this);
}
static [Client.plugin]() {
const inst = this as ExtendedClient;
// Now use `inst` instead of `this` where your example code uses `this`, so:
inst.prop = "somestring";
}
}
The key bits are:
This:
(this.constructor as any)[Client.plugin].call(this);
...by using any we defeat TypeScript's type checking, but we basically have to for this odd structure.
And this:
const inst = this as ExtendedClient;
...by using as ExtendedClient we're telling TypeScript that although normally it would expect this to be the constructor function, it's actually an ExtendedClient. We do that once with a constant to avoid having to repeat it everywhere.

How to change class' prototype at runtime within this class (with ES6 classes)

I have the following class 'X' and I want to add some stuff to its prototype dynamically, like this (this works):
class X{
someUnrelatedFunction(){};
}
//adding some stuff at runtime, in real situation, the ['a','b','c'] aren't "known" in advance like here.
['a','b','c'].forEach(elem => {
X.prototype[elem] = () =>{
console.log('called with : ' + elem);
};
})
//tests:
x = new X();
x.c();//works, outputs 'called with : c';
But in the class declaration itself. I would like to do it, to make things a bit more readable, i.e. I would like the 'prototype' initialization to belong to the class itself.
Right now I'm doing this in a 'constructor', like here:
class X{
constructor(){
//we don't want to do that again:
if(typeof X.prototype.inited === 'undefined'){
X.prototype.inited = true;
console.log('-constructor: initing prototype');
['a','b','c'].forEach(elem => {
X.prototype[elem] = () =>{
console.log('called with : ' + elem);
};
})
}else{
console.log('-constructor: no need to re-init prototype');
}
}
}
x1 = new X();
x1.a();
x2 = new X();
x2.b();
fiddle: https://jsfiddle.net/nypL29f3/4/
But this seems tricky for me, plus inside the class I'm actually referencing it kinda "outside", i.e. I'm using "X". If I ever changed the class name, I will also need to change that part of code.
So the question is - how to do that in a more readable and right way?
FWIW, The "real" case scenario is that I'm playing with ES6 by creating useless scripts like this one:
https://jsfiddle.net/kpion/mqyrewnx/9/
Here my doubts were about line #78 //this was my question - that it seems I need to do it outside the class YaLog.
Edit: just BTW - if you came here with a similar question - all the below answers answer the question in a way, and all are worth reading. Thanks everyone! :)
This sort of class prototype modifications is never performed from inside a constructor - primarily because class constructor is responsible for managing current class instance, not all class instances. A constructor is supposed to declare new methods ('log','info', etc) on this class instance, this is less efficient than declaring methods on class prototype and may be desirable or not for further class inheritance.
This is what decorators are intended for. They provide a convenient syntax for extension or modification class constructor and prototype.
A decorator can modify existing class prototype:
function withLogger(Class) {
['log','info','error','warn','group','groupEnd'].forEach(func => {
Class.prototype[func] = function() {
return this.callConsoleMethod(func, ...arguments);
};
});
return Class;
}
Or return a new class that extends existing class in non-destructive way, this allows to refer original members with super when shadowing them in wrapper class.
Decorators have neat syntax in ECMAScript Next/TypeScript:
#withLogger
class YaLog {
...
}
Since a decorator is basically a helper function, it can be applied directly in ES6 in a bit less expressive manner:
const YaLog = withLogger(class YaLog {
...
});
I would like the 'prototype' initialization to belong to the class itself.
That is not possible. A class body can only contain method definitions, not arbitrary code.
If the number of methods was known and only their names are dynamic, you could use computed property names, but this doesn't seem to be the case in your example.
Since you are looking for a more readable way, just put the class and all assignments of static values or dynamically created methods in a separate module. (This might be an ES6 module, an IIFE module, or a simple block scope for visual separation).
Right now I'm doing this in the constructor
Don't. This is inefficient, error-prone and horribly unreadable.
You can use inheritance. Create new class Y and add it methods from your array. Then you can extend original class (or any other) with Y:
class Y {}
const methods = ['a', 'b', 'c']
methods.forEach(elem => {
Y.prototype[elem] = () => {
console.log('called with : ' + elem)
}
})
class X extends Y {
someUnrelatedFunction() {}
}
const x1 = new X();
x1.a();
I am not sure what your use case is, but you can create a helper function which extends your original class. For example this could also work:
function extendWithMethods(methodNames, generalHandler) {
class Y {}
methodNames.forEach(elem => {
Y.prototype[elem] = () => generalHandler(elem)
})
return Y
}
class X extends extendWithMethods(['a', 'b', 'c'], methodName => {
console.log('called with : ' + methodName)
}) {
someUnrelatedFunction() {}
}
const x1 = new X()
x1.a()

Get array of class's properties without instantiating it?

It seems like you should be able to do this because building a form dynamically based off of a class definition (Angular) would work so much better if the logic could be written agnostically of the class. This would be scalable, so an addition of a field to the class would not also require an update to the logic producing the form and template.
Is there any way to do this or even an NPM module that will do this?
I found that I can do ClassName.toString() but it would be a pain to parse that. And I just might write a module to do it if nothing else.
I just feel like instantiating a dummy instance of the class for the purpose of enumerating over its properties is a poor strategy.
You could use Object.getOwnPropertyNames().
Example class:
class Foo {
setBar() {
throw Error('not implemented');
return false;
}
getBar() {
throw Error('not implemented');
return false;
}
}
And then
Object.getOwnPropertyNames(Foo.prototype)
results in
["constructor", "setBar", "getBar"]
While I was researching this I looked into Object.keys first, and although it didn't work, you may wish to reference the documentation for Object.keys's polyfill. It has code for stripping out constructor, toString, and the like, as well as properly implementing hasOwnProperty.
Also see Bergi's answer here.
Any way? Declare your class as a function, and put the properties on the prototype:
var Presidents = function() {
};
Presidents.prototype = {
"washington" : "george",
"adams" : "john"
}
console.log(Object.keys(Presidents.prototype))
// Output is
// [ 'washington', 'adams' ]
getOwnPropertyDescriptors of a class prototype will only expose methods and accessor descriptors - data properties can not be determined without instantiation (also because constructor arguments can influence the amount, types and values of props). There can be several reasons to not want to instantiate (e.g. because some static counter tracks instances) - therefore a workaround could be to dynamically create a copy of the class and instatiate that "shadow" along with sample constructor arguments.
/**
* get properties from class definition without instantiating it
*
* #param cls: class
* #param args: sample arguments to pass to shadow constructor
* #usage `const props = getInstanceProperties(MyClass);`
* #notice this will regex replace the classname (can be an issue with strings containing that substring)
*/
const getInstanceProperties = (cls, args = [], ignore = ['constructor', 'toString']) => {
const className = cls.prototype.constructor.name;
const shadowCode = cls.toString().replace(new RegExp(`${className}`, 'g'), `_${className}_`);
const shadowClass = eval(`(${shadowCode})`);
const o = new shadowClass(...args);
const methodsAndAccessors = Object.getOwnPropertyDescriptors(cls.prototype);
const dataDescriptors = Object.getOwnPropertyDescriptors(o);
const descriptors = Object.assign({}, methodsAndAccessors, dataDescriptors);
ignore.forEach(name => delete descriptors[name]);
return descriptors;
};
class Foo extends Object {
static instances = 0;
#myPrivateVar = 123;
myValue=123;
constructor(){
super();
this.myConstructorVar = ++Foo.instances;
}
myMethod() {}
set myAccessor(x){}
}
console.log(Object.keys(getInstanceProperties(Foo)));
will return:
[ 'myMethod', 'myAccessor', 'myValue', 'myConstructorProp' ]

Categories