I have a React app, and I'm trying to build out some unit tests.
I got most of this test working, but I still can't figure out how to test one line using jest. I can't tell if it's because of the package I imported or if I'm doing something else incorrectly.
LogOutForDisagreement.js
import React from 'react';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
import ClipboardClip from './../ClipboardClip';
const store = require('store2');
function LogOutForDisagreement(props) {
const handleClick = (e) => {
e.preventDefault();
props.onChange(
props.props.user = {
loggedIn: false,
email: '',
password: '',
firstName: ''
},
);
store.clearAll();
//sessionStorage.clear();
};
return (
<div id="logOutForDisagreementClipboard" className={props.className}>
<Form className="form">
<ClipboardClip />
<div className="centeredPaper">
<p>Sorry, you must agree to our privacy policy to access the site. You must now log out and log back in to agree to the terms.</p>
<Button id="logOutForDisagreement" type="" onClick={handleClick}>
Log Out
</Button>
</div>
</Form>
</div>
);
}
export default LogOutForDisagreement;
LogOutForDisagreement.test.js
import React from 'react';
import { render, fireEvent, cleanup } from '#testing-library/react';
import toJson from 'enzyme-to-json';
import LogOutForDisagreement from './LogOutForDisagreement';
const store = require('store2');
let props = {
props:
{
user: {
loggedIn: false,
email: 'email#example.com',
password: '0cc3dd4ad221f83270f876908064fa67d43407aa4bd580731b30c58c4fe2d8de00464107e5a143594068461af21ece9fb542e2f254b6f895eea6a6627f4f7c438a42fb18af7f539c4be2456661a5beec4a561f9443988182445b3952a5d9321a2d725b24b151f79a2806432c848b0b0c02576676e3a5c6f0661b4e318ca4f3e134d066808fab8fdd5322ed5cf5ad68aae43254a8fefdb69809c1bfdc07fe0365f38baa424d0c059c3a9fbff1f3525dae410740b9719929ef3f34235da519591f0410a08438132600fa802079b8d6f372f6dc439eb1b100aed28bb55cc3c6dc8982644940bd506278943fa8e430836cb874283e9f4438aac04a817f86bd1606036f03d196a211bdd91ac683d4ec63fcd503aa97b53d5c2571d39855b2be5f77be80a7f767271c8789aec26c66530c22387007c704f96b1a76a47e8e13fb263a0a6b24b2959495d34b47e03bcb95e8af13b555c5c403ec01427182afa1bae35ff81224b051fde7a61bd9044ed74042444a05e06186eedacc38c0128ff7d70c',
firstName: 'FirstName'
},
className: "logOutForDisagreement"
}
};
//You don't need the props for everything, just the className, but I'm leaving them here for now.
describe('Test the log out for disagreement button', () => {
it('renders correctly enzyme and has onclick', async () => {
console.log("This is the store at the begining after initial load: {" + Object.entries(sessionStorage).map(([key, val] = sessionItem) => {
return `${key}: ${val}`}) + "}");
const { getByText } = render(<LogOutForDisagreement id="logOutForDisagreementClipboard" className={props.className} />);
const spy = jest.spyOn(store, 'clearAll');
await fireEvent.click(getByText("Log Out"));
console.log("This is in the store after the event fires: {" + Object.entries(sessionStorage).map(([key, val] = sessionItem) => {
return `${key}: ${val}`}) + "}");
expect(spy).toHaveBeenCalled();
expect(toJson(getByText)).toMatchSnapshot();
//spy.mockRestore();
});
});
I still get an error that says the following:
Test the log out for disagreement button › renders correctly enzyme and has onclick
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
37 | return `${key}: ${val}`}) + "}");
38 |
> 39 | expect(spy).toHaveBeenCalled();
| ^
40 |
41 | expect(toJson(getByText)).toMatchSnapshot();
42 | //spy.mockRestore();
...
--------------------------------------------|---------|----------|---------|---------|----------------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------------------------------|---------|----------|---------|---------|--------------------
...
LogOutForDisagreement.js | 83.33 | 100 | 100 | 83.33 | 19
I'm just missing one line. Does anyone know how to get 100% test coverage on this? I know this is picky, but I use this same package in a lot of places.
I'm just posting the answer for anyone who runs into a similar issue. I used a tutorial from youtube.com that also used a third party library (axios) and mocked the built in axios functions: https://www.youtube.com/watch?v=gA-uNj2FgdM. I also had to pass the props down to the component and include onChange in the component, but now I have 100% test coverage.
LogOutForDisagreement.test.js
import React from 'react';
import { render, fireEvent, cleanup, screen } from '#testing-library/react';
import toJson from 'enzyme-to-json';
import LogOutForDisagreement from './LogOutForDisagreement';
import mockStore from 'store2';
jest.mock('store2');
mockStore.clearAll(() => Promise.resolve({}));
let props = {
props:
{
user: {
loggedIn: false,
email: 'email#example.com',
password:'0cc3dd4ad221f83270f876908064fa67d43407aa4bd580731b30c58c4fe2d8de00464107e5a143594068461af21ece9fb542e2f254b6f895eea6a6627f4f7c438a42fb18af7f539c4be2456661a5beec4a561f9443988182445b3952a5d9321a2d725b24b151f79a2806432c848b0b0c02576676e3a5c6f0661b4e318ca4f3e134d066808fab8fdd5322ed5cf5ad68aae43254a8fefdb69809c1bfdc07fe0365f38baa424d0c059c3a9fbff1f3525dae410740b9719929ef3f34235da519591f0410a08438132600fa802079b8d6f372f6dc439eb1b100aed28bb55cc3c6dc8982644940bd506278943fa8e430836cb874283e9f4438aac04a817f86bd1606036f03d196a211bdd91ac683d4ec63fcd503aa97b53d5c2571d39855b2be5f77be80a7f767271c8789aec26c66530c22387007c704f96b1a76a47e8e13fb263a0a6b24b2959495d34b47e03bcb95e8af13b555c5c403ec01427182afa1bae35ff81224b051fde7a61bd9044ed74042444a05e06186eedacc38c0128ff7d70c',
firstName: 'FirstName'
},
className: "logOutForDisagreement"
}
};
//You don't need the props for everything, just the className, but I'm leaving them h
describe('Test the log out for disagreement button', () => {
it('renders correctly enzyme and has onclick', async () => {
const { getByText } = render(<LogOutForDisagreement id="logOutForDisagreementClipboard" className={props.className} props={props} onChange={jest.fn()} />);
fireEvent.click(screen.getByRole('button', {
name: /log out/i
}));
//await expect(spy).toHaveBeenCalled();
expect(mockStore.clearAll).toHaveBeenCalledTimes(1);
expect(toJson(getByText)).toMatchSnapshot();
});
});
Code Coverage
--------------------------------------------|---------|----------|---------|---------|----------------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------------------------------|---------|----------|---------|---------|----------------------------------
All files | 25.95 | 8.06 | 18.94 | 26.33 |
src/components/privacyPolicyComponents | 25 | 0 | 21.05 | 25 |
LogOutForDisagreement.js | 100 | 100 | 100 | 100 |
Does anyone know why it reports that I called the function twice now? Should I just use toHaveBeenCalled? I still get an error that it got called twice.
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 2
Related
I have a simple pop-up that I want to test if the clicked button closes the popup using testing-library/react. The way shown in docs doesn't work for me for some reason. I'm able to console log the button, so the query works but fireEvent doesn't trigger the button.
test("close the popup", () => {
render(
<Popup>
<div id="content"></div>
</Popup>
);
const button = screen.getByText("Got It");
fireEvent.click(button);
expect(button).not.toBeInTheDocument();
});
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
const Popup = (props) => {
const id = "popup-active"
const [popupActive, setPopupActive] = useState(() => {
const value = window.localStorage.getItem(id);
return value !== null ? JSON.parse(value) : true;
});
useEffect(() => {
window.localStorage.setItem(id, popupActive);
}, [popupActive]);
const handleX = () => {
setPopupActive(!popupActive);
};
return (
<div
style={{
visibility: popupActive ? "visible" : "hidden",
opacity: popupActive ? "1" : "0",
}}
className={popupStyles.overlay}
>
<div className={popupStyles.popup}>
<div className={popupStyles.content}>{props.children}</div>
<button
data-testid="button-popup"
className={popupStyles.button}
onClick={handleX}
>
Got It
</button>
</div>
</div>
);
};
export default Popup;
You should use toBeVisible matcher.
This allows you to check if an element is currently visible to the user.
An element is visible if all the following conditions are met:
it is present in the document
it does not have its css property display set to none
it does not have its css property visibility set to either hidden or collapse
it does not have its css property opacity set to 0
its parent element is also visible (and so on up to the top of the DOM tree)
it does not have the hidden attribute
if <details /> it has the open attribute
E.g.
index.tsx:
import React from 'react';
import { useEffect, useState } from 'react';
const id = 'popup-active';
const Popup = (props) => {
const [popupActive, setPopupActive] = useState(() => {
const value = window.localStorage.getItem(id);
return value !== null ? JSON.parse(value) : true;
});
useEffect(() => {
window.localStorage.setItem(id, popupActive);
}, [popupActive]);
const handleX = () => {
setPopupActive(!popupActive);
};
return (
<div
style={{
visibility: popupActive ? 'visible' : 'hidden',
opacity: popupActive ? '1' : '0',
}}
>
<div>
<div>{props.children}</div>
<button data-testid="button-popup" onClick={handleX}>
Got It
</button>
</div>
</div>
);
};
export default Popup;
index.test.tsx:
import { fireEvent, render, screen } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import React from 'react';
import Popup from './';
describe('73407176', () => {
test('should pass', () => {
render(
<Popup>
<div id="content"></div>
</Popup>
);
const button = screen.getByText('Got It');
fireEvent.click(button);
expect(button).not.toBeVisible();
});
});
Test result:
PASS stackoverflow/73407176/index.test.tsx (11.549 s)
73407176
✓ should pass (73 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 83.33 | 100 | 100 |
index.tsx | 100 | 83.33 | 100 | 100 | 8
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.167 s
Also check this question: What is the difference between toBeInTheDocument() and toBeVisible()?
Every time I try to render the page, this error is thrown. I tried multiple solutions but none of them worked.
Current react version is 17.0.2, node version is 14.16.1
Here the important code snippets:
import { useAuthUser, useFeatureFlags, useUser, useClaim, firebase } from './lib'
import React from 'react'
...
function App () {
console.log(React.version)
const [flags, flagsLoading] = useFeatureFlags() // last executed line here
...
import useCollection from './useCollection'
const useFeatureFlags = () => {
const [flagData, , loading, error] = useCollection('featureFlags') // last executed line here
...
}
export default useFeatureFlags
import { useState, useEffect } from 'react'
import { db } from '../firebase'
const useCollection = (path) => {
const [loading, setLoading] = useState(true) // here, the error is thrown
...
}
export default useCollection
I am not the author of this project, but I have to get this web app running.
Thanks for your help!
EDIT:
Here is the Stack trace and error
Error: 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:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
resolveDispatcher
node_modules/react/cjs/react.development.js:1476
useState
node_modules/react/cjs/react.development.js:1507
useCollection
project/lib/build/hooks/useCollection.js:18
15 | * #returns [collectionDocuments, querySnapshot, loading, error]
16 | */
17 | const useCollection = path => {
> 18 | const [loading, setLoading] = (0, _react.useState)(true);
19 | const [ref, setRef] = (0, _react.useState)(null);
20 | const [querySnapshot, setQuerySnapshot] = (0, _react.useState)(null);
21 | const [collectionDocuments, setCollectionDocuments] = (0, _react.useState)([]);
useFeatureFlags
project/lib/build/hooks/useFeatureFlags.js:20
17 | * randomly determined rollout percentage.
18 | */
19 | const useFeatureFlags = () => {
> 20 | const [flagData,, loading, error] = (0, _useCollection.default)('featureFlags'); // const test = <useCollection path='featureFlags' />
21 | // console.log(test)
22 |
23 | const output = {};
App
src/App.js:43
40 |
41 | function App () {
42 | console.log(React.version)
> 43 | const [flags, flagsLoading] = useFeatureFlags()
44 | const [authUser, authUserLoading] = useAuthUser()
45 | const [user, userLoading] = useUser()
46 | const [claim, claimLoading] = useClaim()
./src/index.js/<
src/index.js:23
20 |
21 | console.log('project id', process.env.REACT_APP_FIREBASE_PROJECT_ID)
22 |
> 23 | ReactDOM.render(<App />, document.getElementById('root'))
24 |
./src/index.js
http://localhost:3000/static/js/main.chunk.js:24779:30
__webpack_require__
project/web/webpack/bootstrap:851
848 |
849 | __webpack_require__.$Refresh$.init();
850 | try {
> 851 | modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
| ^ 852 | } finally {
853 | __webpack_require__.$Refresh$.cleanup(moduleId);
854 | }
fn
project/web/webpack/bootstrap:150
147 | );
148 | hotCurrentParents = [];
149 | }
> 150 | return __webpack_require__(request);
| ^ 151 | };
152 | var ObjectFactory = function ObjectFactory(name) {
153 | return {
1
http://localhost:3000/static/js/main.chunk.js:35286:18
__webpack_require__
project/web/webpack/bootstrap:851
848 |
849 | __webpack_require__.$Refresh$.init();
850 | try {
> 851 | modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
| ^ 852 | } finally {
853 | __webpack_require__.$Refresh$.cleanup(moduleId);
854 | }
checkDeferredModules
project/web/webpack/bootstrap:45
42 | }
43 | if(fulfilled) {
44 | deferredModules.splice(i--, 1);
> 45 | result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
| ^ 46 | }
47 | }
48 |
webpackJsonpCallback
project/web/webpack/bootstrap:32
29 | deferredModules.push.apply(deferredModules, executeModules || []);
30 |
31 | // run deferred modules when all chunks ready
> 32 | return checkDeferredModules();
| ^ 33 | };
34 | function checkDeferredModules() {
35 | var result;
(anonymous function)
http://localhost:3000/static/js/main.chunk.js:1:81
In your second and third code snippet, add
import React from "react"
Am trying to use context to allow authentication state to be available to all components. However am stumped at this error
Error: 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 https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
src/utils/hooks.js:22
19 | // }, [pokemonName, run])
20 | const defaultInitialState = { status: "idle", data: null, error: null };
21 | function useAsync(initialState) {
> 22 | const initialStateRef = useRef({
23 | ...defaultInitialState,
24 | ...initialState
25 | });```
UseProvideAuth
src/components/helper/context.js:28
25 |
26 |
27 | function UseProvideAuth(props) {
> 28 | const {
29 | data: user,
30 | status,
31 | error,
handleSubmit
src/components/login/login-form/index.js:13
10 | function handleSubmit(event) {
11 | event.preventDefault();
12 | const { email, password } = event.target.elements;
> 13 | run(
| ^ 14 | onSubmit({
15 | email: email.value,
16 | password: password.value
Index.js
<React.StrictMode>
<ProvideAuth>
<App />
</ProvideAuth>
</React.StrictMode>,
ProvideAuth
function ProvideAuth({ children }) {
return (
<BrowserRouter>
<authContext.Provider value={UseProvideAuth}>{children}</authContext.Provider>
</BrowserRouter>
);
}
UseProvideAuth
function UseProvideAuth(props) {
const {
data: user,
status,
error,
isLoading,
isIdle,
isError,
isSuccess,
run,
setData
} = useAsync();
useAsync
const defaultInitialState = { status: "idle", data: null, error: null };
function useAsync(initialState) {
const initialStateRef = useRef({
...defaultInitialState,
...initialState
});
Here is a minimal setup on [codesandbox][1]
Any help would be appreciated.
[1]: https://codesandbox.io/s/frosty-silence-b0c8i
How to test values that can depend on current localization?
function DateNavigation({ locale, date }) {
return (locale === 'en')
? <span>{moment(date).format('DD.MM.YYYY')}</span>
: <span>{moment(date).format('YYYY.MM.DD')}</span>
}
This test doesn't work sometimes because of locations
it('display date correctly', () => {
const component = mount(<DateNavigation date={'2022-01-31'} />);
expect(component.html()).toEqual("<span>31.01.2022</span>");
});
You can use jest.mock(moduleName, factory, options) to mock moment(), moment().format() functions and its returned value. Test results must be predictable. For the test about date, we need to make the test case return a certain value in different systems, time zones and localization.
E.g.
DateNavigation.jsx:
import React from 'react';
import moment from 'moment';
function DateNavigation({ locale, date }) {
return locale === 'en' ? (
<span>{moment(date).format('DD.MM.YYYY')}</span>
) : (
<span>{moment(date).format('YYYY.MM.DD')}</span>
);
}
export default DateNavigation;
DateNavigation.test.jsx:
import DateNavigation from './DateNavigation';
import moment from 'moment';
jest.mock('moment', () => {
const mMoment = { format: jest.fn() };
return jest.fn(() => mMoment);
});
describe('62042433', () => {
it('display date correctly', () => {
const date = '31.01.2022';
moment().format.mockReturnValueOnce(date);
const component = mount(<DateNavigation date={'2022-01-31'} locale={'en'} />);
expect(component.html()).toEqual('<span>31.01.2022</span>');
expect(moment).toBeCalledWith('2022-01-31');
expect(moment().format).toBeCalledWith('DD.MM.YYYY');
});
});
unit test results:
PASS stackoverflow/62042433/DateNavigation.test.jsx (13.544s)
62042433
✓ display date correctly (54ms)
--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 100 | 50 | 100 | 100 |
DateNavigation.jsx | 100 | 50 | 100 | 100 | 6
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.23s
I'm trying to create a ReactDataGrid and I'm getting this error when I use rowGetter. The grid works fine when I use rows={myData} though, and I really don't understand what is happening.
Here's my code :
import React, {Component, useState } from 'react';
import DataGrid from 'react-data-grid';
import 'react-data-grid/dist/react-data-grid.css';
import { Toolbar, Data, Filters } from "react-data-grid-addons";
const {
NumericFilter
} = Filters;
const selectors = Data.Selectors;
class YvideoList extends Component {
constructor(props, context){
super (props, context);
const selectors = Data.Selectors;
const defaultColumnProperties = {
filterable: true,
// width: 160
};
this.colums = [
{ key: 'title', name: 'Title' },
{ key: 'views', name: 'Views', filterRenderer: NumericFilter },
{ key: 'date', name: 'Date' }]
.map(c => ({ ...c, ...defaultColumnProperties }));
}
rowGetter = (index) => {
console.log("INDEX: ", index);
return selectors.getRows(this.props.videos[0].results)[index];
};
rowsCount = () => {
return selectors.getRows(this.props.videos[0].results).length;
};
render() {
return (
<DataGrid
columns={this.colums}
rowGetter={this.rowGetter} // -> GRID CRASHES
rowsCount={2}
//rows={this.props.videos[0].results} // -> THIS WORKS
/>
)
}
}
export default YvideoList;
The error I'm getting is the following :
TypeError: undefined is not an object (evaluating 'rows.length')
DataGrid
src/DataGrid.tsx:268
265 | lastFrozenColumnIndex={columnMetrics.lastFrozenColumnIndex}
266 | draggableHeaderCell={props.draggableHeaderCell}
267 | onHeaderDrop={props.onHeaderDrop}
> 268 | allRowsSelected={selectedRows?.size === rows.length}
| ^ 269 | onSelectedRowsChange={onSelectedRowsChange}
270 | sortColumn={props.sortColumn}
271 | sortDirection={props.sortDirection}
Any help appreciated ! Just note that the console.log from the function rowGetter is never shown in the console, so the grid dies before that.
EDIT1:
I think the problem lies in the version I'm using of the data grid. In version 5.0.4 the code I added looks like working, but not on version 7.
Unfortunately I can't find examples of filtering/sorting for version 7-canary
So I found out the reason and it was caused by version change from the react-data-grid, they dropped the rowgetters so I was using it wrong, the documentation is not up to date on their website which is a pity because the grid looks really good.
For actually demos you can check here :
https://github.com/adazzle/react-data-grid/tree/canary/stories/demos