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

Add onSuccess and onFailure properties to DeleteButton #5310

Merged
merged 2 commits into from
Oct 1, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
RedirectionSideEffect,
} from '../../sideEffect';
import { Record } from '../../types';
import { OnFailure, OnSuccess } from '../saveModifiers';

/**
* Prepare a set of callbacks for a delete button guarded by confirmation dialog
Expand Down Expand Up @@ -72,27 +73,37 @@ const useDeleteWithConfirmController = ({
redirect: redirectTo,
basePath,
onClick,
onSuccess,
onFailure,
}: UseDeleteWithConfirmControllerParams): UseDeleteWithConfirmControllerReturn => {
const [open, setOpen] = useState(false);
const notify = useNotify();
const redirect = useRedirect();
const refresh = useRefresh();
const [deleteOne, { loading }] = useDelete(resource, null, null, {
action: CRUD_DELETE,
onSuccess: () => {
onSuccess: response => {
setOpen(false);
notify('ra.notification.deleted', 'info', { smart_count: 1 });
redirect(redirectTo, basePath);
refresh();
if (onSuccess === undefined) {
notify('ra.notification.deleted', 'info', { smart_count: 1 });
redirect(redirectTo, basePath);
refresh();
} else {
onSuccess(response);
}
},
onFailure: error => {
setOpen(false);
notify(
typeof error === 'string'
? error
: error.message || 'ra.notification.http_error',
'warning'
);
if (onFailure === undefined) {
notify(
typeof error === 'string'
? error
: error.message || 'ra.notification.http_error',
'warning'
);
} else {
onFailure(error);
}
},
undoable: false,
});
Expand Down Expand Up @@ -128,6 +139,8 @@ export interface UseDeleteWithConfirmControllerParams {
redirect?: RedirectionSideEffect;
resource: string;
onClick?: ReactEventHandler<any>;
onSuccess?: OnSuccess;
onFailure?: OnFailure;
}

export interface UseDeleteWithConfirmControllerReturn {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
RedirectionSideEffect,
} from '../../sideEffect';
import { Record } from '../../types';
import { OnFailure, OnSuccess } from '../saveModifiers';

/**
* Prepare callback for a Delete button with undo support
Expand Down Expand Up @@ -52,25 +53,38 @@ const useDeleteWithUndoController = ({
basePath,
redirect: redirectTo = 'list',
onClick,
onSuccess,
onFailure,
}: UseDeleteWithUndoControllerParams): UseDeleteWithUndoControllerReturn => {
const notify = useNotify();
const redirect = useRedirect();
const refresh = useRefresh();

const [deleteOne, { loading }] = useDelete(resource, null, null, {
action: CRUD_DELETE,
onSuccess: () => {
notify('ra.notification.deleted', 'info', { smart_count: 1 }, true);
redirect(redirectTo, basePath);
refresh();
},
onFailure: error =>
notify(
typeof error === 'string'
? error
: error.message || 'ra.notification.http_error',
'warning'
),
onSuccess:
onSuccess !== undefined
? onSuccess
: () => {
notify(
'ra.notification.deleted',
'info',
{ smart_count: 1 },
true
);
redirect(redirectTo, basePath);
refresh();
},
onFailure:
onFailure !== undefined
? onFailure
: error =>
notify(
typeof error === 'string'
? error
: error.message || 'ra.notification.http_error',
'warning'
),
undoable: true,
});
const handleDelete = useCallback(
Expand All @@ -95,6 +109,8 @@ export interface UseDeleteWithUndoControllerParams {
redirect?: RedirectionSideEffect;
resource: string;
onClick?: ReactEventHandler<any>;
onSuccess?: OnSuccess;
onFailure?: OnFailure;
}

export interface UseDeleteWithUndoControllerReturn {
Expand Down
113 changes: 109 additions & 4 deletions packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { render, cleanup } from '@testing-library/react';
import { render, cleanup, wait, fireEvent } from '@testing-library/react';
import * as React from 'react';
import expect from 'expect';
import { TestContext } from 'ra-core';
import { ThemeProvider } from '@material-ui/core';
import { createMuiTheme } from '@material-ui/core/styles';
import {
DataProvider,
DataProviderContext,
renderWithRedux,
TestContext,
} from 'ra-core';
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
import DeleteWithConfirmButton from './DeleteWithConfirmButton';
import { Toolbar, SimpleForm } from '../form';
import { Edit } from '../detail';
import { TextInput } from '../input';

const theme = createMuiTheme();

Expand Down Expand Up @@ -59,4 +66,102 @@ describe('<DeleteWithConfirmButton />', () => {

spy.mockRestore();
});

const defaultEditProps = {
basePath: '',
id: '123',
resource: 'posts',
location: {},
match: {},
undoable: false,
};

it('should allow to override the onSuccess side effects', async () => {
const dataProvider = ({
getOne: () =>
Promise.resolve({
data: { id: 123, title: 'lorem' },
}),
delete: jest.fn().mockResolvedValueOnce({ data: { id: 123 } }),
} as unknown) as DataProvider;
const onSuccess = jest.fn();
const EditToolbar = props => (
<Toolbar {...props}>
<DeleteWithConfirmButton onSuccess={onSuccess} />
</Toolbar>
);
const {
queryByDisplayValue,
getByLabelText,
getByText,
} = renderWithRedux(
<ThemeProvider theme={theme}>
<DataProviderContext.Provider value={dataProvider}>
<Edit {...defaultEditProps}>
<SimpleForm toolbar={<EditToolbar />}>
<TextInput source="title" />
</SimpleForm>
</Edit>
</DataProviderContext.Provider>
</ThemeProvider>,
{ admin: { resources: { posts: { data: {} } } } }
);
// wait for the dataProvider.getOne() return
await wait(() => {
expect(queryByDisplayValue('lorem')).not.toBeNull();
});
fireEvent.click(getByLabelText('ra.action.delete'));
fireEvent.click(getByText('ra.action.confirm'));
await wait(() => {
expect(dataProvider.delete).toHaveBeenCalled();
expect(onSuccess).toHaveBeenCalledWith({
data: { id: 123 },
});
});
});

it('should allow to override the onFailure side effects', async () => {
jest.spyOn(console, 'error').mockImplementationOnce(() => {});
const dataProvider = ({
getOne: () =>
Promise.resolve({
data: { id: 123, title: 'lorem' },
}),
delete: jest.fn().mockRejectedValueOnce({ message: 'not good' }),
} as unknown) as DataProvider;
const onFailure = jest.fn();
const EditToolbar = props => (
<Toolbar {...props}>
<DeleteWithConfirmButton onFailure={onFailure} />
</Toolbar>
);
const {
queryByDisplayValue,
getByLabelText,
getByText,
} = renderWithRedux(
<ThemeProvider theme={theme}>
<DataProviderContext.Provider value={dataProvider}>
<Edit {...defaultEditProps}>
<SimpleForm toolbar={<EditToolbar />}>
<TextInput source="title" />
</SimpleForm>
</Edit>
</DataProviderContext.Provider>
</ThemeProvider>,
{ admin: { resources: { posts: { data: {} } } } }
);
// wait for the dataProvider.getOne() return
await wait(() => {
expect(queryByDisplayValue('lorem')).toBeDefined();
});
fireEvent.click(getByLabelText('ra.action.delete'));
fireEvent.click(getByText('ra.action.confirm'));
await wait(() => {
expect(dataProvider.delete).toHaveBeenCalled();
expect(onFailure).toHaveBeenCalledWith({
message: 'not good',
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
Record,
RedirectionSideEffect,
useDeleteWithConfirmController,
OnSuccess,
OnFailure,
} from 'ra-core';

import Confirm from '../layout/Confirm';
Expand All @@ -34,6 +36,8 @@ const DeleteWithConfirmButton: FC<DeleteWithConfirmButtonProps> = props => {
record,
resource,
redirect = 'list',
onSuccess,
onFailure,
...rest
} = props;
const translate = useTranslate();
Expand All @@ -50,6 +54,8 @@ const DeleteWithConfirmButton: FC<DeleteWithConfirmButtonProps> = props => {
redirect,
basePath,
onClick,
onSuccess,
onFailure,
});

return (
Expand Down Expand Up @@ -130,6 +136,8 @@ interface Props {
saving?: boolean;
submitOnEnter?: boolean;
undoable?: boolean;
onSuccess?: OnSuccess;
onFailure?: OnFailure;
}

type DeleteWithConfirmButtonProps = Props & ButtonProps;
Expand Down
60 changes: 56 additions & 4 deletions packages/ra-ui-materialui/src/button/DeleteWithUndoButton.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { render, cleanup } from '@testing-library/react';
import { render, cleanup, wait, fireEvent } from '@testing-library/react';
import * as React from 'react';
import expect from 'expect';
import { TestContext } from 'ra-core';
import { ThemeProvider } from '@material-ui/core';
import { createMuiTheme } from '@material-ui/core/styles';
import {
DataProvider,
DataProviderContext,
renderWithRedux,
TestContext,
} from 'ra-core';
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
import { Toolbar, SimpleForm } from '../form';
import { Edit } from '../detail';
import { TextInput } from '../input';
import DeleteWithUndoButton from './DeleteWithUndoButton';

const theme = createMuiTheme();
Expand Down Expand Up @@ -59,4 +66,49 @@ describe('<DeleteWithUndoButton />', () => {

spy.mockRestore();
});

const defaultEditProps = {
basePath: '',
id: '123',
resource: 'posts',
location: {},
match: {},
undoable: false,
};

it('should allow to override the onSuccess side effects', async () => {
const dataProvider = ({
getOne: () =>
Promise.resolve({
data: { id: 123, title: 'lorem' },
}),
delete: () => Promise.resolve({ data: { id: 123 } }),
} as unknown) as DataProvider;
const onSuccess = jest.fn();
const EditToolbar = props => (
<Toolbar {...props}>
<DeleteWithUndoButton onSuccess={onSuccess} />
</Toolbar>
);
const { queryByDisplayValue, getByLabelText } = renderWithRedux(
<ThemeProvider theme={theme}>
<DataProviderContext.Provider value={dataProvider}>
<Edit {...defaultEditProps}>
<SimpleForm toolbar={<EditToolbar />}>
<TextInput source="title" />
</SimpleForm>
</Edit>
</DataProviderContext.Provider>
</ThemeProvider>,
{ admin: { resources: { posts: { data: {} } } } }
);
// wait for the dataProvider.getOne() return
await wait(() => {
expect(queryByDisplayValue('lorem')).not.toBeNull();
});
fireEvent.click(getByLabelText('ra.action.delete'));
await wait(() => {
expect(onSuccess).toHaveBeenCalledWith({});
});
});
});
Loading