From f9160ad9c2e1ca56e41cbc0d8519f631f67950a7 Mon Sep 17 00:00:00 2001 From: arksia <354959692@qq.com> Date: Tue, 2 Dec 2025 01:07:13 +0800 Subject: [PATCH 1/4] fix: uid conflict when multiple upload components paste at same time --- src/AjaxUploader.tsx | 8 ++- tests/uploader.spec.tsx | 122 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 5 deletions(-) diff --git a/src/AjaxUploader.tsx b/src/AjaxUploader.tsx index 9477b99..66b527f 100644 --- a/src/AjaxUploader.tsx +++ b/src/AjaxUploader.tsx @@ -95,7 +95,12 @@ class AjaxUploader extends Component { const { multiple, directory } = this.props; const items: DataTransferItem[] = [...(dataTransfer.items || [])]; - let files: File[] = [...(dataTransfer.files || [])]; + let files: File[] = [...(dataTransfer.files || [])].map((file: File) => { + return new File([file], file.name, { + type: file.type, + lastModified: file.lastModified, + }); + }); if (files.length > 0 || items.some(item => item.kind === 'file')) { existFileCallback?.(); @@ -296,7 +301,6 @@ class AjaxUploader extends Component { delete this.reqs[uid]; }, }; - onStart(origin); this.reqs[uid] = request(requestOption, { defaultRequest }); } diff --git a/tests/uploader.spec.tsx b/tests/uploader.spec.tsx index 5b3040c..54cc688 100644 --- a/tests/uploader.spec.tsx +++ b/tests/uploader.spec.tsx @@ -318,7 +318,7 @@ describe('uploader', () => { Object.defineProperty(files, 'item', { value: i => files[i], }); - + // Only can trigger once let triggerTimes = 0; handlers.onStart = () => { @@ -345,7 +345,7 @@ describe('uploader', () => { fireEvent.drop(input, { dataTransfer: { files } }); setTimeout(() => { - handlers.onSuccess!(['', files[0].name] as any, files[0] as any, null!); + requests[0].respond(200, {}, `["","${files[0].name}"]`); }, 100); }); @@ -432,7 +432,7 @@ describe('uploader', () => { fireEvent.paste(input, { clipboardData: { files } }); await sleep(100); - handlers.onSuccess!(['', files[0].name] as any, files[0] as any, null!); + requests[0].respond(200, {}, `["","${files[0].name}"]`); }); it('support action and data is function returns Promise', async () => { @@ -524,6 +524,122 @@ describe('uploader', () => { expect(preventDefaultSpy).toHaveBeenCalledTimes(0); preventDefaultSpy.mockRestore(); }); + + it('should prevent uid overwritten when multiple upload components paste simultaneously', async () => { + const handlers1: UploadProps = {}; + const handlers2: UploadProps = {}; + + const props1: UploadProps = { + ...props, + beforeUpload(file, fileList) { + if (handlers1.beforeUpload) { + return handlers1.beforeUpload(file, fileList); + } + return true; + }, + onStart(file) { + if (handlers1.onStart) { + handlers1.onStart(file); + } + }, + onSuccess(ret, file) { + if (handlers1.onSuccess) { + handlers1.onSuccess(ret, file, null!); + } + }, + onError(err, result, file) { + if (handlers1.onError) { + handlers1.onError(err, result, file); + } + }, + }; + + const props2: UploadProps = { + ...props, + beforeUpload(file, fileList) { + if (handlers2.beforeUpload) { + return handlers2.beforeUpload(file, fileList); + } + return true; + }, + onStart(file) { + if (handlers2.onStart) { + handlers2.onStart(file); + } + }, + onSuccess(ret, file) { + if (handlers2.onSuccess) { + handlers2.onSuccess(ret, file, null!); + } + }, + onError(err, result, file) { + if (handlers2.onError) { + handlers2.onError(err, result, file); + } + }, + }; + + let uid1: string | undefined; + handlers1.beforeUpload = (file, fileList) => { + expect(file).toHaveProperty('uid'); + uid1 = file.uid; + return true; + }; + handlers1.onStart = file => { + expect(file).toHaveProperty('uid'); + expect(file.uid).toEqual(uid1); + }; + handlers1.onSuccess = (ret, file) => { + expect(ret[1]).toEqual(file.name); + expect(file).toHaveProperty('uid'); + expect(file.uid).toEqual(uid1); + }; + handlers1.onError = (err, result, file) => { + expect(file).toHaveProperty('uid'); + expect(file.uid).toEqual(uid1); + throw err; + }; + + let uid2: string | undefined; + handlers2.beforeUpload = (file, fileList) => { + expect(file).toHaveProperty('uid'); + uid2 = file.uid; + return true; + }; + handlers2.onStart = file => { + expect(file).toHaveProperty('uid'); + expect(file.uid).toEqual(uid2); + }; + handlers2.onSuccess = (ret, file) => { + expect(ret[1]).toEqual(file.name); + expect(file).toHaveProperty('uid'); + expect(file.uid).toEqual(uid2); + }; + handlers2.onError = (err, result, file) => { + expect(file).toHaveProperty('uid'); + expect(file.uid).toEqual(uid2); + throw err; + }; + + const { container: container } = render(); + render(); + + const input = container.querySelector('input')!; + + const files = [ + new File([''], 'success.png', { type: 'image/png' }), + ]; + Object.defineProperty(files, 'item', { + value: i => files[i], + }); + + fireEvent.paste(input, { + clipboardData: { files }, + }); + + await sleep(100); + requests[0].respond(200, {}, `["","${files[0].name}"]`); + }); }); describe('directory uploader', () => { From 456d89bdab8bde0a8eb39067ed21fdbea1c74cae Mon Sep 17 00:00:00 2001 From: arksia <354959692@qq.com> Date: Tue, 2 Dec 2025 22:07:56 +0800 Subject: [PATCH 2/4] style: simplify arrow function --- src/AjaxUploader.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AjaxUploader.tsx b/src/AjaxUploader.tsx index 66b527f..70d133a 100644 --- a/src/AjaxUploader.tsx +++ b/src/AjaxUploader.tsx @@ -95,12 +95,12 @@ class AjaxUploader extends Component { const { multiple, directory } = this.props; const items: DataTransferItem[] = [...(dataTransfer.items || [])]; - let files: File[] = [...(dataTransfer.files || [])].map((file: File) => { - return new File([file], file.name, { + let files: File[] = [...(dataTransfer.files || [])].map(file => ( + new File([file], file.name, { type: file.type, lastModified: file.lastModified, - }); - }); + }) + )); if (files.length > 0 || items.some(item => item.kind === 'file')) { existFileCallback?.(); From 756b65184212ae02c4dd5b3983c00c42cddc4333 Mon Sep 17 00:00:00 2001 From: arksia <354959692@qq.com> Date: Tue, 2 Dec 2025 22:12:17 +0800 Subject: [PATCH 3/4] test: add uid uniqueness check in multi upload paste test --- tests/uploader.spec.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/uploader.spec.tsx b/tests/uploader.spec.tsx index 54cc688..867537c 100644 --- a/tests/uploader.spec.tsx +++ b/tests/uploader.spec.tsx @@ -638,7 +638,14 @@ describe('uploader', () => { }); await sleep(100); + + expect(requests).toHaveLength(2); + expect(uid1).toBeDefined(); + expect(uid2).toBeDefined(); + expect(uid1).not.toEqual(uid2); + requests[0].respond(200, {}, `["","${files[0].name}"]`); + requests[1].respond(200, {}, `["","${files[0].name}"]`); }); }); From 1aeb0198cb718c2c8b2d58963e6bce05a9862718 Mon Sep 17 00:00:00 2001 From: kongwq Date: Wed, 3 Dec 2025 13:34:50 +0800 Subject: [PATCH 4/4] test: optimize test case --- tests/uploader.spec.tsx | 132 +++++++++++----------------------------- 1 file changed, 34 insertions(+), 98 deletions(-) diff --git a/tests/uploader.spec.tsx b/tests/uploader.spec.tsx index 867537c..06a2df1 100644 --- a/tests/uploader.spec.tsx +++ b/tests/uploader.spec.tsx @@ -526,126 +526,62 @@ describe('uploader', () => { }); it('should prevent uid overwritten when multiple upload components paste simultaneously', async () => { - const handlers1: UploadProps = {}; - const handlers2: UploadProps = {}; + const uidsInBeforeUpload: string[] = []; + const uidsInOnStart: string[] = []; + const uidsInOnSuccess: string[] = []; - const props1: UploadProps = { + const createUploadProps = (index: number): UploadProps => ({ ...props, - beforeUpload(file, fileList) { - if (handlers1.beforeUpload) { - return handlers1.beforeUpload(file, fileList); - } - return true; - }, - onStart(file) { - if (handlers1.onStart) { - handlers1.onStart(file); - } - }, - onSuccess(ret, file) { - if (handlers1.onSuccess) { - handlers1.onSuccess(ret, file, null!); - } - }, - onError(err, result, file) { - if (handlers1.onError) { - handlers1.onError(err, result, file); - } - }, - }; - - const props2: UploadProps = { - ...props, - beforeUpload(file, fileList) { - if (handlers2.beforeUpload) { - return handlers2.beforeUpload(file, fileList); - } + pastable: true, + beforeUpload(file) { + uidsInBeforeUpload[index] = file.uid; return true; }, onStart(file) { - if (handlers2.onStart) { - handlers2.onStart(file); - } + uidsInOnStart[index] = file.uid; }, onSuccess(ret, file) { - if (handlers2.onSuccess) { - handlers2.onSuccess(ret, file, null!); - } + uidsInOnSuccess[index] = file.uid; }, - onError(err, result, file) { - if (handlers2.onError) { - handlers2.onError(err, result, file); - } + onError(err) { + throw err; }, - }; - - let uid1: string | undefined; - handlers1.beforeUpload = (file, fileList) => { - expect(file).toHaveProperty('uid'); - uid1 = file.uid; - return true; - }; - handlers1.onStart = file => { - expect(file).toHaveProperty('uid'); - expect(file.uid).toEqual(uid1); - }; - handlers1.onSuccess = (ret, file) => { - expect(ret[1]).toEqual(file.name); - expect(file).toHaveProperty('uid'); - expect(file.uid).toEqual(uid1); - }; - handlers1.onError = (err, result, file) => { - expect(file).toHaveProperty('uid'); - expect(file.uid).toEqual(uid1); - throw err; - }; - - let uid2: string | undefined; - handlers2.beforeUpload = (file, fileList) => { - expect(file).toHaveProperty('uid'); - uid2 = file.uid; - return true; - }; - handlers2.onStart = file => { - expect(file).toHaveProperty('uid'); - expect(file.uid).toEqual(uid2); - }; - handlers2.onSuccess = (ret, file) => { - expect(ret[1]).toEqual(file.name); - expect(file).toHaveProperty('uid'); - expect(file.uid).toEqual(uid2); - }; - handlers2.onError = (err, result, file) => { - expect(file).toHaveProperty('uid'); - expect(file.uid).toEqual(uid2); - throw err; - }; + }); - const { container: container } = render(); - render(); + const { container } = render(); + render(); const input = container.querySelector('input')!; - - const files = [ - new File([''], 'success.png', { type: 'image/png' }), - ]; + const files = [new File([''], 'success.png', { type: 'image/png' })]; Object.defineProperty(files, 'item', { value: i => files[i], }); - fireEvent.paste(input, { - clipboardData: { files }, - }); + fireEvent.paste(input, { clipboardData: { files } }); await sleep(100); - expect(requests).toHaveLength(2); - expect(uid1).toBeDefined(); - expect(uid2).toBeDefined(); - expect(uid1).not.toEqual(uid2); + expect(uidsInBeforeUpload[0]).toBeDefined(); + expect(uidsInBeforeUpload[1]).toBeDefined(); + expect(uidsInBeforeUpload[0]).not.toEqual(uidsInBeforeUpload[1]); + + expect(uidsInOnStart[0]).toBeDefined(); + expect(uidsInOnStart[1]).toBeDefined(); + expect(uidsInOnStart[0]).not.toEqual(uidsInOnStart[1]); + + expect(uidsInOnStart[0]).toEqual(uidsInBeforeUpload[0]); + expect(uidsInOnStart[1]).toEqual(uidsInBeforeUpload[1]); + expect(requests).toHaveLength(2); requests[0].respond(200, {}, `["","${files[0].name}"]`); requests[1].respond(200, {}, `["","${files[0].name}"]`); + + expect(uidsInOnSuccess[0]).toBeDefined(); + expect(uidsInOnSuccess[1]).toBeDefined(); + expect(uidsInOnSuccess[0]).not.toEqual(uidsInOnSuccess[1]); + + expect(uidsInOnSuccess[0]).toEqual(uidsInBeforeUpload[0]); + expect(uidsInOnSuccess[1]).toEqual(uidsInBeforeUpload[1]); }); });