How to test keyup with addEventListener - jestjs

I want to test handleKeyPress method, but when i simulate keyup, then i have this error: Expected spy to have been called, but it was not called.
//App.js
componentDidMount() {
document.addEventListener('keyup', this.handleKeyPress)
}
handleKeyPress = (event) => {
if (event.keyCode === 38) {
this.setState({
up: true
})
}
}
//App.test.js
it('check handleKeyPress with keyup', ()=>{
let instance = wrapper.instance()
let handleKeyPress = spyOn(instance, 'handleKeyPress')
wrapper.simulate('keyup', {keyCode: 38})
expect(handleKeyPress).toHaveBeenCalled()
})

You are simulating the keyup event on your component wrapper but adding the keyup event listener to the document object. The Common Gothcas section mentions some stuff about event propagation not working as you would expect.
The only way I can think to get this working would be to spy on document.addEventListener and manually call the handler in your test file.
it('check handleKeyPress with keyup', () => {
// save keyup event handler added to document
let keyUpHandler;
document.addEventListener = jest.fn((event, handler) => {
if (event === 'keyup') {
keyUpHandler = handler;
}
});
// render component
wrapper = shallow(...);
let instance = wrapper.instance()
let handleKeyPress = jest.spyOn(instance, 'handleKeyPress')
// call the keyup handler with the event data
keyUpHandler({ keyCode: 38 })
expect(handleKeyPress).toHaveBeenCalled()
})

Related

chrome extension content.js file loads too early: unable to find an element in DOM

I'm trying to add a listener to the linkedIn 'create post' button through a chrome extension
Now, because I added a timeout, the button is found, but if I run it directly or with a smaller timeout (eg 1000ms) the button is not found
Here's my code:
function findStartPostField() {
const lnCssSelector = '.share-box-feed-entry__trigger'
let button = document.querySelector(lnCssSelector)
console.log('button found ', button)
if (button)
button.addEventListener('click', () => alert('clicked'))
}
setTimeout(findStartPostField, 5000)
console.log('content js loaded, registering message listener');
In my manifest, I tried run_at with document_end and document_idle values without success.
I don't like the idea of having to put a timeout. Is there an event like 'onload' that would trigger when all JS has finished executing (somehow saying the document is rendered and ready)
1. Using message passing.
Firstly register a onload event listener on the extension client side.
Inside the extension's client side onload event listener, send one time message to the content-script.
On the content-script side, for catching incoming messages, register chrome.runtime.onMessage event listener and read the onload type message sent from extension side. Here you can do your DOM mutation.
For example -
popup.js
addEventListener("load", (event) => {
chrome?.tabs?.sendMessage({
type: 'ONDOMLOADED',
sender: 'EXTENSION'
}, function(response) {
console.log(response);
});
});
content_script.js
chrome?.runtime?.onMessage?.addListener(function (request, sender, sendResponse) {
const type = request?.type;
console.assert(request?.sender === 'EXTENSION');
switch(type) {
case 'ONDOMLOADED': {
// DOM ALL THE onDOM CONTENT LODADED THINGS HERE
findStartPostField();
return sendResponse({
type: 'ONDOMLOADED_RESPONSE'
});
}
default:
return;
}
});
2. Using window.onload
content_script.js
window?.onload = function () {
findStartPostField()
}
Hope, it helps you :)
Here's an implementation using MutationObserver.
const onMutation = (mutations) => {
mo.disconnect();
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node) {
if (node.className) {
if (node.className == 'share-box-feed-entry__trigger') {
node.addEventListener('click', () => alert('clicked'))
}
}
}
}
}
observe();
}
const observe = () => {
mo.observe(document, {
subtree: true,
childList: true,
});
}
const mo = new MutationObserver(onMutation);
observe();
Here's an implementation based on wOxxOm's comment.
document.body.addEventListener('click', (e) => {
if (e.target.className == 'share-box-feed-entry__trigger') {
alert('clicked');
}
})

NodeJS EventEmitter: how to wait for all events to finish?

Got a class, it extends EventEmitter.
Got function that fires a bunch of events on that emitter. These events trigger async tasks and they all have a done() callback.
What's the proper way to wait for all tasks to finish? I just want the process to sit there and wait for events until a certain event is fired (ALL_DONE), in which case it should exit.
I mean I know this can be done in multiple ways probably, but what I'm asking is can I do it without any packages, plugins etc, using just nodeJS APIs?
I want to wait without blocking the main thread.
EDIT:
Thx for the responses! I'm not sure these apply to my case. I should have provided more details. This is what I have:
class FoobarEmitter extends EventEmitter {
protected checkEventStatus() {
// this has some logic to check if all done() callbacks have been called or not.
if(allDone) {
this.emit('ALL_DONE')
}
}
protected fireEvents() {
for() {
this.emit('SOME_EVENT', () => {})
this.checkEventStatuses();
}
}
protected registerHandlers() {
this.on('SOME_EVENT', async (done) => {
// does async stuff
// might also call this.emit('OTHER_EVENT', () => {})
done();
})
this.on('ALL_DONE', () => { process.exit() })
}
constrcutor() {
this.registerHandlers();
this.fireEvents()
}
}
new FoobarEmitter()
So this will not wait for all events. The ones fired from callbacks won't finish. Some of them runs, then the process just stopes and ALL_DONE is never fired.
Doesn't seem like it ought to be any more complex than something like this:
const { once, EventEmitter } = require("events");
class FooBarEmitter extends EventEmitter {
}
async function doSomethingUseful( emitter ) {
// does something useful, emitter emits events in the process
}
async function main() {
const emitter = new FooBarEmitter();
const promise = doSomethingUseful(emitter) ;
await once(emitter, "ALL_DONE");
await promise;
}
let cc;
main()
.then( () => {
console.log(
cc = 0;
})
.catch( err => {
console.error(err.stack);
cc = 1;
})
.finally( () => {
process.exit(cc);
});
Or, another approach:
const { once, EventEmitter } = require("events");
class FooBarEmitter extends EventEmitter {
}
async function doSomethingUseful( emitter ) {
// does something useful, emitter emits events in the process
}
function main() {
let promise;
const emitter = new FooBarEmitter().on('ALL_DONE', async () => {
await promise;
});
promise = doSomethingUseful(emitter) ;
}
main();

react testing library - test window.addEventListener('resize')

I am using jest & react-testing-library.
In my component on didMount I add an event listener to the window reacting on resize events. Based on resize events, another function is called. In my test, that function is mocked.
Now I have the problem, that I am not able to trigger these resize events.
Is there any way to get that done?
window.resizeTo(500, 500);
fireEvent.resize(window);
fireEvent(window, new Event("resize"));
I tried to achieve the triggering of the event listener on different ways, but nothing worked.
Thanks for your help in advance :)
Here's an example of how to spy on window.addEventListener to make sure that it's been invoked (and your mock function is registered) before you dispatch the resize event:
The example component is a functional one and uses the effect hook, but the test should be the same for a class component.
TS Playground
example.test.tsx:
import {useEffect} from 'react';
import {fireEvent, render, waitFor} from '#testing-library/react';
type ComponentProps = { callback: () => unknown };
function Component ({callback}: ComponentProps) {
useEffect(() => {
const handleResize = () => {
// You described some extra logic here,
// but this is where the callback is invoked:
callback();
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [callback]);
return null;
}
test('callback is invoked after window resize event', async () => {
let aResizeEventListenerWasAddedToWindow = false;
const originalMethod = window.addEventListener;
const spy = jest.spyOn(window, 'addEventListener');
spy.mockImplementation((...args) => {
// Because this custom implementation is created for the spy,
// Jest will no longer automatically invoke the original.
// It needs to be done manually:
originalMethod(...args);
const [eventType] = args;
if (eventType === 'resize') aResizeEventListenerWasAddedToWindow = true;
});
const callback = jest.fn();
render(<Component {...{callback}} />);
// Wait for the resize handler in the component to be registered (useEffect callback is async)
await waitFor(() => expect(aResizeEventListenerWasAddedToWindow).toBeTruthy());
fireEvent.resize(window);
expect(callback).toHaveBeenCalled();
// Restore the original method to window.addEventListener
spy.mockRestore();
});

How to abort async function when 'stop' event has been emitted

I trying to make a puppeteer.js bot to be able to pause and resume its work.
In general, i have a class with a dozen of async methods, event emitter and a property called 'state' with setter to change it. When I have event 'stop', I want some async functions to be aborted. How can I achieve this?
I thought i need to observe when this.state becomes 'stop', and run return; but hadn't found any solution.
Then I decided to try to set a handler on an event which changes state to 'stop', but I cannot abort async functions from the handler on the stop event.
constructor() {
this.state = 'none';
this.emiter = new events.EventEmitter();
this.setHandler('stop', () => this.stop());
this.setHandler('resume', () => this.resume());
this.setHandler('onLoginPage', () => this.passAuth());
// ...
// And dozen of other states with its handlers
}
stop= () => this.setState('stoped', true);
resume = () => this.setState(this.getPreviousState());
getPreviousState = () => ...
// Just an example of a state handler. It has async calls as well
// I want to abort this function when event 'stop' is emitted
#errorCatcher()
async passAuth() {
const { credentials } = Setup.Instance;
await this.page.waitForSelector(LOGIN);
await typeToInput(this.page, EMAIL_INPUT, credentials.login);
await typeToInput(this.page, PWD_INPUT, credentials.pass);
await Promise.all([
await this.page.click(LOGIN),
this.page.waitForNavigation({ timeout: 600000 }),
]);
await this.page.waitFor(500);
await DomMutations.setDomMutationObserver(this.page, this.socketEmitter);
// ...
// And dozen of handlers on corresponding state
setState(nextState, resume) {
// Avoiding to change state if we on pause.
// But resume() can force setstate with argument resume = true;
if (this.state === 'stoped' && !resume) return false;
console.log(`\nEmmited FSM#${nextState}`);
this.emiter.emit(`FSM#${nextState}`);
}
setHandler(state, handler) {
this.emiter.on(`FSM#${state}`, async () => {
this.state = state;
console.log(`State has been changed: ${this.getPreviousState()} ==> ${this.state}. Runnig handler.\n`);
//
// On the next line, we run a corresponding handler func,
// like passAuth() for state 'onLoginPage'. It has has to be aborted
// if emiter gets 'FSM#stoped' event.
//
await handler();
});
}
}```
I expect the async functions to be aborted when event emitter emits 'stop';
It is impossible to do it natively.
Alternatively, there are two other way to do it.
check your state after any call of await, for example:
class Stated {
async run() {
await foo()
if(this.stopped) return
await bar()
if(this.stopped) return
await done()
}
}
const s = new Stated()
s.run()
use generator with custom wrapper rather than async/await.
// the wrapper
function co(gen, isStopped = () => false) {
return new Promise((resolve, reject) => {
if (!gen || typeof gen.next !== 'function') return resolve(gen)
onFulfilled()
function onFulfilled(res) {
let ret
try {
ret = gen.next(res)
} catch (e) {
return reject(e)
}
next(ret)
}
function onRejected(err) {
let ret
try {
ret = gen.throw(err)
} catch (e) {
return reject(e)
}
next(ret)
}
function next(ret) {
if (ret.done || isStopped()) return resolve(ret.value)
Promise.resolve(ret.value).then(onFulfilled, onRejected)
}
});
}
// the following is your code:
class Stated {
* run() {
yield foo()
yield bar()
yield done()
}
}
const s = new Stated()
co(s.run(), () => s.stopped)

Knex event on transaction success

I have a function that takes a transaction object as an argument. Can this function subscribe to an event that fires when the transaction is commited?
function createUser (data, trx) {
trx.on('success', .. )
return User.create(data, { transacting: trx })
}
I don't see anything like that in the source, if not inner/outer transaction can be used somehow.
https://github.com/tgriesser/knex/blob/master/src/transaction.js
I never found better solution. But transaction is event emmiter so you can override default knex functions to emit your custom event.
Override commit to fire event.
knex = require('knex')({...});
const _transaction = knex.transaction;
knex.transaction = (cb) => {
return _transaction(trx => {
const _commit = trx.commit;
trx.commit =async (conn, value) => {
const out = await _commit(conn, value);
trx.emit('commit');
return out;
}
return cb(trx);
})
};
Listen to commit event anywhere in code
knex.transaction(async trx => {
trx.on('commit', async () => {
// fired after commit is done
});
await trx.select().from('users').update({...});
})

Resources