I am trying to write test case which has submit type and has prevent default inside submit button. It looks like event is not firing in test case.
form.js
click me
function submit(e) {
e.preventDefault();
}
my test case
it("Click with correct credentials", async () => {
await act(() => {
render(<XYZ />, container);
});
const submitLogin = screen.getByTestId("submit-login-btn");
act(() => {
submitLogin.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await expect(global.window.location.pathname).toEqual("/expectedPath");
});
Onclick on "click me", it should redirect to /expectedPath. But test case failing, I am guessing its due to submit button and prevent default.
Related
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');
}
})
I feel like I am missing something when it comes to testing React components with async fetch operations.
I have a following component like this...
export default function Page() {
const [result, setResult] = useState();
async function initialize() {
const response = api.fetchData();
setResult(response);
}
useEffect(() => {
initialize();
}, []);
return (isLoading ? (
<div>Fetching Data...</div>
) : (
<div className="page-result-name">{result.name}</div>
)
}
I want to create the following test.
test('Page rendering test.', async () => {
jest.spyOn(api, 'fetchData').mockResolvedValue({ name: 'User 1'});
const pageRendering = renderer.create(<Page />);
// Wait? How to wait?
// I have tried the following... as well as other method.
// await waitFor(() => {
// const element = document.getElementByClassName('page-result-name')[0] as
// HTMLElement;
// expect(element.innerHTML.includes('User 1')).toBeTruthy();
// });
expect(pageRendering.toJSON()).toMatchSnapshot();
});
The issue I am running into is that the data has not been returned by the time the snapshot is taken.
Regardless of the approach I run into issues with warnings like the following
Warning: It looks like you're using the wrong act() around your test interactions
Or it always displays Fetching Data...
I have used render from #testing-library/react and the waitFor and things work well. I don't know how to generate a snapshot for that.
Any help here would be appreciated!!
Cheers!!
For async operations the tricky is waiting for loading component to be removed from the screen, this way you assure that the your component is completed loaded and then you can expect the snapshot:
test('Page rendering test.', async () => {
jest.spyOn(api, 'fetchData').mockResolvedValue({ name: 'User 1'});
const pageRendering = renderer.create(<Page />);
// Wait? How to wait?
// wait for your data to be loaded before you make your assertion.
await waitForElementToBeRemoved(screen.getByText('Fetching Data...'));
expect(pageRendering.toJSON()).toMatchSnapshot();
});
I'm trying to test a component that loads data asynchronously when mounted. The component works as expected, it's just the test that's giving me issues. The component's async loadData() function hangs at await axios.get() while jest test runner is in the component.vm.$nextTick(). As a result, the checks in the $nextTick loop never pass.
Immediately after the $nextTick loop times out, the component's await statement completes and the component renders itself. axios is mocked, so it should resolve really fast. If I remove the await and just fill in a constant instead, the entire thing executes as expected.
I'm guessing that $nextTick loop is not asynchronous and it's consuming the thread, even though this is the recommended way of testing asynchronous stuff. The problem is, I don't have an onclick async handler to await: this method is called from onMount.
Unfortunately, I don't know how to make a jsFiddle of this one, so I hope this will be enough:
my component (the relevant parts)
export default {
data() { return { content: '' }; },
mounted() { this.loadDoc() }
methods: {
async loadDoc() {
const res = await axios.get('some url'); // <-- this is the line that hangs until timeout
// const res = { data: 'test data'}; // this would test just fine
this.content = res.data;
}
}
}
and my component.spec.js:
jest.mock('axios', () => ({
get: async (url) => {
return { data: 'test data' };
}
};
describe('my super test', () => {
it('renders', (done) => {
const doc = shallowMount(myComponent);
doc.vm.$nextTick(() => {
expect(doc.html()).toContain('test data'); // <-- this never matches
done();
});
});
});
I would delete, but I just spent quite some hours for something that was suggested in the docs, but not explained that it's the only way... I'm hoping somebody else finds this useful.
Using flush-promises package instead of $nextTick loop immediately "fixed" the problem
Code sample (rework of above):
describe('my super test', () => {
it('renders', async() => {
const doc = shallowMount(myComponent);
await flushPromises();
expect(doc.html()).toContain('test data'); // <-- now it works
});
});
I have a simple react functional component.
The code should be self-explanatory. If the status is equal to 'some status', go to a URL, fetch some information, and set the state of the component to the information that has been fetched. On the return () just display the information. Everything works fine, the id of the data is displayed. However, when I open the dev tools and do the inspection, the console.log("data"+data.id); is run indefinitely. I wonder what is making it run indefinitely.
if I remove the change data(data) from inside the fetch, the console.log does not run indefinitely.
I am scratching my head as to why, changing the status would make the code enter in an infinite loop?
function ReturnInfo(props) {
var currentstatus = props.currentstatus; // receiving the current status as prop from parent.
const [data, changeData] = useState({});
let { slug } = useParams(); // getting the slug.
if (currentstatus == 'some status') {
fetch(`https:someurl/${slug}`).
then(res => res.json()).
then(data => {
console.log("data" + data.id);
changeData(data);
});
return (
<div>
<div>
{data.id}
</div>
</div>
)
}
else {
return (
<p>try harder!</p>
)
}
}
You should use useEffect, The Effect Hook lets you perform side effects in function components:
useEffect docs = https://reactjs.org/docs/hooks-effect.html
And if you don't add the dependency array it will run on each update.
Simple wrap your side-effect/async task code within the useEffect function.
And add the dependency array. add empty array if you want to run it only once.
useEffect(() => {
fetch(`https:someurl/${slug}`).
then(res => res.json()).
then(data => {
console.log("data" + data.id);
changeData(data);
});
}, [slug])
It will stop the unnecessary rerender.
Edit Try this
function ReturnInfo(props) {
var currentstatus = props.currentstatus; // receiving the current status as prop from parent.
const [data, changeData] = useState({});
let { slug } = useParams(); // getting the slug.
useEffect(() => {
fetch(`https:someurl/${slug}`).
then(res => res.json()).
then(data => {
console.log("data" + data.id);
changeData(data);
});
}, [slug])
if (currentstatus === 'some status') {
return (
<div>
<div>
{data.id}
</div>
</div>
)
}
return <p>try harder!</p>
}
You are calling the function every time the component is rendered. The function gets called, it updates the state, and makes the component to re-render.
You should call the function when an event occurrs or change the currentstatus value every time the block is executed.
changeData(data) will cause reevaluation of the component. Which leads to call fetch again, which make infinite loop.
useEffect(() {
if (currentstatus == 'some status') {
fetch(`https:someurl/${slug}`).
then(res => res.json()).
then(data => {
console.log("data" + data.id);
changeData(data);
});
}},[])
// Balance.jsx
...
updateToken () {
const parseResponse = (response) => {
if (response.ok) {
return response.json()
} else {
throw new Error('Could not retrieve access token.')
}
}
const update = (data) => {
if (data.token) {
this.data.accessTokenData = data
} else {
throw new Error('Invalid response from token api')
}
}
if (this.props.balanceEndpoint !== null) {
return fetch(this.props.accessTokenEndpoint, {
method: 'get',
credentials: 'include'
})
.then(parseResponse)
.then(update)
.catch((err) => Promise.reject(err))
}
}
componentDidMount () {
this.updateToken()
.then(() => this.updateBalance())
}
}
// Test
it('updates the balance', () => {
subject = mount(<Balance {...props} />)
expect(fetchMock.called('balance.json')).to.be.true
})
I can't figure out how to test the above using Mocha. The code is does work the method updateBalance is called and the fetch api call actually does happen, but the test still fails. If I call updateBalance() synchronously it passes... How do I tell the test to wait for the promise to resolve?
You don't really say what you want to test that the
method does, but if all you want to test is that the method resolves on a network call, then there is no need for Sinon or any of that, as this is all you need:
describe("BalanceComponent", () => {
it("should resolve the promise on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
// assumes you call a network service that returns a
// successful response of course ...
return component.updateToken();
});
});
This will test that the method actually works, but it is slow and is not a true unit test, as it relies on the network being there and that you run the tests in a browser that can supply you with a working implementation of fetch. It will fail as soon as you run it in Node or if the service is down.
If you want to test that the method actually does something specific, then you would need to to that in a function passed to then in your test:
it("should change the token on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
const oldToken = component.data.accessTokenData;
return component.updateToken().then( ()=> {
assert(oldToken !== component.data.accessTokenData);
});
});
If you want to learn how to test code like this without being reliant on there being a functioning link to the networked service you are calling, you can check out the three different techniques described in this answer.