fireEvent doesn't click button on React component - javascript

I'm writing tests for a React component. It's a timer that starts counting down when you press a button, then stops when you press the same button. I have a test case that tries to press the pause button, wait a second, then press the pause button again, checking the timer to make sure that a second has elapsed:
Timer.test.js
render(<Timer />)
const pauseButton = screen.getByText('pause')
const timerOutput = screen.getAllByRole('heading')[1]
describe('Timer', () => {
test('Timer starts counting down when unpaused', done => {
function fetchTime(callback) {
fireEvent.click(pauseButton)
setTimeout(
fireEvent.click(pauseButton),
1250
)
return callback(timerOutput)
}
function callback(data) {
try {
expect(data).toHaveTextContent('24:59')
done()
} catch(error) {
done(error)
}
}
fetchTime(callback)
})
})
The problem is, the test doesn't seem to be hitting click on pauseButton the way I want it to. Jest tells me in the terminal when I run my test that timerOutput turns out to be '25:00' rather than '24:59', and it seems as if the component failed the test. But this is a problem with the test, not the component; when I run the app in my browser and press the button myself, it works the way it should. How do I get this test to work properly, and hit the buttons the way I want it to?

It's difficult to have an accurate answer with few information about the component itself.
First I would recommend use async arrow function inside test() whenever you need to handle async calls so you don't rely in callback hell.
Besides that, I would try to use jest.useFakeTimers() so you can advance the setTimeout timer in order to test properly. It seems that your second fireEvent.click never gets fired since the test checks it synchronously.
And I just noticed you requested the timerOutput at first but didn't request it after the click events.
I would suggest something like:
test('Timer starts counting down when unpaused', async () => {
jest.useFakeTimers();
fireEvent.click(pauseButton)
setTimeout(
() => {fireEvent.click(pauseButton)},
1250
)
jest.runPendingTimers(); // This would run the above function
expect(screen.getAllByRole('heading')[1]).toHaveTextContent('24:59')
}
})
Indeed, the expect statement would be better from a user perspective assertion, like:
expect(screen.getByText("24:59")).toBeVisible();
Since you don't matter about the HTML elements that contains that text content

Related

How to pause javascript code execution until fetch() actually gets the file

Let's say the standard progression on the page kickstarts with the window load event like this,
window.addEventListener("load",function(){ doTheseFirst(); }, { once: true });
function doTheseFirst()
{
setTimeout(function(){ andThenDoThese(); }, 5000);
// Maybe show some pictures and play some sounds for 5 seconds
}
function andThenDoThese()
{
// Show other pictures and play other sounds until user clicks OK
elementOnPageForOK.addEventListener("click", andThenDoTheseTooWhenUserClicksOK);
}
function andThenDoTheseTooWhenUserClicksOK()
{
// etc etc
// You get the idea
}
Let's say this piece of code is used over and over again.
But on certain occasions the user has to see a notification before the execution chain gets going. An easy solution for that is using an alert box since it blocks/pauses the code execution. The following code makes the alert box pop up as soon as fetch() gets the content from the txt file. Note that fetch() and window load are working independently.
fetch("somefolder/notification.txt").then(function(response){return response.text();}).then(function(contentOfTheTxtFile){ alert(contentOfTheTxtFile); });
This works because when the alert box is closed, the main code goes back to its normal flow just as needed.
But how do I do the same thing without using a crude alert box that I cannot assign any styles to?
Can I make it so that the code execution is paused as soon as fetch() gets the file and then it is unpaused immediately once the user clicks something like an OK or NEXT button?
Is this possible with async/await?
EDIT1: Further clarification
Please remember that it is uncertain whether fetch() or window load will fire the completion signal first. Because download speed is totally unpredictable.
So if we set them both like this, we don't know just how many milliseconds would pass for the other to catch up.
if(thisTimeItIsASpecialCase){
fetch("somefolder/notification.txt").then(function(response){return response.text();}).then(function(contentOfTheTxtFile){ /*What to do when the file is ready*/ });
}
window.addEventListener("load",function(){ doTheseFirst(); }, { once: true });
A simplified way to achieve the desired result: Put doTheseFirst() function on hold and make it wait for fetch() and let it finish its task of getting the file if fetch() is indeed trying to get a file. Also let doTheseFirst() go ahead and run in case there is no fetching to do.
You can do something like the following
You define some html where you want to display your content and hide it until your content is really available
You define a button to hide this element again
The next eventhandler is just there to call a Promise resolve callback which will be defined when needed
When your text is available to add it to your display and create await a new Promise which is resolved upon the click of the next button
The next handler in the promise chain hide the display again.
This code is of course very rudimentary and schematic. For real use you would encapsulate this in a own class or module. If you are using some framework like React, Angular, ... there are tons of components ready to use, which provide exactly this functionalty. For instance as modal dialog or whatsoever.
var resolver = undefined;
function next() {
if (resolver) resolver.call();
}
function getText() {
fetch("https://baconipsum.com/api/?type=meat-and-filler&paras=3&format=text")
.then(response => response.text())
.then(text => {
document.getElementById("thetext").innerText = text;
document.getElementById("display").hidden = false;
return new Promise(resolve => {
resolver = resolve;
});
})
.then(() => {
document.getElementById("display").hidden = true;
resolver = undefined;
})
.catch(e => console.log(e));
}
<input type="button" onclick="getText()" value="Get Text">
<div id="display" hidden>
<div id="thetext"></div>
<input type="button" onclick="next()" value="OK">
</div>
You can show the content of the text file, which you received by fetch, in a nice <div> or in any element in the fetch and you exit the function.
You add an EventListener to a button upon which when user clicks you can move to next action.

TestCafe - How to increase maximum wait time for an If Statement or explicitly defined timeout for If Statement

I am trying to write tow If Statements in which, if the conditions are coming alternately then my code is working fine.
But when both Statements are true together then the second If Statement always skipped because there is a time difference of 5-6 seconds after the first condition executed.
So even though second condition is valid ( waiting for a window to appear) it never went inside the second if-Statement.
I tried writing timeout but it didn't work in TestCafe.
Someone please help if there is any inbuild function to be use for If-Condition just like it is there for assertion -
// await t.expect('.boarding-pass', { timeout: 10000 });
Similar option is not working under If Condition -
// if ( '.boarding-pass'.exists, { timeout: 10000 }){ do something}
It is working only if the Boarding Pass Screen is appearing, if not then it is waiting for 10 seconds and skip second If Statement.
I am not putting the codes for now. If you really need real codes to resolve my issue then I will try to recreate it using some public application.
You can use the built-in Wait Mechanism for Selectors to conditionally execute test statements.
For example, the following code waits for two elements to appear in DOM (you can specify the timeout option), and if an element exists and is visible, clicks it.
import { Selector } from 'testcafe';
fixture('Selector')
.page('http://devexpress.github.io/testcafe/example');
test ('test1',async (t)=\> {
const selector1 = Selector('#remote-testing');
const selector2 = Selector('#remote-testing-wrong', { timeout: 10000 } ); //wrong id, it will never appear
const element1 = await selector1();
const element2 = await selector2();
if(element1 && element1.visible) {
await t.click(selector1);
}
if(element2 && element2.visible) {
await t.click(selector2);
}
});
Please note, that you should use this approach only if the page contains elements that appear occasionally and unpredictably (like a chat window from a third-party component), and you can't turn them off in a test. If an element always appears on executing the test scenario, the recommended way is to use built-in wait mechanisms for actions like click, typetext, etc

Reactjs: beforeunload doesn't get triggered as expected

I'm trying to call a function every time I refresh the page. I'm doing the following:
componentDidMount() {
this.props.fetchData(this.state.data);
window.addEventListener('beforeunload', this.handleRefresh);
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.handleRefresh);
}
And the function I'm trying to call is the following:
handleRefresh = () => {
// refreshPage here is an action method that talks to the backend
this.props.refreshPage(this.props.some_data)
}
The issue here is, the first couple of times I refresh the page, I get the expected output on the backend. After a couple of times, refreshing the page doesn't seem to trigger the backend method at all. Is this the correct way to trigger and get access to the refresh event?
Why are you using an event listener? React provides "lifecycle methods" that fire during specific stages of a page's life. If you want to call a function every time you refresh the page, just put the function inside componentDidMount or the class constructor. Those are executed whenever the page is loaded. See https://reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class
componentDidMount() {
this.props.refreshPage(this.props.some_data);
}
What is the purpose of fetchData? You seem to be running the function but not doing anything with its return value.

NW.js have a "loading" icon while processing request

I have an app created with NW?js that works fine.
However, it does a lot of processing (loading, save file, reload it and processing, ...) All of that is done by functions.
While I see that it is doing something (because when I click on the "button" that execute the function, the button remains "clicked" until it is finished), it seems impossible to get a spinner instead.
I have 2 functions that are used to respectively show and hide a spinner, but when I use them at start and end of the function, they are executed almost immediately (normal for show, but not foe hide) and I can't find the reason why
Any idea how I could have a spinner while my function are still processing and hide it when done ?
Thank you
In JavaScript, functions can't "sleep", all the code is executed without any stop, they return immediately. If you want to delay the execution of a function, you need a callback :
var button = document.getElementById("button");
var spinner = document.getElementById("spinner");
button.onclick = function () {
startSpinning();
button.setAttribute("disabled", "disabled");
doSomethingTimeConsumingThen(function () {
button.removeAttribute("disabled");
stopSpinning();
});
}
function doSomethingTimeConsumingThen (onDone) {
setTimeout(onDone, 1000);
}
function startSpinning () {
spinner.innerHTML = "Spinning...";
}
function stopSpinning () {
spinner.innerHTML = "Not spinning.";
}
<button id="button">Do something time consuming</button>
<div id="spinner">Not spinning.</div>
A callback is called when something happens, for example when a time consuming task is done. In the above snippet the callback is :
function () {
button.removeAttribute("disabled");
stopSpinning();
}
There must be something similar in your API.

Unexpected window.confirm behaviour while hooking into angular-ui-router $transitions API

I've got this listener setup on a form of mine that checks for a state transition to occur via angular router. When the listener is tripped it checks if the form is dirty, if it is it throws a window.confirm alert up saying the user may have unsaved changes.
All of that looks like this
this.setListener('form.dirty-check', this.setExitCheck);
setListener = (el, cb) => {
if ($(el).length) {
cb();
} else {
setTimeout(() => {
this.setListener(el, cb);
}, 500);
}
};
setExitCheck = () => {
this.$transitions.onStart({}, () => {
if ($('#compForm').hasClass('ng-dirty')) {
if (window.confirm('You may have unsaved changes! Press ok to continue, or press cancel to go back and save your work.') === false) {
return false;
} else {
return true;
}
}
});
};
This code is working pretty well, except for a singular bit of unexpected behaviour.
For some reason, when I hit, "Ok" to leave the page the transition will fire off just fine, but if I go back to the page and try it again, I now have to hit okay twice, and get two window.confirm alerts. If I go back a third time to try, I get three window.confirm alerts, and have to hit Ok on all three of them. I tried this up to the point of receiving 10 alerts, and have to press ok 10 times.
Once I refresh the page though, it seems to reset, and I start it all over again. Works right away, then takes two Ok's, then three, and so on.
Does anyone know what might be going on causing this incremental behaviour?
ui-router won't clear listeners automatically, so you have to clear it manually.
and $transitions.onStart returns a function which will destroy the listener's hook when it's called. documentation here(the last line).
the syntax is the same as deregister events of $rootScope, refer How can I unregister a broadcast event to rootscope in AngularJS?
$scope.onStartHandler = this.$transitions.onStart(...);
$scope.$on('destroy', function() {
$scope.onStartHandler();
});

Categories