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) {