Clicking a Checkbox in an Enzyme test - reactjs

I am trying to simulate a click on a material-ui checkbox. I have tried
selectAllCheckbox.simulate("change", { target: { checked: true } });
and
act(() => {
selectAllCheckbox.props().onClick();
});
I have tried re-finding the item and updating the wrapper, and I cannot get the checked prop to change.
I feel like I missing something fundamental.
I have a codesandbox here: https://codesandbox.io/s/enzymetestformaterialuitable-t1ruq
the sandbox has a material-ui table (lifted from their demos page).
TIA

I got a hand from a few sources including https://github.com/airbnb/enzyme/issues/216
complete sandbox https://codesandbox.io/s/enzymetestformaterialuitable-updb9 with tests passing
check the 4th checkbox on the table:
let innerInputElement5 = wrapper
.find('[role="checkbox"]')
.hostNodes()
.at(4);
innerInputElement5.simulate("click");
check the indeterminate checkbox in the header:
let selectAllCheckboxInHeader = wrapper
.find(TableHead)
.find('input[type="checkbox"]')
.simulate("change", { target: { checked: true } });

Related

How can I check all checkboxes with one checkbox and add a class to their parent element in React using use-state?

I am trying to do a to-do app and at one point I am completely stuck.
This is my current page so far:
I am going to explain to you what I am trying to do on the above page in short.
I have text input in which I write my to-dos.
When I submit it, it appears below in a li element. There is an array and those strings are stored there and printed with map method.
There are circle checkboxes left of the writings and when I checked them a class is added to a li element which is "completed"
Till here I have had no problem. Now there is a down arrow left of text input. When I checked it I wanted it to check all checkboxes down below and add "completed" class to their li elements. It is coded like:
<input className = "toggle-all" type = "checkbox"/>
<label htmlFor = "toggle-all" >
Mark all as complete
</label>
And here is my App.js, two components. Footer is not important.
Main component, Footer is not important again.
Header component
And the component I am struggling with is the Section
I have tried a lot of things yet I couldn't find a solution. Might be too many images and explanations but I wanted to make it clear for everybody. I want to use usestate to track all checkboxes but if the solution is not possible like that I am okay with the other solutions.
4.11.22
I created this question 2 days ago. During these two days, I tried various codes yet I couldn't make them work the way I wanted.
Instead of creating setTexts as array of string set it as array of object. Where object can be like this:
let obj = {
text: "todo item 1",
ischecked: false
}
const [listItem, setListItem] = useState([])
when adding new items in list you can do like this:
setListItem([...listItem, {text: "something to do another list", ischecked: false}])
when checking individual item call method onCheck(args):
const onCheck = (selectedItem) => {
let updatedList = listItem.map((item) => {
if(item.text === selectedItem.text){
return {
text: item.text,
ischecked: true
}
}
return item
})
setListItem([...updatedList])
}
And Finally to your questions answer. Once you click dropdown arrow call method checkedAllToDoList():
const checkedAlToDoList = () => {
let updatedList = listItem.map((item) => {
return {
text: item,
ischecked: true
}
})
setListItem([...updatedList])
}
Note: complete the flow as which method should pass as a props on
which components

React Testing library testing error `NaN` is an invalid value for the `left` css style property

I am using internal library component that uses React popper. Component is a dropdown that should open when i click on a marked element, as any other dropdown menu should.
I am trying to test with React testing library flow where user opens dropdown and does some interaction. But when i make my test open that dropdown it throws this error (warning):
Warning: `NaN` is an invalid value for the `left` css style property.
at div
at Popover__MenuArrow (/my-project/node_modules/styled-components/src/models/Keyframes.js:20:51)
at WithTheme(Component) (/my-project/node_modules/styled-components/src/models/ServerStyleSheet.js:66:13)
at div
at Popover__DropdownContainer (/my-project/node_modules/styled-components/src/models/Keyframes.js:20:51)
at WithTheme(Component) (/my-project/node_modules/styled-components/src/models/ServerStyleSheet.js:66:13)
...
This is not a blocking error, it is a warning, and test actually passes, but it is annoying to see it all the time when i run my tests.
My question is, how can I make this warning text not show when i run my tests?
This is the test code:
it('Should open dropdown menu', () => {
const { getByTestId } = render(<DropdownMenu />);
// Click on dropdown and open it
const dropdownButton = getByTestId('my-dropdown-menu');
fireEvent.click(dropdownButton);
// Assert if dropdown list is visible
const dropdownList = getByTestId('my-dropdown-list');
expect(dropdownList).toBeTruthy();
});
After some browsing around, I found this interesting approach on GitHub to not allow this warning to show.
The idea is to mock popper.js and set placements value. You need to put this code above your test it or describe code block.
jest.mock('popper.js', () => {
const PopperJS = jest.requireActual('popper.js');
return class {
static placements = PopperJS.placements;
constructor() {
return {
destroy: () => {},
scheduleUpdate: () => {},
};
}
};
});
This does not fix the problem, it just masks it and prevents that warning to show in the terminal at all. It will not influence on a test and your test will be able to simulate click on a element in that dropdown menu.
CAUSE:
It seems that the problem lies in the fact that test is being run in a headless browser and there is no point of reference for Popper.js to position itself when the dropdown is opened.
With the above code, we give Popper default placement values him to run in headless environment.

How to test anchor's href with react-testing-library

I am trying to test my anchor tag. Once I click it, I want to see if the window.location.href is what I expect.
I've tried to render the anchor, click it, and then test window.location.href:
test('should navigate to ... when link is clicked', () => {
const { getByText } = render(Click Me);
const link = getByText('Click Me');
fireEvent.click(link);
expect(window.location.href).toBe("https://www.test.com/");
});
I am expecting the test to pass, but instead the window.location.href is just "http://localhost/" meaning it is not getting updated for whatever reason. I even tried wrapping my expect with await wait, but that didn't work either. I can't find much information about testing anchors with react-testing-library. Maybe there is even a better way to test them than what I am doing. 🤷‍♂️
Jest uses jsdom to run its test. jsdom is simulating a browser but it has some limitations. One of these limitations is the fact that you can't change the location. If you want to test that your link works I suggest to check the href attribute of your <a>:
expect(screen.getByText('Click Me').closest('a')).toHaveAttribute('href', 'https://www.test.com/')
I found a solution that may help others. The <a> element is considered a link role by React Testing Library. This should work:
expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.test.com');
If you are using screen which should be the preferred way, by RTL authors:
const linkEl = screen.getByRole('link', { name: 'Click Me' });
expect(linkEl).toHaveAttribute('href', '...')
Similar, without screen (name can be string or RegExp):
const linkEl = getByRole('link', { name: 'Click Me' });
You can simply use this instead:
expect(getByText("Click Me").href).toBe("https://www.test.com/")
simple and easy.
try this
it('should be a link that have href value to "/login"', () => {
render(<SigningInLink />);
const link = screen.getByRole('link', { name: /log in/i });
expect(link.getAttribute('href')).toBe('/login');
});
This is what I use:
const linkToTest = screen.getByRole("link", { name: /link to test/i })
// I used regex here as a value of name property which ignores casing
expect(linkToTest.getAttribute("href")).toMatchInlineSnapshot();
and then run the test, brackets of toMatchInlineSnapshot will be filled with the value that's there in your code.
This has the advantage of not hard coding it, and maintaining this will be easier.
For eg: it will be filled like this:
expect(linkToTest.getAttribute("href")).toMatchInlineSnapshot(`"link/to/somewhere"`);
and next time, suppose you change this link to something else in your codebase, the runner will ask you if you want to update, press u and it will be updated. (Note, that you need to check that this update is correct).
Know more about inline snapshots on Jest Docs
Maybe its overtly engineered in this case. But you can also use data-testid attribute. This guarantees that you are getting the a tag. I think there are benefit to this for more complex components.
test('should navigate to ... when link is clicked', () => {
const { getByTestId } = render(<a data-testid='link' href="https://test.com">Click Me</a>);
expect(getByTestId('link')).toHaveAttribute('href', 'https://test.com');
});
You may have several links to check on a page. I found these suggestions very useful. What I did to adapt it to checking 5 links on the same page -
query the link on the page with the screen() method best practice - it is more reliable
assert the link is on the page
assign the link to a variable
call event on the link
ensure the url toHaveAttribute method rather than navigating with the window object - In react with the virtual DOM, if you try and navigate to another page the link directs to http://localhost/link rather than testing the redirect
test('should navigate to url1 when link is clicked', () => {
const componentName = render(<Component/>)
const url1 = getByText("https://www.test.com/")
expect(ur1).toBeInTheDocument()
screen.userEvent.click(url1);
expect(url1).toHaveAttribute('href', 'https://www.test.com/')
});
This is what worked for me:
expect(screen.getByText('Click Me').closest('a')?.href).toEqual('https://test.com');
Here is what I did, works for me using testing-library/react
import { render, screen } from '#testing-library/react';
import {HomeFeature} from '../Components/HomeFeature.tsx';
let imp_content = [
{
title: "Next.js",
link: "https://nextjs.org/",
},
{
title: "React",
link: "https://reactjs.org/",
},
{
title: "Firebase",
link: "https://firebase.google.com/",
},
{
title: "Tailwind",
link: "https://tailwindcss.com/",
},
{
title: "TypeScript",
link: "https://www.typescriptlang.org/",
},
{
title: "Jest.js",
link: "https://jestjs.io/",
},
{
title: "testing-library/react",
link: "https://testing-library.com/",
}
];
imp_content.map((content) => {
test(`${content.title} contains ${content.link}`, () => {
render(<HomeFeature />);
expect(screen.getByText(content.title).closest('a')).toHaveAttribute('href', content.link);
})
})

How to test enzyme, react-virtualized, material-ui?

I have to test a very unusual case, in my test I should click to some component which is wrapped by from material-ui and it is inside the List from react-vertualized.
I have dived to it -
wrapper
.find(TreeView)
.dive()
.find(AutoSizer)
.renderProp('children', {})
.find(VirtualizedTree)
.dive()
.renderProp('rowRenderer', { index: 0, props: {...} });
And if I debug it I see this result -
<WithStyles(TreeNode) data-test="projected-tree" components={{...}} onSelectToggle={[Function]} onExpandToggle={[Function]} width={...} style={...} node={{...}} level={0} isOdd={true} />
The component which I want to click is inside the TreeNode, but when I try to dive I receive the error related to my custom theme which I use in my project, this happening because has lost the connection to the custom theme.
I think this happening because it has been rendered through the rowRenderer property of List.
Maybe somebody has any ideas on how to pass the custom theme inside the List.rowRenderer?
you need to mock the width and height to make AutoSizer return a width to children can showup
you can use this function mockOffsetSize
// AutoSizer uses offsetWidth and offsetHeight.
// Jest runs in JSDom which doesn't support measurements APIs.
function mockOffsetSize(width, height) {
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
configurable: true,
value: height,
});
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
configurable: true,
value: width,
});
}
ref: https://github.com/bvaughn/react-virtualized/blob/master/source/AutoSizer/AutoSizer.jest.js#L68

What is the best way to trigger change or input event in react js from jQuery or plain JavaScript

We use Backbone + ReactJS bundle to build a client-side app.
Heavily relying on notorious valueLink we propagate values directly to the model via own wrapper that supports ReactJS interface for two way binding.
Now we faced the problem:
We have jquery.mask.js plugin which formats input value programmatically thus it doesn't fire React events. All this leads to situation when model receives unformatted values from user input and misses formatted ones from plugin.
It seems that React has plenty of event handling strategies depending on browser. Is there any common way to trigger change event for particular DOM element so that React will hear it?
For React 16 and React >=15.6
Setter .value= is not working as we wanted because React library overrides input value setter but we can call the function directly on the input as context.
var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, 'react 16 value');
var ev2 = new Event('input', { bubbles: true});
input.dispatchEvent(ev2);
For textarea element you should use prototype of HTMLTextAreaElement class.
New codepen example.
All credits to this contributor and his solution
Outdated answer only for React <=15.5
With react-dom ^15.6.0 you can use simulated flag on the event object for the event to pass through
var ev = new Event('input', { bubbles: true});
ev.simulated = true;
element.value = 'Something new';
element.dispatchEvent(ev);
I made a codepen with an example
To understand why new flag is needed I found this comment very helpful:
The input logic in React now dedupe's change events so they don't fire
more than once per value. It listens for both browser onChange/onInput
events as well as sets on the DOM node value prop (when you update the
value via javascript). This has the side effect of meaning that if you
update the input's value manually input.value = 'foo' then dispatch a
ChangeEvent with { target: input } React will register both the set
and the event, see it's value is still `'foo', consider it a duplicate
event and swallow it.
This works fine in normal cases because a "real" browser initiated
event doesn't trigger sets on the element.value. You can bail out of
this logic secretly by tagging the event you trigger with a simulated
flag and react will always fire the event.
https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128
At least on text inputs, it appears that onChange is listening for input events:
var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
Expanding on the answer from Grin/Dan Abramov, this works across multiple input types. Tested in React >= 15.5
const inputTypes = [
window.HTMLInputElement,
window.HTMLSelectElement,
window.HTMLTextAreaElement,
];
export const triggerInputChange = (node, value = '') => {
// only process the change on elements we know have a value setter in their constructor
if ( inputTypes.indexOf(node.__proto__.constructor) >-1 ) {
const setValue = Object.getOwnPropertyDescriptor(node.__proto__, 'value').set;
const event = new Event('input', { bubbles: true });
setValue.call(node, value);
node.dispatchEvent(event);
}
};
I know this answer comes a little late but I recently faced a similar problem. I wanted to trigger an event on a nested component. I had a list with radio and check box type widgets (they were divs that behaved like checkboxes and/or radio buttons) and in some other place in the application, if someone closed a toolbox, I needed to uncheck one.
I found a pretty simple solution, not sure if this is best practice but it works.
var event = new MouseEvent('click', {
'view': window,
'bubbles': true,
'cancelable': false
});
var node = document.getElementById('nodeMyComponentsEventIsConnectedTo');
node.dispatchEvent(event);
This triggered the click event on the domNode and my handler attached via react was indeed called so it behaves like I would expect if someone clicked on the element. I have not tested onChange but it should work, and not sure how this will fair in really old versions of IE but I believe the MouseEvent is supported in at least IE9 and up.
I eventually moved away from this for my particular use case because my component was very small (only a part of my application used react since i'm still learning it) and I could achieve the same thing another way without getting references to dom nodes.
UPDATE:
As others have stated in the comments, it is better to use this.refs.refname to get a reference to a dom node. In this case, refname is the ref you attached to your component via <MyComponent ref='refname' />.
You can simulate events using ReactTestUtils but that's designed for unit testing.
I'd recommend not using valueLink for this case and simply listening to change events fired by the plugin and updating the input's state in response. The two-way binding utils more as a demo than anything else; they're included in addons only to emphasize the fact that pure two-way binding isn't appropriate for most applications and that you usually need more application logic to describe the interactions in your app.
For HTMLSelectElement, i.e. <select>
var element = document.getElementById("element-id");
var trigger = Object.getOwnPropertyDescriptor(
window.HTMLSelectElement.prototype,
"value"
).set;
trigger.call(element, 4); // 4 is the select option's value we want to set
var event = new Event("change", { bubbles: true });
element.dispatchEvent(event);
I stumbled upon the same issue today. While there is default support for the 'click', 'focus', 'blur' events out of the box in JavaScript, other useful events such as 'change', 'input' are not implemented (yet).
I came up with this generic solution and refactored the code based on the accepted answers.
export const triggerNativeEventFor = (elm, { event, ...valueObj }) => {
if (!(elm instanceof Element)) {
throw new Error(`Expected an Element but received ${elm} instead!`);
}
const [prop, value] = Object.entries(valueObj)[0] ?? [];
const desc = Object.getOwnPropertyDescriptor(elm.__proto__, prop);
desc?.set?.call(elm, value);
elm.dispatchEvent(new Event(event, { bubbles: true }));
};
How does it work?
triggerNativeEventFor(inputRef.current, { event: 'input', value: '' });
Any 2nd property you pass after the 'event' key-value pair, it will be taken into account and the rest will be ignored/discarded.
This is purposedfully written like this in order not to clutter arguments definition of the helper function.
The reason as to why not default to get descriptor for 'value' only is that for instance, if you have a native checkbox <input type="checkbox" />, than it doesn't have a value rather a 'checked' prop/attribute. Then you can pass your desired check state as follows:
triggerNativeEventFor(checkBoxRef.current, { event: 'input', checked: false });
I found this on React's Github issues: Works like a charm (v15.6.2)
Here is how I implemented to a Text input:
changeInputValue = newValue => {
const e = new Event('input', { bubbles: true })
const input = document.querySelector('input[name=' + this.props.name + ']')
console.log('input', input)
this.setNativeValue(input, newValue)
input.dispatchEvent(e)
}
setNativeValue (element, value) {
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set
const prototype = Object.getPrototypeOf(element)
const prototypeValueSetter = Object.getOwnPropertyDescriptor(
prototype,
'value'
).set
if (valueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value)
} else {
valueSetter.call(element, value)
}
}
Triggering change events on arbitrary elements creates dependencies between components which are hard to reason about. It's better to stick with React's one-way data flow.
There is no simple snippet to trigger React's change event. The logic is implemented in ChangeEventPlugin.js and there are different code branches for different input types and browsers. Moreover, the implementation details vary across versions of React.
I have built react-trigger-change that does the thing, but it is intended to be used for testing, not as a production dependency:
let node;
ReactDOM.render(
<input
onChange={() => console.log('changed')}
ref={(input) => { node = input; }}
/>,
mountNode
);
reactTriggerChange(node); // 'changed' is logged
CodePen
well since we use functions to handle an onchange event, we can do it like this:
class Form extends Component {
constructor(props) {
super(props);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
this.state = { password: '' }
}
aForceChange() {
// something happened and a passwordChange
// needs to be triggered!!
// simple, just call the onChange handler
this.handlePasswordChange('my password');
}
handlePasswordChange(value) {
// do something
}
render() {
return (
<input type="text" value={this.state.password} onChange={changeEvent => this.handlePasswordChange(changeEvent.target.value)} />
);
}
}
The Event type input did not work for me on <select> but changing it to change works
useEffect(() => {
var event = new Event('change', { bubbles: true });
selectRef.current.dispatchEvent(event); // ref to the select control
}, [props.items]);
This ugly solution is what worked for me:
let ev = new CustomEvent('change', { bubbles: true });
Object.defineProperty(ev, 'target', {writable: false, value: inpt });
Object.defineProperty(ev, 'currentTarget', {writable: false, value: inpt });
const rHandle = Object.keys(inpt).find(k => k.startsWith("__reactEventHandlers"))
inpt[rHandle].onChange(ev);
A working solution can depend a bit on the implementation of the onChange function you're trying to trigger. Something that worked for me was to reach into the react props attached to the DOM element and call the function directly.
I created a helper function to grab the react props since they're suffixed with a hash like .__reactProps$fdb7odfwyz
It's probably not the most robust but it's good to know it's an option.
function getReactProps(el) {
const keys = Object.keys(el);
const propKey = keys.find(key => key.includes('reactProps'));
return el[propKey];
}
const el = document.querySelector('XX');
getReactProps(el).onChange({ target: { value: id } });
Since the onChange function was only using target.value I could pass a simple object to onChange to trigger my change.
This method can also help with stubborn react owned DOM elements that are listing for onMouseDown and do not respond to .click() like you'd expect.
getReactProps(el).onMouseDown(new Event('click'));
If you are using Backbone and React, I'd recommend one of the following,
Backbone.React.Component
react.backbone
They both help integrate Backbone models and collections with React views. You can use Backbone events just like you do with Backbone views. I've dabbled in both and didn't see much of a difference except one is a mixin and the other changes React.createClass to React.createBackboneClass.

Resources