8
0
Просмотр исходного кода

Added tests to FileRepository and FileLoader.

Szymon Kupś 8 лет назад
Родитель
Сommit
ddbc6b0080

+ 2 - 2
packages/ckeditor5-upload/src/filereader.js

@@ -53,7 +53,7 @@ export default class FileReader {
 	/**
 	 * Reads provided file.
 	 *
-	 * @param file Native File object.
+	 * @param {File} file Native File object.
 	 * @returns {Promise} Returns a promise that will resolve with file's contents. Promise can be rejected in case of
 	 * error or when reading process is aborted.
 	 */
@@ -71,7 +71,7 @@ export default class FileReader {
 			};
 
 			reader.onabort = () => {
-				reject( 'abort' );
+				reject( 'aborted' );
 			};
 
 			this._reader.readAsDataURL( file );

+ 78 - 55
packages/ckeditor5-upload/src/filerepository.js

@@ -7,12 +7,13 @@
  * @module upload/filerepository
  */
 
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin.js';
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror.js';
-import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin.js';
-import Collection from '@ckeditor/ckeditor5-utils/src/collection.js';
-import mix from '@ckeditor/ckeditor5-utils/src/mix.js';
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
+import Collection from '@ckeditor/ckeditor5-utils/src/collection';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import log from '@ckeditor/ckeditor5-utils/src/log';
 
 import FileReader from './filereader.js';
 
@@ -43,10 +44,25 @@ export default class FileRepository extends Plugin {
 		this.loaders = new Collection();
 
 		/**
-		 * Function that should be defined before using FileRepository. It should return adapter that will be
-		 * used to upload files.
+		 * Function that should be defined before using FileRepository. It should return new instance of
+		 * {@link module:upload/filerepository~Adapter Adapter} that will be used to upload files.
+		 * {@link module:upload/filerepository~FileLoader FileLoader} instance will be passed to that function.
 		 *
-		 * @member {function} #createAdapter
+		 *	fileRepository.createAdapter = function( loader ) {
+		 *		return {
+		 *			upload: function() {
+		 *				return doSomeUpload( loader.file );
+		 *			},
+		 *
+		 *			abort: function() {
+		 *				abortUpload();
+		 *			}
+		 *		};
+		 *	};
+		 *
+		 * @abstract
+		 * @function
+		 * @name #createAdapter
 		 */
 
 		/**
@@ -59,11 +75,11 @@ export default class FileRepository extends Plugin {
 		this.set( 'uploaded', 0 );
 
 		/**
-		 * Number of total bytes to upload.
+		 * Number of total bytes to upload. It contains `null` if value is not available yet.
 		 *
 		 * @readonly
 		 * @observable
-		 * @member {Number} #uploadTotal
+		 * @member {Number|null} #uploadTotal
 		 */
 		this.set( 'uploadTotal', null );
 
@@ -83,7 +99,7 @@ export default class FileRepository extends Plugin {
 	 * Returns the loader associated with specified file.
 	 * To get loader by id use `fileRepository.loaders.get( id )`.
 	 *
-	 * @param file Native File object.
+	 * @param {File} file Native File object.
 	 * @returns {module:upload/filerepository~FileLoader|null}
 	 */
 	getLoader( file ) {
@@ -98,15 +114,16 @@ export default class FileRepository extends Plugin {
 
 	/**
 	 * Creates loader for specified file.
-	 * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `filerepository-no-adapter` when
-	 * {@link #createAdapter} method is not defined for this FileRepository.
+	 * Shows console warning and returns `null` if {@link #createAdapter} method is not defined..
 	 *
-	 * @param file Native File object.
-	 * @returns {module:upload/filerepository~FileLoader}
+	 * @param {File} file Native File object.
+	 * @returns {module:upload/filerepository~FileLoader|null}
 	 */
 	createLoader( file ) {
 		if ( !this.createAdapter ) {
-			throw new CKEditorError( 'filerepository-no-adapter: No createAdapter method found.' );
+			log.warn( 'FileRepository: no createAdapter method found. Please define it before creating a loader.' );
+
+			return null;
 		}
 
 		const loader = new FileLoader( file );
@@ -115,25 +132,25 @@ export default class FileRepository extends Plugin {
 		this.loaders.add( loader );
 
 		loader.on( 'change:uploaded', () => {
-			let agregatedUploaded = 0;
+			let aggregatedUploaded = 0;
 
 			for ( const loader of this.loaders ) {
-				agregatedUploaded += loader.uploaded;
+				aggregatedUploaded += loader.uploaded;
 			}
 
-			this.uploaded = agregatedUploaded;
+			this.uploaded = aggregatedUploaded;
 		} );
 
 		loader.on( 'change:uploadTotal', () => {
-			let agregatedTotal = 0;
+			let aggregatedTotal = 0;
 
 			for ( const loader of this.loaders ) {
 				if ( loader.uploadTotal ) {
-					agregatedTotal += loader.uploadTotal;
+					aggregatedTotal += loader.uploadTotal;
 				}
 			}
 
-			this.uploadTotal = agregatedTotal;
+			this.uploadTotal = aggregatedTotal;
 		} );
 
 		return loader;
@@ -142,10 +159,11 @@ export default class FileRepository extends Plugin {
 	/**
 	 * Destroys loader.
 	 *
-	 * @param {Number|module:upload/filerepository~FileLoader} fileOrIdOrLoader Loader itself, id of the loader or file associated with that loader.
+	 * @param {File|module:upload/filerepository~FileLoader} fileOrLoader File associated with that loader or loader
+	 * itself.
 	 */
-	destroyLoader( fileOrIdOrLoader ) {
-		const loader = fileOrIdOrLoader instanceof FileLoader ? fileOrIdOrLoader : this.getLoader( fileOrIdOrLoader );
+	destroyLoader( fileOrLoader ) {
+		const loader = fileOrLoader instanceof FileLoader ? fileOrLoader : this.getLoader( fileOrLoader );
 
 		loader._destroy();
 
@@ -163,7 +181,7 @@ class FileLoader {
 	/**
 	 * Creates instance of FileLoader.
 	 *
-	 * @param file
+	 * @param {File} file
 	 * @param {module:upload/filerepository~Adapter} adapter
 	 */
 	constructor( file, adapter ) {
@@ -176,21 +194,29 @@ class FileLoader {
 		this.id = uid();
 
 		/**
-		 * File instance associated with this FileLoader.
+		 * File instance associated with FileLoader.
 		 *
 		 * @readonly
-		 * @member
+		 * @member {File}
 		 */
 		this.file = file;
 
 		/**
-		 * Adapter instance associated with this FileLoader.
+		 * Adapter instance associated with FileLoader.
 		 *
-		 * @readonly
+		 * @private
 		 * @member {module:upload/filerepository~Adapter}
 		 */
 		this._adapter = adapter;
 
+		/**
+		 * FileReader used by FileLoader.
+		 *
+		 * @protected
+		 * @member {module:upload/filereader~FileReader}
+		 */
+		this._reader = new FileReader();
+
 		/**
 		 * Current status of FileLoader. It can be one of the following:
 		 * * 'idle',
@@ -247,9 +273,9 @@ class FileLoader {
 		 *
 		 * @readonly
 		 * @observable
-		 * @member {Object|null} #uploadedPercent
+		 * @member {Object|null} #uploadResponse
 		 */
-		this.set( 'uploadReponse', null );
+		this.set( 'uploadResponse', null );
 	}
 
 	/**
@@ -277,22 +303,21 @@ class FileLoader {
 		}
 
 		this.status = 'reading';
-		this._reader = new FileReader();
 
 		return this._reader.read( this.file )
 			.then( data => {
-				if ( this.status === 'aborted' ) {
-					throw 'aborted';
-				}
 				this.status = 'idle';
 
 				return data;
 			} )
 			.catch( err => {
-				if ( this.status != 'aborted' ) {
-					this.status = 'error';
+				if ( err === 'aborted' ) {
+					this.status = 'aborted';
+					throw 'aborted';
 				}
-				throw err;
+
+				this.status = 'error';
+				throw this._reader.error;
 			} );
 	}
 
@@ -324,18 +349,18 @@ class FileLoader {
 
 		return this._adapter.upload()
 			.then( data => {
-				if ( this.status === 'aborted' ) {
-					throw 'aborted';
-				}
-				this.uploadReponse = data;
+				this.uploadResponse = data;
 				this.status = 'idle';
 
 				return data;
 			} )
 			.catch( err => {
-				if ( this.status != 'aborted' ) {
-					this.status = 'error';
+				if ( err === 'aborted' ) {
+					this.status = 'aborted';
+					throw 'aborted';
 				}
+
+				this.status = 'error';
 				throw err;
 			} );
 	}
@@ -344,7 +369,7 @@ class FileLoader {
 	 * Aborts loading process.
 	 */
 	abort() {
-		if ( this.status == 'loading' ) {
+		if ( this.status == 'reading' ) {
 			this._reader.abort();
 		}
 
@@ -365,7 +390,7 @@ class FileLoader {
 		this._reader = undefined;
 		this._adapter = undefined;
 		this.data = undefined;
-		this.uploadReponse = undefined;
+		this.uploadResponse = undefined;
 		this.file = undefined;
 	}
 }
@@ -375,21 +400,19 @@ mix( FileLoader, ObservableMixin );
 /**
  * Adapter abstract class used by FileRepository to handle file upload.
  *
- * @abstract
- * @class Adapter
+ * @interface Adapter
  */
 
 /**
  * Executes the upload process.
  *
- * @function
- * @name module:upload/filerepository~Adapter#upload
- * @returns {Promise}
+ * @method #upload
+ * @returns {Promise} Promise that should be resolved when data is uploaded.
  */
 
 /**
- * Aborts the upload process.
+ * Aborts the upload proccess.
+ * After aborting it should reject promise returned from {@link #upload} method with "aborted" string.
  *
- * @function
- * @name module:upload/filerepository~Adapter#abort
+ * @method #abort
  */

+ 65 - 0
packages/ckeditor5-upload/tests/_utils/mocks.js

@@ -0,0 +1,65 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+export const createNativeFileMock = () => ( {
+	size: 1024
+} );
+
+export class AdapterMock {
+	constructor( loader ) {
+		this.loader = loader;
+	}
+
+	upload() {
+		return new Promise( ( resolve, reject ) => {
+			this._resolveCallback = resolve;
+			this._rejectCallback = reject;
+		} );
+	}
+
+	abort() {
+		this._rejectCallback( 'aborted' );
+	}
+
+	mockError( error ) {
+		this._rejectCallback( error );
+	}
+
+	mockSuccess( data ) {
+		this._resolveCallback( data );
+	}
+
+	mockProgress( uploaded, total ) {
+		this.loader.uploaded = uploaded;
+		this.loader.uploadTotal = total;
+	}
+}
+
+export class NativeFileReaderMock {
+	readAsDataURL() {}
+
+	abort() {
+		this.mockAbort();
+	}
+
+	mockSuccess( result ) {
+		this.result = result;
+		this.onload();
+	}
+
+	mockError( error ) {
+		this.error = error;
+		this.onerror();
+	};
+
+	mockAbort() {
+		this.onabort();
+	}
+
+	mockProgress( progress ) {
+		this.onprogress( { loaded: progress } );
+	}
+}
+

+ 12 - 39
packages/ckeditor5-upload/tests/filereader.js

@@ -7,6 +7,7 @@
 
 import FileReader from '../src/filereader';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import { NativeFileReaderMock, createNativeFileMock } from './_utils/mocks';
 
 describe( 'FileReader', () => {
 	let reader, fileMock, nativeReaderMock;
@@ -14,15 +15,12 @@ describe( 'FileReader', () => {
 
 	beforeEach( () => {
 		testUtils.sinon.stub( window, 'FileReader', () => {
-			nativeReaderMock = new NativeReaderMock();
+			nativeReaderMock = new NativeFileReaderMock();
 
 			return nativeReaderMock;
 		} );
 
-		fileMock = {
-			size: 1024
-		};
-
+		fileMock = createNativeFileMock();
 		reader = new FileReader();
 	} );
 
@@ -31,11 +29,11 @@ describe( 'FileReader', () => {
 	} );
 
 	it( 'should update loaded property', () => {
-		nativeReaderMock._mockProgress( 10 );
+		nativeReaderMock.mockProgress( 10 );
 		expect( reader.loaded ).to.equal( 10 );
-		nativeReaderMock._mockProgress( 20 );
+		nativeReaderMock.mockProgress( 20 );
 		expect( reader.loaded ).to.equal( 20 );
-		nativeReaderMock._mockProgress( 55 );
+		nativeReaderMock.mockProgress( 55 );
 		expect( reader.loaded ).to.equal( 55 );
 	} );
 
@@ -50,7 +48,7 @@ describe( 'FileReader', () => {
 					expect( result ).to.equal( 'File contents.' );
 				} );
 
-			nativeReaderMock._mockLoading( 'File contents.' );
+			nativeReaderMock.mockSuccess( 'File contents.' );
 
 			return promise;
 		} );
@@ -64,20 +62,20 @@ describe( 'FileReader', () => {
 					expect( reader.error ).to.equal( 'Error during file reading.' );
 				} );
 
-			nativeReaderMock._mockError( 'Error during file reading.' );
+			nativeReaderMock.mockError( 'Error during file reading.' );
 
 			return promise;
 		} );
 
-		it( 'should reject on loading abort', () => {
+		it( 'should reject promise on loading abort', () => {
 			const promise = reader.read( fileMock )
 				.then( () => {
 					throw new Error( 'Reader should not resolve.' );
 				}, ( status ) => {
-					expect( status ).to.equal( 'abort' );
+					expect( status ).to.equal( 'aborted' );
 				} );
 
-			nativeReaderMock._mockAbort();
+			nativeReaderMock.mockAbort();
 
 			return promise;
 		} );
@@ -89,7 +87,7 @@ describe( 'FileReader', () => {
 				.then( () => {
 					throw new Error( 'Reader should not resolve.' );
 				}, ( status ) => {
-					expect( status ).to.equal( 'abort' );
+					expect( status ).to.equal( 'aborted' );
 				} );
 
 			reader.abort();
@@ -97,29 +95,4 @@ describe( 'FileReader', () => {
 			return promise;
 		} );
 	} );
-
-	function NativeReaderMock() {
-		this.readAsDataURL = () => {};
-		this.abort = () => {
-			this._mockAbort();
-		};
-
-		this._mockLoading = result => {
-			this.result = result;
-			this.onload();
-		};
-
-		this._mockError = error => {
-			this.error = error;
-			this.onerror();
-		};
-
-		this._mockAbort = () => {
-			this.onabort();
-		};
-
-		this._mockProgress = progress => {
-			this.onprogress( { loaded: progress } );
-		};
-	}
 } );

+ 367 - 0
packages/ckeditor5-upload/tests/filerepository.js

@@ -0,0 +1,367 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals window */
+
+import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import FileRepository from '../src/filerepository';
+import Collection from '@ckeditor/ckeditor5-utils/src/collection';
+import { createNativeFileMock, AdapterMock, NativeFileReaderMock } from './_utils/mocks';
+import log from '@ckeditor/ckeditor5-utils/src/log';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import FileReader from '../src/filereader';
+
+describe( 'FileRepository', () => {
+	let editor, fileRepository, adapterMock;
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		return VirtualTestEditor.create( {
+			plugins: [ FileRepository ]
+		} )
+			.then( newEditor => {
+				editor = newEditor;
+				fileRepository = editor.plugins.get( 'fileRepository' );
+				fileRepository.createAdapter = loader => {
+					adapterMock = new AdapterMock( loader );
+
+					return adapterMock;
+				};
+			} );
+	} );
+
+	it( 'should be initialized', () => {
+		expect( fileRepository ).to.be.instanceOf( FileRepository );
+	} );
+
+	describe( 'init', () => {
+		it( 'should create loaders collection', () => {
+			expect( fileRepository.loaders ).to.be.instanceOf( Collection );
+		} );
+
+		it( 'should initialize uploaded observable', ( done ) => {
+			expect( fileRepository.uploaded ).to.equal( 0 );
+
+			fileRepository.on( 'change:uploaded', ( evt, name, value ) => {
+				expect( value ).to.equal( 10 );
+				done();
+			} );
+
+			fileRepository.uploaded = 10;
+		} );
+
+		it( 'should initialize uploadTotal', ( done ) => {
+			expect( fileRepository.uploadTotal ).to.be.null;
+
+			fileRepository.on( 'change:uploadTotal', ( evt, name, value ) => {
+				expect( value ).to.equal( 10 );
+				done();
+			} );
+
+			fileRepository.uploadTotal = 10;
+		} );
+
+		it( 'should initialize uploadedPercent', ( done ) => {
+			expect( fileRepository.uploadedPercent ).to.equal( 0 );
+
+			fileRepository.on( 'change:uploadedPercent', ( evt, name, value ) => {
+				expect( value ).to.equal( 20 );
+				done();
+			} );
+
+			fileRepository.uploadTotal = 200;
+			fileRepository.uploaded = 40;
+		} );
+	} );
+
+	describe( 'createLoader', () => {
+		it( 'should show warning if adapter is not present', () => {
+			const stub = testUtils.sinon.stub( log, 'warn' );
+			fileRepository.createAdapter = undefined;
+			fileRepository.createLoader( createNativeFileMock() );
+
+			sinon.assert.calledOnce( stub );
+			sinon.assert.calledWithExactly( stub, 'FileRepository: no createAdapter method found. Please define it before creating a loader.' );
+		} );
+
+		it( 'should setup listeners to update progress observables', () => {
+			const loader1 = fileRepository.createLoader( createNativeFileMock() );
+			const loader2 = fileRepository.createLoader( createNativeFileMock() );
+			const loader3 = fileRepository.createLoader( createNativeFileMock() );
+
+			loader1.uploaded = 10;
+			loader1.uploadTotal = 100;
+			loader2.uploaded = 50;
+			loader2.uploadTotal = 200;
+			loader3.uploaded = 40;
+			loader3.uploadTotal = 200;
+
+			expect( fileRepository.uploaded ).to.equal( 100 );
+			expect( fileRepository.uploadTotal ).to.equal( 500 );
+			expect( fileRepository.uploadedPercent ).to.equal( 20 );
+		} );
+	} );
+
+	describe( 'getLoader', () => {
+		it( 'should return null if loader does not exists', () => {
+			const file1 = createNativeFileMock();
+			const file2 = createNativeFileMock();
+			fileRepository.createLoader( file2 );
+
+			expect( fileRepository.getLoader( file1 ) ).to.be.null;
+		} );
+
+		it( 'should return loader by file instance', () => {
+			const file = createNativeFileMock();
+			const loader = fileRepository.createLoader( file );
+
+			expect( fileRepository.getLoader( file ) ).to.equal( loader );
+		} );
+	} );
+
+	describe( 'destroyLoader', () => {
+		let file, loader, destroySpy;
+
+		beforeEach( () => {
+			file = createNativeFileMock();
+			loader = fileRepository.createLoader( file );
+			destroySpy = testUtils.sinon.spy( loader, '_destroy' );
+		} );
+
+		it( 'should destroy provided loader', () => {
+			fileRepository.destroyLoader( loader );
+
+			sinon.assert.calledOnce( destroySpy );
+			expect( fileRepository.getLoader( file ) ).to.be.null;
+		} );
+
+		it( 'should destroy loader by provided file', () => {
+			fileRepository.destroyLoader( file );
+
+			sinon.assert.calledOnce( destroySpy );
+			expect( fileRepository.getLoader( file ) ).to.be.null;
+		} );
+	} );
+
+	describe( 'Loader', () => {
+		let loader, file, nativeReaderMock;
+
+		beforeEach( () => {
+			testUtils.sinon.stub( window, 'FileReader', () => {
+				nativeReaderMock = new NativeFileReaderMock();
+
+				return nativeReaderMock;
+			} );
+
+			file = createNativeFileMock();
+			loader = fileRepository.createLoader( file );
+		} );
+
+		describe( 'constructor', () => {
+			it( 'should initialize id', () => {
+				expect( loader.id ).to.be.number;
+			} );
+
+			it( 'should initialize file', () => {
+				expect( loader.file ).to.equal( file );
+			} );
+
+			it( 'should initialize adapter', () => {
+				expect( loader._adapter ).to.equal( adapterMock );
+			} );
+
+			it( 'should initialize reader', () => {
+				expect( loader._reader ).to.be.instanceOf( FileReader );
+			} );
+
+			it( 'should initialize status observable', ( done ) => {
+				expect( loader.status ).to.equal( 'idle' );
+
+				loader.on( 'change:status', ( evt, name, value ) => {
+					expect( value ).to.equal( 'uploading' );
+					done();
+				} );
+
+				loader.status = 'uploading';
+			} );
+
+			it( 'should initialize uploaded observable', ( done ) => {
+				expect( loader.uploaded ).to.equal( 0 );
+
+				loader.on( 'change:uploaded', ( evt, name, value ) => {
+					expect( value ).to.equal( 100 );
+					done();
+				} );
+
+				loader.uploaded = 100;
+			} );
+
+			it( 'should initialize uploadTotal observable', ( done ) => {
+				expect( loader.uploadTotal ).to.equal( null );
+
+				loader.on( 'change:uploadTotal', ( evt, name, value ) => {
+					expect( value ).to.equal( 100 );
+					done();
+				} );
+
+				loader.uploadTotal = 100;
+			} );
+
+			it( 'should initialize uploadedPercent observable', ( done ) => {
+				expect( loader.uploadedPercent ).to.equal( 0 );
+
+				loader.on( 'change:uploadedPercent', ( evt, name, value ) => {
+					expect( value ).to.equal( 23 );
+					done();
+				} );
+
+				loader.uploadedPercent = 23;
+			} );
+
+			it( 'should initialize uploadResponse observable', ( done ) => {
+				expect( loader.uploadResponse ).to.equal( null );
+
+				loader.on( 'change:uploadResponse', ( evt, name, value ) => {
+					expect( value ).to.equal( response );
+					done();
+				} );
+
+				const response = {};
+				loader.uploadResponse = response;
+			} );
+		} );
+
+		describe( 'read', () => {
+			it( 'should throw error when status is defferent than idle', () => {
+				loader.status = 'uploading';
+
+				expect( () => {
+					loader.read();
+				} ).to.throw( 'filerepository-read-wrong-status: You cannot call read if the status is different than idle.' );
+			} );
+
+			it( 'should return a promise', () => {
+				expect( loader.read() ).to.be.instanceof( Promise );
+			} );
+
+			it( 'should set status to "reading"', () => {
+				loader.read();
+
+				expect( loader.status ).to.equal( 'reading' );
+			} );
+
+			it( 'should reject promise when reading is aborted', () => {
+				const promise = loader.read().catch( e => {
+					expect( e ).to.equal( 'aborted' );
+					expect( loader.status ).to.equal( 'aborted' );
+				} );
+
+				loader.abort();
+
+				return promise;
+			} );
+
+			it( 'should reject promise on reading error', () => {
+				const promise = loader.read().catch( e => {
+					expect( e ).to.equal( 'reading error' );
+					expect( loader.status ).to.equal( 'error' );
+				} );
+
+				nativeReaderMock.mockError( 'reading error' );
+
+				return promise;
+			} );
+
+			it( 'should resolve promise on reading complete', () => {
+				const promise = loader.read()
+					.then( data => {
+						expect( data ).to.equal( 'result data' );
+						expect( loader.status ).to.equal( 'idle' );
+					} );
+
+				nativeReaderMock.mockSuccess( 'result data' );
+
+				return promise;
+			} );
+		} );
+
+		describe( 'upload', () => {
+			it( 'should throw error when status is defferent than idle', () => {
+				loader.status = 'reading';
+
+				expect( () => {
+					loader.upload();
+				} ).to.throw( 'filerepository-upload-wrong-status: You cannot call upload if the status is different than idle.' );
+			} );
+
+			it( 'should return a promise', () => {
+				expect( loader.upload() ).to.be.instanceof( Promise );
+			} );
+
+			it( 'should set status to "uploading"', () => {
+				loader.upload();
+
+				expect( loader.status ).to.equal( 'uploading' );
+			} );
+
+			it( 'should reject promise when uploading is aborted', () => {
+				const promise = loader.upload().catch( e => {
+					expect( e ).to.equal( 'aborted' );
+					expect( loader.status ).to.equal( 'aborted' );
+				} );
+
+				loader.abort();
+
+				return promise;
+			} );
+
+			it( 'should reject promise on reading error', () => {
+				const promise = loader.upload().catch( e => {
+					expect( e ).to.equal( 'uploading error' );
+					expect( loader.status ).to.equal( 'error' );
+				} );
+
+				adapterMock.mockError( 'uploading error' );
+
+				return promise;
+			} );
+
+			it( 'should resolve promise on reading complete', () => {
+				const promise = loader.upload()
+					.then( data => {
+						expect( data ).to.equal( 'result data' );
+						expect( loader.status ).to.equal( 'idle' );
+					} );
+
+				adapterMock.mockSuccess( 'result data' );
+
+				return promise;
+			} );
+
+			it( 'should monitor upload progress', () => {
+				const promise = loader.upload()
+					.then( data => {
+						expect( data ).to.equal( 'result data' );
+						expect( loader.status ).to.equal( 'idle' );
+					} );
+
+				expect( loader.uploaded ).to.equal( 0 );
+				expect( loader.uploadTotal ).to.be.null;
+
+				adapterMock.mockProgress( 1, 10 );
+				expect( loader.uploaded ).to.equal( 1 );
+				expect( loader.uploadTotal ).to.equal( 10 );
+
+				adapterMock.mockProgress( 6, 10 );
+				expect( loader.uploaded ).to.equal( 6 );
+				expect( loader.uploadTotal ).to.equal( 10 );
+
+				adapterMock.mockSuccess( 'result data' );
+
+				return promise;
+			} );
+		} );
+	} );
+} );