Accessing onClick fn of a nested button in Enzyme & React Hooks - javascript

Essentially, I'm trying to test if my modal opens when my button is clicked & I'm a noob with Jest & Enzyme.
I am hitting an issue accessing the props on the button & I'm not sure if it's because it's nested inside of a third party package or if I'm not importing it correctly into my test. Please see my condensed(ish) code below as I wasn't about to recreate the code on CodePen..
DataTable.jsx
const UploadDownloadComponent = ({ handleOpen }) => (
<UploadDownloadButtonContainer>
<PrimaryButton
id="bulk-add-button"
onClick={handleOpen} //this is what I need to access
>
Bulk Add Members
</PrimaryButton>
<SecondaryButton id="email-csv-button">
</SecondaryButton>
</UploadDownloadButtonContainer>
);
//beginning of data table component
export const Table = () => {
const [bulkUpload, setBulkUpload] = useState(false);
//upload modal
const openUpload = () => {
setBulkUpload(true);
};
const closeUpload = () => {
setBulkUpload(false);
};
//query container
const subHeaderComponentMemo = useMemo(() => {
{*/ other code /*}
return (
<div>
<UploadDownloadComponent
handleOpen={openUpload}
bulkUpload={bulkUpload}
/>
</div>
);
}, []);
return (
<React.Fragment>
<DataTable
{*/ bunch of other things unrelated /*}
subHeaderComponent={subHeaderComponentMemo}
/>
<UploadModal closeModal={closeUpload} open={bulkUpload} />
</React.Fragment>
);
};
DataTable.test.js
import React from "react";
import { configure, mount, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { Table } from "../components/MemberView/DataTable";
import { UploadModal } from "../components/MemberView/UploadModal";
import Modal from "react-modal";
configure({ adapter: new Adapter() });
describe("<Table />", () => {
const wrapper = mount(<Table />);
//using .upDate() is the only way I can get this test to pass
it("should have upload button", () => {
const uploadButton = wrapper.find("#bulk-add-button").update();
expect(uploadButton).toHaveLength(1);
});
//this passes
it("renders Upload Modal", () => {
const upModal = shallow(<UploadModal />);
expect(upModal.find(Modal)).toHaveLength(1);
});
it("opens Upload Modal when state is changed", () => {
const modal = mount(<UploadModal />);
expect(modal.find(Modal).prop("isOpen")).toBe(false);
const uploadButton = wrapper.find("#bulk-add-button").update();
expect(uploadButton.length).toEqual(1);
//this is where my test fails as it cannot simulate click on uploadButton
uploadButton.simulate("click");
//if I change it to:
wrapper.find("#bulk-add-button").simulate("click')
//my error message says it expected 1 Node. ) found instead.
//I don't make it this far
expect(modal.find(Modal).prop("isOpen")).toBe(true);
});
});
I am also using Hooks if that makes any difference...
Any and all help/advice welcome!
Thanks

When accessing props the following approach works for me.
expect(modal.find(Modal).props().isOpen).toBe(false);
Hope this works!

Related

Importing React Autosuggest as Functional Component from Another JSX File

I'm currently making a simple web frontend with react using react-autosuggest to search a specified user from a list. I want to try and use the Autosuggest to give suggestion when the user's type in the query in the search field; the suggestion will be based on username of github profiles taken from github user API.
What I want to do is to separate the AutoSuggest.jsx and then import it into Main.jsx then render the Main.jsx in App.js, however it keeps giving me 'TypeError: _ref2 is undefined' and always refer to my onChange function of AutoSuggest.jsx as the problem.
Below is my App.js code:
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './views/header/Header';
import Main from './views/main/Main';
import Footer from './views/footer/Footer';
const App = () => {
return (
<>
<Header/>
<Main/> <- the autosuggest is imported in here
<Footer/>
</>
);
}
export default App;
Below is my Main.jsx code:
import React, { useState } from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
import { useEffect } from 'react';
import AutoSuggest from '../../components/AutoSuggest';
const Main = () => {
const [userList, setUserList] = useState([]);
useEffect(() => {
axios.get('https://api.github.com/users?per_page=100')
.then((res) => setUserList(res.data))
.catch((err) => console.log(err));
}, [])
return (
<Container>
<br/>
<Row>
<AutoSuggest userList={userList} placeHolderText={'wow'} />
</Row>
</Container>
);
}
export default Main;
Below is my AutoSuggest.jsx code:
import React, { useState } from "react";
import Autosuggest from 'react-autosuggest';
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getSuggestions(value, userList) {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return userList.filter(user => regex.test(user.login));
}
function getSuggestionValue(suggestion) {
return suggestion.name;
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
);
}
const AutoSuggest = ({userList, placeHolderText}) => {
const [value, setValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const onChange = (event, { newValue, method }) => { <- error from console always refer here, I'm not quite sure how to handle it..
setValue(newValue);
};
const onSuggestionsFetchRequested = ({ value }) => {
setValue(getSuggestions(value, userList))
};
const onSuggestionsClearRequested = () => {
setSuggestions([]);
};
const inputProps = {
placeholder: placeHolderText,
value,
onChange: () => onChange()
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={() => onSuggestionsFetchRequested()}
onSuggestionsClearRequested={() => onSuggestionsClearRequested()}
getSuggestionValue={() => getSuggestionValue()}
renderSuggestion={() => renderSuggestion()}
inputProps={inputProps} />
);
}
export default AutoSuggest;
The error on browser (Firefox) console:
I have no idea what does the error mean or how it happened and therefore unable to do any workaround.. I also want to ask if what I do here is already considered a good practice or not and maybe some inputs on what I can improve as well to make my code cleaner and web faster. Any input is highly appreciated, thank you in advance!
you have to write it like this... do not use the arrow function in inputProps
onChange: onChange

ReactDom createPortal() doesn't work but render() does, and only once not if trigger is repeated - why is this?

Newbie to react here.
TLDR: I have a helper function called createNotification which when called inserts a <ToastNotification /> component into a container element using render(). If I use createPortal() nothing is appended. If I use render, the component is only added once despite multiple triggers.
Can anyone help me figure out whats happening please?
Thank you
helpers.js
import { ToastNotification } from "carbon-components-react";
import { render, createPortal } from "react-dom";
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container); //just to check function is running and has found container
return render(<ToastNotification />, container); //works but only once, not on multiple triggers
return createPortal(<ToastNotification />, container); //doesn't render anything in container
};
the function above is called from other components as needed:
login.js
import { createNotification } from "../../helpers";
const Login = () => {
const validateLogin = async (event) => {
createNotification();
// validation logic
performLogin();
};
const performLogin = async () => {
//axios call here
};
// main component content
return (
<>
<!-- validateLogin() called on form submit -->
</>
);
};
export default Login;
app.js
//imports
function App() {
return (
<div>
<div className="App"></div>
</div>
);
}
export default App;
Thank you
Solved this myself by adding the createPortal() within the render().
If anyone can provide an explanation, it would be much appreciated.
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container);
return render(createPortal(<ToastNotification />, container), document.createElement("div"));
};
createNotification aren't mounted in component in app Virtual Dom... when you use render(createPortal) then you just create spearted app.
import { createNotification } from "../../helpers";
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container); //just to check function is running and has found container
return createPortal(<ToastNotification />, container); //doesn't render anything in container
};
const Login = () => {
const [validate, setValidate] = useState(false);
const validateLogin = async (event) => {
if('some logic')
return setValidte(true)
setVAlidte(false)
};
useEffect(() => {
if(!valite)
return;
//axios heare
}, [validate])
// main component content
return (
<>
{!validate && <CreateNotfication/>}
<!-- validateLogin() called on form submit -->
</>
);
};

React test conditional rendering using the useState hook based on API response

I have this React component that has a block that renders based on an API response.
const SomeComponent = () => {
const [render, setRender] = useState(false)
const handleClick = async () => {
const someFunction = await callToAPI()
if (someFunction) {
setRender(true)
}
}
return (
<main>
<button onClick={handleClick}></button>
<ShouldRender when={render} data-test-id='should-render'>
Something
</ShouldRender>
</main>
)
}
As I'm writing the test for it, I'm confused on how to test this conditional rendering.
I'd like to know if there's a way to use something like the fireEvent to set this state in my SomeComponent.test.js file.
So far the test looks like this:
test('renders component when button is clicked', () => {
render(<SomeComponent />)
const button = screen.queryByRole('button')
fireEvent.click(button)
const conditionalComponent = screen.getByTestId('should-render')
expect(conditionalComponent).toBeInTheDocument()
})

conditional rendering with toast and usestate does not work with react

I have my state and I want to display the component if the value is true but in the console I receive the error message Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state my code
import React, { useState} from "react";
import { useToasts } from "react-toast-notifications";
const Index = () => {
const [test, setTest]= useState(true);
const { addToast } = useToasts();
function RenderToast() {
return (
<div>
{ addToast('message') }
</div>
)}
return (
<div>
{test && <RenderToast /> }
</div>
)
}
You cannot set state during a render. And I'm guessing that addToast internally sets some state.
And looking at the docs for that library, you don't explicitly render the toasts. You just call addToast and then the <ToastProvider/> farther up in the tree shows them.
So to make this simple example works where a toast is shown on mount, you should use an effect to add the toast after the first render, and make sure your component is wrapped by <ToastProvider>
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
addToast('message')
}, [])
return <>Some Content here</>
}
// Example app that includes the toast provider
const MyApp = () => {
<ToastProvider>
<Index />
</ToastProvider>
}
how i can display the toast based on a variable for exemple display toast after receive error on backend?
You simply call addToast where you are handling your server communication.
For example:
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
fetchDataFromApi()
.then(data => ...)
.catch(error => addToast(`error: ${error}`))
}, [])
//...
}

Mocking a React component function that is enclosed by Redux Provider component

Here is my situation:
I am trying to unit-test a React component (TodoList) that does nothing more on the Render method than map the items and show them.
It gets the items (TodoItem) from the Redux store by using MapStateToProps.
This is the javascript code for the TodoList component:
class TodoList extends React.Component {
onRemoveClick = (id) => {
diContainer.dataLayer.todo.remove(id);
}
render() {
const todos = this.props.todos;
if(!todos)
return null;
return (
todos.map(todo => (
<TodoItem key={todo.id} todo={todo} onRemove={this.onRemoveClick} />
))
);
}
}
const mapStateToProps = (state) => ({
todos: state.todos
});
export default connect(mapStateToProps)(TodoList);
What I want to test now, is that whenever the button inside TodoItem (child object) gets called, the onRemoveClick delegate method gets called.
I tried using the function mocking that Jest provides, in conjunction with Enzyme. However, because TodoList gets his data from Redux, I have to surround my Enzyme mount() call with a Provider component and mock the store.
Here is my test code:
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import TodoList from '../../../../react/components/todo/TodoList';
describe('TodoList component', () => {
//global arrange
const storeState = {
todos: [
{
id: 'testId1',
title: 'testTitle1',
description: 'testDescription1'
},
{
id: 'testId2',
title: 'testTitle2',
description: 'testDescription2'
},
]
};
const mockStore = configureStore();
let store;
beforeEach(() => {
store = mockStore(storeState)
});
it('Removes a todo from the list if the remove button the todo was pressed', () => {
//arrange
//let mockFn = jest.fn();
//TodoList.prototype.onRemoveClick = mockFn; => tried, doesn't work...
const component = mount(
<Provider store={store}>
<TodoList />
</Provider>
);
//component.instance() => tried working with this, but couldn't find it
//component.instance().children() => is not the TodoList
const items = component.find('.todoItem');
//act
const button = items.first().find('button');
button.simulate('click');
//assert
//Todo: check function spy here
});
});
I commented out some things I tried. But I can't seem to be able to access the TodoList component in my test code, because of the Provider wrapper...
Any clues?
Got it fixed through a lot of trial and error.
I was able to access TodoList through the enzyme find functionality, which apparently also works on ComponentNames (and not just plain HTML selectors).
The second trick was to call forceUpdate() on the TodoList component AND update() on the parent wrapper.
it('Removes a todo from the list if the remove button on the todo was pressed', () => {
//arrange
const component = mount(
<Provider store={store}>
<TodoList />
</Provider>
);
const list = component.find('TodoList').instance();
let mockFn = jest.fn();
list.onRemoveClick = mockFn;
list.forceUpdate();
component.update();
//act
const items = component.find('.todoItem');
const button = items.first().find('button');
button.simulate('click');
//assert
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith('testId1');
});

Categories