import React from 'react'; import expect from 'expect'; import { render, act } from '@testing-library/react'; import { Location } from 'react-router-dom'; import { getRecordFromLocation } from './useCreateController'; import { CreateController } from './CreateController'; import { testDataProvider, useCreate } from '../../dataProvider'; import { useNotificationContext } from '../../notification'; import { CoreAdminContext } from '../../core'; import { Middleware, SaveContextProvider, useRegisterMutationMiddleware, } from '../saveContext'; import { DataProvider } from '../..'; describe('useCreateController', () => { describe('getRecordFromLocation', () => { const location: Location = { key: 'a_key', pathname: '/foo', search: undefined, state: undefined, hash: undefined, }; it('should return location state record when set', () => { expect( getRecordFromLocation({ ...location, state: { record: { foo: 'bar' } }, }) ).toEqual({ foo: 'bar' }); }); it('should return location search when set', () => { expect( getRecordFromLocation({ ...location, search: '?source={"foo":"baz","array":["1","2"]}', }) ).toEqual({ foo: 'baz', array: ['1', '2'] }); }); it('should return location state record when both state and search are set', () => { expect( getRecordFromLocation({ ...location, state: { record: { foo: 'bar' } }, search: '?foo=baz', }) ).toEqual({ foo: 'bar' }); }); }); const defaultProps = { hasCreate: true, hasEdit: true, hasList: true, hasShow: true, resource: 'posts', debounce: 200, }; it('should call the dataProvider.create() function on save', async () => { const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create: jest .fn() .mockImplementationOnce((_, { data }) => Promise.resolve({ data: { id: 123, ...data } }) ), }); let saveCallback; render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback({ foo: 'bar' })); expect(dataProvider.create).toHaveBeenCalledWith('posts', { data: { foo: 'bar' }, }); }); it('should execute default success side effects on success', async () => { let saveCallback; const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create: (_, { data }) => Promise.resolve({ data: { id: 123, ...data } }), }); let notificationsSpy; const Notification = () => { const { notifications } = useNotificationContext(); React.useEffect(() => { notificationsSpy = notifications; }, [notifications]); return null; }; render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback({ foo: 'bar' })); expect(notificationsSpy).toEqual([ { message: 'ra.notification.created', type: 'info', notificationOptions: { messageArgs: { smart_count: 1 } }, }, ]); }); it('should execute default failure side effects on failure', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}); let saveCallback; const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create: () => Promise.reject({ message: 'not good' }), }); let notificationsSpy; const Notification = () => { const { notifications } = useNotificationContext(); React.useEffect(() => { notificationsSpy = notifications; }, [notifications]); return null; }; render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback({ foo: 'bar' })); expect(notificationsSpy).toEqual([ { message: 'not good', type: 'error', notificationOptions: { messageArgs: { _: 'not good' } }, }, ]); }); it('should allow mutationOptions to override the default success side effects', async () => { let saveCallback; const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create: (_, { data }) => Promise.resolve({ data: { id: 123, ...data } }), }); const onSuccess = jest.fn(); let notificationsSpy; const Notification = () => { const { notifications } = useNotificationContext(); React.useEffect(() => { notificationsSpy = notifications; }, [notifications]); return null; }; render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback({ foo: 'bar' })); expect(onSuccess).toHaveBeenCalled(); expect(notificationsSpy).toEqual([]); }); it('should allow the save onSuccess option to override the success side effects override', async () => { let saveCallback; const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create: (_, { data }) => Promise.resolve({ data: { id: 123, ...data } }), }); const onSuccess = jest.fn(); const onSuccessSave = jest.fn(); let notificationsSpy; const Notification = () => { const { notifications } = useNotificationContext(); React.useEffect(() => { notificationsSpy = notifications; }, [notifications]); return null; }; render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback( { foo: 'bar' }, { onSuccess: onSuccessSave, } ) ); expect(onSuccess).not.toHaveBeenCalled(); expect(onSuccessSave).toHaveBeenCalled(); expect(notificationsSpy).toEqual([]); }); it('should allow mutationOptions to override the default failure side effects', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}); let saveCallback; const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create: () => Promise.reject({ message: 'not good' }), }); const onError = jest.fn(); let notificationsSpy; const Notification = () => { const { notifications } = useNotificationContext(); React.useEffect(() => { notificationsSpy = notifications; }, [notifications]); return null; }; render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback({ foo: 'bar' })); expect(onError).toHaveBeenCalled(); expect(notificationsSpy).toEqual([]); }); it('should accept meta in mutationOptions', async () => { let saveCallback; const create = jest .fn() .mockImplementationOnce((_, { data }) => Promise.resolve({ data: { id: 123, ...data } }) ); const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create, }); render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback({ foo: 'bar' })); expect(create).toHaveBeenCalledWith('posts', { data: { foo: 'bar' }, meta: { lorem: 'ipsum' }, }); }); it('should allow the save onError option to override the failure side effects override', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}); let saveCallback; const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create: () => Promise.reject({ message: 'not good' }), }); const onError = jest.fn(); const onErrorSave = jest.fn(); let notificationsSpy; const Notification = () => { const { notifications } = useNotificationContext(); React.useEffect(() => { notificationsSpy = notifications; }, [notifications]); return null; }; render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback( { foo: 'bar' }, { onError: onErrorSave, } ) ); expect(onError).not.toHaveBeenCalled(); expect(onErrorSave).toHaveBeenCalled(); expect(notificationsSpy).toEqual([]); }); it('should allow transform to transform the data before calling create', async () => { let saveCallback; const create = jest .fn() .mockImplementationOnce((_, { data }) => Promise.resolve({ data: { id: 123, ...data } }) ); const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create, }); const transform = jest.fn().mockImplementationOnce(data => ({ ...data, transformed: true, })); render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback({ foo: 'bar' })); expect(transform).toHaveBeenCalledWith({ foo: 'bar' }); expect(create).toHaveBeenCalledWith('posts', { data: { foo: 'bar', transformed: true }, }); }); it('should allow the save transform option to override the controller transform option', async () => { let saveCallback; const create = jest .fn() .mockImplementationOnce((_, { data }) => Promise.resolve({ data: { id: 123, ...data } }) ); const dataProvider = testDataProvider({ getOne: () => Promise.resolve({ data: { id: 12 } } as any), create, }); const transform = jest.fn(); const transformSave = jest.fn().mockImplementationOnce(data => ({ ...data, transformed: true, })); render( {({ save }) => { saveCallback = save; return null; }} ); await act(async () => saveCallback( { foo: 'bar' }, { transform: transformSave, } ) ); expect(transform).not.toHaveBeenCalled(); expect(transformSave).toHaveBeenCalledWith({ foo: 'bar' }); expect(create).toHaveBeenCalledWith('posts', { data: { foo: 'bar', transformed: true }, }); }); it('should allow to register middlewares', async () => { let saveCallback; const create = jest .fn() .mockImplementationOnce((_, { data }) => Promise.resolve({ data: { id: 123, ...data } }) ); const dataProvider = testDataProvider({ create, }); const middleware: Middleware[0]> = jest.fn( (resource, params, options, next) => { return next( resource, { ...params, meta: { addedByMiddleware: true } }, options ); } ); const Child = () => { useRegisterMutationMiddleware[0]>( middleware ); return null; }; render( {({ save, saving, registerMutationMiddleware, unregisterMutationMiddleware, }) => { saveCallback = save; return ( ); }} ); await act(async () => saveCallback({ foo: 'bar' })); expect(create).toHaveBeenCalledWith('posts', { data: { foo: 'bar' }, meta: { addedByMiddleware: true }, }); expect(middleware).toHaveBeenCalledWith( 'posts', { data: { foo: 'bar' }, }, expect.any(Object), expect.any(Function) ); }); it('should return errors from the create call', async () => { const create = jest.fn().mockImplementationOnce(() => { return Promise.reject({ body: { errors: { foo: 'invalid' } } }); }); const dataProvider = ({ create, } as unknown) as DataProvider; let saveCallback; render( {({ save, record }) => { saveCallback = save; return
; }} ); await new Promise(resolve => setTimeout(resolve, 10)); let errors; await act(async () => { errors = await saveCallback({ foo: 'bar' }); }); expect(errors).toEqual({ foo: 'invalid' }); expect(create).toHaveBeenCalledWith('posts', { data: { foo: 'bar' }, }); }); });