Procházet zdrojové kódy

Added tests to ImageUploadProgress plugin.

Szymon Kupś před 8 roky
rodič
revize
8833810f89

+ 3 - 1
packages/ckeditor5-upload/src/imageuploadengine.js

@@ -97,6 +97,8 @@ export default class ImageUploadEngine extends Plugin {
 			.then( data => {
 				const viewFigure = editor.editing.mapper.toViewElement( imageElement );
 				const viewImg = viewFigure.getChild( 0 );
+				const promise = loader.upload();
+
 				viewImg.setAttribute( 'src', data );
 				editor.editing.view.render();
 
@@ -104,7 +106,7 @@ export default class ImageUploadEngine extends Plugin {
 					batch.setAttribute( imageElement, 'uploadStatus', 'uploading' );
 				} );
 
-				return loader.upload();
+				return promise;
 			} )
 			.then( data => {
 				doc.enqueueChanges( () => {

+ 61 - 60
packages/ckeditor5-upload/src/imageuploadprogress.js

@@ -36,66 +36,69 @@ export default class ImageUploadProgress extends Plugin {
 
 	init() {
 		const editor = this.editor;
+
+		// Upload status change - update image's view according to that status.
+		editor.editing.modelToView.on( 'addAttribute:uploadStatus:image', ( ...args ) => this.uploadStatusChange( ...args ) );
+		editor.editing.modelToView.on( 'changeAttribute:uploadStatus:image', ( ...args ) => this.uploadStatusChange( ...args ) );
+	}
+
+	/**
+	 * This ethod is called each time image's `uploadStatus` attribute is changed.
+	 *
+	 * @param {module:utils/eventinfo~EventInfo} evt Object containing information about the fired event.
+	 * @param {Object} data Additional information about the change.
+	 * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
+	 */
+	uploadStatusChange( evt, data, consumable ) {
+		const editor = this.editor;
 		const fileRepository = editor.plugins.get( FileRepository );
 		const placeholder = this.placeholder;
 
-		// Upload status change - update image's view according to that status.
-		editor.editing.modelToView.on( 'addAttribute:uploadStatus:image', uploadStatusChange );
-		editor.editing.modelToView.on( 'changeAttribute:uploadStatus:image', uploadStatusChange );
-
-		// Called each time uploadStatus attribute is added or changed.
-		function uploadStatusChange( evt, data, consumable ) {
-			if ( !consumable.consume( data.item, eventNameToConsumableType( evt.name ) ) ) {
-				return;
-			}
-
-			const status = data.attributeNewValue;
-			const modelImage = data.item;
-			const viewFigure = editor.editing.mapper.toViewElement( modelImage );
-			const uploadId = modelImage.getAttribute( 'uploadId' );
-
-			if ( !uploadId ) {
-				return;
-			}
-
-			// Show placeholder with infinite progress bar on the top while image is read from disk.
-			if ( status == 'reading' ) {
-				viewFigure.addClass( 'ck-appear', 'ck-infinite-progress' );
-				const viewImg = viewFigure.getChild( 0 );
-				viewImg.setAttribute( 'src', placeholder );
-
-				return;
-			}
-
-			// Show progress bar on the top of the image when image is uploading.
-			if ( status == 'uploading' ) {
-				viewFigure.removeClass( 'ck-infinite-progress' );
-				const progressBar = createProgressBar();
-				viewFigure.appendChildren( progressBar );
-				const loader = fileRepository.loaders.get( uploadId );
-
-				// Update progress bar width when uploadedPercent is changed.
-				loader.on( 'change:uploadedPercent', ( evt, name, value ) => {
-					progressBar.setStyle( 'width', value + '%' );
-					editor.editing.view.render();
-				} );
-
-				return;
-			}
-
-			// Hide progress bar and clean up classes.
-			if ( status == 'complete' ) {
-				const viewFigure = editor.editing.mapper.toViewElement( modelImage );
-				const progressBar = getProgressBar( viewFigure );
-
-				viewFigure.removeClass( 'ck-appear' );
-
-				if ( progressBar ) {
-					progressBar.remove();
-					editor.editing.view.render();
-				}
-			}
+		if ( !consumable.consume( data.item, eventNameToConsumableType( evt.name ) ) ) {
+			return;
+		}
+
+		const status = data.attributeNewValue;
+		const modelImage = data.item;
+		const viewFigure = editor.editing.mapper.toViewElement( modelImage );
+		const uploadId = modelImage.getAttribute( 'uploadId' );
+		const loader = fileRepository.loaders.get( uploadId );
+
+		if ( !loader ) {
+			return;
+		}
+
+		// Show placeholder with infinite progress bar on the top while image is read from disk.
+		if ( status == 'reading' ) {
+			viewFigure.addClass( 'ck-appear', 'ck-infinite-progress' );
+			const viewImg = viewFigure.getChild( 0 );
+			viewImg.setAttribute( 'src', placeholder );
+
+			return;
+		}
+
+		// Show progress bar on the top of the image when image is uploading.
+		if ( status == 'uploading' ) {
+			viewFigure.removeClass( 'ck-infinite-progress' );
+			const progressBar = createProgressBar();
+			viewFigure.appendChildren( progressBar );
+
+			// Update progress bar width when uploadedPercent is changed.
+			loader.on( 'change:uploadedPercent', ( evt, name, value ) => {
+				progressBar.setStyle( 'width', value + '%' );
+				editor.editing.view.render();
+			} );
+
+			return;
 		}
+
+		// Hide progress bar and clean up classes.
+		const progressBar = getProgressBar( viewFigure );
+
+		viewFigure.removeClass( 'ck-appear' );
+		progressBar.remove();
+
+		editor.editing.view.render();
 	}
 }
 
@@ -113,18 +116,16 @@ function createProgressBar() {
 	return progressBar;
 }
 
-// Returns progress bar {@link module:engine/view/uielement~UIElement} from image figure element. Returns `null` if
+// Returns progress bar {@link module:engine/view/uielement~UIElement} from image figure element. Returns `undefined` if
 // progress bar element is not found.
 //
 // @private
 // @param {module:engine/view/element~Element} imageFigure
-// @returns {module:engine/view/uielement~UIElement|null}
+// @returns {module:engine/view/uielement~UIElement|undefined}
 function getProgressBar( imageFigure ) {
 	for ( const child of imageFigure.getChildren() ) {
 		if ( child.getCustomProperty( progressBarSymbol ) ) {
 			return child;
 		}
 	}
-
-	return null;
 }

+ 8 - 30
packages/ckeditor5-upload/tests/imageuploadengine.js

@@ -15,7 +15,6 @@ import FileRepository from '../src/filerepository';
 import { AdapterMock, createNativeFileMock, NativeFileReaderMock } from './_utils/mocks';
 import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
-import imagePlaceholder from '../theme/icons/image_placeholder.svg';
 import { eventNameToConsumableType } from '@ckeditor/ckeditor5-engine/src/conversion/model-to-view-converters';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
@@ -71,7 +70,7 @@ describe( 'ImageUploadEngine', () => {
 
 		const id = fileRepository.getLoader( fileMock ).id;
 		expect( getModelData( document ) ).to.equal(
-			`[<image uploadId="${ id }"></image>]<paragraph>foo bar baz</paragraph>`
+			`[<image uploadId="${ id }" uploadStatus="reading"></image>]<paragraph>foo bar baz</paragraph>`
 		);
 	} );
 
@@ -90,16 +89,6 @@ describe( 'ImageUploadEngine', () => {
 		sinon.assert.notCalled( spy );
 	} );
 
-	it( 'should convert image\'s uploadId attribute from model to view', () => {
-		setModelData( document, '<image uploadId="1234"></image>' );
-
-		expect( getViewData( viewDocument ) ).to.equal(
-			'[]<figure class="image ck-widget" contenteditable="false">' +
-				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
-			'</figure>'
-		);
-	} );
-
 	it( 'should not convert image\'s uploadId attribute if is consumed already', () => {
 		editor.editing.modelToView.on( 'addAttribute:uploadId:image', ( evt, data, consumable ) => {
 			consumable.consume( data.item, eventNameToConsumableType( evt.name ) );
@@ -113,20 +102,21 @@ describe( 'ImageUploadEngine', () => {
 			'</figure>' );
 	} );
 
-	it( 'should replace placeholder with read data once it is present', ( done ) => {
+	it( 'should use read data once it is present', ( done ) => {
 		const file = createNativeFileMock();
 		setModelData( document, '<paragraph>{}foo bar</paragraph>' );
 		editor.execute( 'imageUpload', { file } );
 
-		adapterMock.uploadStartedCallback = () => {
+		document.once( 'changesDone', () => {
 			expect( getViewData( viewDocument ) ).to.equal(
 				'[<figure class="image ck-widget" contenteditable="false">' +
-					`<img src="${ base64Sample }"></img>` +
+				`<img src="${ base64Sample }"></img>` +
 				'</figure>]' +
 				'<p>foo bar</p>' );
 			expect( loader.status ).to.equal( 'uploading' );
+
 			done();
-		};
+		} );
 
 		expect( loader.status ).to.equal( 'reading' );
 		nativeReaderMock.mockSuccess( base64Sample );
@@ -137,7 +127,7 @@ describe( 'ImageUploadEngine', () => {
 		setModelData( document, '<paragraph>{}foo bar</paragraph>' );
 		editor.execute( 'imageUpload', { file } );
 
-		adapterMock.uploadStartedCallback = () => {
+		document.once( 'changesDone', () => {
 			document.once( 'changesDone', () => {
 				expect( getViewData( viewDocument ) ).to.equal(
 					'[<figure class="image ck-widget" contenteditable="false"><img src="image.png"></img></figure>]<p>foo bar</p>'
@@ -148,7 +138,7 @@ describe( 'ImageUploadEngine', () => {
 			} );
 
 			adapterMock.mockSuccess( { original: 'image.png' } );
-		};
+		} );
 
 		nativeReaderMock.mockSuccess( base64Sample );
 	} );
@@ -197,16 +187,4 @@ describe( 'ImageUploadEngine', () => {
 			'[]<figure class="image ck-widget" contenteditable="false"><img src="image.png"></img></figure>'
 		);
 	} );
-
-	it( 'should allow to customize placeholder image', () => {
-		const uploadEngine = editor.plugins.get( ImageUploadEngine );
-		uploadEngine.placeholder = base64Sample;
-		setModelData( document, '<image uploadId="1234"></image>' );
-
-		expect( getViewData( viewDocument ) ).to.equal(
-			'[]<figure class="image ck-widget" contenteditable="false">' +
-			`<img src="${ base64Sample }"></img>` +
-			'</figure>'
-		);
-	} );
 } );

+ 155 - 0
packages/ckeditor5-upload/tests/imageuploadprogress.js

@@ -0,0 +1,155 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals window */
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import ImageEngine from '@ckeditor/ckeditor5-image/src/image/imageengine';
+import ImageUploadEngine from '../src/imageuploadengine';
+import ImageUploadProgress from '../src/imageuploadprogress';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import FileRepository from '../src/filerepository';
+import { AdapterMock, createNativeFileMock, NativeFileReaderMock } from './_utils/mocks';
+import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
+import { eventNameToConsumableType } from '@ckeditor/ckeditor5-engine/src/conversion/model-to-view-converters';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import imagePlaceholder from '../theme/icons/image_placeholder.svg';
+
+describe( 'ImageUploadProgress', () => {
+	const base64Sample = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=';
+	let editor, document, fileRepository, viewDocument, nativeReaderMock, loader, adapterMock;
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		testUtils.sinon.stub( window, 'FileReader', () => {
+			nativeReaderMock = new NativeFileReaderMock();
+
+			return nativeReaderMock;
+		} );
+
+		return ClassicTestEditor.create( {
+			plugins: [ ImageEngine, ImageUploadEngine, Paragraph, ImageUploadProgress ]
+		} )
+		.then( newEditor => {
+			editor = newEditor;
+			document = editor.document;
+			viewDocument = editor.editing.view;
+
+			fileRepository = editor.plugins.get( FileRepository );
+			fileRepository.createAdapter = newLoader => {
+				loader = newLoader;
+				adapterMock = new AdapterMock( loader );
+
+				return adapterMock;
+			};
+		} );
+	} );
+
+	it( 'should convert image\'s "reading" uploadStatus attribute', () => {
+		setModelData( document, '<paragraph>foo[]</paragraph>' );
+		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
+
+		expect( getViewData( viewDocument ) ).to.equal(
+			'[<figure class="image ck-widget ck-appear ck-infinite-progress" contenteditable="false">' +
+				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
+			'</figure>]<p>foo</p>'
+		);
+	} );
+
+	it( 'should convert image\'s "uploading" uploadStatus attribute', ( done ) => {
+		setModelData( document, '<paragraph>foo[]</paragraph>' );
+		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
+
+		document.once( 'changesDone', () => {
+			expect( getViewData( viewDocument ) ).to.equal(
+				'[<figure class="image ck-widget ck-appear" contenteditable="false">' +
+					`<img src="${ base64Sample }"></img>` +
+					'<div class="ck-progress-bar"></div>' +
+				'</figure>]<p>foo</p>'
+			);
+
+			done();
+		} );
+
+		nativeReaderMock.mockSuccess( base64Sample );
+	} );
+
+	it( 'should update progressbar width on progress', ( done ) => {
+		setModelData( document, '<paragraph>foo[]</paragraph>' );
+		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
+
+		document.once( 'changesDone', () => {
+			adapterMock.mockProgress( 40, 100 );
+
+			expect( getViewData( viewDocument ) ).to.equal(
+				'[<figure class="image ck-widget ck-appear" contenteditable="false">' +
+				`<img src="${ base64Sample }"></img>` +
+				'<div class="ck-progress-bar" style="width:40%;"></div>' +
+				'</figure>]<p>foo</p>'
+			);
+
+			done();
+		} );
+
+		nativeReaderMock.mockSuccess( base64Sample );
+	} );
+
+	it( 'should convert image\'s "complete" uploadStatus attribute', ( done ) => {
+		setModelData( document, '<paragraph>foo[]</paragraph>' );
+		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
+
+		document.once( 'changesDone', () => {
+			document.once( 'changesDone', () => {
+				expect( getViewData( viewDocument ) ).to.equal(
+					'[<figure class="image ck-widget" contenteditable="false">' +
+						'<img src="image.png"></img>' +
+					'</figure>]<p>foo</p>'
+				);
+
+				done();
+			} );
+
+			adapterMock.mockSuccess( { original: 'image.png' } );
+		} );
+
+		nativeReaderMock.mockSuccess( base64Sample );
+	} );
+
+	it( 'should allow to customize placeholder image', () => {
+		const uploadProgress = editor.plugins.get( ImageUploadProgress );
+		uploadProgress.placeholder = base64Sample;
+
+		setModelData( document, '<paragraph>foo[]</paragraph>' );
+		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
+
+		expect( getViewData( viewDocument ) ).to.equal(
+			'[<figure class="image ck-widget ck-appear ck-infinite-progress" contenteditable="false">' +
+				`<img src="${ base64Sample }"></img>` +
+			'</figure>]<p>foo</p>'
+		);
+	} );
+
+	it( 'should not process attribute change if it is already consumed', () => {
+		editor.editing.modelToView.on( 'addAttribute:uploadStatus:image', ( evt, data, consumable ) => {
+			consumable.consume( data.item, eventNameToConsumableType( evt.name ) );
+		}, { priority: 'highest' } );
+
+		setModelData( document, '<paragraph>foo[]</paragraph>' );
+		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
+
+		expect( getViewData( viewDocument ) ).to.equal(
+			'[<figure class="image ck-widget" contenteditable="false"><img></img></figure>]<p>foo</p>'
+		);
+	} );
+
+	it( 'should not process attribute change if there is wrong uploadId', () => {
+		setModelData( document, '<image uploadId="123" uploadStatus="reading"></image><paragraph>foo{}</paragraph>' );
+
+		expect( getViewData( viewDocument ) ).to.equal(
+			'<figure class="image ck-widget" contenteditable="false"><img></img></figure><p>foo{}</p>'
+		);
+	} );
+} );