So I'm getting started with React testing. I have developed a basic Catalog Viewer Application which should be working fine but it is failing the test cases. One basic test is to click on a thumbnail and image should reflect the changes. It works when I do it manually but the test case is getting failed.
This is the application when I clicked the "white-light-candle.jpg":
As you can see, the preview thumbnail has been updated but the test case still failed:
This is the test script that is developed:
import "#testing-library/jest-dom/extend-expect";
import { act, cleanup, fireEvent, render } from "#testing-library/react";
import React from "react";
import App from "./App";
import { image1, image2, image3, image4 } from "./assets/images";
jest.useFakeTimers();
const TEST_IDS = {
viewerId: "catalog-view",
prevBtnId: "prev-slide-btn",
nextBtnId: "next-slide-btn",
thumbBtnPrefix: "thumb-button-",
toggleSlideShowBtnId: "toggle-slide-show-button",
};
describe("Catalog Viewer", () => {
let getByTestId;
let viewer;
let prevBtn;
let nextBtn;
let toggleSlideShowBtn;
let catalogs;
let thumbBtn2;
let thumbBtn4;
beforeEach(() => {
const app = render(<App />);
getByTestId = app.getByTestId;
viewer = getByTestId(TEST_IDS.viewerId);
prevBtn = getByTestId(TEST_IDS.prevBtnId);
nextBtn = getByTestId(TEST_IDS.nextBtnId);
thumbBtn2 = getByTestId(TEST_IDS.thumbBtnPrefix + "1");
thumbBtn4 = getByTestId(TEST_IDS.thumbBtnPrefix + "3");
toggleSlideShowBtn = getByTestId(TEST_IDS.toggleSlideShowBtnId);
catalogs = [
{
thumb: image1,
image: image1,
},
{
thumb: image2,
image: image2,
},
{
thumb: image3,
image: image3,
},
{
thumb: image4,
image: image4,
},
];
});
afterEach(() => {
cleanup();
});
test("clicking on any catalog thumbnail should show the appropriate image", () => {
fireEvent.click(thumbBtn2, { button: "0" });
expect(viewer.src).toContain(catalogs[1].image);
fireEvent.click(thumbBtn4, { button: "0" });
expect(viewer.src).toContain(catalogs[3].image);
});
});
Can anyone suggest where I should look to debug this problem? At this point I just ran out of options.
I can't unfortunately upload on Codepen at the moment. Here is the code for my application if anyone can help please:
Update-1
Modified the test case to include waitFor and changed extension to .jpg but it didn't help:
test("clicking on any catalog thumbnail should show the appropriate image", async () => {
fireEvent.click(thumbBtn2, { button: "0" });
await waitFor(() => {
expect(viewer.src).toContain(catalogs[1].image);
});
fireEvent.click(thumbBtn4, { button: "0" });
await waitFor(() => {
expect(viewer.src).toContain(catalogs[3].image);
});
});
The image src is "http://localhost/tea-light-candle.jpeg" and you are expecting "white-light-candle.jpg"
the src of the image is the full path of where the image is located. You are comparing to the wrong string, add the path and also verify the src of the image, because the real src says
"tea-light-candle"
and you wrote
"white-light-candle"
I know it's a bit late, but i have recently implemented the project and all testcase are running fine you can have a look at https://github.com/MSaddamKamal/react-catalog-viewer also make sure in your src/assets/images folder white-light-candle.jpg exists.
If every thing is fine then all test cases will pass as below
Related
Regarding { DatePicker } from '#mui/x-date-pickers':
I can't figure out how to change the value using Jest.
Here's my DatePicker-wrapper DatePickerX:
import React, { useState } from 'react';
import { DatePicker } from '#mui/x-date-pickers';
import { LocalizationProvider } from '#mui/x-date-pickers/LocalizationProvider';
import AdapterDateFns from '#mui/lab/AdapterDateFns';
import { de } from 'date-fns/locale';
import { TextField } from '#mui/material';
export const DatePickerX: React.FC = () => {
const [date, setDate] = useState<Date>(new Date());
const changeDate = (newDate: Date | null) => {
if (newDate) {
setDate(newDate);
}
};
return (
<>
<LocalizationProvider locale={de} dateAdapter={AdapterDateFns}>
<DatePicker
label="datepicker_label"
value={date}
inputFormat="yyyy/MM/dd"
views={['year', 'month', 'day']}
mask="____/__/__"
onChange={changeDate}
renderInput={(params) => (
<TextField type="text" {...params} data-testid="textInput_testid" name="textInput_name"/>
)}
/>
</LocalizationProvider>
</>
);
}
This works perfectly fine on the UI.
Here are my attempts to change the date. All tests fail:
describe('change date picker value test 1', () => {
test('use datepicker label; set string', async () => {
render(<DatePickerX />);
const input = screen.getByLabelText('datepicker_label');
await act(async () => {
await fireEvent.change(input, { target: { value: '3000/01/01' } });
});
expect(screen.getByText('3000/01/01')).toBeVisible();
});
test('use text input; set string', async () => {
render(<DatePickerX />);
const input2 = screen.getByTestId('textInput_testid');
await act(async () => {
await fireEvent.change(input2, { target: { value: '3000/01/01' } });
});
expect(screen.getByText('3000/01/01')).toBeVisible();
});
test('use datepicker label; set date', async () => {
render(<DatePickerX />);
const input = screen.getByLabelText('datepicker_label');
await act(async () => {
await fireEvent.change(input, { target: { value: new Date('3000/01/01') } });
});
expect(screen.getByText('3000/01/01')).toBeVisible();
});
test('use text input; set date', async () => {
render(<DatePickerX />);
const input2 = screen.getByTestId('textInput_testid');
await act(async () => {
await fireEvent.change(input2, { target: { value: new Date('3000/01/01') } });
});
expect(screen.getByText('3000/01/01')).toBeVisible();
});
});
What am I doing wrong?
Before you render any component that has dependencies, is important to load those before.
So one of the issue that Test are failing could be that when you render the component en each case the test runner is looking for the provider, adapters and the others dependencies.
To solve this you can use the jest.mock function or just import them
This is one of the example the doc link include.
import axios from 'axios';
import Users from './users';
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
})
Hope this help
Firstly: I wanted to note that #mui/lab adapter should not be used together with #mui/x pickers. I'd suggest syncing those usages by changing your adapter import to import { AdapterDateFns } from '#mui/x-date-pickers/AdapterDateFns'; (based on setup documentation).
Secondly: Have you checked which component is rendered during your test cases? I see that you are importing DatePicker, this component renders either Desktop or Mobile version of the picker depending on desktopModeMediaQuery prop. This rendering logic has some caveats in test environments, I'd suggested reading testing caveats doc section for more information on how to reach your desired result.
Lastly: Are those test cases you provided in the question your real project examples or just for illustration purposes? If they are real cases, I'd suggest thinking if it's worth testing behaviours, that are already tested by MUI on their end. Ideally, you should write tests asserting your own code.
Edit:
I've had a bit deeper investigation and manual testing of your cases and have the following conclusions:
3rd and 4th cases are invalid, because you can only set value on an input element, but those queries return a TextField root - div element.
2nd case does not work, because setting value to new Date() will cause the toString method to be called, which will not be in the format the component expects.
And as far as I can tell, your main issue might have been the usage of getByText query in the assertions. This query does not look for text in input element's value. Replacing it with getByDisplayValue seems to resolve your issue.
Please check this example repository with working examples.
I am new to React development and am studying testing with Jest and React Testing Library (RTL).
But I'm having difficulty doing the complete coverage of the component below:
import {
CustomCardActions,
CustomCardHeader,
} from '#Custom/react';
import React from 'react';
import {
PortalAccessButton,
PortalAccessContext,
PortalAccessInternalCard,
PortalAccessTitle,
} from './styles';
interface PortalAccessCard {
children: React.ReactNode
buttonText: string;
hrefLink: string;
}
export const redirectToUrl = (hrefLink: string) => {
window.open(hrefLink, '_self');
};
const PortalAccessCard = (props: PortalAccessCard) => {
const { children, buttonText, hrefLink } = props;
return (
<PortalAccessContext inverse>
<PortalAccessInternalCard>
<CustomCardHeader>
<PortalAccessTitle variant="heading-4">
{children}
</PortalAccessTitle>
</CustomCardHeader>
<CustomCardActions>
<PortalAccessButton onCustomClick={() => redirectToUrl(hrefLink)}>
{buttonText}
</PortalAccessButton>
</CustomCardActions>
</PortalAccessInternalCard>
</PortalAccessContext>
);
};
export default React.memo(PortalAccessCard);
There are two details here:
1- I exported the "redirectToUrl" method to be able to test it. I can't say if there's a better way out, but maybe the second question solves this one.
2- When I check the coverage report it says that this part () => redirectToUrl(hrefLink) has not been tested, but it is basically the pointer to the method I exported above.
My test looks like this:
import { render, RenderResult } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import PortalAccessCard from '.';
import * as PortalAccessCardComponent from '.';
describe('PortalAccessCard', () => {
let renderResult: RenderResult;
const hrefLink = '#';
beforeEach(() => {
renderResult = render(
<PortalAccessCard
buttonText="Texto do botão"
hrefLink={hrefLink}
>
Texto interno PortalAccessCard.
</PortalAccessCard>,
);
});
it('should call onCustomClick and redirectToUrl', async () => {
window.open = jest.fn();
jest.spyOn(PortalAccessCardComponent, 'redirectToUrl');
const onCustomClick = jest.fn(() => PortalAccessCardComponent.redirectToUrl(hrefLink));
const CustomButtonElement = renderResult.container.getElementsByTagName('Custom-button')[0];
CustomButtonElement.onclick = onCustomClick;
await userEvent.click(CustomButtonElement);
expect(onCustomClick).toBeCalledTimes(1);
expect(PortalAccessCardComponent.redirectToUrl).toBeCalledTimes(1);
});
});
What can I do to make the test call of the onCustomClick event call the redirectToUrl method so that Jest understands that this snippet has been tested?
Not sure which exactly line is not covered... Though, toBeCalledTimes is a sign of bad test expectation, so try to append to the very bottom line:
expect(PortalAccessCardComponent.redirectToUrl).toBeCalledWith(hrefLink);
It's better to test for the side effect you want (opening a window). redirectToUrl is an implementation detail. I think you're making this much harder than it needs to be.
Spy on window.open, click the item, check the spy. I think that's all you need.
jest.spyOn(window, 'open')
const CustomButtonElement = renderResult.container.getElementsByTagName('Custom-button')[0];
await userEvent.click(CustomButtonElement);
// or maybe: getByRole('something...').click()
expect(window.open).toHaveBeenCallWith('#', '_self')
I am facing an issue in using docxtemplater library to generate a doc file from my react application.
If anybody know how to solve this please help me.
import React from "react";
import Docxtemplater from "docxtemplater";
import PizZip from "pizzip";
import PizZipUtils from "pizzip/utils/index.js";
import { saveAs } from "file-saver";
import Sample from './sample.js';
function loadFile(url, callback) {
PizZipUtils.getBinaryContent(url, callback);
}
function App(){
//const generateDocument = () => {
// fetch("http://localhost:5000/api/doc")
// .then(res => console.log(res))
//};
const generateDocument = () => {
loadFile("./assets/docs/newTemplate.docx",function (error, content) {
if (error) {
throw error;
}
console.log(content);
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
// render the document (replace all occurences of {first_name} by John, {last_name} by Doe, ...)
doc.render({
client_name: "John",
});
const out = doc.getZip().generate({
type: "blob",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
}); //Output the document using Data-URI
saveAs(out, "output.docx");
}
)};
return (
<div className="p-2">
<button onClick={generateDocument}>
Generate document
</button>
<Sample></Sample>
</div>
);
};
export default App;
My template file is in assets/docs folder.
I tried various ways of using template in same folder & changing template with new templates but nothing worked. Please help me!
It might be caused by the name of the file containing Upper case, fix that - et voilà!
I am trying to use the forge viewer with markups extension inside of a react app and have ran into a problem.
The viewer is docked within a sheet that slides out from the right handside of the page. The first time I open the viewer it works fine, I click the markup icon and can draw an arrow e.g.:
When I reopen the same document, click the markup icon and draw another arrow, the arrow is huge, like the scale is all wrong:
This is the full react component so far
import React, { useRef, useEffect } from 'react';
import { ReadonlyFormHTMLContainer } from '../../Components/Form/FormComponents';
import { useGlobalContext } from '../../GlobalState';
type ForgeViewerProps = {
id: string;
fileUrl: string;
filename: string;
};
let viewer: Autodesk.Viewing.Viewer3D | null | undefined;
// I can't find a type for Autodesk.Viewing.MarkupsCore - if you can find it, replace any with the correct type, if not you are on your own, no intellisense!
let markup: any;
export const ForgeViewer = (props: ForgeViewerProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const context = useGlobalContext();
useEffect(() => {
window.Autodesk.Viewing.Initializer({ env: 'Local', useADP: false }, () => {
viewer = new window.Autodesk.Viewing.GuiViewer3D(containerRef.current!);
viewer.start();
viewer.loadExtension('Autodesk.PDF').then(() => {
viewer!.loadModel(props.fileUrl, viewer!);
viewer!.loadExtension('Autodesk.Viewing.MarkupsCore').then(ext => {
markup = ext;
});
viewer!.loadExtension('Autodesk.Viewing.MarkupsGui');
});
});
return () => {
console.log('Running clean up');
viewer?.tearDown();
viewer?.finish();
viewer = null;
};
}, []);
return (
<ReadonlyFormHTMLContainer
title={'Document markup'}
subtitle={props.filename}
formErrorMessage=""
formErrorTitle=""
onClose={() => context.hideSheet()}
>
<div ref={containerRef}></div>{' '}
</ReadonlyFormHTMLContainer>
);
};
I have imported following modules from npm:
Forge version: https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js
Does anyone have any idea on how to resolve this? As you can see I've tried some clean up code when the component unmounts but I cannot get the viewer to work "normally" after the initial open.
Edit
repro link: https://github.com/philwindsor/forge-repro/blob/master/index.html
This is a timing issue.
When you open the viewer for the first time, the markup extension takes some time to download and it is therefore initialized after the model has already been loaded. Because of that, the extension knows how to initialize the scale of its markups properly.
When you open the viewer for the second time, the markup extension is already available, and it is loaded and initialized before any model is available. And because of that, it cannot configure the "expected" scale.
To resolve the issue, simply load the Autodesk.Viewing.MarkupsCore and Autodesk.Viewing.MarkupsGui extensions after the model is loaded, for example:
viewer.loadModel(urn, options, function onSuccess() {
viewer.loadExtension("Autodesk.Viewing.MarkupsCore");
viewer.loadExtension("Autodesk.Viewing.MarkupsGui");
});
I have a React component which renders a list of components. I'm running some tests which mock the axios module which loads in the users from JSONPlaceHolder. All works fine including the the async test and it's mocks data as expected. However if you look at the code below it only passes as long as the first test is commented out? Am I missing something? Been banging my head for ages. Is there some cleanup that needs to be done between tests? Thanks in advance.
import { waitForElement } from 'enzyme-async-helpers';
import UsersList from '../UsersList';
import axios from 'axios';
const mockUsers = [
{
"id": 1,
"name": "Leanne Mock",
"username": "Bret",
"email": "Sincere#april.biz"
},
{
"id": 2,
"name": "John Mock",
"username": "Jospeh",
"email": "wacky#april.biz"
}
]
axios.get.mockImplementationOnce(() => Promise.resolve({
data: mockUsers
}))
describe('<UsersList /> tests:', () => {
//WHEN I UNCOMMENT THIS TEST THE SECOND TEST FAILS?
test('It renders without crashing', (done) => {
// const wrapper = shallow(<UsersList />);
});
test('It renders out <User /> components after axios fetches users', async () => {
const wrapper = shallow(<UsersList />);
expect(wrapper.find('#loading').length).toBe(1); //loading div should be present
await waitForElement(wrapper, 'User'); //When we have a User component found we know data has loaded
expect(wrapper.find('#loading').length).toBe(0); //loading div should no longer be rendered
expect(axios.get).toHaveBeenCalledTimes(1);
expect(wrapper.state('users')).toEqual(mockUsers); //check the state is now equal to the mockUsers
expect(wrapper.find('User').get(0).props.name).toBe(mockUsers[0].name); //check correct data is being sent down to User components
expect(wrapper.find('User').get(1).props.name).toBe(mockUsers[1].name);
})
})
The Error message I get is:
The render tree at the time of timeout:
<div
id="loading"
>
Loading users
</div>
console.warn node_modules/enzyme-async-helpers/lib/wait.js:42
As JSON:
{ node:
{ nodeType: 'host',
type: 'div',
props: { id: 'loading', children: ' Loading users ' },
key: undefined,
ref: null,
instance: null,
rendered: ' Loading users ' },
type: 'div',
props: { id: 'loading' },
children: [ ' Loading users ' ],
'$$typeof': Symbol(react.test.json) }
Test Suites: 1 failed, 1 total
Tests: 2 failed, 2 total
You only mock the first axios.get call because you are using mockImplementationOnce.
When you shallow(<UsersList />) twice, the second time is timing out loading the users.
You can add a beforeEach method with a mockResolvedValueOnce inside, to mock the axios.get before every single test:
beforeEach(() => {
axios.get.mockResolvedValueOnce({data: mockUsers});
}
Having the same issue, but I'm not making a request. I'm building a client-side React application and testing for the render of sub-components. I have an image carousel that loads on my Home component and I'm writing tests for it. If I comment out all but one test (any test) it passes. If I have more than one test (any combination of tests), it fails. I've tried async/await/waitFor, react-test-renderer, using done() - nothing seems to change this behavior.
import { render, screen } from '#testing-library/react';
import ImageCarousel from '../carousel/ImageCarousel';
import localPhotos from '../../data/localPhotos';
// passing in the full array of images is not necessary, it will cause the test to time out
const testArray = localPhotos.slice(0, 3);
describe('Image Carousel', () => {
it('renders without error', () => {
render(<ImageCarousel images={testArray} />);
const imageCarousel = screen.getByTestId('image-carousel');
expect(imageCarousel).toBeInTheDocument();
});
// it('displays the proper alt text for images', () => {
// render(<ImageCarousel images={testArray} />);
// const photo1 = screen.getByAltText(localPhotos[0].alt);
// const photo2 = screen.getByAltText(localPhotos[1].alt);
// expect(photo1.alt).toBe(localPhotos[0].alt);
// expect(photo2.alt).toBe(localPhotos[1].alt);
// });
// it("displays full-screen icons", () => {
// render(<ImageCarousel images={testArray} />);
// const fullScreenIcons = screen.getAllByTestId('full-screen-icon');
// expect(fullScreenIcons.length).toBe(testArray.length);
// })
// shows controls when showControls is true
// does not show controls when showControls is false
// it('displays the proper number of images', () => {
// render(<ImageCarousel images={testArray} />);
// const carousel_images = screen.getAllByTestId('carousel_image');
// expect(carousel_images.length).toBe(testArray.length);
// });
// calls next when clicked
// calls previous when clicked
// returns to first image when next is clicked and last image is shown
// moves to last image when previous is clicked and first image is shown
});