How Can I Test an MUI ToggleButtonGroup with a userEvent? - jestjs

I am using MUI ToggleButtonGroup component like so:
<ToggleButtonGroup
color="primary"
value={mode}
exclusive
onChange={changeHandler}
fullWidth
className="mt-4"
>
<ToggleButton value="login">Login</ToggleButton>
<ToggleButton value="register">Register</ToggleButton>
</ToggleButtonGroup>
When clicking the 'Register' button, it works fine in the UI. I'd like to get a proper test written with React Testing Library.
Here's what I have:
setup();
heading = screen.getByRole("heading", { level: 2 });
const registerButton = screen.getByRole("button", { name: /register/i });
userEvent.click(registerButton);
expect(heading).toHaveTextContent("Register");
The crux of the issue seems to be that userEvent.click somehow doesn't call the changeHandler. Is there some type of bubbling or something that I need to concern myself with?
Here's a prettyDOM log of the relevant components:
<button
aria-pressed="false"
class="MuiButtonBase-root MuiToggleButton-root MuiToggleButton-fullWidth MuiToggleButton-sizeMedium MuiToggleButton-primary MuiToggleButtonGroup-grouped MuiToggleButtonGroup-groupedHorizontal css-j4p6el-MuiButtonBase-root-MuiToggleButton-root"
tabindex="0"
type="button"
value="register"
>
Register
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button> <h2
class="MuiTypography-root MuiTypography-h5 css-ag7rrr-MuiTypography-root"
>
Login
</h2>

Add an id to the component:
<ToggleButton id={${data.value}}> {data.label}
test case:
describe("ToggleButtonGroupComponent onChange Testing", () => {
const onChange = vi.fn();
it("toggle group change value and new value updated and last value no more checked", () => {
const { container } = render(<ToggleButtonGroupComponent {...props} onChange={onChange} />);
// Before change selection
const allValueToggleButton = QueryAttributeById(container, "ssh") as HTMLInputElement;
expect(allValueToggleButton.value).toEqual("ssh");
// Change selection
const withValueToggleButton = QueryAttributeById(container, "http") as HTMLInputElement;
fireEvent.click(withValueToggleButton);
expect(withValueToggleButton.value).toEqual("http");
// Old value is no more checked
expect(allValueToggleButton.value).toEqual("ssh");
});
});

Related

How to change the text and path of a navigation button?

I have a navbar component, which has a button that transforming me to path (React-Router) "/NEXUM", But, When I'm transformed to NEXUM, i want to change the text of the navbar and the navigation path, so when I click it, it will bring me to a different path, called "/".
const NavBar = () => {
const navigate = useNavigate()
const homeToNavigate = () => {
navigate('/')
}
const Navigation = () => {
navigate('/nexum')
}
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar id="bar">
<Toolbar>
<img onClick={homeToNavigate} className="logo" src={logo}></img>
<Button onClick={Navigation} id='nexumNavigation'> להכנסת קובץ אקסל ישיר </Button>
</Toolbar>
</AppBar>
</Box>
)
}
export default NavBar
This is The nav bar component, The "navBar" component is used in both "HomePage" Component that I'm using and "SpagetiComponent", So as I said, I need the purpose of the button to change, I tried doing it with props, but I was unsuccessful, if you have an idea, please let me know.
let location = useLocation();
<Button onClick={location.pathName==="/NEXUM"?homeToNavigate:Navigation} id='nexumNavigation'>{location.pathName==="/NEXUM"?"go home":"go NEXUM"} </Button>

Button press triggers the last button's press

I'm new to react an am trying to create an app to use in my portfolio. Essentially the program is a menu that has access to different menus(json files: texas_pick.js, breakfast.js...), the program is meant to display the menu options in form of buttons, the buttons' details are retrieved from their respective json file. The problem that I am facing is that when making a click on a menu option the data of the last menu item is retrieved. I programmed the backend to only push the item's name and price to the database, and the frontend, to retrieve this data and display it on a table. The data retrieved is only the last button's and not any others. I believe the problem to possibly be within my button code. Any help/tips/recommendations you could give are greatly appreciated.
I clicked every menu item and only the data from the last one was retrieved
import React from 'react'
import {useEffect,useState} from 'react'
import axios from 'axios'
import Texas_Pick from '../../json_files/texas_pick.json'
import './Mid_Container.css'
function Mid_Container() {
const [items, setItems] = useState(Texas_Pick);
const [order, setOrder] = useState({
item: '',
cost: ''
})
const createOrder = () => {
axios
.post("http://localhost:5000/orders", order)
.then(res => {window.location.reload(false)})
.catch(err => console.error(err));
}
const item1 = items[0];
const item2 = items[1];
const item3 = items[2];
const item4 = items[3];
const item5 = items[4];
const item6 = items[5];
return (
<div className="Mid_Container">
<button
style={{backgroundImage: `url(${item1.image})`}}
value={order.item=item1.item,order.cost=item1.price}
onClick={createOrder}
>
<p id="pPrice">${item1.price}</p>
<p id="pItem" >{item1.item}</p>
</button>
<button
style={{backgroundImage: `url(${item2.image})`}}
value={order.item=item2.item,order.cost=item2.price}
onClick={createOrder}
>
<p id="pPrice">${item2.price}</p>
<p id="pItem" >{item2.item}</p>
</button>
<button
style={{backgroundImage: `url(${item3.image})`}}
value={order.item=item3.item,order.cost=item3.price}
onClick={createOrder}
>
<p id="pPrice">${item3.price}</p>
<p id="pItem" >{item3.item}</p>
</button>
<button
style={{backgroundImage: `url(${item4.image})`}}
value={order.item=item4.item,order.cost=item4.price}
onClick={createOrder}
>
<p id="pPrice">${item4.price}</p>
<p id="pItem" >{item4.item}</p>
</button>
</div>
)
}
export default Mid_Container
I think that you should have this approach:
function SomeComponent() {
// Mocking your datas
const [items, setItems] = React.useState([
{
price: "1",
item: "i am the first",
image: "image1.png",
},
{
price: "7",
item: "I am the second",
image: "image2.png",
},
{
price: "3",
item: "i am the third",
image: "image3.png",
},
]);
const [order, setOrder] = React.useState();
const [myResponse, setMyResponse] = React.useState();
const createOrder = (clickedItem) => {
setOrder(clickedItem);
console.log(clickedItem);
// axios
// .post("http://somewhere", clickedItem)
// .then((res) => {
// setMyResponse(res); // or setMyResponse(res.json());
// })
// .catch((err) => console.error(err));
};
console.log('Log selected order in render loop ==> ', order);
console.log('Log response in render loop ==> ', myResponse);
return (
<div>
<div className="Mid_Container">
{items.length && items.map((currItem, index) => {
return (
<button
key={index}
style={{ backgroundImage: `url(${currItem.image})` }}
onClick={() => createOrder(currItem)}
>
<p id="pPrice">${currItem.price}</p>
<p id="pItem">{currItem.item}</p>
</button>
);
})}
</div>
</div>
);
}
Mapping on your items with map function, and pass the current item to your onClickEvent.
I also think you don't need a value attribute on your buttons. It's also not the place to do operations like you do :)
You also don't have to reload the page in the "then" of your promise. React is made to do SPA (single page application), so in the "then", you can put some code like "setResult(myResponse)" to store in you component the new data you got from your API.

Use mapping function to render buttons and how can each button works independently in React js

I have 3 sets of button here, I want to disable 'cancel button' after clicking once, and vice versa.
However when I disable the 'cancel' button from first set, the 'cancel' button from other sets will be disabled too.
In this case I want to disable the 'cancel' button from first set only.
How do I solve this issue or is there any approach to do so.
any help and suggestions will be appreciated
note ** I am using Mapping function to render the buttons
my client side:
function App() {
const [taskNumber, setTaskNumber] = useState('')
const [disable, setDisable] = useState(true)
const onChange = (e) => {
setTaskNumber(e.target.value)
}
const onClick = () => {
console.log('world')
setDisable(!disable)
}
const button = (index) => {
return (
< div >
<button onClick={() => onClick()} disabled={!disable}>hello</button>
<button onClick={() => onClick()} disabled={disable}>cancel</button>
</div >
)
}
let items = []
for (let i = 0; i < taskNumber; i++) {
// items.push(button(i))
items.push(i)
}
<Form>
<Form.Group as={Col}>
<Form.Label>Number of Task</Form.Label>
<Form.Control
type="number"
min='1'
placeholder="Enter number of task"
name='taskNumber'
value={taskNumber}
onChange={onChange}
/>
</Form.Group>
</Form>
{items.map((number) => {
return button(number)
})}
My React user Interface
You were close, you can use an array in disable to control which element is enabled.
*** edit ***
I didn't have access to the form components you were using so I just made a more basic example for you to refer to. See my codesandbox:
https://codesandbox.io/s/prod-fast-0zneb?file=/src/App.js

State is not upating in renderer() component

I'm very new to react JS, and I am using it to build a app now. I have a question.
In of of the Button Click event i have a code logic like this:
handlestartbutton(event) {
const accesskey = localStorage.getItem(localStorageKeys.accessTokenKey);
const decodedAccessKey = jwt_decode(accesskey);
const date = dateConverter.epochToReadableDate(decodedAccessKey.exp);
if (date.currentTime < date.expiryDate) {
this.setState({
accesstokenexpirydate: true
}, () => {
if(this.state.accesstokenexpirydate === false) {
//rest of the code
}
})
In renderer() i have a a pop up UI like this:
renderer()
{
{this.state.accesstokenexpirydate === true ? (
<Popup
open ={this.state.open}
closeOnDocumentClick={false}
closeOnEscape={false}
onClose={this.closeModal}className
>
<div className={popstyles.popupBody}>
<div className={popstyles.modalClose}>
<a className="close" onClick={this.closeModal}>
×
</a>
{""}
<div className ={popstyles.unAutherizedUser}>
<label >{homeConstantMessages.accessTokenExpire}</label>
<div className ={popstyles.unAutherizedUserMsg}>
<label>{homeConstantMessages.accessTokenExpireMsg}</label>
<button className ={styles.refreshaccessbtn} onClick = {this.navigateToHomePage.bind(this)} label = {homeConstantMessages.refreshbtn}>
</button>
</div>
</div>
</div>
</div>
</Popup>
) : (
''
)}
}
The problem is when the first time start button is clicked this pop up UI is not getting popped even though the state variable accesstokenexpirydate is set to true. when second time the button is cicked UI is popping up. can anyone please help me out here
1) I think you have to apply arrow function like follows and then you can use this
handlestartbutton=(event)=> {...}
2) I'm quite confused about the name, don't you think it should be render(...) instead of renderer()

Enabling submit button button when all inputs is filled?

Entire examples doesn't show simple solution how to keep submit button disabled until all fields is filled up in redux-form.
I tried to use this approach (TypeScript):
import * as React from 'react';
import * as reduxForm from 'redux-form';
export interface Props extends reduxForm.InjectedFormProps {}
let passedUsername = false;
let passedPassword = false;
const required = (value: string, callback: (passed: boolean) => void) => {
console.info(`PERFORM REQUIRED FIELD CHECK FOR ${value}`);
if (value && value.trim().length > 0) {
callback(true);
} else {
callback(false);
}
};
const usernameRequired = (value: string) => {
required(value, passed => { passedUsername = passed; });
};
const passwordRequired = (value: string) => {
required(value, passed => { passedPassword = passed; });
};
const isPassed = () => {
console.info(`USER PASSED: ${passedUsername}`);
console.info(`PASSWORD PASSED: ${passedPassword}`);
const result = passedUsername && passedPassword;
console.info(`PASSED: ${result}`);
return result;
};
const LoginForm = ({handleSubmit, pristine, submitting}: Props) => (
<form onSubmit={handleSubmit}>
<div>
<label>Username </label>
<reduxForm.Field
name="username"
component="input"
type="text"
validate={[usernameRequired]}
placeholder="Username"
/>
<br/>
<label>Password </label>
<reduxForm.Field
name="password"
component="input"
type="password"
validate={[passwordRequired]}
placeholder="Password"
/>
</div>
<br/>
<div>
<button type="submit" disabled={!isPassed()}>
<i className="fa fa-spinner fa-spin" style={{visibility: (submitting) ? 'visible' : 'hidden'}}/>
<strong>Login</strong>
</button>
</div>
</form>
);
export default reduxForm.reduxForm({
form: 'login'
})(LoginForm);
But this code above doesn't seems to be working. The form doesn't want to re-render even if I force it through subscribe event. It only re-render when pristine or submitting event is triggered. But if I want to re-render myself the form just ignore it. Maybe some flag I missed to re-render manually the form when I need to?
Ok, finally the solution has been found: just need to modify parameter pure in reduxForm constructor from true (default) to false
export default reduxForm.reduxForm({
form: 'login',
pure: false
})(LoginForm);
And the from will re-render whenever you need.

Resources