Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix useWarnWhenUsavedChanges and nested fields #6185

Merged
merged 1 commit into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/simple/src/comments/CommentEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const CommentEdit = props => {
record={record}
save={save}
version={version}
warnWhenUnsavedChanges
>
<TextInput disabled source="id" fullWidth />
<ReferenceInput
Expand Down
2 changes: 1 addition & 1 deletion examples/simple/src/tags/TagEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
const TagEdit = props => (
<>
<Edit {...props}>
<SimpleForm redirect="list">
<SimpleForm redirect="list" warnWhenUnsavedChanges>
<TextField source="id" />
<TranslatableInputs locales={['en', 'fr']}>
<TextInput source="name" validate={[required()]} />
Expand Down
99 changes: 62 additions & 37 deletions packages/ra-core/src/form/useWarnWhenUnsavedChanges.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ const FormBody = ({ handleSubmit }) => {
aria-labelledby="firstname-label"
component="input"
/>
<label id="author-label">Author</label>
<Field
name="author.name"
aria-labelledby="author-label"
component="input"
/>
<button type="button" onClick={onLeave}>
Leave
</button>
Expand Down Expand Up @@ -59,45 +65,64 @@ describe('useWarnWhenUnsavedChanges', () => {
getByText('Submitted');
});

it('should not warn when leaving form with submit button', () => {
const { getByLabelText, getByText } = render(<App />);
const input = getByLabelText('First Name') as HTMLInputElement;
input.value = 'John Doe';
fireEvent.click(getByText('Submit'));
getByText('Submitted');
});
test.each([
['simple', 'First Name'],
['nested', 'Author'],
])(
'should not warn when leaving form with submit button after updating %s field',
(_, field) => {
const { getByLabelText, getByText } = render(<App />);
fireEvent.change(getByLabelText(field), {
target: { value: 'John Doe' },
});
fireEvent.click(getByText('Submit'));
getByText('Submitted');
}
);

it('should warn when leaving form with unsaved changes', () => {
// mock click on "cancel" in the confirm dialog
window.confirm = jest.fn().mockReturnValue(false);
const { getByLabelText, getByText, queryByText } = render(<App />);
const input = getByLabelText('First Name') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'John Doe' } });
fireEvent.click(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((getByLabelText('First Name') as HTMLInputElement).value).toBe(
'John Doe'
);
expect(queryByText('Somewhere')).toBeNull();
});
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);
const { getByLabelText, getByText, queryByText } = render(<App />);
const input = getByLabelText(field) as HTMLInputElement;
fireEvent.change(input, { target: { value: 'John Doe' } });
fireEvent.click(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(
(getByLabelText('First Name') as HTMLInputElement).value
).toBe('John Doe');
expect(queryByText('Somewhere')).toBeNull();
}
);

it('should warn when leaving form with unsaved changes but accept override', () => {
// mock click on "OK" in the confirm dialog
window.confirm = jest.fn().mockReturnValue(true);
const { getByLabelText, getByText, queryByText } = render(<App />);
const input = getByLabelText('First Name') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'John Doe' } });
fireEvent.click(getByText('Leave'));
expect(window.confirm).toHaveBeenCalledWith(
'ra.message.unsaved_changes'
);
// check that we're no longer in the form
expect(queryByText('First Name')).toBeNull();
getByText('Somewhere');
});
test.each([
['simple', 'First Name'],
['nested', 'Author'],
])(
'should warn when leaving form with unsaved changes but accept override',
(_, field) => {
// mock click on "OK" in the confirm dialog
window.confirm = jest.fn().mockReturnValue(true);
const { getByLabelText, getByText, queryByText } = render(<App />);
const input = getByLabelText(field) as HTMLInputElement;
fireEvent.change(input, { target: { value: 'John Doe' } });
fireEvent.click(getByText('Leave'));
expect(window.confirm).toHaveBeenCalledWith(
'ra.message.unsaved_changes'
);
// check that we're no longer in the form
expect(queryByText(field)).toBeNull();
getByText('Somewhere');
}
);

afterAll(() => delete window.confirm);
});
4 changes: 3 additions & 1 deletion packages/ra-core/src/form/useWarnWhenUnsavedChanges.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useRef } from 'react';
import { useForm } from 'react-final-form';
import { useHistory } from 'react-router-dom';
import get from 'lodash/get';

import { useTranslate } from '../i18n';

Expand Down Expand Up @@ -39,6 +40,7 @@ const useWarnWhenUnsavedChanges = (enable: boolean) => {
const unsavedChanges = JSON.parse(
window.sessionStorage.getItem('unsavedChanges')
);

if (unsavedChanges) {
Object.keys(unsavedChanges).forEach(key =>
form.change(key, unsavedChanges[key])
Expand All @@ -61,7 +63,7 @@ const useWarnWhenUnsavedChanges = (enable: boolean) => {
: formState.dirtyFields;
const dirtyFieldValues = Object.keys(dirtyFields).reduce(
(acc, key) => {
acc[key] = formState.values[key];
acc[key] = get(formState.values, key);
return acc;
},
{}
Expand Down