Opening and closing dialog modal and waiting for response through provider - reactjs

Using React Testing Library to test a dialog provider. I can get it to open, and assert on it appearing — but for some reason I can't get it to close in the test. Do I need to rerender or something?
test('await the closing or confirming of the modal', async () => {
const { debug, getByText, queryByText } = render(
<DialogProvider>
<Test />
</DialogProvider>,
);
const openDialogButton = getByText(/click me/i);
fireEvent.click(openDialogButton);
await wait(() => getByText(/ok/i));
fireEvent.click(getByText(/ok/i));
debug();
});
function Test() {
const confirm = useConfirmation();
return (
<button
onClick={() => {
confirm({ variant: 'info' });
}}
>
click me
</button>
);
}

Apparently, the following seemed to work
await waitForElement(() => getByText(/ok/i));
fireEvent.click(getByText(/ok/i));
await waitForElementToBeRemoved(() => queryByText(/ok/i));
expect(queryByText(/ok/i)).toBeNull();

Related

Act Warning When Using MUI Popover

I am having a hard time getting a component to render without any act warnings from react. Here is the component that I am testing. It contains a TextField and a Popover. The idea is that the parent component controls when and what the Popover displays.
const PopoverContainer = (props: TextFieldWithPopoverProps) => {
const [anchorEl, setAnchorEl] = React.useState(null);
const anchorRef = React.useRef(null);
React.useEffect(() => {
setAnchorEl(anchorRef.current);
}, [anchorRef]);
return (
<>
<TextField type="text" ref={anchorRef}/>
<Popover id={props.popperId} open={props.open} sx={{ zIndex: 1001 }} anchorEl={anchorEl}>
<Paper>
{props.renderedChild}
</Paper>
</Popover>
</>
);
};
And here is the test.
test('should open popover', async () => {
const { rerender } = render(<PopoverContainer open={false} popperId="popper-id" renderedChild={renderInnerPopover()} />);
expect(screen.queryByText('Hello World!')).toBe(null);
await userEvent.type(await screen.findByRole('textbox'), 'HELLO');
rerender(<PopoverContainer open={true} popperId="popper-id" renderedChild={renderInnerPopover()} />);
expect(await screen.findByText('Hello World!'));
});
const renderInnerPopover = () => {
return (
<div>
Hello World!
</div>
);
};
And here is an example of a few of the act warnings that are reported.
Warning: An update to ForwardRef(FormControl) inside a test was not wrapped in act(...)
Warning: An update to ForwardRef(Popover) inside a test was not wrapped in act(...)
Act warnings are always on the rerender call. I have tried putting act() around the rerender call and the type event but neither fixes the problem. I have also tried not setting the TextField which does prevent the act warnings but of course that defeats what the component is trying to accomplish.
For anyone else running into a similar issue. I swapped userEvent for fireEvent for the typing event. Not sure if its a bug with the recent changes to userEvent or something that I am doing but it did resolve the problem.
test('should open popover', async () => {
const { rerender } = render(<PopoverContainer open={false} popperId="popper-id" renderedChild={renderInnerPopover()} />);
expect(screen.queryByText('Hello World!')).toBe(null);
// await userEvent.type(await screen.findByRole('textbox'), 'HELLO');
fireEvent.change(await screen.findByRole('textbox'), { target: { value: 'HELLO' } });
rerender(<PopoverContainer open={true} popperId="popper-id" renderedChild={renderInnerPopover()} />);
expect(await screen.findByText('Hello World!'));
});
const renderInnerPopover = () => {
return (
<div>
Hello World!
</div>
);
};

React - Jest test failing - TestingLibraryElementError: Unable to find an element with the text

I've a jest test that is failing on addition of a new component to the page. The test is about showing of an error alert once error occurs. Code works in local environment but fails during commit.
Error Text:
TestingLibraryElementError: Unable to find an element with the text:
Student is unable to perform register/unregister activities.. This could be because
the text is broken up by multiple elements. In this case, you can
provide a function for your text matcher to make your matcher more
flexible.
Test:
jest.mock('react-query', () => ({
...jest.requireActual('react-query'),
useMutation: jest.fn((_key, cb) => {
cb();
return { data: null };
})
}));
const useMutation = useMutationHook as ReturnType<typeof jest.fn>;
describe('StatusAlert', () => {
beforeEach(() => {
useMutation.mockReturnValue({});
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should show error', () => {
useMutation.mockReturnValueOnce({
isError: true
});
const { getByText } = render(
<StudentRegister
students={[studentStub, studentStub]}
onSuccess={jest.fn()}
/>
);
expect(getByText(ErrorDict.ErrorRequest)).toBeInTheDocument();
});
StudentRegister:
Adding this component is causing the above mentioned error:
interface Props {
selectedStudents: Array<Student>;
onSuccessCallback: () => void;
}
export const StudentSelectionBar: FC<Props> = ({
selectedStudents,
onSuccessCallback
}) => {
const [isOpenDropCourseModal, setisOpenDropCourseModal] =
useState(false);
const [studentIds, setStudentIds] = useState<string[]>([]);
useEffect(() => {
setStudentIds(selectedStudents.map((student) =>
student.id));
}, [selectedStudents]);
const onToggleOpenDropCourseModal = useCallback(() => {
setisOpenDropCourseModal(
(state) => !state
);
}, []);
const {
isError: isDropCourseError,
isSuccess: isDropCourseSuccess,
isLoading: isDropCourseLoading,
mutateAsync: DropCourseMutation,
error: DropCourseError
} = useMutation<void, ApiError>(
() => dropCourse(selectedStudents.map((student) =>
student.id)),
{
onSuccess() {
onToggleOpenDropCourseModal();
onSuccess();
}
}
);
return (
<>
<StatusAlert
isError={isDropCourseError}
isSuccess={isDropCourseSuccess}
errorMessage={
dropCourseError?.errorMessage ||
ErrorMessages.FailedPostRequest
}
successMessage="Students successfully dropped from
course"
/>
<StatusAlert
isError={registerMutation.isError}
isSuccess={registerMutation.isSuccess}
errorMessage={
registerMutation.error?.errorMessage ||
ErrorDict.ErrorRequest
}
successMessage="Students successfully registered"
/>
<StatusAlert
isError={isError}
isSuccess={isSuccess}
errorMessage={
error?.errorMessage ||
ErrorDict.ErrorRequest
}
successMessage="Students successfully unregistered"
/>
<Permissions scope={[DropCourseUsers]}>
<LoadingButton
color="error"
variant="contained"
onClick={onToggleDropCourseUserModal}
className={styles['action-button']}
loading={isDropCourseLoading}
loadingPosition="center"
disabled={registerMutation.isLoading || isLoading}
>
drop Course
</LoadingButton>
</Permissions>
<DropCourseModal
isOpen={isOpenDropCourseModal}
onCloseModal={onToggleOpenDropCourseModal}
onArchiveUsers={DropCourseMutation}
users={studentIds}
/>
</>
);
};
Update:
I've noticed that removing useEffect() hook from the component, makes it render correctly in the test. Its function is to update the state variable holding studentIds on every selection on the list.
Is there a way to mock following useEffect hook with dependency in the test?
const [studentIds, setStudentIds] = useState<string[]>([]);
useEffect(() => {
setStudentIds(selectedStudents.map((student) => student.id));
}, [selectedStudents]);

How to verify method is not invoked on mock passed as prop

I am developing a React application with jest and TypeMoq.
I can't test the negative path of a decision tree when the mocked call is a method on the object which needs to be undefined. Is there a method on TypeMoq that can help me verify that the provided method is not called?
type TopicComponentProps = {
topic: Topic
history?: History<any>
}
export const TopicComponent = ({topic, history} : TopicComponentProps) => {
const { Id, Name } = topic;
const filterTopic = () => {
if (history) { // <-- this is my problem
history.push(`/topic/overview/${Id}`);
}
}
return(
<Fragment>
<span
style={topicStyle}
onClick={() => filterTopic()}
className="topic">
{Name}
</span>
</Fragment>
)
}
The positive test case looks like this:
it('should trigger the navigation when clicked', () => {
const mockHistory = Mock.ofType<History<any>>();
const wrapper = mount(
<TopicComponent topic={testTopic} history={mockHistory.object} />
);
wrapper.simulate('click');
mockHistory.verify(x => x.push(It.isAnyString()), Times.once());
});
How do I setup the mock object, so i can test that no navigation happens when no history is provided?
it('should not trigger the navigation when history is undefined', () => {
let mockHistory = Mock.ofType<History<any>>();
???
const wrapper = mount(
<TopicComponent topic={testTopic} history={???} />
);
wrapper.simulate('click');
mockHistory.verify(x => x.push(It.isAnyString()), Times.never());
});

How do I/should I split my react integration test?

I have this test which does 3 things:
input onChange gets updated
button onClick fires searchMovies request with input value
Results are in the document
test code:
import { searchMovies as mockSearchMovies, getPopular, getFiltered, getGenreIds } from "./tmdbAPI"
afterEach(cleanup)
jest.mock('./tmdbAPI', () => {
return {
searchMovies: jest.fn(() => Promise.resolve([{ id: 1, title: "test-1", poster_path: 'test-path' }])),
getPopular: jest.fn(() => Promise.resolve([{}])),
getFiltered: jest.fn(() => Promise.resolve([{}])),
getGenreIds: jest.fn(() => Promise.resolve([{}])),
}
})
test('1. input onChange gets updated, 2. button onClick fires searchMovies request with input value, 3. Results are in the document', async () => {
const { debug, getByPlaceholderText, getByTestId } = render(
<Provider>
<FindMovies />
</Provider>
)
const testText = 'some movie title'
const input = getByPlaceholderText('Search movies...')
fireEvent.change(input, { target: { value: testText } })
expect(input.value).toBe(testText)
const button = getByTestId('search-button')
fireEvent.click(button)
expect(mockSearchMovies).toHaveBeenCalledTimes(1)
expect(mockSearchMovies).toHaveBeenCalledWith(testText)
await wait(() => expect(getByTestId("found-movie-item")).toBeInTheDocument())
})
How do I/should I split this test so each test keeps it's "state" so I can move on next one?
I'm running useEffect function in my provider which runs getPopular, getFiltered and getGenreIds async request. If I'm not mocking them as in example above I get an error. Is there a way to get around these unnecessary mocks that I'm currently not testing?

Simulate is meant to be run on 1 node = 0 found

Unable to make the following test pass :
Using React JS / enzyme and jest
I already asked a similar question and try to apply the same method, but its not going through. Any reason ?? Substitute shallow = mount ? or add a dive() ?
file.test.js -
// jest mock functions (mocks this.props.func)
const updateSelectedFormJSON = jest.fn();
const closeModal = jest.fn();
const onClick = jest.fn();
const onSaveClick = jest.fn();
// defining this.props
const baseProps = {
selectedFormJSON :{
FORM_COLUMN:[],
},
updateSelectedFormJSON,
closeModal,
onClick,
onSaveClick,
describe('SpecifyBodyModal Test', () => {
let wrapper;
let tree;
beforeEach(() => wrapper = mount(<SpecifyBodyModal {...baseProps} />));
it('should call closeModal functions on button click', () => {
baseProps.closeModal.mockClear();
wrapper.setProps({
updateSelectedFormJSON :null
});
wrapper.find('.add-custom-field-close').at(0).simulate('click')
expect(baseProps.closeModal).toHaveBeenCalled();
});
the 2nd test is not passing: error Method “simulate” is meant to be run on 1 node. 0 found instead.
it('should call onSaveClick functions on button click', () => {
baseProps.onSaveClick.mockClear();
wrapper.setProps({
closeModal :null
});
wrapper.find('.tran-button specify-body-continue').at(1).simulate('click')
expect(baseProps.onSaveClick).toHaveBeenCalled();
here is the render file js.
onSaveClick = () => {
let json = Object.assign({}, this.props.selectedFormJSON);
for(let i in json.FORM_COLUMN) {
json.FORM_COLUMN[i].IncludeInBody = this.state[json.FORM_COLUMN[i].Name];
}
this.props.updateSelectedFormJSON(json);
this.props.closeModal();
render() {
return (
<div className='specify-grid-modal'>
<div className='fullmodal'>
<div className='fullmodal_title'>Specify Body</div>
<div title="Close Window" className="add-custom-field-close" onClick={() => this.props.closeModal()}><FontAwesome name='xbutton' className='fa-times preview-close' /></div>
</div>
<button className='tran-button specify-body-continue' onClick={() => {this.onSaveClick()}} >
Continue
</button>
<div className='specify-body-wrapper'>
{this.renderColumns()}
</div>
</div>
)
}
The error means that there are no matches for className.add-custom-field-close selector.
className is prop name and shouldn't be included into the selector:
wrapper.find('.add-custom-field-close').at(0).simulate('click')
The selector of to find the element looks wrong. Its className.add-custom-field-close but should be .add-custom-field-close
Thanks for the help
it('should call onSaveClick functions on button click', () => {
baseProps.closeModal.mockClear();
wrapper.setProps({
updateSelectedFormJSON :null
});
wrapper.find('.add-custom-field-close').at(0).simulate('click')
expect(baseProps.closeModal).toHaveBeenCalled();
});

Resources