One of my files have is creating a new Worker, and it uses file-loader module in order to do so.
Here's my code:
import * as workerPath from 'file-loader?name=[name].js!./worker';
class VideoPlayerWorker {
private worker: Worker;
constructor({ onMessageCallback }: { onMessageCallback: Function }) {
this.worker = new Worker(workerPath);
this.initHandleMessage({ onMessageCallback });
}
initTimer = (): void => {
this.postMessage({ type: 'WORKER_INIT_TIMER' });
};
postMessage = (message: ApeVideo.VideoWorkerEventMessage): void => {
this.worker.postMessage(message);
};
initHandleMessage = ({ onMessageCallback }: { onMessageCallback: Function }): void => {
this.worker.addEventListener('message', ({ data }: { data: ApeVideo.VideoWorkerEventMessage }): void => {
onMessageCallback(data.type);
});
};
destroy = (): void => {
this.worker.terminate();
};
}
export default VideoPlayerWorker;
The problem is when running jest, i get an error Cannot find module 'file-loader?name=[name].js!./worker' from 'playerWorkerGetaway.ts'
How can i work around this?
Related
I'm quite new to ml5 and p5 libraries and during implementation to my Angular project I'm receiving this error:
TypeError: this.objectDetector.detect is not a function
After logging objectDetector object console shows this:
ZoneAwarePromise {__zone_symbol__state: null, __zone_symbol__value: Array(0)}
p5 drawing working good but combined with ml5 is not working.
Here's my component code:
import { Component, OnInit } from '#angular/core';
import * as p5 from 'p5';
declare let ml5: any;
#Component({
selector: 'app-new-found',
templateUrl: './new-found.component.html',
styleUrls: ['./new-found.component.scss']
})
export class NewFoundComponent implements OnInit {
objectDetector;
img;
constructor(
) { }
ngOnInit(): void {
const sketch = (s) => {
s.preload = () => {
this.objectDetector = ml5.objectDetector('cocossd');
console.log('detector object is loaded', this.objectDetector);
this.img = s.loadImage('https://i.imgur.com/Mzh4cHR.jpg');
}
s.setup = () => {
s.createCanvas(700, 700).parent('test-canvas');
this.objectDetector.detect(this.img, this.gotResult);
s.image(this.img, 0, 0);
};
s.draw = () => {
};
}
let canvas = new p5(sketch);
}
gotResult(error, results) {
if (error) {
console.error(error);
} else {
console.log(results);
//drawResults(results);
}
}
}
ml5 library is imported in <HEAD> of my index.html file.
Does someone know how to get rid of this error?
Thank you.
Finally I figured it out. The ml5.objectDetector('cocossd'); function must be marked as await because it takes quite long time to execute. Below is working code:
import { Component, OnInit } from '#angular/core';
import * as p5 from 'p5';
declare let ml5: any;
#Component({
selector: 'app-new-found',
templateUrl: './new-found.component.html',
styleUrls: ['./new-found.component.scss']
})
export class NewFoundComponent implements OnInit {
objectDetector;
img;
constructor(
) { }
async ngOnInit(): Promise<void> {
this.objectDetector = await ml5.objectDetector('cocossd');
const sketch = (s) => {
s.preload = () => {
console.log(ml5);
console.log('detector object is loaded', this.objectDetector);
this.img = s.loadImage('https://i.imgur.com/Mzh4cHR.jpg');
}
s.setup = () => {
s.createCanvas(700, 700).parent('test-canvas');
this.objectDetector.detect(this.img, this.gotResult);
s.image(this.img, 0, 0);
};
s.draw = () => {
};
}
let canvas = new p5(sketch);
}
gotResult(error, results) {
if (error) {
console.error(error);
} else {
console.log(results);
//drawResults(results);
}
}
}
It is possible that the library has not fully loaded yet. I would create a polling technique here where you keep checking if the value has been initialized and only then proceed.
This is the code I use for polling that xola script has loaded:
let subscription = interval(1000)
.pipe(timeout(3 * 60 * 1000))
.subscribe({
next: () => {
if (this.window.xola) {
const xola = this.window.xola();
subscription.unsubscribe();
this.xolaSubject.next(xola);
}
},
error: (error) => {
if (error instanceof TimeoutError) {
console.error('Xola took too long to load, check your connection.');
}
subscription.unsubscribe();
},
});
I am not getting any clue how to mock a method. I have to write a unit test for this function:
index.ts
export async function getTenantExemptionNotes(platform: string) {
return Promise.all([(await getCosmosDbInstance()).getNotes(platform)])
.then(([notes]) => {
return notes;
})
.catch((error) => {
return Promise.reject(error);
});
}
api/CosmosDBAccess.ts
import { Container, CosmosClient, SqlQuerySpec } from "#azure/cosmos";
import { cosmosdbConfig } from "config/Config";
import { Workload } from "config/PlatformConfig";
import { fetchSecret } from "./FetchSecrets";
export class CosmoDbAccess {
private static instance: CosmoDbAccess;
private container: Container;
private constructor(client: CosmosClient) {
this.container = client
.database(cosmosdbConfig.database)
.container(cosmosdbConfig.container);
}
static async getInstance() {
if (!CosmoDbAccess.instance) {
try {
const connectionString = await fetchSecret(
"CosmosDbConnectionString"
);
const client: CosmosClient = new CosmosClient(connectionString);
// Deleting to avoid error: Refused to set unsafe header "user-agent"
delete client["clientContext"].globalEndpointManager.options
.defaultHeaders["User-Agent"];
CosmoDbAccess.instance = new CosmoDbAccess(client);
return CosmoDbAccess.instance;
} catch (error) {
// todo - send to app insights
}
}
return CosmoDbAccess.instance;
}
public async getAllNotesForLastSixMonths() {
const querySpec: SqlQuerySpec = {
// Getting data from past 6 months
query: `SELECT * FROM c
WHERE (udf.convertToDate(c["Date"]) > DateTimeAdd("MM", -6, GetCurrentDateTime()))
AND c.IsArchived != true
ORDER BY c.Date DESC`,
parameters: [],
};
const query = this.container.items.query(querySpec);
const response = await query.fetchAll();
return response.resources;
}
}
export const getCosmosDbInstance = async () => {
const cosmosdb = await CosmoDbAccess.getInstance();
return cosmosdb;
};
index.test.ts
describe("getExemptionNotes()", () => {
beforeEach(() => {
jest.resetAllMocks();
});
it("makes a network call to getKustoResponse which posts to axios and returns what axios returns", async () => {
const mockNotes = [
{
},
];
const cosmosDBInstance = jest
.spyOn(CosmoDbAccess, "getInstance")
.mockReturnValue(Promise.resolve(CosmoDbAccess.instance));
const kustoResponseSpy = jest
.spyOn(CosmoDbAccess.prototype, "getAllNotesForLastSixMonths")
.mockReturnValue(Promise.resolve([mockNotes]));
const actual = await getExemptionNotes();
expect(kustoResponseSpy).toHaveBeenCalledTimes(1);
expect(actual).toEqual(mockNotes);
});
});
I am not able to get instance of CosmosDB or spyOn just the getAllNotesForLastSixMonths method. Please help me code it or give hints. The complexity is because the class is singleton or the methods are static and private
I'm creating a façade for the nats streaming lib as follows:
import nats, { Message, Stan, Subscription, SubscriptionOptions } from 'node-nats-streaming'
class NatsHelper {
private client: Stan | null = null
public connect(url: string, clusterID: string, clientID: string, listener: (...args: any[]) => void, verboseConnection: boolean = true): void {
const clientIDString = `${clientID}-${randomBytes(4).toString('hex')}`
if (verboseConnection) {
console.log(`Connecting to NATS cluster '${clusterID}' with clientID '${clientIDString}' on url '${url}'`)
}
const connectionAttempt = nats.connect(
clusterID,
clientIDString,
{
url
}
)
const setupConnection = (...args: any[]): void => {
this.client = connectionAttempt
this.client.on('close', (): void => {
if (verboseConnection) {
console.log(`Connection with NATS cluster '${clusterID}' with clientID '${clientIDString}' on url '${url}' was closed`)
}
this.client = null
process.exit()
})
process.on('SIGINT', () => this.client?.close())
process.on('SIGTERM', () => this.client?.close())
if (verboseConnection) {
console.log(`Connected to NATS cluster '${clusterID}' with clientID '${clientIDString}' on url '${url}' successfuly`)
}
listener(...args)
}
connectionAttempt.on('connect', setupConnection)
}
}
It happens though that I'm not able to test if the provided listener function is called, because it relies on the Stan 'connect' event to happen and jest finishes the test before it happens.
How can I make jest wait for this event to happen, and then executes the expect function?
You have overcomplicated this. It's perfectly possible to write the test for the original code without modifying it by mocking out the library using jest.mock(), and injecting mock implementations for your on method. Like this:
import nats from "node-nats-streaming";
import { mock } from "jest-mock-extended";
import { NatsHelper } from "./nats";
jest.mock("node-nats-streaming");
describe("NatsHelper", () => {
it("calls listener on connectEvent", () => {
const client = mock<nats.Stan>();
client.on.mockImplementation((name, callback) => {
if (name !== "close") {
callback();
}
return client;
});
jest.mocked(nats).connect.mockReturnValue(client);
const connector = new NatsHelper();
const listener = jest.fn();
connector.connect("foo", "foo", "foo", listener);
expect(listener).toHaveBeenCalled();
});
});
[EDIT] Found the solution I was looking
It happens that we can "convert" an event into a Promise, as follows:
import { randomBytes } from 'crypto'
import nats from 'node-nats-streaming'
export class NullClientError extends Error {
constructor() {
super('Nats client is not connected')
this.name = 'NullClientError'
}
}
export class NatsHelper {
private verboseConnectionString: string
private client: nats.Stan
private connector: nats.Stan
constructor(
private readonly verboseConnection: boolean = true
) { }
public async connect(url: string, clusterID: string, clientID: string, callback: (...args: any[]) => void): Promise<void> {
const clientIDString = `${clientID}-${randomBytes(4).toString('hex')}`
this.verboseConnectionString = `NATS cluster '${clusterID}' with clientID '${clientIDString}' on url '${url}'`
if (this.verboseConnection) {
console.log(`Connecting to ${this.verboseConnectionString}`)
}
this.connector = nats.connect(
clusterID,
clientIDString,
{
url
}
)
this.connector.on('connect', (...args: any[]) => {
const realCallback = this.setupListener(callback)
realCallback(...args)
})
return await new Promise(
resolve => {
if (this.connector) {
this.connector.on('connect', () => {
resolve()
})
}
}
)
}
private setupListener(listener: (...args: any[]) => void): (...args: any[]) => void {
const setupConnection = (...args: any[]): void => {
if (this.connector === undefined) {
throw new NullClientError()
}
this.client = this.connector
if (this.client === undefined) {
throw new NullClientError()
}
this.client.on('close', (): void => {
if (this.verboseConnection) {
console.log(`Connection with ${this.verboseConnectionString} was closed`)
}
process.exit()
})
process.on('SIGINT', () => this.client?.close())
process.on('SIGTERM', () => this.client?.close())
if (this.verboseConnection) {
console.log(`Connected to ${this.verboseConnectionString} successfuly`)
}
listener(...args)
}
return setupConnection
}
}
And then test it with asynchronous tests:
describe('NatsHelper', () => {
test('ensure NatsHelper calls connect with correct values', async () => {
const connectSpy = jest.spyOn(nats, 'connect')
const sut = new NatsHelper(false)
const { url, clusterID, clientID, listener } = makeConnectionParams()
await sut.connect(url, clusterID, clientID, listener)
const clientIDString = connectSpy.mock.calls[0][1]
expect(clientIDString).toContain(clientID)
expect(connectSpy).toHaveBeenCalledWith(clusterID, clientIDString, { url })
})
test('ensure NatsHelper forwards the callback when connected', async () => {
const connectionParms = makeConnectionParams()
const { url, clusterID, clientID } = connectionParms
const listenerSpy = jest.spyOn(connectionParms, 'listener')
const sut = new NatsHelper(false)
await sut.connect(url, clusterID, clientID, connectionParms.listener)
expect(listenerSpy).toHaveBeenCalledTimes(1)
})
}
This is rather a stylistic question. I'm using Pino in some of my Javascript/Typescript microservices. As they're running on AWS I'd like to propagate the RequestId.
When one of my functions is invoked, I'm creating a new child logger like this:
const parentLogger = pino(pinoDefaultConfig)
function createLogger(context) {
return parentLogger.child({
...context,
})
}
function createLoggerForAwsLambda(awsContext) {
const context = {
requestId: awsContext.awsRequestId,
}
return createLogger(context)
}
I'm then passing down the logger instance to all methods. That said, (... , logger) is in almost every method signature which is not too nice. Moreover, I need to provide a logger in my tests.
How do you do it? Is there a better way?
you should implement some sort of Dependency Injection and include your logger there.
if your using microservices and maybe write lambdas in a functional approach, you can handle it by separating the initialization responsibility in a fashion like this:
import { SomeAwsEvent } from 'aws-lambda';
import pino from 'pino';
const createLogger = (event: SomeAwsEvent) => {
return pino().child({
requestId: event.requestContext.requestId
})
}
const SomeUtil = (logger: pinno.Logger) => () => {
logger.info('SomeUtil: said "hi"');
}
const init(event: SomeAwsEvent) => {
const logger = createLogger(event);
someUtil = SomeUtil(logger);
return {
logger,
someUtil
}
}
export const handler = (event: SomeAwsEvent) => {
const { someUtil } = init(event);
someUtil();
...
}
The simplest way is to use some DI library helper to tackle this
import { createContainer } from "iti"
interface Logger {
info: (msg: string) => void
}
class ConsoleLogger implements Logger {
info(msg: string): void {
console.log("[Console]:", msg)
}
}
class PinoLogger implements Logger {
info(msg: string): void {
console.log("[Pino]:", msg)
}
}
interface UserData {
name: string
}
class AuthService {
async getUserData(): Promise<UserData> {
return { name: "Big Lebowski" }
}
}
class User {
constructor(private data: UserData) {}
name = () => this.data.name
}
class PaymentService {
constructor(private readonly logger: Logger, private readonly user: User) {}
sendMoney() {
this.logger.info(`Sending monery to the: ${this.user.name()} `)
return true
}
}
export async function runMyApp() {
const root = createContainer()
.add({
logger: () =>
process.env.NODE_ENV === "production"
? new PinoLogger()
: new ConsoleLogger(),
})
.add({ auth: new AuthService() })
.add((ctx) => ({
user: async () => new User(await ctx.auth.getUserData()),
}))
.add((ctx) => ({
paymentService: async () =>
new PaymentService(ctx.logger, await ctx.user),
}))
const ps = await root.items.paymentService
ps.sendMoney()
}
console.log(" ---- My App START \n\n")
runMyApp().then(() => {
console.log("\n\n ---- My App END")
})
it is easy to write tests too:
import { instance, mock, reset, resetCalls, verify, when } from "ts-mockito"
import { PaymentService } from "./payment-service"
import type { Logger } from "./logger"
const mockedLogger = mock<Logger>()
when(mockedLogger.info).thenReturn(() => null)
describe("Payment service: ", () => {
beforeEach(() => {
resetCalls(mockedLogger)
// reset(mockedLogger)
})
it("should call logger info when sending money", () => {
const paymentService = new PaymentService(instance(mockedLogger))
expect(paymentService.sendMoney()).toBe(true)
})
})
I would not use the requestId as part of the context of the logger, but use it as the payload of the logger, like logger.info({ requestId }, myLogMessage). This was you can have a simple function create a child logger that you can use for the entire module.
I am using rxjs 6 and I am executing two async operations where the order is important.
I do have this chunk of code which works perfectly:
dbmsProxy.createDatastores().subscribe(() => {
UsersDAO.insert(users).subscribe(() => {
console.log('FINISHED ALL THE CHAIN');
});
});
But when I try to use concat of rxjs I do have an issue because the second one is executed before the first one finishes:
concat([dbmsProxy.createDatastores(), UsersDAO.insert(users)]).subscribe();
Below the DBMSProxy methods
public createDatastores(): Observable<string> {
const _this: DBMSProxy = this;
const subject = new Subject<string>();
const subscription: Subscription = UsersDAO.createDatastore().subscribe(
onSuccess,
onError,
onFinally
);
return subject;
function onSuccess(datastore: Nedb): void {
console.log(`USERS Datastore Created Successfully`);
_this.db.users = datastore;
subject.next('success');
}
function onError(err: string) {
subject.error('error');
console.error(err);
}
function onFinally() {
subject.complete();
subscription.unsubscribe();
}
}
public insertDocuments(documents: any, datastore: Nedb): Subject<any> {
const subject = new Subject<any>();
datastore.insert(documents, onInsert);
return subject;
function onInsert(err: Error, newDocuments: any) {
if (err) {
subject.error(err);
} else {
// add to the documents to insert the id just created from nedb when inserting the document
documents.forEach((document: any, ind: number) => {
document.id = newDocuments[ind]._id;
});
subject.next(documents);
}
subject.complete();
}
}
And below the UsersDAO methods:
public static createDatastore(): Subject<Nedb | string> {
const subject = new Subject<Nedb | string>();
const datastore = new Nedb({
filename: USERS_DATASTORE_FULL_NAME,
autoload: true,
onload
});
return subject;
function onload(err: Error) {
if (err) {
subject.error(
`Error creating USERS datastore: ${err.name} - ${err.message}`
);
} else {
subject.next(datastore);
}
subject.complete();
}
}
public static insert(users: User[]): Observable<any> {
return DBMSProxy.getInstance()
.insertDocuments(users, DBMSProxy.getInstance().db.users)
.pipe(catchError((val: any) => of('Error inserting the users')));
}
Any idea of what's going on please?
My current solution is to convert the Subject to Observable, create a new Observable with the second one, and remove the square brackets (otherwise I will get back the observables and not the results) and this seems to work:
const operations = concat(
dbmsProxy.createDatastores().asObservable(),
defer(() => UsersDAO.insert(users))
);
operations.subscribe(onSubscribe);
function onSubscribe(result: any) {
console.log('Finished all: ', result);
}