diff --git a/packages/ra-ui-materialui/src/input/FileInput.spec.tsx b/packages/ra-ui-materialui/src/input/FileInput.spec.tsx index 7a384e0fea8..8282038e1fb 100644 --- a/packages/ra-ui-materialui/src/input/FileInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/FileInput.spec.tsx @@ -6,12 +6,13 @@ import { waitFor, waitForElementToBeRemoved, } from '@testing-library/react'; -import { testDataProvider } from 'ra-core'; +import { required, testDataProvider } from 'ra-core'; import { AdminContext } from '../AdminContext'; import { SimpleForm, Toolbar } from '../form'; import { FileField, ImageField } from '../field'; import { FileInput } from './FileInput'; +import { TextInput } from './TextInput'; import { SaveButton } from '../button'; describe('', () => { @@ -469,6 +470,105 @@ describe('', () => { test(, 'Custom label in component'); }); + describe('Validation', () => { + it('should display a validation error if the value is required and there is no file', async () => { + const onSubmit = jest.fn(); + + render( + + + + + + + + + ); + + fireEvent.change( + await screen.findByLabelText('resources.posts.fields.title'), + { + target: { value: 'Hello world!' }, + } + ); + fireEvent.click(screen.getByLabelText('ra.action.save')); + + await screen.findByText('ra.validation.required'); + expect(onSubmit).not.toHaveBeenCalled(); + }); + + it('should display a validation error if the value is required and the file is removed', async () => { + const onSubmit = jest.fn(); + + render( + + + + + + + + ); + + expect(screen.getByTitle('cats')).not.toBeNull(); + fireEvent.click(screen.getAllByLabelText('ra.action.delete')[0]); + fireEvent.click(screen.getByLabelText('ra.action.save')); + + await screen.findByText('ra.validation.required'); + expect(onSubmit).not.toHaveBeenCalled(); + }); + + it('should display a validation error right away when form mode is onChange', async () => { + const onSubmit = jest.fn(); + + render( + + + + + + + + ); + + expect(screen.getByTitle('cats')).not.toBeNull(); + fireEvent.click(screen.getAllByLabelText('ra.action.delete')[0]); + + await screen.findByText('ra.validation.required'); + expect(onSubmit).not.toHaveBeenCalled(); + }); + }); + describe('Image Preview', () => { it('should display file preview using child as preview component', () => { render( diff --git a/packages/ra-ui-materialui/src/input/FileInput.tsx b/packages/ra-ui-materialui/src/input/FileInput.tsx index 16b74d4b932..100c2be0bac 100644 --- a/packages/ra-ui-materialui/src/input/FileInput.tsx +++ b/packages/ra-ui-materialui/src/input/FileInput.tsx @@ -83,7 +83,7 @@ export const FileInput = (props: FileInputProps) => { const { id, - field: { onChange, value }, + field: { onChange, onBlur, value }, fieldState, formState: { isSubmitted }, isRequired, @@ -102,8 +102,10 @@ export const FileInput = (props: FileInputProps) => { if (multiple) { onChange(updatedFiles); + onBlur(); } else { onChange(updatedFiles[0]); + onBlur(); } if (onDropProp) { @@ -124,8 +126,10 @@ export const FileInput = (props: FileInputProps) => { stateFile => !shallowEqual(stateFile, file) ); onChange(filteredFiles as any); + onBlur(); } else { onChange(null); + onBlur(); } if (onRemoveProp) {