diff --git a/src/upchunk.spec.ts b/src/upchunk.spec.ts index 5a27310..fc89891 100644 --- a/src/upchunk.spec.ts +++ b/src/upchunk.spec.ts @@ -222,6 +222,73 @@ test('chunkSuccess event is fired after each successful upload', (done) => { }); }); +const isNumberArraySorted = (a: number[]): boolean => { + for (let i = 0; i < a.length - 1; i += 1) { + if (a[i] > a[i + 1]) { + return false; + } + } + return true; +}; + +test('progress event fires the correct upload percentage', (done) => { + const fileBytes = 1048576; + const upload = createUploadFixture( + {}, + new File([new ArrayBuffer(fileBytes)], 'test.mp4') + ); + + const scopes = [ + nock('https://example.com') + .matchHeader('content-range', `bytes 0-${fileBytes / 4 - 1}/${fileBytes}`) + .put('/upload/endpoint') + .reply(200), + nock('https://example.com') + .matchHeader( + 'content-range', + `bytes ${fileBytes / 4}-${fileBytes / 2 - 1}/${fileBytes}` + ) + .put('/upload/endpoint') + .reply(200), + nock('https://example.com') + .matchHeader( + 'content-range', + `bytes ${fileBytes / 2}-${3 * fileBytes / 4 - 1}/${fileBytes}` + ) + .put('/upload/endpoint') + .reply(200), + nock('https://example.com') + .matchHeader( + 'content-range', + `bytes ${3 * fileBytes / 4}-${fileBytes - 1}/${fileBytes}` + ) + .put('/upload/endpoint') + .reply(200), + ]; + + const progressCallback = jest.fn((percentage) => percentage); + + upload.on('error', (err) => { + done(err); + }); + + upload.on('progress', (progress) => { + progressCallback(progress.detail); + }); + + upload.on('success', () => { + scopes.forEach((scope) => { + if (!scope.isDone()) { + done('All scopes not completed'); + } + }); + expect(progressCallback).toHaveBeenCalledTimes(7); + const progressPercentageArray = progressCallback.mock.calls.map(([percentage]) => percentage); + expect(isNumberArraySorted(progressPercentageArray)).toBeTruthy(); + done(); + }); +}, 10000); + test('abort pauses the upload and cancels the current XHR request', (done) => { /* This is hacky and I don't love it, but the gist is: diff --git a/src/upchunk.ts b/src/upchunk.ts index 08f7401..8447c96 100644 --- a/src/upchunk.ts +++ b/src/upchunk.ts @@ -220,9 +220,12 @@ export class UpChunk { private xhrPromise(options: XhrUrlConfig): Promise { const beforeSend = (xhrObject: XMLHttpRequest) => { xhrObject.upload.onprogress = (event: ProgressEvent) => { - const successfulPercentage = (100 / this.totalChunks) * this.chunkCount; - const chunkPercentage = (event.loaded / this.file.size) * 100; - this.dispatch('progress', successfulPercentage + chunkPercentage); + const percentagePerChunk = 100 / this.totalChunks; + const sizePerChunk = percentagePerChunk * this.file.size; + const successfulPercentage = percentagePerChunk * this.chunkCount; + const currentChunkProgress = event.loaded / (event.total ?? sizePerChunk); + const chunkPercentage = currentChunkProgress * percentagePerChunk; + this.dispatch('progress', Math.min(successfulPercentage + chunkPercentage, 100)); }; }; @@ -317,9 +320,10 @@ export class UpChunk { this.dispatch('success'); } - const percentProgress = Math.round( - (100 / this.totalChunks) * this.chunkCount - ); + const chunkFraction = this.chunkCount / this.totalChunks; + const uploadedBytes = chunkFraction * this.file.size; + + const percentProgress = (100 * uploadedBytes) / this.file.size; this.dispatch('progress', percentProgress); } else if (TEMPORARY_ERROR_CODES.includes(res.statusCode)) {