How to test if a functions is called in React? - javascript

Working template of the project: https://codesandbox.io/s/blue-currying-3me1t
The only test I have set up is in src/App.test.js to check that a name is rendered. I am trying to setup another test that checks to see if a function is called before the buttons are generated.
In FontAwesome.js there is a registerIcons() function that registers all of the icons needed for the website. I call this function in Buttons.js as it is the place where I use the icons.
I want to create a test in App.test.js that checks to see if registerIcons() is called before the buttons are created in Buttons.js.

You could typically do this by manually mocking your FontAwesome dependency like this:
import React from "react";
import { render } from "#testing-library/react";
import Button from "./Buttons.js";
import registerIcons from './FontAwesome'
jest.mock('./FontAwesome', () => ({
__esModule: true
default: jest.fn()
}))
test("registers icons", () => {
render(<Button />);
expect(registerIcons).toHaveBeenCalled();
});
However, it seems code sandbox does not currently support mocking. You could try it in your own IDE though.

Related

I have this React Invalid Hook Call Error that I'm getting and I wanted to see if I was breaking the rules of hooks or is it something else?

I'm getting the error below.
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See (link I couldn't add) for tips about how to debug and fix this problem.
You might have mismatching versions of React and the renderer (such as React DOM)
Here is my code. Is it breaking rules of hooks or is the issue something else?
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useData } from 'useData';
export const checkReference = ({
refId,
}) => {
const data = useData(); //useContext hook
let refData = {};
if (refId) refData = data.getReference(refId);
useEffect(() => {
console.log('INITIAL LOGIC');
if (refData.parameter){
console.log('SECONDARY LOGIC', refData);
}
}, [])
checkReference.propTypes = {
refId: PropTypes.string,
}
checkReference.defaultProps = {
refId: null,
}
}
I am calling it from another file using
checkReference('page-name');
I've got the same issue in my Next.js app. It was a cache related issue. you can try these steps.
Delete node_modules and .next folders
Run npm install or yarn
Start your project again
React hooks are intended to be used inside Functional components like:
export const CustomComponent = () => {}
export function CustomComponent() {}
They cannot be used in normal function because it won't exist inside the context of a React component. If you want to use things like useState or useEffect inside functions defined outside a component, you have to create a custom hook. That is, create a function with the use prefix (e.g. useCheckReference) and then use it like:
export const MyComponent = () => {
const reference = useCheckReference()
}
In that way React knows that that function is presumably gonna be called inside a component and the use of hooks is reliable, also is going to make some optimizations related to hooks and components life cycle.

Javascript working before page rendering in Gatsby

I try to convert a HTML template (Bootstrap 5) to Gatsby template. CSS and pages working expected but in HTML template there is a main.js file and it need to load after page rendered.
I modify the main.js file like that;
import { Swiper } from "swiper/swiper-react.cjs.js";
import GLightbox from "glightbox/dist/js/glightbox.min.js";
import AOS from "aos";
AOS.init();
export const onClientEntry = () => {
window.onload = () => {
console.log("deneme");
/*rest of code*/
};
};
In here I try two way. One of them, I create main.js file inside src->components->assets->js folder. Then in layout.js I try to import that file.
import React from "react";
import PropTypes from "prop-types";
import { Breadcrumb } from "gatsby-plugin-breadcrumb";
import Header from "./partials/header";
import { Helmet } from "react-helmet";
import useSiteMetadata from "./hooks/siteMetadata";
import "./assets/css/style.css";
import "./assets/js/main.js"
However in here in debug not hit the any method inside onClientEntry. So I decide to change my way.
Secondly, I try to add code inside main.js to gatsby-browser.js. That's time again getting Cannot read property 'addEventListener' of null because of html is not ready yet.
My file structure:
window (and other global objects like document) are not available during the SSR (Server-Side Rendering) because this action is performed by the Node server (where for obvious reasons there's no window, yet) so you can't access directly to onload function. In addition, accessing these global objects outside the scope of React (without hooks) can potentially break React's hydration process.
That said, you have a few approaches:
Using React hooks. Specifically, useEffect with empty dependencies ([]) fits your specifications, since the effect will be fired once the DOM tree is loaded (that's what empty deps means):
const Layout = ({children}) => {
useEffect(()=>{
mainJs();
}, [])
return <main>{children}</main>
}
Assuming that your ./assets/js/main.js file has a mainJs() function exported, this approach will load it when the DOM tree is loaded. For example:
const mainJs= ()=> console.log("deneme");
The console.log() will be triggered when the HTML tree is built by the browser. Tweak it to adapt it to your needs.
Adding a window-availability condition like:
export const onClientEntry = () => {
if(typeof window !== 'undefined'){
window.onload = () => {
console.log("deneme");
/*rest of code*/
};
}
};
Alternatively, you can output the console.log directly in your onClientEntry, depending on your needs:
export const onClientEntry = () => {
console.log("deneme");
/*rest of code*/
};
You can even combine both approaches by adding a useEffect in your gatsby-browser if it works for you.

React Hook "useState" cannot be called inside a callback. Using useMediaQuery responsive JS media query

I'm trying to use responsive javascript media queries using useMediaQuery however I can't get it to work, I get: -
Error message:
"useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function
Playground
https://stackblitz.com/edit/react-ts-5vseqr?file=media-query.ts
I think it's erroring on line 4 of media-query.ts
import { useState, useEffect } from 'react'
const useMediaQuery = (query: string) => {
const [match, setMatch] = useState(false)
useEffect(() => {
const updateMatch = () => setMatch(window.matchMedia(query).matches)
updateMatch()
window.matchMedia(query).addEventListener('change', updateMatch)
return () => {
window.matchMedia(query).removeEventListener('change', updateMatch)
}
}, [query])
return match
}
export default useMediaQuery
What you've done here is writing a custom hook(useMediaQuery). You've done that properly so no issues there. Above code snipped is fine.
The problem is in the index.tsx file when you try to use the above custom hook that you've written. As the error suggests your custom hook is called outside the react component there in line 7 of index.tsx.
You have to move the useMediaQuery call inside the App component. Also currently your App component is a class component which you have to convert to a functional component to use hooks inside it.
here's the adjusted code:
https://stackblitz.com/edit/react-ts-m6rwpd?file=index.tsx

prismjs not working between different routes using vuerouter in vuejs

I've imported prism.js globally in main.js file.
Code block syntax highlighting working fine in Home components, but after routing to another page using vue-router, there is no effect.
in main.js
// Global Import
import 'prismjs/prism.js'
import 'prismjs/components/prism-swift.min.js' // swift lang
import './theme/prism-swift-theme.css'
in my about page component...
<pre><code class="language-swift">
private func setupSubviews() {
let value = "Loading code block";
}
</code></pre>
Unable to understand what's wrong here. Is there any way I can import node_modules prismjs files globally? I thought keeping main.js will work fine, but it's not adding globally between routes...
Once you install it with npm the best approach is to import it whenever it is used in each component seperately with import Prism from 'prismjs'. Just make sure to use the Prism.highlightAll() method in the component where you're using prism after the DOM is rendered whether in mount() hook or in the updated Vuejs hook using nextTick method to make sure all the DOM is rendered before using prism. So in your case you should use it this way:
import Prism from "prismjs"
updated: function () {
this.$nextTick(function () {
Prism.highlightAll();
})
}
make sure you call highlightAll in yor components seperately and not globaly.

How to trigger event with Vue Test Utils on a BootstrapVue element?

This issue gives me a hard time and I can't understand how to make Vue Test Utils and BootstrapVue play nice together.
A minimal example would look like this:
MyComponent.vue
<template>
<div>
<b-button variant="primary" #click="play">PLAY</b-button>
</div>
</template>
<script>
export default {
name: 'MyComponent',
methods: {
play() {
console.log("Let's play!");
}
}
}
</script>
In the main.js we use BootstrapVue: Vue.use(BootstrapVue).
This is how I'm trying to test that the click event has been triggered:
import { expect } from 'chai';
import sinon from 'sinon';
import Vue from 'vue';
import { shallowMount, createLocalVue } from '#vue/test-utils';
import BootstrapVue, { BButton } from 'bootstrap-vue';
import MyComponent from '#/components/MyComponent.vue';
const localVue = createLocalVue();
localVue.use(BootstrapVue);
describe('MyComponent.vue', () => {
it('should call the method play when button is clicked', () => {
const playSpy = sinon.spy();
const wrapper = shallowMount(MyComponent, {
localVue,
methods: {
play: playSpy,
},
});
wrapper.find(BButton).trigger('click');
expect(playSpy.called).to.equal(true);
});
});
This gives me:
AssertionError: expected false to equal true
+ expected - actual
-false
+true
I checked How to test for the existance of a bootstrap vue component in unit tests with jest?, but it doesn't apply to BButton.
When running the test I also don't see any output on the command line, where I would expect this line to be executed:
console.log("Let's play!");
What's wrong here?
The reason why the event click couldn't be triggered is the way how shallowMount works in contrast to mount.
As we know, Vue Test Utils provide two methods to mount a component, i.e. render the template and generate a DOM tree:
mount
shallowMount
The first method mount generates a complete DOM tree and traverses through all child components. Most of the time this is not necessary, so the method shallowMount is preferred - it stubs the child components just one level below the parent component.
In my case this was also the root of the problem. BootstrapVue provides components, like BButton, which can be used in your own Vue templates. That means that in the following template:
<template>
<div>
<b-button variant="primary" #click="play">PLAY</b-button>
</div>
</template>
the b-button is a child component, which is stubbed when using shallowMount in the unit tests for our component. That's the reason why we can't find an element button:
const wrapper = shallowMount(MyComponent);
wrapper.find('button'); // won't work
We can find the child component like this:
wrapper.find(BButton); // BButton has to be imported from bootstrap-vue
but if we try to output the rendered element:
console.log(wrapper.find(BButton).element);
we'll get:
HTMLUnknownElement {}
The BButton as a child component hasn't been fully rendered and there is no button element in the DOM tree. But when we use mount we have this behaviour:
const wrapper = mount(MyComponent);
console.log(wrapper.find(BButton).element);
we'll get:
HTMLButtonElement { _prevClass: 'btn btn-primary' }
We see that mount has rendered the child component. When we use mount we can directly access the button element:
wrapper.find('button');
Now that we have the button we can trigger an event like click on it.
I hope this helps other beginners too. The examples are very simplified, don't forget to create localVue using createLocalVue and pass it to the mount method as illustrated in the question.
When using BootstrapVue think very carefully which mounting method you need.
While still performing a shallowMount you should be able to do this:
wrapper.find(BButton).vm.$listeners.click();

Categories