Skip to content

Commit

Permalink
Merge pull request #3605 from marmelab/backward-compatible-query-muta…
Browse files Browse the repository at this point in the history
…tion-withdataprovider

[RFR] Ensure Query, Mutation and withDataProvider are backward compatible
  • Loading branch information
fzaninotto committed Aug 28, 2019
2 parents 9a6f554 + 22bc4aa commit b1ec657
Show file tree
Hide file tree
Showing 9 changed files with 613 additions and 12 deletions.
246 changes: 245 additions & 1 deletion packages/ra-core/src/dataProvider/Mutation.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import React from 'react';
import { cleanup } from '@testing-library/react';
import {
cleanup,
fireEvent,
waitForDomChange,
act,
render,
} from '@testing-library/react';
import expect from 'expect';
import { push } from 'connected-react-router';

import Mutation from './Mutation';
import renderWithRedux from '../util/renderWithRedux';
import { showNotification, refreshView, setListSelectedIds } from '../actions';
import DataProviderContext from './DataProviderContext';
import TestContext from '../util/TestContext';
import { useNotify } from '../sideEffect';

describe('Mutation', () => {
afterEach(cleanup);
Expand Down Expand Up @@ -37,4 +49,236 @@ describe('Mutation', () => {
loading: false,
});
});

it('supports declarative onSuccess side effects', async () => {
let dispatchSpy;
const dataProvider = jest.fn();
dataProvider.mockImplementationOnce(() =>
Promise.resolve({ data: { foo: 'bar' } })
);

let getByTestId;
act(() => {
const res = render(
<DataProviderContext.Provider value={dataProvider}>
<TestContext>
{({ store }) => {
dispatchSpy = jest.spyOn(store, 'dispatch');
return (
<Mutation
type="mytype"
resource="foo"
options={{
onSuccess: {
notification: {
body: 'Youhou!',
level: 'info',
},
redirectTo: '/a_path',
refresh: true,
unselectAll: true,
},
}}
>
{(mutate, { data }) => (
<button
data-testid="test"
onClick={mutate}
>
{data ? data.foo : 'no data'}
</button>
)}
</Mutation>
);
}}
</TestContext>
</DataProviderContext.Provider>
);
getByTestId = res.getByTestId;
});

const testElement = getByTestId('test');
fireEvent.click(testElement);
await waitForDomChange({ container: testElement });

expect(dispatchSpy).toHaveBeenCalledWith(
showNotification('Youhou!', 'info', {
messageArgs: {},
undoable: false,
})
);
expect(dispatchSpy).toHaveBeenCalledWith(push('/a_path'));
expect(dispatchSpy).toHaveBeenCalledWith(refreshView());
expect(dispatchSpy).toHaveBeenCalledWith(setListSelectedIds('foo', []));
});

it('supports onSuccess side effects using hooks', async () => {
let dispatchSpy;
const dataProvider = jest.fn();
dataProvider.mockImplementationOnce(() =>
Promise.resolve({ data: { foo: 'bar' } })
);

const Foo = () => {
const notify = useNotify();
return (
<Mutation
type="mytype"
resource="foo"
options={{
onSuccess: () => {
notify('Youhou!', 'info');
},
}}
>
{(mutate, { data }) => (
<button data-testid="test" onClick={mutate}>
{data ? data.foo : 'no data'}
</button>
)}
</Mutation>
);
};
let getByTestId;
act(() => {
const res = render(
<DataProviderContext.Provider value={dataProvider}>
<TestContext>
{({ store }) => {
dispatchSpy = jest.spyOn(store, 'dispatch');
return <Foo />;
}}
</TestContext>
</DataProviderContext.Provider>
);
getByTestId = res.getByTestId;
});

const testElement = getByTestId('test');
fireEvent.click(testElement);
await waitForDomChange({ container: testElement });

expect(dispatchSpy).toHaveBeenCalledWith(
showNotification('Youhou!', 'info', {
messageArgs: {},
undoable: false,
})
);
});

it('supports declarative onFailure side effects', async () => {
let dispatchSpy;
const dataProvider = jest.fn();
dataProvider.mockImplementationOnce(() =>
Promise.reject({ message: 'provider error' })
);

let getByTestId;
act(() => {
const res = render(
<DataProviderContext.Provider value={dataProvider}>
<TestContext>
{({ store }) => {
dispatchSpy = jest.spyOn(store, 'dispatch');
return (
<Mutation
type="mytype"
resource="foo"
options={{
onFailure: {
notification: {
body: 'Damn!',
level: 'warning',
},
redirectTo: '/a_path',
refresh: true,
unselectAll: true,
},
}}
>
{(mutate, { error }) => (
<button
data-testid="test"
onClick={mutate}
>
{error ? error.message : 'no data'}
</button>
)}
</Mutation>
);
}}
</TestContext>
</DataProviderContext.Provider>
);
getByTestId = res.getByTestId;
});

const testElement = getByTestId('test');
fireEvent.click(testElement);
await waitForDomChange({ container: testElement });

expect(dispatchSpy).toHaveBeenCalledWith(
showNotification('Damn!', 'warning', {
messageArgs: {},
undoable: false,
})
);
expect(dispatchSpy).toHaveBeenCalledWith(push('/a_path'));
expect(dispatchSpy).toHaveBeenCalledWith(refreshView());
expect(dispatchSpy).toHaveBeenCalledWith(setListSelectedIds('foo', []));
});

it('supports onFailure side effects using hooks', async () => {
let dispatchSpy;
const dataProvider = jest.fn();
dataProvider.mockImplementationOnce(() =>
Promise.reject({ message: 'provider error' })
);

const Foo = () => {
const notify = useNotify();
return (
<Mutation
type="mytype"
resource="foo"
options={{
onFailure: () => {
notify('Damn!', 'warning');
},
}}
>
{(mutate, { error }) => (
<button data-testid="test" onClick={mutate}>
{error ? error.message : 'no data'}
</button>
)}
</Mutation>
);
};
let getByTestId;
act(() => {
const res = render(
<DataProviderContext.Provider value={dataProvider}>
<TestContext>
{({ store }) => {
dispatchSpy = jest.spyOn(store, 'dispatch');
return <Foo />;
}}
</TestContext>
</DataProviderContext.Provider>
);
getByTestId = res.getByTestId;
});

const testElement = getByTestId('test');
fireEvent.click(testElement);
await waitForDomChange({ container: testElement });

expect(dispatchSpy).toHaveBeenCalledWith(
showNotification('Damn!', 'warning', {
messageArgs: {},
undoable: false,
})
);
});
});
8 changes: 7 additions & 1 deletion packages/ra-core/src/dataProvider/Mutation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ const Mutation: FunctionComponent<Props> = ({
resource,
payload,
options,
}) => children(...useMutation({ type, resource, payload }, options));
}) =>
children(
...useMutation(
{ type, resource, payload },
{ ...options, withDeclarativeSideEffectsSupport: true }
)
);

export default Mutation;
Loading

0 comments on commit b1ec657

Please sign in to comment.