Custom global javascript event dispatcher - javascript

I am trying to create a custom event dispatcher. I have read this article and implemented the code
export default class Dispatcher{
constructor(){
}
addListener (event, callback) {
// Check if the callback is not a function
if (typeof callback !== 'function') {
console.error(`The listener callback must be a function, the given type is ${typeof callback}`);
return false;
}
// Check if the event is not a string
if (typeof event !== 'string') {
console.error(`The event name must be a string, the given type is ${typeof event}`);
return false;
}
// Create the event if not exists
if (this.events[event] === undefined) {
this.events[event] = {
listeners: []
}
}
this.events[event].listeners.push(callback);
}
removeListener(event, callback){
//check if this event not exists
if(this.events[event] === undefined){
console.error(`This event: ${event} does not exist`);
return false;
}
this.events[event].listeners = this.events[event].listeners.filter(listener => {
return listener.toString() !== callback.toString();
})
}
dispatch(event, details){
//check if this event not exists
if(this.events[event] === undefined){
console.error(`This event: ${event} does not exist`);
return false;
}
this.events[event].listeners.forEach((listener) => {
listener(details);
})
}
}
my goal is for my external classes that I import into my main JavaScript file to be able to dispatch events globaly. so i want my js imported like this
import {API2} from '/js/API2.js'
to be able to dispatch an event that can be captured from the main.js file that originally imports the API2 class.
one way that I have tried is to attach the imported dispatcher class to window but this is obviously wrong and produces no result.
How could one implement a dispatcher class that allows me to add events and dispatch events globally from anywhere in the code imported or not?

If I understand it correctly, you want 2 things:
Import the dispatcher into a file and use the class directly
Use a global object to interact with the class
1. import into a file and use it directly
In order to import it directly and use it, create a singleton (so 1 instance of the class) and export it directly:
class Dispatcher{
...
}
export default new Dispatcher() // this will initialise the singleton instantly
In order to use it, you can now import it in any file:
import dispatcher from './dispatcher.js';
It will be the same instance anywhere.
2. make it global
In order to make it global you could actually update the file with the following (either global for nodejs or window for web):
class Dispatcher{
...
}
const dispatcherSingleton = new Dispatcher();
window.dispatcherSingleton = dispatcherSingleton // web
global.dispatcherSingleton = dispatcherSingleton // nodejs
export default dispatcherSingleton // export the singleton

Related

Vitest -Unable to access the DOM inside a test

I am new to front-end testing, and I am trying to get a good grasp on it.
In the process, inside one project, I am creating a test for a Vue component. I want to test that the behaviour of the code is correct (The component has code inside the mounted() hook that has to perform basically some checks and an API call).
I want to check that the code reaches one method. Previously to that, the code creates a click event listener to one element in the DOM.
My test emulates a click event (triggers it), but it cannot assert that the proper method has been called after the click event.
This is due to it not finding the element in the DOM to which it has to add the event listener. It seems that the code cannot find anything inside the document (using .getElementById()).
I wonder why, and how I would resolve this, since I have been stuck here for hours and I haven't found any solution that could work here, even when I have learned some interesting things in the process. I will leave a code example with the code structure I have built:
Inside the component:
<template>
// ...
<button id = "myButton">Add</button>
</template>
<script>
import { classInExternalScriptsFile } from "#/scripts/externalScriptsFile ";
let classIESF = new classInExternalScriptsFile();
export default {
methods: {
setup: function () {
classIESF.setupMethod();
},
},
mounted() {
this.setupMethod();
},
};
</script>
Inside the scriptsFile
export class classInExternalScriptsFile {
setupMethod() {
let myButton = document.getElementById("myButton") // <-- getElementById() returns a null here
if (typeof myButton !== "undefined" && myButton !== null) {
myButton.onclick = () => { // <-- The test code complains because it cannot enter here
// Some lines...
this.mySuperMethod()
}
}
}
mySuperMethod() {
// API call etc.
}
}
Inside the .spec.js test file:
// imports...
import { classInExternalScriptsFile } from "#/scripts/externalScriptsFile.js";
describe("description...", () => {
const mySuperMethodMock = vi
.spyOn(classInExternalScriptsFile.prototype, "mySuperMethod")
.mockImplementation(() => {});
test("That the button performs x when clicked", () => {
let wrapper = mount(myComponent, {
props: ...,
});
let myButton = wrapper.find('[test-id="my-button"]');
myButton.trigger("click");
expect(mySuperMethodMock).toHaveBeenCalled(); // <-- The test fails here
}
}

connectedCallback () on custom elements cannot use forEach to loop data on javascript

can anyone provide a solution to the problem that I'm currently encountering? I created a custom element where this custom element must have been detected on the dom, but I need to have the data contained in this custom element loaded, so my program code is like this.
import './menu-item.js';
class MenuList extends HTMLElement {
// forEach cannot be used if I use the ConnectedCallback () method
connectedCallback() {
this.render()
}
// my data can be from this method setter
set menus(menus) {
this._menus = menus;
this.render();
}
render() {
this._menus.forEach(menu => {
const menuItemElement = document.createElement('menu-item');
menuItemElement.menu = menu;
this.appendChild(menuItemElement);
});
}
}
customElements.define('menu-list', MenuList);
and this is the data I sent in the main.js file
import '../component/menu/menu-list.js';
import polo from '../data/polo/polo.js';
const menuListElement = document.querySelector('menu-list');
menuListElement.menus = polo;
please give me the solution.
The connectedCallback runs before the menus=polo statement.
So there is no this._menus declared.
If all the menus setter does is call render, then why not merge them:
set menus(menus) {
this.append(...menus.map(menu => {
const menuItemElement = document.createElement('menu-item');
menuItemElement.menu = menu;
return menuItemElement;
}));
}

Gia: How to pass event object to handler via eventbus

I am learning how to work with Gia (for small web projects) and I cannot find out how to pass an event object from one component to an event handler of another component over Gia's eventbus.
Here's two basic components, communicating over the eventbus:
class navigation extends Component {
constructor(element) {
super(element);
//
// Define "Sub-components"
this.ref = {
navLinks: [],
};
}
mount() {
//
// Listen for clicks on nav.-links
for (let i = 0; i < this.ref.navLinks.length; i++) {
const link = this.ref.navLinks[i];
link.addEventListener("click", this.handleNavLinkClick.bind(this));
}
}
handleNavLinkClick(e) {
//
// Emit event
let clickedLink = e.target;
if (clickedLink.classList.contains("callHeader")) {
eventbus.emit("callingSubpageHeader");
}
}
}
class subpageHeader extends Component {
mount() {
//
// Listen for call from eventbus
eventbus.on(
"callingSubpageHeader",
this.handleEventBusCall_callHeader.bind(this)
);
}
//
// Eventbus handler(s)
handleEventBusCall_callHeader() {
console.log("The subpage-header was called.");
}
}
The emitting of the event and the subsequent call of the handler inside the second component works just fine. But I would like to pass additional information from the first to the second component when the handler is called. The Gia documentation mentions that the emit method of the eventbus can pass an eventObject to the handler:
Calls any handlers previously registered with the same event name.
Optional event object can be used as a argument, which gets passed
into a handlers as an argument.
eventbus.emit('eventName'[, eventObject]);
Unfortunately, there is no example and I don't know how passing the object works. I tried adding "something" (in this case the link that was clicked in the first component) to the call of the emit-function, but have no idea how/where I can read/use this nor if passing something as an eventObject works this way:
class navigation extends Component {
constructor(element) {
super(element);
//
// Define "Sub-components"
this.ref = {
navLinks: [],
};
}
mount() {
//
// Listen for clicks on nav.-links
for (let i = 0; i < this.ref.navLinks.length; i++) {
const link = this.ref.navLinks[i];
link.addEventListener("click", this.handleNavLinkClick.bind(this));
}
}
handleNavLinkClick(e) {
//
// Emit event
if (clickedLink.classList.contains("callHeader")) {
eventbus.emit("callingSubpageHeader", [e.target]);
}
}
}
It'd be great if someone could explain the concept and syntax of passing an eventObject in this scenario.
The event handler is passed the object from the event as a parameter, so your handler can grab that object as a variable from its function signature, like this:
handleEventBusCall_callHeader(target) {
console.log("The subpage-header was called.");
}
the variable target inside of your event handler is now equal to the object you passed with the event.
When you call the event, you don't need to put your argument in [], that will just put it into an array before passing it which will give you headaches later on. The brackets in the documentation just show that the second argument for eventbus.emit is optional.

How to make service in angular 2+ accessible in javascript

I have the following scenario.
I have a simple angular 2 app with a service which I add to providers in app.module. When I click on a button the app should load a javascript file and execute a function e.g function A defined in this javascript file. So the question is how can I access the service within this function A. My consideration is to append the service to the global window variable.
Are there better ways to achieve it?
Code:
export class MyService{
public editProjectDone = new Subject<string>();
}
app.module.ts
{
...providers: [MyService]
}
app.component.html
<button (click)="editProject()">Edit project</button>
app.component.ts
function editProject(){
... load Javascript file
call javascript file
js.editProject("projectId") // call function defined in javascript file
}
javascript file
{
function editProject(projectId)
{
//do calculation
// fire event that calculation is done
// the calcuation is not done in typescript, but here
MyService.editProjectDone.next()
// The question is here how to access the event and fire it
}
}
So you want to access angular service method in javascript function A().
For example:
Your service class:
export class SettingsService{
getLanguage() {
return 'en-GB';
}
}
Your javascript file
function A() {
settingsService.getLanguage();
}
Solution: Custom Event.
Basically you define a custom event handler in javascript file. And define the Custom Event and dispatchEvent the Custom Event in Angular click event function.
app.component.html:
<input type="button" value='log' (click)="logclick($event)">
app.component.ts
constructor(private settings: SettingsService){}
logclick(event){
// define custom event
var customevent = new CustomEvent(
"newMessage",
{
detail: {
message: "Hello World!",
time: new Date(),
myservice: this.settings //passing SettingsService reference
},
bubbles: true,
cancelable: true
}
);
event.target.dispatchEvent(customevent); //dispatch custom event
}
javascript file:
// event handler function
function newMessageHandler(e) {
console.log(
"Event subscriber on "+e.currentTarget.nodeName+", "
+e.detail.time.toLocaleString()+": "+e.detail.message
);
//calling SettingsService.getLanguage()
console.log(e.detail.myservice.getLanguage());
}
//adding listener to custom event.
document.addEventListener("newMessage", newMessageHandler, false);
Example output:
Event subscriber on #document, 9/11/2018, 11:31:36 AM: Hello World!
en-GB
Note: I have not added section for dynamically loading javascript file. I assume you are already able to do that from your comments.
Declare variable as public using window object but in proper way. export only some functions not whole service and in some standard way like below.
In Angular
export class AbcService {
constructor() {
const exportFunctions = {
xyzFunction: this.xyzFunction.bind(this),
pqrFunction: this.pqrFunction.bind(this)
}; // must use .bind(this)
window['ngLib']['abcService'] = exportFunctions;
}
xyzFunction(param1, param2) {
// code
}
pqrFunction() {
// code
}
private oneFunction() {
// code
}
private twoFunction() {
// code
}
}
In Javascript
ngLib.abcService.xyzFunction(value1, value2);
ngLib.abcService.pqrFunction();
Firstly you need to import the js file before calling the service and that can be done by:
TS
let js: any = require('JSFileNAME')['default']; //Something lik this in constructer
then once the file is imported you need to create a instantiate of the js in your Ts
something like
this.newObjectOfJs = new js(); // pass any paramater if its required.
hereafter you will be able to access the methods and service from the JSfile.
You have to use service variable to access functions and variable of particular services.
Here I demonstrate how you can do it.
export class AppComponent {
name = 'Angular';
constructor(private myServices: MyServices){
this.myServices.sayHello();
}
}
Whole code available here : https://stackblitz.com/edit/angular-services-example

Mock Es6 classes using Jest

I'm trying to mock an ES6 class with a constructor that receives parameters, and then mock different class functions on the class to continue with testing, using Jest.
Problem is I can't find any documents on how to approach this problem. I've already seen this post, but it doesn't resolve my problem, because the OP in fact didn't even need to mock the class! The other answer in that post also doesn't elaborate at all, doesn't point to any documentation online and will not lead to reproduceable knowledge, since it's just a block of code.
So say I have the following class:
//socket.js;
module.exports = class Socket extends EventEmitter {
constructor(id, password) {
super();
this.id = id;
this.password = password;
this.state = constants.socket.INITIALIZING;
}
connect() {
// Well this connects and so on...
}
};
//__tests__/socket.js
jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');
expect(Socket).toHaveBeenCalledTimes(1);
socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');
As obvious, the way I'm trying to mock Socket and the class function connect on it is wrong, but I can't find the right way to do so.
Please explain, in your answer, the logical steps you make to mock this and why each of them is necessary + provide external links to Jest official docs if possible!
Thanks for the help!
Update:
All this info and more has now been added to the Jest docs in a new guide, "ES6 Class Mocks."
Full disclosure: I wrote it. :-)
The key to mocking ES6 classes is knowing that an ES6 class is a function. Therefore, the mock must also be a function.
Call jest.mock('./mocked-class.js');, and also import './mocked-class.js'.
For any class methods you want to track calls to, create a variable that points to a mock function, like this: const mockedMethod = jest.fn();. Use those in the next step.
Call MockedClass.mockImplementation(). Pass in an arrow function that returns an object containing any mocked methods, each set to its own mock function (created in step 2).
The same thing can be done using manual mocks (__mocks__ folder) to mock ES6 classes. In this case, the exported mock is created by calling jest.fn().mockImplementation(), with the same argument described in (3) above. This creates a mock function. In this case, you'll also need to export any mocked methods you want to spy on.
The same thing can be done by calling jest.mock('mocked-class.js', factoryFunction), where factoryFunction is again the same argument passed in 3 and 4 above.
An example is worth a thousand words, so here's the code.
Also, there's a repo demonstrating all of this, here:
https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working
First, for your code
if you were to add the following setup code, your tests should pass:
const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want
Socket.mockImplementation(() => {
return {
connect: connectMock
};
});
(Note, in your code: Socket.mock.calls[0][1] should be [0][0], and [0][2] should be [0][1]. )
Next, a contrived example
with some explanation inline.
mocked-class.js. Note, this code is never called during the test.
export default class MockedClass {
constructor() {
console.log('Constructed');
}
mockedMethod() {
console.log('Called mockedMethod');
}
}
mocked-class-consumer.js. This class creates an object using the mocked class. We want it to create a mocked version instead of the real thing.
import MockedClass from './mocked-class';
export default class MockedClassConsumer {
constructor() {
this.mockedClassInstance = new MockedClass('yo');
this.mockedClassInstance.mockedMethod('bro');
}
}
mocked-class-consumer.test.js - the test:
import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';
jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.
// console.log(MockedClass()); // logs 'undefined'
let mockedClassConsumer;
const mockedMethodImpl = jest.fn();
beforeAll(() => {
MockedClass.mockImplementation(() => {
// Replace the class-creation method with this mock version.
return {
mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
};
});
});
beforeEach(() => {
MockedClass.mockClear();
mockedMethodImpl.mockClear();
});
it('The MockedClassConsumer instance can be created', () => {
const mockedClassConsumer = new MockedClassConsumer();
// console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
expect(mockedClassConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
const mockedClassConsumer = new MockedClassConsumer();
expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});
it('We can check if the consumer called a method on the class instance', () => {
const mockedClassConsumer = new MockedClassConsumer();
expect(mockedMethodImpl).toHaveBeenCalledWith('bro');
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
});
For me this kind of Replacing Real Class with mocked one worked.
// Content of real.test.ts
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../test/__mocks__/RealClass"
);
return {
...mockedModule,
};
});
var codeTest = require("../real");
it("test-real", async () => {
let result = await codeTest.handler();
expect(result).toMatch(/mocked.thing/);
});
// Content of real.ts
import {RealClass} from "../RealClass";
export const handler = {
let rc = new RealClass({doing:'something'});
return rc.realMethod("myWord");
}
// Content of ../RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "The.real.deal "+input;
}
// Content of ../test/__mocks__/RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "mocked.thing "+input;
}
Sorry if I misspelled something, but I'm writing it on the fly.

Categories