How to change the value of an MUI DatePicker in Jest (x-date-pickers) - reactjs

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.

Related

How can I test an input with Jest

I've been trying to figure out how to test different input methods but since I am new to this test methodology, I cannot get even close to the answer. Here is what I have:
const App = (props) => {
const newGame = props.newGame;
const [typeracertext, setTyperacertext] = useState(props.typeracertext);
const [wholeText, setWholeText] = useState("");
const onChange = (e) => {
//here I have code that read the input and is comparing it with variable - typeracertext and if so, it sets the property wholeText to that value
};
return (
<input ref={(node) => this.textInput = node} placeholder="Message..." onChange={onChange}></input>
);
}
so what I am trying to figure out is a test that should set the typeracertext to a certain value (for example "This is a test), and set the input value to "This" so if it passes the onChange() check it should set wholeText to "This". I hope that makes sense.
This is the best I could get and I don't have an idea what should I write on "expect".
test('Test the input value', () => {
const node = this.textInput;
node.value = 'This';
ReactTestUtils.Simulate.change(node);
expect()
});
Since this is a react app, I'll advice you take advantage of react testing library to make this easy
import React from 'react';
import { fireEvent, render, screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
// In describe block
test('Test input component', () => {
const onChange = jest.fn();
render(<InputComponent onChange={onChange} data-test-id="input" />);
const input = screen.getByTestId('input');
fireEvent.change(input, { target: { value: 'a value' } });
// You can also do this with userEvent
userEvent.type(input, 'test')
// Check if change event was fired
expect((input as HTMLInputElement).onchange).toHaveBeenCalled();
});
See documentation here

How do I write this test for complete coverage?

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')

Test for a component: How to access to a property of a tag with getByTestId

I'm doing a component test in react (My first) and I want to verify a number, when I pass it the value, it returns undefined and I remove the value to see what it returned and it was fine, find the element
import React, { useState } from 'react';
import { render, cleanup, screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import { NumberUpDown } from '../../components/number-
updown/NumberUpDown';
import '#testing-library/jest-dom/extend-expect'
const UpDownNumber = () => {
const [quantity, setQuantity] = useState<number>(1);
const packageType = 'box'
return (
<NumberUpDown
value={quantity}
valueToShow={
packageType === 'box' || 'pack' || 'piece' || 'bag' || 'sbox'
? quantity : quantity * 12
}
min={1}
max={5000}
step={1}
onChange={value => setQuantity(value)}
/>
);
};
describe('Plus or minus in the product modal', () => {
afterEach(cleanup);
beforeEach(() => render(<UpDownNumber />));
it('Validate is if exists', () => {
expect(screen.getByTestId('product-minus')).toBeInTheDocument();
expect(screen.getByTestId('product-input')).toBeInTheDocument();
expect(screen.getByTestId('product-plus')).toBeInTheDocument();
});
it('Validate function onclick', () => {
const minusButton = screen.getByTestId('product-minus');
const plusButton = screen.getByTestId('product-plus');
const input = screen.getByTestId('product-input');
userEvent.click(plusButton);
userEvent.click(plusButton);
expect(getByRole('textbox', { name: /email/i })).toHaveValue('test#email.com);
expect((input as HTMLInputElement).value).toBe(3);
userEvent.click(minusButton);
expect((input as HTMLInputElement)).toBe(2);
});
});
Expected: 3
Received: <ion-input class="value-cell" data-testid="product-input" type="number"
value="3" />
expect((input as HTMLInputElement).value).toBe(3);
Expected: 3
Received: undefined
I need that when I access the tag, when it finds it, get the value...
You already use #testing-library, so I suggest taking it one step further and add https://www.npmjs.com/package/#testing-library/jest-dom as a devDependency. If using a Create React App based app, you can add an import like to your setupTests.js file e.g.
import '#testing-library/jest-dom/extend-expect';
You can then write tests to check the value of a field using something like:
expect(getByRole('textbox', { name: /email/i })).toHaveValue('test#email.com);
Using the jest-dom lets you write tests that read far nicer, but that is just my opinion.

How to fire and test a real paste event (not simulated by calling the prop) in Jest and Enzyme

I'm trying to unit test a very simple feature in a React app where I'm blocking the user from pasting into a textarea by adding an event.preventDefault() in the event handler, like so:
function handlePaste(event) {
event.preventDefault();
}
// ... pass it down as props
<TextareaComponent onPaste={handlePaste} />
The problem I'm having is that every method I've found of dispatching events in Jest or Enzyme just "simulates" the event by getting the function passed to the onPaste prop and calling it directly with a mock event object. That's not what I'm interested in testing.
Ideally I want to do something like this, testing that the actual value of the input hasn't changed after pasting:
const wrapper = mount(<ParentComponent inputValue="Prefilled text" />);
const input = wrapper.find(TextareaComponent);
expect(input.value).toEqual("Prefilled text")
input.doAPaste("Pasted text")
expect(input.value).not.toEqual("Pasted text")
expect(input.value).toEqual("Prefilled text")
But haven't been able to find a method that works. Any help would be appreciated!
Since you're just testing against a synthetic event (and not some sort of secondary action -- like a pop up that warns the user that pasting is disabled), then the easiest and correct solution is to simulate a paste event, pass it a mocked preventDefault function, and then assert that the mocked function was called.
Attempting to make assertions against a real paste event is pointless as this a React/Javascript implementation (for example, making assertions that a callback function is called when an onPaste/onChange event is triggered). Instead, you'll want to test against what happens as a result of calling the callback function (in this example, making assertions that event.preventDefault was called -- if it wasn't called, then we know the callback function was never executed!).
Working example (click the Tests tab to run the assertions):
To keep it simple, I'm just asserting that the input is initially empty and then only updates the value if an onChange event was triggered. This can very easily be adapted to have some sort of passed in prop influence the default input's value.
App.js
import React, { useCallback, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
const handleChange = useCallback(
({ target: { value } }) => setValue(value),
[]
);
const handlePaste = useCallback((e) => {
e.preventDefault();
}, []);
const resetValue = useCallback(() => {
setValue("");
}, []);
const handleSubmit = useCallback(
(e) => {
e.preventDefault();
console.log(`Submitted value: ${value}`);
setValue("");
},
[value]
);
return (
<form onSubmit={handleSubmit}>
<label htmlFor="foo">
<input
id="foo"
type="text"
data-testid="test-input"
value={value}
onPaste={handlePaste}
onChange={handleChange}
/>
</label>
<br />
<button data-testid="reset-button" type="button" onClick={resetValue}>
Reset
</button>
<button type="submit">Submit</button>
</form>
);
};
export default App;
App.test.js
import React from "react";
import { configure, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import App from "./App";
configure({ adapter: new Adapter() });
const value = "Hello";
describe("App", () => {
let wrapper;
let inputNode;
beforeEach(() => {
wrapper = mount(<App />);
// finding the input node by a 'data-testid'; this is not required, but easier
// when working with multiple form elements and can be easily removed
// when the app is compiled for production
inputNode = () => wrapper.find("[data-testid='test-input']");
});
it("initially displays an empty input", () => {
expect(inputNode()).toHaveLength(1);
expect(inputNode().props().value).toEqual("");
});
it("updates the input's value", () => {
inputNode().simulate("change", { target: { value } });
expect(inputNode().props().value).toEqual(value);
});
it("prevents the input's value from updating from a paste event", () => {
const mockPreventDefault = jest.fn();
const prefilledText = "Goodbye";
// updating input with prefilled text
inputNode().simulate("change", { target: { value: prefilledText } });
// simulating a paste event with a mocked preventDefault
// the target.value isn't required, but included for illustration purposes
inputNode().simulate("paste", {
preventDefault: mockPreventDefault,
target: { value }
});
// asserting that "event.preventDefault" was called
expect(mockPreventDefault).toHaveBeenCalled();
// asserting that the input's value wasn't changed
expect(inputNode().props().value).toEqual(prefilledText);
});
it("resets the input's value", () => {
inputNode().simulate("change", { target: { value } });
wrapper.find("[data-testid='reset-button']").simulate("click");
expect(inputNode().props().value).toEqual("");
});
it("submits the input's value", () => {
inputNode().simulate("change", { target: { value } });
wrapper.find("form").simulate("submit");
expect(inputNode().props().value).toEqual("");
});
});

react-testing-library | Cannot Split Test into smaller chunks inside describe method

I'm learning about unit testing React components using react-testing-library
I have the component rendering correctly, however, when I aim to break the test into smaller chunks inside a describe() function. The test breaks and here's why.
Current only one or the other test() passes but not both
import React from 'react'
import 'react-testing-library/cleanup-after-each'
import { render, fireEvent } from 'react-testing-library'
import Quantity from '../components/Quantity'
describe('Quantity Component', () => {
const { container, getByTestId } = render(<Quantity />)
// first test
test('checks that quantity is never 0', () => {
expect(getByTestId('quantity')).not.toBe('0')
})
// second test
test('checks for the initial product quantity count', () => {
expect(getByTestId('quantity')).toHaveTextContent('1')
fireEvent.click(getByTestId('increment'))
expect(getByTestId('quantity')).toHaveTextContent('2')
})
})
When trying to run both tests it errors:
Unable to find an element by: [data-testid="quantity"]
[data-testid="quantity"] is just an attribute that I passed inside my desired JSX tag.
The test passes when running only the first or second test but not both concurrently.
What am I missing here?
Cross-contamination is strictly discouraged in unit testing.
The problem is that a setup occurs only once per Quantity Component suite, while it should be done for each test. This is what beforeEach is for:
describe('Quantity Component', () => {
let container, getByTestId;
beforeEach(() => {
({ container, getByTestId } = render(<Quantity />));
});
...
You need to also use an afterEach cleanup.
describe('your tests', () => {
afterEach(cleanup);
beforeEach(() => ({container, getById} = render(<Quantity />))
it('does something', () => {
expect(getByTestId('quantity')).toHaveTextContent(0);
}
}
I suggest you call the render inside your it clauses, it keeps the tests easier to manage:
describe('Quantity Component', () => {
test('checks that quantity is never 0', () => {
const { container, getByTestId } = render(<Quantity />)
expect(getByTestId('quantity')).not.toBe('0')
})
test('checks for the initial product quantity count', () => {
const { container, getByTestId } = render(<Quantity />)
expect(getByTestId('quantity')).toHaveTextContent('1')
fireEvent.click(getByTestId('increment'))
expect(getByTestId('quantity')).toHaveTextContent('2')
})
})
The added advantage is that if for some reason one of your tests needs to run with different props you can do that more easily with this setup.

Resources