diff --git a/tests/uploader.spec.tsx b/tests/uploader.spec.tsx
index 44a4771..bb7b8fa 100644
--- a/tests/uploader.spec.tsx
+++ b/tests/uploader.spec.tsx
@@ -307,7 +307,32 @@ describe('uploader', () => {
done();
}, 100);
});
+
+ it('drag unaccepted type files with multiple false to upload will not trigger onStart ', done => {
+ const { container } = render();
+
+ const input = container.querySelector('input')!;
+ const files = [
+ {
+ name: 'success.jpg',
+ toString() {
+ return this.name;
+ },
+ },
+ ];
+ (files as any).item = (i: number) => files[i];
+ const mockStart = jest.fn();
+ handlers.onStart = mockStart;
+ fireEvent.drop(input, {
+ dataTransfer: { files },
+ });
+ setTimeout(() => {
+ expect(mockStart.mock.calls.length).toBe(0);
+ done();
+ }, 100);
+ });
+
it('drag files with multiple false', done => {
const { container } = render();
const input = container.querySelector('input')!;
@@ -1255,3 +1280,770 @@ describe('uploader', () => {
expect(container.querySelector('span')!).not.toHaveAttribute('role', 'button');
});
});
+
+ describe('comprehensive edge cases and accessibility', () => {
+ describe('keyboard accessibility', () => {
+ it('should trigger file selection on Enter key', () => {
+ const { container } = render();
+ const uploadWrapper = container.querySelector('span')!;
+ const input = container.querySelector('input')!;
+
+ const clickSpy = jest.spyOn(input, 'click').mockImplementation(() => {});
+
+ fireEvent.keyDown(uploadWrapper, { key: 'Enter', code: 'Enter' });
+
+ expect(clickSpy).toHaveBeenCalled();
+ clickSpy.mockRestore();
+ });
+
+ it('should trigger file selection on Space key', () => {
+ const { container } = render();
+ const uploadWrapper = container.querySelector('span')!;
+ const input = container.querySelector('input')!;
+
+ const clickSpy = jest.spyOn(input, 'click').mockImplementation(() => {});
+
+ fireEvent.keyDown(uploadWrapper, { key: ' ', code: 'Space' });
+
+ expect(clickSpy).toHaveBeenCalled();
+ clickSpy.mockRestore();
+ });
+
+ it('should not trigger file selection on other keys', () => {
+ const { container } = render();
+ const uploadWrapper = container.querySelector('span')!;
+ const input = container.querySelector('input')!;
+
+ const clickSpy = jest.spyOn(input, 'click').mockImplementation(() => {});
+
+ fireEvent.keyDown(uploadWrapper, { key: 'Tab', code: 'Tab' });
+ fireEvent.keyDown(uploadWrapper, { key: 'Escape', code: 'Escape' });
+
+ expect(clickSpy).not.toHaveBeenCalled();
+ clickSpy.mockRestore();
+ });
+
+ it('should have proper ARIA attributes for screen readers', () => {
+ const { container } = render();
+ const uploadWrapper = container.querySelector('span')!;
+
+ expect(uploadWrapper).toHaveAttribute('role', 'button');
+ expect(uploadWrapper).toHaveAttribute('tabIndex', '0');
+ });
+ });
+
+ describe('file validation edge cases', () => {
+ it('should handle files with unicode characters in names', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const unicodeFile = new File(['content'], '测试文件.txt', { type: 'text/plain' });
+
+ fireEvent.change(input, { target: { files: [unicodeFile] } });
+
+ expect(onStart).toHaveBeenCalledWith(expect.objectContaining({
+ name: '测试文件.txt'
+ }));
+ });
+
+ it('should handle files with special characters in names', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const specialFile = new File(['content'], 'file@#$%^&*()!.txt', { type: 'text/plain' });
+
+ fireEvent.change(input, { target: { files: [specialFile] } });
+
+ expect(onStart).toHaveBeenCalledWith(expect.objectContaining({
+ name: 'file@#$%^&*()!.txt'
+ }));
+ });
+
+ it('should handle files with very long names', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const longName = 'a'.repeat(255) + '.txt';
+ const longNameFile = new File(['content'], longName, { type: 'text/plain' });
+
+ fireEvent.change(input, { target: { files: [longNameFile] } });
+
+ expect(onStart).toHaveBeenCalledWith(expect.objectContaining({
+ name: longName
+ }));
+ });
+
+ it('should handle files without extensions', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const noExtFile = new File(['content'], 'README', { type: 'text/plain' });
+
+ fireEvent.change(input, { target: { files: [noExtFile] } });
+
+ expect(onStart).toHaveBeenCalledWith(expect.objectContaining({
+ name: 'README'
+ }));
+ });
+
+ it('should handle zero-byte files', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const emptyFile = new File([''], 'empty.txt', { type: 'text/plain' });
+
+ fireEvent.change(input, { target: { files: [emptyFile] } });
+
+ expect(onStart).toHaveBeenCalledWith(expect.objectContaining({
+ name: 'empty.txt',
+ size: 0
+ }));
+ });
+
+ it('should handle files with no type specified', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const noTypeFile = new File(['content'], 'unknown-type', {});
+
+ fireEvent.change(input, { target: { files: [noTypeFile] } });
+
+ expect(onStart).toHaveBeenCalledWith(expect.objectContaining({
+ name: 'unknown-type',
+ type: ''
+ }));
+ });
+ });
+
+ describe('concurrent upload scenarios', () => {
+ it('should handle multiple rapid file selections', async () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const files1 = [new File(['1'], 'file1.txt', { type: 'text/plain' })];
+ const files2 = [new File(['2'], 'file2.txt', { type: 'text/plain' })];
+
+ fireEvent.change(input, { target: { files: files1 } });
+ fireEvent.change(input, { target: { files: files2 } });
+
+ expect(onStart).toHaveBeenCalledTimes(2);
+ });
+
+ it('should handle mixed drag and click uploads', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const clickFile = new File(['click'], 'click.txt', { type: 'text/plain' });
+ const dragFile = new File(['drag'], 'drag.txt', { type: 'text/plain' });
+
+ // Simulate click upload
+ fireEvent.change(input, { target: { files: [clickFile] } });
+
+ // Simulate drag upload
+ fireEvent.drop(input, { dataTransfer: { files: [dragFile] } });
+
+ expect(onStart).toHaveBeenCalledTimes(2);
+ });
+ });
+
+ describe('network error scenarios', () => {
+ it('should handle network timeout', done => {
+ const onError = jest.fn((err, result, file) => {
+ try {
+ expect(err).toBeInstanceOf(Error);
+ done();
+ } catch (error) {
+ done(error);
+ }
+ });
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+
+ fireEvent.change(input, { target: { files: [file] } });
+
+ setTimeout(() => {
+ const request = requests[requests.length - 1];
+ if (request.ontimeout) {
+ request.ontimeout();
+ } else {
+ // Fallback for timeout simulation
+ const timeoutError = new Error('Request timeout');
+ timeoutError.name = 'TimeoutError';
+ request.onerror(timeoutError);
+ }
+ }, 100);
+ });
+
+ it('should handle request abort', done => {
+ const onError = jest.fn((err, result, file) => {
+ try {
+ expect(err).toBeInstanceOf(Error);
+ done();
+ } catch (error) {
+ done(error);
+ }
+ });
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+
+ fireEvent.change(input, { target: { files: [file] } });
+
+ setTimeout(() => {
+ const request = requests[requests.length - 1];
+ if (request.onabort) {
+ request.onabort();
+ } else {
+ // Fallback for abort simulation
+ const abortError = new Error('Request aborted');
+ abortError.name = 'AbortError';
+ request.onerror(abortError);
+ }
+ }, 100);
+ });
+
+ it('should handle server unavailable (503)', done => {
+ const onError = jest.fn((err, result, file) => {
+ try {
+ expect(err.status).toBe(503);
+ expect(result).toBe('Service Unavailable');
+ done();
+ } catch (error) {
+ done(error);
+ }
+ });
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+
+ fireEvent.change(input, { target: { files: [file] } });
+
+ setTimeout(() => {
+ requests[requests.length - 1].respond(503, {}, 'Service Unavailable');
+ }, 100);
+ });
+
+ it('should handle malformed response JSON', done => {
+ const onError = jest.fn((err, result, file) => {
+ try {
+ expect(err).toBeInstanceOf(Error);
+ done();
+ } catch (error) {
+ done(error);
+ }
+ });
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+
+ fireEvent.change(input, { target: { files: [file] } });
+
+ setTimeout(() => {
+ requests[requests.length - 1].respond(200, {}, 'invalid json {');
+ }, 100);
+ });
+ });
+
+ describe('beforeUpload edge cases', () => {
+ it('should handle beforeUpload returning undefined', () => {
+ const onStart = jest.fn();
+ const beforeUpload = jest.fn(() => undefined);
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ expect(beforeUpload).toHaveBeenCalled();
+ });
+
+ it('should handle beforeUpload returning a Promise that rejects', async () => {
+ const onStart = jest.fn();
+ const onError = jest.fn();
+ const beforeUpload = jest.fn(() => Promise.reject(new Error('Validation failed')));
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ await sleep(100);
+
+ expect(beforeUpload).toHaveBeenCalled();
+ expect(onStart).not.toHaveBeenCalled();
+ });
+
+ it('should handle beforeUpload returning a modified File object', () => {
+ const onStart = jest.fn();
+ const modifiedFile = new File(['modified'], 'modified.txt', { type: 'text/plain' });
+ const beforeUpload = jest.fn(() => modifiedFile);
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const originalFile = new File(['original'], 'original.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [originalFile] } });
+
+ expect(beforeUpload).toHaveBeenCalledWith(originalFile, [originalFile]);
+ });
+
+ it('should handle beforeUpload returning Blob instead of File', () => {
+ const onStart = jest.fn();
+ const blob = new Blob(['blob content'], { type: 'text/plain' });
+ const beforeUpload = jest.fn(() => blob);
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['original'], 'original.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ expect(beforeUpload).toHaveBeenCalled();
+ });
+ });
+
+ describe('progress tracking edge cases', () => {
+ it('should handle progress events with zero total', () => {
+ const onProgress = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ const request = requests[requests.length - 1];
+ if (request.upload && request.upload.onprogress) {
+ request.upload.onprogress({ loaded: 500, total: 0 });
+
+ expect(onProgress).toHaveBeenCalledWith(
+ expect.objectContaining({ percent: 0 }),
+ expect.any(Object)
+ );
+ }
+ });
+
+ it('should handle progress events with loaded > total', () => {
+ const onProgress = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ const request = requests[requests.length - 1];
+ if (request.upload && request.upload.onprogress) {
+ request.upload.onprogress({ loaded: 1500, total: 1000 });
+
+ expect(onProgress).toHaveBeenCalledWith(
+ expect.objectContaining({ percent: 100 }),
+ expect.any(Object)
+ );
+ }
+ });
+
+ it('should handle progress events with negative values', () => {
+ const onProgress = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ const request = requests[requests.length - 1];
+ if (request.upload && request.upload.onprogress) {
+ request.upload.onprogress({ loaded: -100, total: 1000 });
+
+ expect(onProgress).toHaveBeenCalledWith(
+ expect.objectContaining({ percent: 0 }),
+ expect.any(Object)
+ );
+ }
+ });
+ });
+
+ describe('custom request scenarios', () => {
+ it('should handle customRequest with async success', async () => {
+ const onSuccess = jest.fn();
+ const customRequest = jest.fn(async (options) => {
+ await sleep(100);
+ options.onSuccess({ success: true }, options.file);
+ });
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ await sleep(200);
+
+ expect(customRequest).toHaveBeenCalled();
+ expect(onSuccess).toHaveBeenCalledWith({ success: true }, expect.any(Object), null);
+ });
+
+ it('should handle customRequest with async error', async () => {
+ const onError = jest.fn();
+ const customRequest = jest.fn(async (options) => {
+ await sleep(100);
+ options.onError(new Error('Custom request failed'));
+ });
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ await sleep(200);
+
+ expect(customRequest).toHaveBeenCalled();
+ expect(onError).toHaveBeenCalledWith(expect.any(Error), undefined, expect.any(Object));
+ });
+
+ it('should handle customRequest with progress updates', async () => {
+ const onProgress = jest.fn();
+ const customRequest = jest.fn(async (options) => {
+ // Simulate progress updates
+ options.onProgress({ percent: 25 });
+ await sleep(50);
+ options.onProgress({ percent: 50 });
+ await sleep(50);
+ options.onProgress({ percent: 100 });
+ options.onSuccess({ success: true }, options.file);
+ });
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ await sleep(200);
+
+ expect(onProgress).toHaveBeenCalledTimes(3);
+ expect(onProgress).toHaveBeenCalledWith({ percent: 25 }, expect.any(Object));
+ expect(onProgress).toHaveBeenCalledWith({ percent: 50 }, expect.any(Object));
+ expect(onProgress).toHaveBeenCalledWith({ percent: 100 }, expect.any(Object));
+ });
+ });
+
+ describe('drag and drop edge cases', () => {
+ it('should handle dragEnter and dragLeave events gracefully', () => {
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ expect(() => {
+ fireEvent.dragEnter(input);
+ fireEvent.dragLeave(input);
+ fireEvent.dragOver(input);
+ }).not.toThrow();
+ });
+
+ it('should handle drop event with no files', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ fireEvent.drop(input, { dataTransfer: { files: [] } });
+
+ expect(onStart).not.toHaveBeenCalled();
+ });
+
+ it('should handle drop event with malformed dataTransfer', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ expect(() => {
+ fireEvent.drop(input, { dataTransfer: null });
+ }).not.toThrow();
+
+ expect(() => {
+ fireEvent.drop(input, {});
+ }).not.toThrow();
+
+ expect(onStart).not.toHaveBeenCalled();
+ });
+
+ it('should handle drop with mixed file and non-file items', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+
+ fireEvent.drop(input, {
+ dataTransfer: {
+ files: [file],
+ items: [
+ { kind: 'string', type: 'text/plain' },
+ { kind: 'file', getAsFile: () => file }
+ ]
+ }
+ });
+
+ expect(onStart).toHaveBeenCalled();
+ });
+ });
+
+ describe('accept prop comprehensive testing', () => {
+ it('should handle case-insensitive file extensions', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const files = [
+ new File(['content'], 'test.JPG', { type: 'image/jpeg' }),
+ new File(['content'], 'test.Jpg', { type: 'image/jpeg' }),
+ new File(['content'], 'test.jPg', { type: 'image/jpeg' })
+ ];
+
+ files.forEach(file => {
+ fireEvent.change(input, { target: { files: [file] } });
+ });
+
+ expect(onStart).toHaveBeenCalledTimes(3);
+ });
+
+ it('should handle MIME types with parameters', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', {
+ type: 'text/plain; charset=utf-8'
+ });
+
+ fireEvent.change(input, { target: { files: [file] } });
+
+ expect(onStart).toHaveBeenCalled();
+ });
+
+ it('should handle mixed extension and MIME type accepts', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const txtFile = new File(['content'], 'test.txt', { type: 'text/plain' });
+ const jpgFile = new File(['content'], 'test.jpg', { type: 'image/jpeg' });
+ const pdfFile = new File(['content'], 'test.pdf', { type: 'application/pdf' });
+
+ fireEvent.change(input, { target: { files: [txtFile] } });
+ fireEvent.change(input, { target: { files: [jpgFile] } });
+ fireEvent.change(input, { target: { files: [pdfFile] } });
+
+ // txt and jpg should be accepted, pdf should not trigger onStart
+ expect(onStart).toHaveBeenCalledTimes(2);
+ });
+
+ it('should handle whitespace in accept prop', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const txtFile = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [txtFile] } });
+
+ expect(onStart).toHaveBeenCalled();
+ });
+ });
+
+ describe('component lifecycle and props', () => {
+ it('should handle props changes during upload', done => {
+ const TestComponent = ({ action }: { action: string }) => (
+ done()} />
+ );
+
+ const { container, rerender } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ // Change props during upload
+ rerender();
+
+ setTimeout(() => {
+ requests[requests.length - 1].respond(200, {}, '{"success": true}');
+ }, 100);
+ });
+
+ it('should handle disabled state changes', () => {
+ const TestComponent = ({ disabled }: { disabled: boolean }) => (
+
+ );
+
+ const { container, rerender } = render();
+ const input = container.querySelector('input')!;
+
+ expect(input.disabled).toBe(false);
+
+ rerender();
+
+ expect(input.disabled).toBe(true);
+ });
+
+ it('should handle accept prop changes', () => {
+ const TestComponent = ({ accept }: { accept: string }) => (
+
+ );
+
+ const { container, rerender } = render();
+ const input = container.querySelector('input')!;
+
+ expect(input.accept).toBe('.jpg');
+
+ rerender();
+
+ expect(input.accept).toBe('.png');
+ });
+ });
+
+ describe('performance and memory considerations', () => {
+ it('should handle large number of files efficiently', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ // Create 50 files to test performance
+ const files = Array.from({ length: 50 }, (_, i) =>
+ new File([`content-${i}`], `file-${i}.txt`, { type: 'text/plain' })
+ );
+
+ Object.defineProperty(files, 'item', {
+ value: i => files[i],
+ });
+
+ const startTime = performance.now();
+ fireEvent.change(input, { target: { files } });
+ const endTime = performance.now();
+
+ expect(onStart).toHaveBeenCalledTimes(50);
+ expect(endTime - startTime).toBeLessThan(1000); // Should complete within 1 second
+ });
+
+ it('should handle rapid consecutive uploads without memory leaks', () => {
+ const onStart = jest.fn();
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ // Simulate rapid file changes
+ for (let i = 0; i < 20; i++) {
+ const file = new File([`content-${i}`], `file-${i}.txt`, { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+ }
+
+ expect(onStart).toHaveBeenCalledTimes(20);
+ });
+
+ it('should cleanup properly on unmount', () => {
+ const onStart = jest.fn();
+ const { container, unmount } = render();
+
+ // Start an upload
+ const input = container.querySelector('input')!;
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ // Unmount before completion
+ expect(() => unmount()).not.toThrow();
+ });
+ });
+
+ describe('error boundary scenarios', () => {
+ it('should handle callback errors gracefully', () => {
+ const originalError = console.error;
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ const onStart = jest.fn(() => {
+ throw new Error('onStart callback failed');
+ });
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+
+ expect(() => {
+ fireEvent.change(input, { target: { files: [file] } });
+ }).not.toThrow();
+
+ expect(onStart).toHaveBeenCalled();
+ consoleErrorSpy.mockRestore();
+ });
+ });
+
+ describe('transformation and data handling', () => {
+ it('should handle transformFile returning null', done => {
+ const onSuccess = jest.fn((ret, file) => {
+ expect(ret[1]).toEqual('success.png');
+ expect(file).toHaveProperty('uid');
+ done();
+ });
+
+ const transformFile = jest.fn(() => null);
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'success.png', { type: 'image/png' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ setTimeout(() => {
+ requests[requests.length - 1].respond(200, {}, '["","success.png"]');
+ }, 100);
+ });
+
+ it('should handle data as a function returning Promise that rejects', async () => {
+ const onError = jest.fn();
+ const data = jest.fn(() => Promise.reject(new Error('Data fetch failed')));
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ await sleep(100);
+
+ expect(data).toHaveBeenCalled();
+ });
+
+ it('should handle action as a function returning Promise that rejects', async () => {
+ const onError = jest.fn();
+ const action = jest.fn(() => Promise.reject(new Error('Action fetch failed')));
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
+ fireEvent.change(input, { target: { files: [file] } });
+
+ await sleep(100);
+
+ expect(action).toHaveBeenCalled();
+ });
+ });
+ });