Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
29 changes: 27 additions & 2 deletions packages/upload/src/vaadin-upload-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,24 @@ export const UploadMixin = (superClass) =>
files = [files];
}
files = files.filter((file) => !file.complete);
Array.prototype.forEach.call(files, this._uploadFile.bind(this));
// Upload only the first file in the queue, not all at once
if (files.length > 0) {
this._uploadFile(files[0]);
}
}

/** @private */
_processNextFileInQueue() {
// Find the next file that is queued but not yet uploaded
// Search from the end since files are prepended (newest first)
// This ensures files upload in the order they were added
const nextFile = this.files
.slice()
.reverse()
.find((file) => !file.complete && !file.uploading && !file.abort);
if (nextFile) {
this._uploadFile(nextFile);
}
}

/** @private */
Expand Down Expand Up @@ -776,6 +793,8 @@ export const UploadMixin = (superClass) =>
}),
);
this._renderFileList();
// Process the next file in the queue after this one completes
this._processNextFileInQueue();
}
};

Expand Down Expand Up @@ -881,6 +900,8 @@ export const UploadMixin = (superClass) =>
file.xhr.abort();
}
this._removeFile(file);
// Process the next file in the queue after aborting this one
this._processNextFileInQueue();
}
}

Expand Down Expand Up @@ -934,7 +955,11 @@ export const UploadMixin = (superClass) =>
this.files = [file, ...this.files];

if (!this.noAuto) {
this._uploadFile(file);
// Only start uploading if no other file is currently being uploaded
const isAnyFileUploading = this.files.some((f) => f.uploading);
if (!isAnyFileUploading) {
this._uploadFile(file);
}
}
}

Expand Down
8 changes: 6 additions & 2 deletions packages/upload/test/adding-files.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,12 @@ describe('adding files', () => {
upload.addEventListener('upload-start', uploadStartSpy);

files.forEach(upload._addFile.bind(upload));
expect(uploadStartSpy.calledTwice).to.be.true;
expect(upload.files[0].held).to.be.false;
// With queue behavior, only the first file starts uploading immediately
expect(uploadStartSpy.calledOnce).to.be.true;
// Files are prepended, so the first file added is at index 1
expect(upload.files[1].held).to.be.false;
// Second file (at index 0) should be held in queue
expect(upload.files[0].held).to.be.true;
});

it('should not automatically start upload when noAuto flag is set', () => {
Expand Down
154 changes: 147 additions & 7 deletions packages/upload/test/upload.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,16 +437,21 @@ describe('upload', () => {
upload.files.forEach((file) => {
expect(file.uploading).not.to.be.ok;
});
let firstUploadStartFired = false;
upload.addEventListener('upload-start', (e) => {
expect(e.detail.xhr).to.be.ok;
expect(e.detail.file).to.be.ok;
expect(e.detail.file.name).to.equal(tempFileName);
expect(e.detail.file.uploading).to.be.ok;
if (!firstUploadStartFired) {
firstUploadStartFired = true;
expect(e.detail.xhr).to.be.ok;
expect(e.detail.file).to.be.ok;
expect(e.detail.file.name).to.equal(tempFileName);
expect(e.detail.file.uploading).to.be.ok;

for (let i = 0; i < upload.files.length - 1; i++) {
expect(upload.files[i].uploading).not.to.be.ok;
for (let i = 0; i < upload.files.length - 1; i++) {
expect(upload.files[i].uploading).not.to.be.ok;
}
done();
}
done();
// With queue behavior, other files will start after the first completes - ignore those events
});
upload.uploadFiles([upload.files[2]]);
});
Expand Down Expand Up @@ -539,6 +544,141 @@ describe('upload', () => {
});
});

describe('Upload Queue', () => {
let clock, files;

beforeEach(() => {
upload._createXhr = xhrCreator({ size: file.size, uploadTime: 200, stepTime: 50 });
clock = sinon.useFakeTimers();
});

afterEach(() => {
clock.restore();
});

it('should upload multiple files one at a time', async () => {
files = createFiles(3, 512, 'application/json');
upload._addFiles(files);

// Files are prepended, so files[0] is at index 2, files[1] at index 1, files[2] at index 0
// First file added (files[0]) should start uploading
await clock.tickAsync(10);
expect(upload.files[2].uploading).to.be.true;
expect(upload.files[2].held).to.be.false;
expect(upload.files[1].held).to.be.true;
expect(upload.files[0].held).to.be.true;

// Wait for first file to complete (connectTime + uploadTime + serverTime = 10 + 200 + 10 = 220ms)
await clock.tickAsync(220);
expect(upload.files[2].complete).to.be.true;
expect(upload.files[2].uploading).to.be.false;

// Second file (files[1]) should now start uploading
await clock.tickAsync(10);
expect(upload.files[1].uploading).to.be.true;
expect(upload.files[1].held).to.be.false;
expect(upload.files[0].held).to.be.true;

// Wait for second file to complete
await clock.tickAsync(220);
expect(upload.files[1].complete).to.be.true;
expect(upload.files[1].uploading).to.be.false;

// Third file (files[2]) should now start uploading
await clock.tickAsync(10);
expect(upload.files[0].uploading).to.be.true;
expect(upload.files[0].held).to.be.false;

// Wait for third file to complete
await clock.tickAsync(220);
expect(upload.files[0].complete).to.be.true;
expect(upload.files[0].uploading).to.be.false;
});

it('should process next file in queue after one completes with error', async () => {
upload._createXhr = xhrCreator({
size: 512,
uploadTime: 200,
stepTime: 50,
serverValidation: () => {
return { status: 500, statusText: 'Server Error' };
},
});

const errorSpy = sinon.spy();
const startSpy = sinon.spy();
upload.addEventListener('upload-error', errorSpy);
upload.addEventListener('upload-start', startSpy);

files = createFiles(2, 512, 'application/json');
upload._addFiles(files);

// First file should start
await clock.tickAsync(10);
expect(startSpy.callCount).to.equal(1);

// Wait for first file to complete with error
await clock.tickAsync(220);
expect(errorSpy.callCount).to.equal(1);

// Second file should now start
await clock.tickAsync(10);
expect(startSpy.callCount).to.equal(2);
expect(upload.files.some((f) => f.uploading)).to.be.true;
});

it('should process next file in queue after one is aborted', async () => {
files = createFiles(2, 512, 'application/json');
upload._addFiles(files);

// First file added (at index 1) should start uploading
await clock.tickAsync(10);
expect(upload.files[1].uploading).to.be.true;
expect(upload.files[0].held).to.be.true;

// Abort the first file (at index 1)
upload._abortFileUpload(upload.files[1]);

// Second file (now at index 0 after first is removed) should now start uploading
await clock.tickAsync(10);
expect(upload.files[0].uploading).to.be.true;
});

it('should only start one file when uploadFiles is called with multiple files', async () => {
upload.noAuto = true;
files = createFiles(3, 512, 'application/json');
upload._addFiles(files);

// No files should be uploading yet - all should be held
await clock.tickAsync(10);
expect(upload.files[0].held).to.be.true;
expect(upload.files[1].held).to.be.true;
expect(upload.files[2].held).to.be.true;

// Call uploadFiles
upload.uploadFiles();

// Only first file (at index 2) should start uploading - wait for it to begin
await clock.tickAsync(20);
expect(upload.files.length).to.equal(3);
// One file should be uploading (the oldest one added)
const uploadingFile = upload.files.find((f) => f.uploading);
expect(uploadingFile).to.be.ok;
// The other two should still be held
const heldFiles = upload.files.filter((f) => f.held);
expect(heldFiles.length).to.equal(2);

// Wait for first file to complete
await clock.tickAsync(220);

// Second file should start automatically
await clock.tickAsync(10);
expect(upload.files.some((f) => f.uploading)).to.be.true;
const remainingHeldFiles = upload.files.filter((f) => f.held);
expect(remainingHeldFiles.length).to.equal(1);
});
});

describe('Upload format', () => {
let clock;

Expand Down