import * as React from 'react'; import expect from 'expect'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { useForm, useFormContext, FormProvider } from 'react-hook-form'; import { MemoryRouter, Route, Routes, useNavigate, useParams, } from 'react-router-dom'; import { useWarnWhenUnsavedChanges } from './useWarnWhenUnsavedChanges'; const Form = ({ onSubmit }) => { useWarnWhenUnsavedChanges(true, '/form'); const params = useParams<'part'>(); const navigate = useNavigate(); const form = useFormContext(); const onLeave = () => { navigate('/somewhere'); }; return (

Form {params.part}

); }; const FormUnderTest = () => { const navigate = useNavigate(); const form = useForm(); // Simulate mv-tmp save methods const save = () => new Promise(resolve => { setTimeout(() => navigate('/submitted'), 100); resolve(); }); const onSubmit = () => { save(); }; return (
); }; const App = ({ initialEntries = ['/form'] }) => ( } /> Show} /> } /> Submitted} /> Somewhere} /> ); describe('useWarnWhenUnsavedChanges', () => { let originalConsoleError; beforeAll(() => { originalConsoleError = console.error; console.error = jest.fn(message => { if (message.includes('Error: Not implemented: window.confirm')) { return; } originalConsoleError(message); }); }); afterAll(() => { console.error = originalConsoleError; }); it('should not warn when leaving form with no changes', async () => { window.confirm = jest.fn().mockReturnValue(true); render(); fireEvent.click(screen.getByText('Leave')); await waitFor(() => screen.getByText('Somewhere')); expect(window.confirm).not.toHaveBeenCalled(); }); test.each([ ['simple', 'First Name'], ['nested', 'Author'], ])( 'should not warn when leaving form with submit button after updating %s field', async (_, field) => { window.confirm = jest.fn().mockReturnValue(true); render(); fireEvent.change(screen.getByLabelText(field), { target: { value: 'John Doe' }, }); expect(screen.getByDisplayValue('John Doe')).not.toBeNull(); fireEvent.click(screen.getByText('Submit')); await waitFor(() => expect(window.confirm).not.toHaveBeenCalled()); await waitFor(() => screen.getByText('Submitted')); } ); test.each([ ['simple', 'First Name'], ['nested', 'Author'], ])( 'should not warn when navigating to a sub page of a form with submit button after updating %s field', async (_, field) => { window.confirm = jest.fn().mockReturnValue(true); render(); fireEvent.change(screen.getByLabelText(field), { target: { value: 'John Doe' }, }); expect(screen.getByDisplayValue('John Doe')).not.toBeNull(); fireEvent.click(screen.getByText('Form part 1')); await waitFor(() => screen.getByText('Form part1')); expect(window.confirm).not.toHaveBeenCalled(); } ); test.each([ ['simple', 'First Name'], ['nested', 'Author'], ])( 'should not warn when navigating from a sub page with submit button after updating %s field', async (_, field) => { window.confirm = jest.fn().mockReturnValue(true); render(); fireEvent.change(screen.getByLabelText(field), { target: { value: 'John Doe' }, }); expect(screen.getByDisplayValue('John Doe')).not.toBeNull(); fireEvent.click(screen.getByText('Form part 2')); await waitFor(() => screen.getByText('Form part2')); expect(window.confirm).not.toHaveBeenCalled(); } ); test.each([ ['simple', 'First Name'], ['nested', 'Author'], ])( 'should not warn when navigating from a sub page of a form to the root part with submit button after updating %s field', async (_, field) => { window.confirm = jest.fn().mockReturnValue(true); render(); fireEvent.change(screen.getByLabelText(field), { target: { value: 'John Doe' }, }); expect(screen.getByDisplayValue('John Doe')).not.toBeNull(); fireEvent.click(screen.getByText('Root form')); await waitFor(() => screen.getByText('Form')); expect(window.confirm).not.toHaveBeenCalled(); } ); test.each([ ['simple', 'First Name'], ['nested', 'Author'], ])( 'should warn when leaving form with unsaved changes after updating %s field', (_, field) => { // mock click on "cancel" in the confirm dialog window.confirm = jest.fn().mockReturnValue(false); render(); const input = screen.getByLabelText(field) as HTMLInputElement; fireEvent.change(input, { target: { value: 'John Doe' } }); fireEvent.blur(input); expect(screen.queryByDisplayValue('John Doe')).not.toBeNull(); fireEvent.click(screen.getByText('Leave')); expect(window.confirm).toHaveBeenCalledWith( 'ra.message.unsaved_changes' ); // check that we're still in the form and that the unsaved changes are here expect(screen.queryByDisplayValue('John Doe')).not.toBeNull(); expect(screen.queryByText('Somewhere')).toBeNull(); } ); test.each([ ['simple', 'First Name'], ['nested', 'Author'], ])( 'should warn when leaving form with unsaved changes but accept override', async (_, field) => { // mock click on "OK" in the confirm dialog window.confirm = jest.fn().mockReturnValue(true); render(); const input = screen.getByLabelText(field) as HTMLInputElement; fireEvent.change(input, { target: { value: 'John Doe' } }); fireEvent.click(screen.getByText('Leave')); expect(window.confirm).toHaveBeenCalledWith( 'ra.message.unsaved_changes' ); // check that we're no longer in the form await waitFor(() => { expect(screen.queryByText(field)).toBeNull(); }); screen.getByText('Somewhere'); } ); it('should warn when navigating from root to the show view with unsaved changes', () => { // mock click on "cancel" in the confirm dialog window.confirm = jest.fn().mockReturnValue(false); render(); const input = screen.getByLabelText('First Name') as HTMLInputElement; fireEvent.change(input, { target: { value: 'John Doe' } }); fireEvent.blur(input); expect(screen.queryByDisplayValue('John Doe')).not.toBeNull(); fireEvent.click(screen.getByText('Go to Show view')); expect(window.confirm).toHaveBeenCalledWith( 'ra.message.unsaved_changes' ); // check that we're still in the form and that the unsaved changes are here expect(screen.queryByDisplayValue('John Doe')).not.toBeNull(); expect(screen.queryByText('Show')).toBeNull(); }); it('should warn when navigating from a sub page to the show view with unsaved changes', () => { // mock click on "cancel" in the confirm dialog window.confirm = jest.fn().mockReturnValue(false); render(); const input = screen.getByLabelText('First Name') as HTMLInputElement; fireEvent.change(input, { target: { value: 'John Doe' } }); fireEvent.blur(input); expect(screen.queryByDisplayValue('John Doe')).not.toBeNull(); fireEvent.click(screen.getByText('Go to Show view')); expect(window.confirm).toHaveBeenCalledWith( 'ra.message.unsaved_changes' ); // check that we're still in the form and that the unsaved changes are here expect(screen.queryByDisplayValue('John Doe')).not.toBeNull(); expect(screen.queryByText('Show')).toBeNull(); }); afterAll(() => delete window.confirm); });