Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/AjaxUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ class AjaxUploader extends Component<UploadProps> {
const { multiple, directory } = this.props;

const items: DataTransferItem[] = [...(dataTransfer.items || [])];
let files: File[] = [...(dataTransfer.files || [])];
let files: File[] = [...(dataTransfer.files || [])].map(file => (
new File([file], file.name, {
type: file.type,
lastModified: file.lastModified,
})
));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Clone File objects to avoid shared uid mutation between multiple Upload components
const files: File[] = Array.from(dataTransfer.files ?? [], file =>
  new File([file], file.name, {
    type: file.type,
    lastModified: file.lastModified,
  })
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

改为 Array.form 克隆文件数组,但还是得用 let,因为后面有对 files 赋值

if (directory) {
  files = await traverseFileTree(Array.prototype.slice.call(items), this.filterFile);
  this.uploadFiles(files);
} else {
  let acceptFiles = [...files].filter(file => this.filterFile(file, true));
  if (multiple === false) {
     acceptFiles = files.slice(0, 1);
  }
  this.uploadFiles(acceptFiles);
}


if (files.length > 0 || items.some(item => item.kind === 'file')) {
existFileCallback?.();
Expand Down Expand Up @@ -296,7 +301,6 @@ class AjaxUploader extends Component<UploadProps> {
delete this.reqs[uid];
},
};
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whitespace-only change appears to be unintentional. Consider keeping the blank line after the object definition for better readability.

Suggested change
};
};

Copilot uses AI. Check for mistakes.

onStart(origin);
this.reqs[uid] = request(requestOption, { defaultRequest });
}
Expand Down
65 changes: 62 additions & 3 deletions tests/uploader.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ describe('uploader', () => {
Object.defineProperty(files, 'item', {
value: i => files[i],
});

Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whitespace-only change appears to be unintentional. Consider keeping the original blank line for consistency with the rest of the codebase.

Suggested change

Copilot uses AI. Check for mistakes.
// Only can trigger once
let triggerTimes = 0;
handlers.onStart = () => {
Expand All @@ -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);
});

Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -524,6 +524,65 @@ describe('uploader', () => {
expect(preventDefaultSpy).toHaveBeenCalledTimes(0);
preventDefaultSpy.mockRestore();
});

it('should prevent uid overwritten when multiple upload components paste simultaneously', async () => {
const uidsInBeforeUpload: string[] = [];
const uidsInOnStart: string[] = [];
const uidsInOnSuccess: string[] = [];

const createUploadProps = (index: number): UploadProps => ({
...props,
pastable: true,
beforeUpload(file) {
uidsInBeforeUpload[index] = file.uid;
return true;
},
onStart(file) {
uidsInOnStart[index] = file.uid;
},
onSuccess(ret, file) {
uidsInOnSuccess[index] = file.uid;
},
onError(err) {
throw err;
},
});

const { container } = render(<Upload {...createUploadProps(0)} />);
render(<Upload {...createUploadProps(1)} />);

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

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}"]`);
Comment on lines +562 to +576
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这个测试用例很好地覆盖了并发粘贴的场景。不过,它可以更健壮一些。目前它只完成了一个上传请求,并且没有验证两个组件生成的 uid 是唯一的。

我建议更新测试以:

  1. 断言创建了两个独立的请求。
  2. 断言 uid 是不同的。
  3. 完成两个上传请求,以确保两个组件的生命周期回调都得到测试。
      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}"]`);

requests[1].respond(200, {}, `["","${files[0].name}"]`);

expect(uidsInOnSuccess[0]).toBeDefined();
expect(uidsInOnSuccess[1]).toBeDefined();
expect(uidsInOnSuccess[0]).not.toEqual(uidsInOnSuccess[1]);

Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing whitespace detected. Consider removing it for consistency with the codebase style.

Suggested change

Copilot uses AI. Check for mistakes.
expect(uidsInOnSuccess[0]).toEqual(uidsInBeforeUpload[0]);
expect(uidsInOnSuccess[1]).toEqual(uidsInBeforeUpload[1]);
});
});

describe('directory uploader', () => {
Expand Down