From 7a2f0ccbb80fce2cb1d8087826d78c6a9efc1aad Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 16 Jan 2018 23:39:50 +0100 Subject: [PATCH 01/78] First mocks to get the new upload working. So far it's messy PoC concept with some logs and ugly commented code. --- plugins/easyimage/plugin.js | 6 ++- plugins/imagebase/plugin.js | 83 +++++++++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/plugins/easyimage/plugin.js b/plugins/easyimage/plugin.js index 966e67dbdcd..60041134995 100644 --- a/plugins/easyimage/plugin.js +++ b/plugins/easyimage/plugin.js @@ -148,11 +148,13 @@ img: { attributes: '!src,srcset,alt,width,sizes' - } + }, }, requiredContent: 'figure; img[!src]', + supportedTypes: /image\/(jpeg|png|gif|bmp)/, + upcasts: { figure: function( element ) { if ( ( !figureClass || element.hasClass( figureClass ) ) && @@ -198,6 +200,8 @@ widgetDefinition = CKEDITOR.plugins.imagebase.addFeature( editor, 'link', widgetDefinition ); } + widgetDefinition = CKEDITOR.plugins.imagebase.addFeature( editor, 'upload', widgetDefinition ); + CKEDITOR.plugins.imagebase.addImageWidget( editor, 'easyimage', widgetDefinition ); } diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 0ca8701c990..e5f3810ab4e 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -186,17 +186,83 @@ }; } + function getUploadFeature() { + var ret = { + setUp: function( editor, definition ) { + console.log( 'added' ); + editor.on( 'paste', function( evt ) { + if ( evt.data.method === 'drop' ) { + console.log( 'dropped' ); + + var dataTransfer = evt.data.dataTransfer, + filesCount = dataTransfer.getFilesCount(), + blobUrls = [], + files = [], + curFile; + + console.log( definition ); + + for ( var i = 0; i < filesCount; i++ ) { + curFile = dataTransfer.getFile( i ); + + if ( CKEDITOR.fileTools.isTypeSupported( curFile, definition.supportedTypes ) ) { + files.push( curFile ); + blobUrls.push( URL.createObjectURL( curFile ) ); + } + } + + // Refetch the definition... original definition looks like an outdated copy, it doesn't things inherited form imagebase. + definition = editor.widgets.registered.easyimage; + + if ( files.length ) { + evt.cancel(); + // This should not be required, let's leave it for development time to make sure + // that nothing else affects the listeners: + evt.stop(); + + console.log( 'inserting the widget' ); + ret._insertWidget( editor, definition, files[ 0 ], blobUrls[ 0 ] ); + + // @todo: make sure balloon toolbar is repositioned once img[src="blob:*"] is loaded or at least its height is available. + } + + } else { + console.log( 'unsupported ' + evt.data.method + ' method.' ); + } + } ); + }, + _insertWidget: function( editor, widgetDef, file, blobUrl ) { + var defaults = ( typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults ) || { + src: blobUrl, + alt: '', + caption: '' + }, + element = CKEDITOR.dom.element.createFromHtml( widgetDef.template.output( defaults ) ), + wrapper = editor.widgets.wrapElement( element, widgetDef.name ), + temp = new CKEDITOR.dom.documentFragment( wrapper.getDocument() ), + instance; + + // Append wrapper to a temporary document. This will unify the environment + // in which #data listeners work when creating and editing widget. + temp.append( wrapper ); + instance = editor.widgets.initOn( element, widgetDef ); + + editor.widgets.finalizeCreation( temp ); + + console.log( 'done' ); + } + }; + + return ret; + } + var featuresDefinitions = { + upload: getUploadFeature(), link: getLinkFeature() }; function createWidgetDefinition( editor, definition ) { - var defaultTemplate = new CKEDITOR.template( - '
' + - '' + - '
{captionPlaceholder}
' + - '
' ), - baseDefinition; + var baseDefinition; /** * This is an abstract class that describes a definition of a basic image widget @@ -215,7 +281,10 @@ baseDefinition = { pathName: editor.lang.imagebase.pathName, - template: defaultTemplate, + template: '
' + + '{alt}' + + '
{captionPlaceholder}
' + + '
', allowedContent: { img: { From 7adddabe4e353c79bb7ed9ce50eeadcc3229553f Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 17 Jan 2018 01:01:25 +0100 Subject: [PATCH 02/78] Another round of this fabolous (and dirty) PoC to bring reusable widget upload feature. --- plugins/easyimage/plugin.js | 2 + plugins/imagebase/plugin.js | 121 ++++++++++++++++++++++++++++++++---- plugins/widget/plugin.js | 2 + 3 files changed, 113 insertions(+), 12 deletions(-) diff --git a/plugins/easyimage/plugin.js b/plugins/easyimage/plugin.js index 60041134995..8e934a390bb 100644 --- a/plugins/easyimage/plugin.js +++ b/plugins/easyimage/plugin.js @@ -155,6 +155,8 @@ supportedTypes: /image\/(jpeg|png|gif|bmp)/, + loaderType: CKEDITOR.plugins.cloudservices.cloudServicesLoader, + upcasts: { figure: function( element ) { if ( ( !figureClass || element.hasClass( figureClass ) ) && diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index e5f3810ab4e..9149511d4b0 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -187,6 +187,21 @@ } function getUploadFeature() { + // Natural width of the image can be fetched only after image is loaded. + // However cached images won't fire `load` event, but just mark themselves + // as complete. + function getNaturalWidth( image, callback ) { + var $image = image.$; + + if ( $image.complete && $image.naturalWidth ) { + return callback( $image.naturalWidth ); + } + + image.once( 'load', function() { + callback( $image.naturalWidth ); + } ); + } + var ret = { setUp: function( editor, definition ) { console.log( 'added' ); @@ -221,9 +236,12 @@ evt.stop(); console.log( 'inserting the widget' ); - ret._insertWidget( editor, definition, files[ 0 ], blobUrls[ 0 ] ); + var widgetInstance = ret._insertWidget( editor, definition, files[ 0 ], blobUrls[ 0 ] ); + + ret._loadWidget( editor, widgetInstance, definition, files[ 0 ] ); // @todo: make sure balloon toolbar is repositioned once img[src="blob:*"] is loaded or at least its height is available. + // @todo: handle more than one dropped image } } else { @@ -231,13 +249,87 @@ } } ); }, + + init: function() { + // @todo: this code should be actually moved to easyimage (core) widget init function, as it's a EI plugin responsibility + // to tell exactly how the image should be loaded. + function setImageWidth( widget, height ) { + if ( !widget.parts.image.hasAttribute( 'width' ) ) { + widget.editor.fire( 'lockSnapshot' ); + + widget.parts.image.setAttribute( 'width', height ); + + widget.editor.fire( 'unlockSnapshot' ); + } + } + + this.on( 'uploadDone', function( evt ) { + var loader = evt.data.sender, + resp = loader.responseData.response; + + var srcset = CKEDITOR.plugins.easyimage._parseSrcSet( resp ), + widget = this; + + widget.parts.image.setAttributes( { + src: resp[ 'default' ], + srcset: srcset, + sizes: '100vw', + // @todo: currently there's a race condition, if the with has not been fetched for `img[blob:*]` it will not be set. + width: widget.parts.image.getAttribute( 'width' ) + } ); + + console.log( 'updated the image' ); + } ); + + this.on( 'uploadBegan', function() { + var widget = this; + // Attempt to pick width from the img[src="blob:*"]. + getNaturalWidth( widget.parts.image, function( width ) { + setImageWidth( widget, width ); + } ); + } ); + }, + + _loadWidget: function( editor, widget, def, file ) { + var uploads = editor.uploadRepository, + loadMethod = def.loadMethod || 'loadAndUpload', + loader = uploads.create( file, undefined, def.loaderType ); + + function failHandling( evt ) { + console.warn( 'Could not load Easy Image widget', evt ); + if ( widget.fire( 'uploadError', evt ) !== false ) { + widget.destroy( true ); + } + } + + function uploadComplete( evt ) { + console.log( 'all good, image uploaded' ); + + widget.fire( 'uploadDone', evt ); + } + + loader.on( 'abort', failHandling ); + loader.on( 'error', failHandling ); + loader.on( 'uploaded', uploadComplete ); + + loader[ loadMethod ]( def.uploadUrl, def.additionalRequestParameters ); + + widget.fire( 'uploadBegan', loader ); + + // @todo: It make sense to mark the widget at this point as incomplete. Similarly as fileTools.markElement does. + + if ( ( loadMethod == 'loadAndUpload' || loadMethod == 'upload' ) && !def.skipNotifications ) { + // Todo: bind notifications. + // CKEDITOR.fileTools.bindNotifications( editor, loader ); + } + }, + _insertWidget: function( editor, widgetDef, file, blobUrl ) { - var defaults = ( typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults ) || { - src: blobUrl, - alt: '', - caption: '' - }, - element = CKEDITOR.dom.element.createFromHtml( widgetDef.template.output( defaults ) ), + var tplParams = ( typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults ) || {}; + + tplParams.src = blobUrl; + + var element = CKEDITOR.dom.element.createFromHtml( widgetDef.template.output( tplParams ) ), wrapper = editor.widgets.wrapElement( element, widgetDef.name ), temp = new CKEDITOR.dom.documentFragment( wrapper.getDocument() ), instance; @@ -247,9 +339,7 @@ temp.append( wrapper ); instance = editor.widgets.initOn( element, widgetDef ); - editor.widgets.finalizeCreation( temp ); - - console.log( 'done' ); + return editor.widgets.finalizeCreation( temp ); } }; @@ -281,9 +371,16 @@ baseDefinition = { pathName: editor.lang.imagebase.pathName, - template: '
' + + defaults: { + imageClass: ( editor.config.easyimage_class || '' ), + alt: '', + src: '', + caption: '' + }, + + template: '
' + '{alt}' + - '
{captionPlaceholder}
' + + '
{caption}
' + '
', allowedContent: { diff --git a/plugins/widget/plugin.js b/plugins/widget/plugin.js index 08ad8da4afe..46c4d2fa7e3 100644 --- a/plugins/widget/plugin.js +++ b/plugins/widget/plugin.js @@ -438,6 +438,8 @@ widget.ready = true; widget.fire( 'ready' ); widget.focus(); + + return widget; } }, From 51a2556ed5bb72a6f58ad1656efc478f2252c16e Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 17 Jan 2018 02:02:09 +0100 Subject: [PATCH 03/78] An attempt to integrate our custom EI progress bar with the upload widget feature. --- plugins/easyimage/plugin.js | 121 ++++++++++++++++++------- plugins/easyimage/styles/easyimage.css | 4 + 2 files changed, 91 insertions(+), 34 deletions(-) diff --git a/plugins/easyimage/plugin.js b/plugins/easyimage/plugin.js index 8e934a390bb..0f1d9eda4e8 100644 --- a/plugins/easyimage/plugin.js +++ b/plugins/easyimage/plugin.js @@ -176,6 +176,22 @@ if ( editor.config.easyimage_class ) { this.addClass( editor.config.easyimage_class ); } + + if ( !this.definition._createProgressBar ) { + // Do it only once. + mixinProgressBarToWidgetDef( this.definition ); + } + + this.on( 'uploadBegan', function( evt ) { + attachProgressBarToLoader( evt.data, this ); + } ); + + this.once( 'uploadDone', function( evt ) { + if ( this.parts.progressBar ) { + this.parts.progressBar.remove(); + this.parts.progressBar = null; + } + } ); }, data: function( evt ) { @@ -315,6 +331,74 @@ } ); } + function mixinProgressBarToWidgetDef( definition ) { + definition.parts.loader = '.cke_loader'; + + /* + * Creates a progress bar in a given widget. + * + * Also puts it in it's {@link CKEDITOR.plugins.widget#parts} structure as `progressBar` + * + * @private + * @param {CKEDITOR.plugins.widget} widget + */ + definition._createProgressBar = function( widget, progressBarWrapper ) { + progressBarWrapper = progressBarWrapper || widget.element; + + widget.parts.progressBar = CKEDITOR.dom.element.createFromHtml( '
' + + '
' + + '
' ); + + progressBarWrapper.append( widget.parts.progressBar, true ); + }; + } + + /* + * Attaches a progress bar to a given loader. + * + * @param {CKEDITOR.fileTools.fileLoader} loader + * @param {CKEDITOR.plugins.widget} widget + */ + function attachProgressBarToLoader( loader, widget ) { + var progressListeners = []; + + // Add a progress bar. + widget.definition._createProgressBar( widget ); + + function removeProgressListeners() { + if ( progressListeners ) { + CKEDITOR.tools.array.forEach( progressListeners, function( listener ) { + listener.removeListener(); + } ); + + progressListeners = null; + } + } + + var updateListener = CKEDITOR.tools.eventsBuffer( UPLOAD_PROGRESS_THROTTLING, function() { + if ( !widget.parts.progressBar ) { + return; + } + + var progressBar = widget.parts.progressBar.findOne( '.cke_bar' ), + percentage; + + if ( progressBar && loader.uploadTotal ) { + percentage = ( loader.uploaded / loader.uploadTotal ) * 100; + + widget.editor.fire( 'lockSnapshot' ); + progressBar.setStyle( 'width', percentage + '%' ); + widget.editor.fire( 'unlockSnapshot' ); + } + }, widget ); + + progressListeners.push( loader.on( 'update', updateListener.input ) ); + + progressListeners.push( loader.once( 'abort', removeProgressListeners ) ); + progressListeners.push( loader.once( 'error', removeProgressListeners ) ); + progressListeners.push( loader.once( 'uploaded', removeProgressListeners ) ); + } + // Extends given uploadWidget `definition` with an upload progress bar, added within wrapper. function addUploadProgressBar( editor, definition ) { definition.skipNotifications = true; @@ -330,7 +414,7 @@ */ definition._createProgressBar = function( widget ) { widget.parts.progressBar = CKEDITOR.dom.element.createFromHtml( '
' + - '
' + + '
' + '
' ); widget.wrapper.append( widget.parts.progressBar, true ); }; @@ -344,40 +428,9 @@ baseInit = definition.init; definition.init = function() { - var loader = this._getLoader( this ), - progressListeners = []; - - function removeProgressListeners() { - if ( progressListeners ) { - CKEDITOR.tools.array.forEach( progressListeners, function( listener ) { - listener.removeListener(); - } ); - - progressListeners = null; - } - } - - // Add a progress bar. - this.definition._createProgressBar( this ); - - var updateListener = CKEDITOR.tools.eventsBuffer( UPLOAD_PROGRESS_THROTTLING, function() { - var progressBar = this.parts.progressBar.findOne( '.cke_bar' ), - percentage; - - if ( progressBar && loader.uploadTotal ) { - percentage = ( loader.uploaded / loader.uploadTotal ) * 100; - - editor.fire( 'lockSnapshot' ); - progressBar.setStyle( 'width', percentage + '%' ); - editor.fire( 'unlockSnapshot' ); - } - }, this ); - - progressListeners.push( loader.on( 'update', updateListener.input ) ); + var loader = this._getLoader( this ); - progressListeners.push( loader.once( 'abort', removeProgressListeners ) ); - progressListeners.push( loader.once( 'error', removeProgressListeners ) ); - progressListeners.push( loader.once( 'uploaded', removeProgressListeners ) ); + attachProgressBarToLoader( this, loader ); // Call base init implementation. baseInit.call( this ); diff --git a/plugins/easyimage/styles/easyimage.css b/plugins/easyimage/styles/easyimage.css index bb6997dab89..3942117c690 100644 --- a/plugins/easyimage/styles/easyimage.css +++ b/plugins/easyimage/styles/easyimage.css @@ -57,6 +57,10 @@ The outline is not a part of the element's dimensions, we have to use a border a /* Loaders */ +.cke_widget_element.easyimage { + position: relative; +} + .cke_loader { position: absolute; left: 0px; From 40288769ed47aaf8fc9a9b41e063c760ec44224e Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 17 Jan 2018 23:35:02 +0100 Subject: [PATCH 04/78] Added a ProgressBar type, so far it's going to be placed in Easy Image plugin.It's going to be extracted later. This type will be passed into Upload Widget Feature. The Idea is to have the ability to implement a different progress indicators for other widgets. --- plugins/imagebase/plugin.js | 82 +++++++++++- tests/plugins/easyimage/balloontoolbar.js | 152 ++++++++++++++++++++++ tests/plugins/imagebase/progressbar.html | 26 ++++ 3 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 tests/plugins/easyimage/balloontoolbar.js create mode 100644 tests/plugins/imagebase/progressbar.html diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 9149511d4b0..ff84d4b6823 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -437,6 +437,84 @@ return definition; } + var UPLOAD_PROGRESS_THROTTLING = 100; + + /** + * This is a base class for progress bars. + * + * Progress bars could be updated: + * + * * Automatically, by binding it to a existing {@link CKEDITOR.fileTools.fileLoader} instance. + * * Manually, using {@link #updated}, {@link #done}, {@link #failed} and {@link #aborted} methods. + * + * @class CKEDITOR.plugins.imagebase.progressBar + * @constructor + */ + function ProgressBar() { + /** + * @property {CKEDITOR.dom.element} wrapper An element created for wrapping the progress bar. + */ + this.wrapper = CKEDITOR.dom.element.createFromHtml( '
' + + '
' + + '
' ); + + this.bar = this.wrapper.getFirst(); + } + + /** + * @param {CKEDITOR.dom.element} wrapper Element where the progress bar will be **prepended**. + * @returns {CKEDITOR.plugins.imagebase.progressBar} + */ + ProgressBar.createForElement = function( wrapper ) { + var ret = new ProgressBar(); + + wrapper.append( ret.wrapper, true ); + + return ret; + }; + + ProgressBar.prototype = { + bindToLoader: function( loader ) { + // pass + }, + /** + * Marks a progress on the progress bar. + * + * @param {Number} progress Progress representation where `1.0` is a complete and `0` means no progress. + */ + updated: function( progress ) { + var percentage = Math.round( progress * 100 ); + + percentage = Math.max( percentage, 0 ); + percentage = Math.min( percentage, 100 ); + + // widget.editor.fire( 'lockSnapshot' ); + this.bar.setStyle( 'width', percentage + '%' ); + // widget.editor.fire( 'unlockSnapshot' ); + }, + + /** + * To be called when the progress should be marked as complete. + */ + done: function() { + this.wrapper.remove(); + }, + + /** + * To be called when the progress should be marked as aborted. + */ + aborted: function() { + this.failed(); + }, + + /** + * To be called when the progress should be marked as failed. + */ + failed: function() { + this.wrapper.remove(); + } + }; + CKEDITOR.plugins.add( 'imagebase', { requires: 'widget', lang: 'en' @@ -516,6 +594,8 @@ ret.features.push( name ); return ret; - } + }, + + progressBar: ProgressBar }; }() ); diff --git a/tests/plugins/easyimage/balloontoolbar.js b/tests/plugins/easyimage/balloontoolbar.js new file mode 100644 index 00000000000..c9813063323 --- /dev/null +++ b/tests/plugins/easyimage/balloontoolbar.js @@ -0,0 +1,152 @@ +/* bender-tags: editor,widget */ +/* bender-ckeditor-plugins: easyimage,toolbar,undo */ +/* bender-include: _helpers/tools.js */ +/* global easyImageTools */ + +( function() { + 'use strict'; + + bender.editors = { + classic: {}, + + divarea: { + config: { + extraPlugins: 'divarea' + } + }, + + inline: { + creator: 'inline' + } + }; + + function getEasyImageBalloonContext( editor ) { + return editor.balloonToolbars._contexts[ 0 ]; + } + + // Forces the Balloon Toolbar to be always drawn below the target. + function patchBalloonPositioning( toolbar ) { + var original = toolbar._view._getAlignments; + + toolbar._view._getAlignments = function() { + var ret = original.apply( this, arguments ); + + return { + 'bottom hcenter': ret[ 'bottom hcenter' ] + }; + }; + } + + /* + * Returns an expected balloon Y position for a given widget. + * + * @param {CKEDITOR.plugins.widget} widget + * @returns {Number} + */ + function getExpectedYOffset( widget ) { + var editor = widget.editor, + wrapperRect = widget.element.getClientRect(), + toolbar = getEasyImageBalloonContext( editor ).toolbar, + ret = wrapperRect.bottom + toolbar._view.triangleHeight; + + if ( !editor.editable().isInline() ) { + // In case of classic editor we also need to include position of the editor iframe too. + ret += editor.window.getFrame().getClientRect().top; + } + + return ret; + } + + var testSuiteIframe = CKEDITOR.document.getWindow().getFrame(), + initialFrameHeight = testSuiteIframe && testSuiteIframe.getStyle( 'height' ), + tests = { + setUp: function() { + // This test checks real balloon panel positioning. To avoid affecting position with scroll offset, set the parent iframe height + // enough to contain entire content. Note that iframe is not present if the test suite is open in a separate window, or ran on IEs. + if ( testSuiteIframe ) { + testSuiteIframe.setStyle( 'height', '3000px' ); + } + }, + + tearDown: function() { + if ( testSuiteIframe ) { + testSuiteIframe.setStyle( 'height', initialFrameHeight ); + } + }, + + 'test balloontoolbar integration': function( editor, bot ) { + var widgetHtml = '
foo
Test image
'; + + bot.setData( widgetHtml, function() { + var widget = editor.widgets.getByElement( editor.editable().findOne( 'figure' ) ), + toolbar = getEasyImageBalloonContext( editor ).toolbar; + + toolbar._view.once( 'show', function() { + easyImageTools.assertCommandsState( editor, { + easyimageFull: CKEDITOR.TRISTATE_ON, + easyimageSide: CKEDITOR.TRISTATE_OFF, + easyimageAlt: CKEDITOR.TRISTATE_OFF + } ); + + editor.once( 'afterCommandExec', function() { + resume( function() { + easyImageTools.assertCommandsState( editor, { + easyimageFull: CKEDITOR.TRISTATE_OFF, + easyimageSide: CKEDITOR.TRISTATE_ON, + easyimageAlt: CKEDITOR.TRISTATE_OFF + } ); + } ); + } ); + + editor.execCommand( 'easyimageSide' ); + } ); + + widget.focus(); + wait(); + } ); + }, + + 'test balloontoolbar positioning': function( editor, bot ) { + var source = '
foo
'; + + bot.setData( source, function() { + var widget = editor.widgets.getByElement( editor.editable().findOne( 'figure' ) ), + toolbar = getEasyImageBalloonContext( editor ).toolbar; + + patchBalloonPositioning( toolbar ); + + widget.once( 'focus', function() { + setTimeout( function() { + var expectedY = getExpectedYOffset( widget ), + moveSpy = sinon.spy( toolbar._view, 'move' ); + + widget.parts.caption.focus(); + + widget.focus(); + + setTimeout( function() { + resume( function() { + moveSpy.restore(); + // We care only about y axis. + var actual = moveSpy.args[ 0 ][ 0 ]; + + if ( CKEDITOR.env.ie && CKEDITOR.env.ie <= 11 ) { + // IE11 tends to be off by a fraction of a pixel on high DPI displays. + assert.isNumberInRange( actual, expectedY - 1, expectedY + 1, 'Balloon y position' ); + } else { + assert.areSame( expectedY, actual, 'Balloon y position' ); + } + } ); + }, 0 ); + }, 0 ); + } ); + + widget.focus(); + wait(); + } ); + } + }; + + tests = bender.tools.createTestsForEditors( CKEDITOR.tools.objectKeys( bender.editors ), tests ); + bender.test( tests ); +} )(); diff --git a/tests/plugins/imagebase/progressbar.html b/tests/plugins/imagebase/progressbar.html new file mode 100644 index 00000000000..d1224b35e35 --- /dev/null +++ b/tests/plugins/imagebase/progressbar.html @@ -0,0 +1,26 @@ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
From a6bbe9abdbfa8b9bab41d856a6df08856fcdd725 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 18 Jan 2018 00:41:05 +0100 Subject: [PATCH 05/78] Generic loader integration for ProgressBar type. Added unit tests coverage. --- plugins/imagebase/plugin.js | 49 ++++++- tests/plugins/imagebase/progressbar.js | 194 +++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 tests/plugins/imagebase/progressbar.js diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index ff84d4b6823..e08d7f89149 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -440,6 +440,9 @@ var UPLOAD_PROGRESS_THROTTLING = 100; /** + * + * @TODO: rename type to ProgressIndicator. Bar implies, well... bar. + * * This is a base class for progress bars. * * Progress bars could be updated: @@ -474,9 +477,6 @@ }; ProgressBar.prototype = { - bindToLoader: function( loader ) { - // pass - }, /** * Marks a progress on the progress bar. * @@ -512,7 +512,48 @@ */ failed: function() { this.wrapper.remove(); - } + }, + + /** + * Binds progress indicator to a given loader. + * + * It will automatically remove its listeners when the `loader` has triggered one of following events: + * + * * {@link CKEDITOR.fileTools.fileLoader#abort} + * * {@link CKEDITOR.fileTools.fileLoader#error} + * * {@link CKEDITOR.fileTools.fileLoader#uploaded} + * + * @param {CKEDITOR.fileTools.fileLoader} loader Loader that should be observed. + */ + bindToLoader: function( loader ) { + var progressListeners = []; + + function removeProgressListeners() { + if ( progressListeners ) { + CKEDITOR.tools.array.forEach( progressListeners, function( listener ) { + listener.removeListener(); + } ); + + progressListeners = null; + } + } + + var updateListener = CKEDITOR.tools.eventsBuffer( UPLOAD_PROGRESS_THROTTLING, function() { + if ( loader.uploadTotal ) { + this.updated( loader.uploaded / loader.uploadTotal ); + } + }, this ); + + progressListeners.push( loader.on( 'uploading', updateListener.input, this ) ); + progressListeners.push( loader.once( 'abort', this.aborted, this ) ); + progressListeners.push( loader.once( 'uploaded', this.done, this ) ); + progressListeners.push( loader.once( 'error', this.failed, this ) ); + + // Some events should cause all listeners to be removed. + progressListeners.push( loader.once( 'abort', removeProgressListeners ) ); + progressListeners.push( loader.once( 'uploaded', removeProgressListeners ) ); + progressListeners.push( loader.once( 'error', removeProgressListeners ) ); + }, }; CKEDITOR.plugins.add( 'imagebase', { diff --git a/tests/plugins/imagebase/progressbar.js b/tests/plugins/imagebase/progressbar.js new file mode 100644 index 00000000000..b5d711cb6de --- /dev/null +++ b/tests/plugins/imagebase/progressbar.js @@ -0,0 +1,194 @@ +/* bender-tags: editor */ +/* bender-ckeditor-plugins: imagebase */ + +( function() { + 'use strict'; + + var ProgressBar, + doc = CKEDITOR.document, + loaderMock = {}, + tests = { + init: function() { + ProgressBar = CKEDITOR.plugins.imagebase.progressBar; + + CKEDITOR.event.implementOn( loaderMock ); + + // Store the content of #nested-sandbox - it will be used to restore original HTML + // before each test case. + this.nestedSandbox = doc.getById( 'nested-sandbox' ); + this._nestedSandboxContent = this.nestedSandbox.getHtml(); + }, + + setUp: function() { + this.nestedSandbox.setHtml( this._nestedSandboxContent ); + + this.dummyProgress = new ProgressBar(); + + sinon.stub( this.dummyProgress, 'aborted' ); + sinon.stub( this.dummyProgress, 'done' ); + sinon.stub( this.dummyProgress, 'failed' ); + sinon.stub( this.dummyProgress, 'updated' ); + }, + + 'test createForElement()': function() { + var ret = ProgressBar.createForElement( this.nestedSandbox.findOne( '.nested2' ) ); + + assert.isInstanceOf( ProgressBar, ret, 'Returned type' ); + + assert.beautified.html( doc.getById( 'expected-create-from-element' ).getHtml(), this.nestedSandbox.getHtml() ); + }, + + 'test createForElement() creates proper elements': function() { + var ret = ProgressBar.createForElement( this.nestedSandbox.findOne( '.nested2' ) ); + + assert.areSame( this.nestedSandbox.findOne( '.cke_loader' ), ret.wrapper, 'ret.wrapper' ); + assert.areSame( this.nestedSandbox.findOne( '.cke_bar' ), ret.bar, 'ret.bar' ); + }, + + 'test createForElement() prepends the element': function() { + ProgressBar.createForElement( this.nestedSandbox ); + + assert.beautified.html( doc.getById( 'expected-create-from-element-prepend' ).getHtml(), this.nestedSandbox.getHtml() ); + }, + + 'test update()': function() { + var values = [ + [ 0.0, '0%' ], + [ 0.2, '20%' ], + [ 0.25, '25%' ], + [ 0.301, '30%' ], + [ 0.309, '31%' ], + [ 1.0, '100%' ], + [ -1.0, '0%' ], + [ 1.1, '100%' ] + ], + ret = ProgressBar.createForElement( this.nestedSandbox ); + + CKEDITOR.tools.array.forEach( values, function( assertSet ) { + ret.updated( assertSet[ 0 ] ); + assert.areSame( assertSet[ 1 ], ret.bar.getStyle( 'width', assertSet[ 0 ] ), 'Width for ' + assertSet[ 0 ] ); + } ) + }, + + // 'test update() locks the snapshot': function() { + // // todo + // }, + + 'test failed()': function() { + var ret = ProgressBar.createForElement( this.nestedSandbox ); + + ret.failed(); + + assert.isNull( ret.wrapper.getParent(), 'Parent element' ); + }, + + 'test aborted()': function() { + var ret = ProgressBar.createForElement( this.nestedSandbox ); + + ret.aborted(); + + assert.isNull( ret.wrapper.getParent(), 'Parent element' ); + }, + + 'test done()': function() { + var ret = ProgressBar.createForElement( this.nestedSandbox ); + + ret.done(); + + assert.isNull( ret.wrapper.getParent(), 'Parent element' ); + }, + + 'test bindToLoader() abort event removes listeners': function() { + this.dummyProgress.bindToLoader( loaderMock ); + + loaderMock.fire( 'abort' ); + + sinon.assert.calledOnce( this.dummyProgress.aborted ); + sinon.assert.calledOn( this.dummyProgress.aborted, this.dummyProgress ); + + // Subsequent calls should not result with more calls. + loaderMock.fire( 'abort' ); + loaderMock.fire( 'abort' ); + + assert.areSame( 1, this.dummyProgress.aborted.callCount, 'Aborted call count' ); + + // Uploaded event should not trigger done too. + loaderMock.fire( 'uploaded' ); + assert.areSame( 0, this.dummyProgress.done.callCount, 'Done call count' ); + }, + + 'test bindToLoader() uploaded event removes listeners': function() { + this.dummyProgress.bindToLoader( loaderMock ); + + loaderMock.fire( 'uploaded' ); + + sinon.assert.calledOnce( this.dummyProgress.done ); + sinon.assert.calledOn( this.dummyProgress.done, this.dummyProgress ); + + // Subsequent calls should not result with more calls. + loaderMock.fire( 'uploaded' ); + loaderMock.fire( 'uploaded' ); + + assert.areSame( 1, this.dummyProgress.done.callCount, 'Done call count' ); + }, + + 'test bindToLoader() uploading event is throttled': function() { + this.dummyProgress.bindToLoader( loaderMock ); + // Make sure that if file loader spams uploading events, progress does not go crazy. + + loaderMock.fire( 'uploading' ); + loaderMock.fire( 'uploading' ); + loaderMock.fire( 'uploading' ); + loaderMock.fire( 'uploading' ); + + assert.areSame( 1, this.dummyProgress.updated.callCount, 'Updated call count' ); + }, + + 'test bindToLoader() uploading event is throttled': function() { + this.dummyProgress.bindToLoader( loaderMock ); + // Make sure that if file loader spams uploading events, progress does not go crazy. + + loaderMock.uploadTotal = 5; + + loaderMock.fire( 'uploading' ); + loaderMock.fire( 'uploading' ); + loaderMock.fire( 'uploading' ); + + delete loaderMock.uploadTotal; + + assert.areSame( 1, this.dummyProgress.updated.callCount, 'Updated call count' ); + }, + + 'test bindToLoader() uploading argument translation': function() { + this.dummyProgress.bindToLoader( loaderMock ); + + loaderMock.uploaded = 3; + loaderMock.uploadTotal = 5; + + loaderMock.fire( 'uploading' ); + + delete loaderMock.uploaded; + delete loaderMock.uploadTotal; + + sinon.assert.calledWithExactly( this.dummyProgress.updated, 0.6 ); + assert.areSame( 1, this.dummyProgress.updated.callCount, 'Call count' ); + }, + + 'test bindToLoader() error event removes listeners': function() { + this.dummyProgress.bindToLoader( loaderMock ); + + loaderMock.fire( 'error' ); + + sinon.assert.calledOnce( this.dummyProgress.failed ); + sinon.assert.calledOn( this.dummyProgress.failed, this.dummyProgress ); + + // Subsequent calls should not result with more calls. + loaderMock.fire( 'error' ); + loaderMock.fire( 'error' ); + + assert.areSame( 1, this.dummyProgress.failed.callCount, 'Failed call count' ); + } + }; + + bender.test( tests ); +} )(); From 45486e5350aad0e01795fb7f757daece3ed24fae Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 18 Jan 2018 01:29:01 +0100 Subject: [PATCH 06/78] Added ProgressBar#remove and a basic manual test. --- plugins/imagebase/plugin.js | 9 +- .../plugins/imagebase/manual/progressbar.html | 89 +++++++++++++++++++ tests/plugins/imagebase/manual/progressbar.md | 9 ++ tests/plugins/imagebase/progressbar.js | 8 ++ 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 tests/plugins/imagebase/manual/progressbar.html create mode 100644 tests/plugins/imagebase/manual/progressbar.md diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index e08d7f89149..c83f169559a 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -504,7 +504,7 @@ * To be called when the progress should be marked as aborted. */ aborted: function() { - this.failed(); + this.remove(); }, /** @@ -514,6 +514,13 @@ this.wrapper.remove(); }, + /** + * Removes the progress indicator from DOM. + */ + remove: function() { + this.wrapper.remove(); + }, + /** * Binds progress indicator to a given loader. * diff --git a/tests/plugins/imagebase/manual/progressbar.html b/tests/plugins/imagebase/manual/progressbar.html new file mode 100644 index 00000000000..529563e53c2 --- /dev/null +++ b/tests/plugins/imagebase/manual/progressbar.html @@ -0,0 +1,89 @@ +
+
+

line

+

line

+

line

+
+

Above div will gain your progress indicator.

+
+ + + + + + diff --git a/tests/plugins/imagebase/manual/progressbar.md b/tests/plugins/imagebase/manual/progressbar.md new file mode 100644 index 00000000000..f69d3eede93 --- /dev/null +++ b/tests/plugins/imagebase/manual/progressbar.md @@ -0,0 +1,9 @@ +@bender-tags: 4.9.0, feature, 932 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, imagebase + +# Progress Indicator + +The progress bar should be drawn within a green div inside of the editor. + +Use inputs below the editor to control the state of progress bar. See how it reacts. \ No newline at end of file diff --git a/tests/plugins/imagebase/progressbar.js b/tests/plugins/imagebase/progressbar.js index b5d711cb6de..baa3ee8c697 100644 --- a/tests/plugins/imagebase/progressbar.js +++ b/tests/plugins/imagebase/progressbar.js @@ -51,6 +51,14 @@ assert.beautified.html( doc.getById( 'expected-create-from-element-prepend' ).getHtml(), this.nestedSandbox.getHtml() ); }, + 'test remove()': function() { + var ret = ProgressBar.createForElement( this.nestedSandbox ); + + ret.remove(); + + assert.isNull( ret.wrapper.getParent(), 'Parent node' ); + }, + 'test update()': function() { var values = [ [ 0.0, '0%' ], From 47ffe734b5c98d49c6ef83694eb81149c915893e Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 18 Jan 2018 01:52:54 +0100 Subject: [PATCH 07/78] Tests: implemented a different type of progress indicator in manual test. --- .../plugins/imagebase/manual/progressbar.html | 61 ++++++++++++++++--- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/tests/plugins/imagebase/manual/progressbar.html b/tests/plugins/imagebase/manual/progressbar.html index 529563e53c2..7ec93ca25da 100644 --- a/tests/plugins/imagebase/manual/progressbar.html +++ b/tests/plugins/imagebase/manual/progressbar.html @@ -1,5 +1,6 @@
-
+
+

line

line

line

line

@@ -16,6 +17,20 @@ width: 100%; } + .progress-range-legend { + position: relative; + height: 1em; + } + + .progress-range-legend span { + position: absolute; + top: 0px; + } + + .progress-range-legend .done { + right: 0px; + } + + diff --git a/tests/plugins/easyimage/manual/customprogressbar.md b/tests/plugins/easyimage/manual/customprogressbar.md new file mode 100644 index 00000000000..2c2e7e3a9bc --- /dev/null +++ b/tests/plugins/easyimage/manual/customprogressbar.md @@ -0,0 +1,19 @@ +@bender-tags: 4.9.0, feature, 932 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, easyimage, autogrow + +# Custom Progress + +This manual test shows an example of different progress indicators. + +1. Drag and drop any image file into "Default progress bar" editor. +1. Observe progress indicator. + ## Expected + + Progress indicator matches editor name and is different from other editors. +1. Repeat above steps for remaining editors. + + +Note: it might not work on **Edge** due to an [upstream bug](https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12224510/). + +Pro tip: you might want to [use browser throttling](https://developers.google.com/web/tools/chrome-devtools/network-performance/reference?hl=en#throttling) to make uploads longer. \ No newline at end of file diff --git a/tests/plugins/imagebase/manual/progressbar.html b/tests/plugins/imagebase/manual/progressbar.html index 7ec93ca25da..eeeecc1d7fd 100644 --- a/tests/plugins/imagebase/manual/progressbar.html +++ b/tests/plugins/imagebase/manual/progressbar.html @@ -62,38 +62,19 @@

+ + diff --git a/tests/plugins/imagebase/features/manual/upload.md b/tests/plugins/imagebase/features/manual/upload.md new file mode 100644 index 00000000000..7f95e6eed8a --- /dev/null +++ b/tests/plugins/imagebase/features/manual/upload.md @@ -0,0 +1,15 @@ +@bender-tags: 4.9.0, feature, 932 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, toolbar, imagebase, elementspath, placeholder, cloudservices + +## Upload Feature + +1. Drop any file from your file system to the editable. + +## Expected + +A placeholder widget is created, containing file name. + +**Note:** at the time of writing Cloud Service accepts only images, and return `400` code for any other resource type. + +_If you're truly curious about the actual URL returned by Cloud Services, you can find it in console log._ \ No newline at end of file From 37cbd7281361cc81b4113c774772b93f3311c7f1 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Sun, 21 Jan 2018 13:31:17 +0100 Subject: [PATCH 33/78] Tests: extracted common assertion. --- tests/plugins/easyimage/uploadintegrations.js | 261 +++---- .../imagebase/features/_helpers/tools.js | 48 ++ tests/plugins/imagebase/features/upload.js | 695 +++++++++--------- 3 files changed, 487 insertions(+), 517 deletions(-) create mode 100644 tests/plugins/imagebase/features/_helpers/tools.js diff --git a/tests/plugins/easyimage/uploadintegrations.js b/tests/plugins/easyimage/uploadintegrations.js index bfa9cb0d284..de3698ba287 100644 --- a/tests/plugins/easyimage/uploadintegrations.js +++ b/tests/plugins/easyimage/uploadintegrations.js @@ -1,167 +1,128 @@ /* bender-tags: editor, clipboard, upload */ /* bender-ckeditor-plugins: sourcearea, wysiwygarea, easyimage */ -/* bender-include: %BASE_PATH%/plugins/clipboard/_helpers/pasting.js */ -/* global pasteFiles */ +/* bender-include: %BASE_PATH%/plugins/clipboard/_helpers/pasting.js, %BASE_PATH%/plugins/imagebase/features/_helpers/tools.js */ +/* global imageBaseFeaturesTools */ ( function() { 'use strict'; bender.editor = true; - function objToArray( obj ) { - var tools = CKEDITOR.tools; - - return tools.array.map( tools.objectKeys( obj ), function( key ) { - return obj[ key ]; - } ); - } - // Loader mock that successes asynchronously. function AsyncSuccessFileLoader( editor, fileOrData, fileName ) { CKEDITOR.fileTools.fileLoader.call( this, editor, fileOrData, fileName ); } - var tests = { - init: function() { - var sampleCloudServicesResponse = { - 210: '%BASE_PATH%/_assets/logo.png?w=210', - 420: '%BASE_PATH%/_assets/logo.png?w=420', - 630: '%BASE_PATH%/_assets/logo.png?w=630', - 840: '%BASE_PATH%/_assets/logo.png?w=840', - 1050: '%BASE_PATH%/_assets/logo.png?w=1050', - 1260: '%BASE_PATH%/_assets/logo.png?w=1260', - 1470: '%BASE_PATH%/_assets/logo.png?w=1470', - 1680: '%BASE_PATH%/_assets/logo.png?w=1680', - 1890: '%BASE_PATH%/_assets/logo.png?w=1890', - 2048: '%BASE_PATH%/_assets/logo.png?w=2048', - 'default': '%BASE_PATH%/_assets/logo.png' - }; - - // Array of listeners to be cleared after each TC. - this.listeners = []; - - this.editor.widgets.registered.easyimage.loaderType = AsyncSuccessFileLoader; - - this.editor.on( 'fileUploadResponse', function( evt ) { - // Prevent this guy from picking up https://github.com/ckeditor/ckeditor-dev/blob/565d9c3a3613f35167d6555123b6ca316ead7ab9/plugins/filetools/plugin.js#L93-L122 - // it would complain about missing uploaded/error properties. - evt.cancel(); - }, null, null, 5 ); - - AsyncSuccessFileLoader.prototype = CKEDITOR.tools.extend( { - upload: function() { - var that = this; - - setTimeout( function() { - that.changeStatus( 'uploading' ); - }, 0 ); - - setTimeout( function() { - that.update(); - }, 200 ); - - setTimeout( function() { - that.update(); - }, 400 ); - - setTimeout( function() { - var evtData = { - sender: that - }; - - that.responseData = { - response: sampleCloudServicesResponse - }; - - that.editor.fire( 'fileUploadResponse', evtData ); - that.changeStatus( 'uploaded' ); - }, 1000 ); - } - }, CKEDITOR.fileTools.fileLoader.prototype ); - }, - - tearDown: function() { - // Clean up the listeners so it doesn't affect subsequent tests. - CKEDITOR.tools.array.forEach( this.listeners, function( listener ) { - listener.removeListener(); - } ); - - this.editor.widgets.destroyAll( true ); - - this.listeners = []; - this.editor.uploadRepository.loaders = []; - }, - - setUp: function() { - this.editorBot.setHtmlWithSelection( '

^

' ); - }, - - // To test - edge case: changing mode during upload. - 'test nothing explodes when upload finishes in a different mode': function() { - var editor = this.editor, - disposableListeners = this.listeners; - - this._assertPasteFiles( editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function( widgets ) { - assert.areSame( 1, widgets.length, 'Widgets count' ); - - var doneSpy = sinon.spy(); - disposableListeners.push( widgets[ 0 ].on( 'uploadDone', doneSpy ) ); - - editor.setMode( 'source', function() { - resume( function() { - // It's unlikely, but theoretically possible that upload will complete before - // the mode is changed. Inform about this case. - assert.isFalse( doneSpy.called, 'Race condition didn\'t occur' ); - - disposableListeners.push( widgets[ 0 ].once( 'uploadDone', function() { - resume( function() { - assert.isTrue( true ); - } ); - } ) ); - - wait(); + var assertPasteFiles = imageBaseFeaturesTools.assertPasteFiles, + tests = { + init: function() { + var sampleCloudServicesResponse = { + 210: '%BASE_PATH%/_assets/logo.png?w=210', + 420: '%BASE_PATH%/_assets/logo.png?w=420', + 630: '%BASE_PATH%/_assets/logo.png?w=630', + 840: '%BASE_PATH%/_assets/logo.png?w=840', + 1050: '%BASE_PATH%/_assets/logo.png?w=1050', + 1260: '%BASE_PATH%/_assets/logo.png?w=1260', + 1470: '%BASE_PATH%/_assets/logo.png?w=1470', + 1680: '%BASE_PATH%/_assets/logo.png?w=1680', + 1890: '%BASE_PATH%/_assets/logo.png?w=1890', + 2048: '%BASE_PATH%/_assets/logo.png?w=2048', + 'default': '%BASE_PATH%/_assets/logo.png' + }; + + // Array of listeners to be cleared after each TC. + this.listeners = []; + + this.editor.widgets.registered.easyimage.loaderType = AsyncSuccessFileLoader; + + this.editor.on( 'fileUploadResponse', function( evt ) { + // Prevent this guy from picking up https://github.com/ckeditor/ckeditor-dev/blob/565d9c3a3613f35167d6555123b6ca316ead7ab9/plugins/filetools/plugin.js#L93-L122 + // it would complain about missing uploaded/error properties. + evt.cancel(); + }, null, null, 5 ); + + AsyncSuccessFileLoader.prototype = CKEDITOR.tools.extend( { + upload: function() { + var that = this; + + setTimeout( function() { + that.changeStatus( 'uploading' ); + }, 0 ); + + setTimeout( function() { + that.update(); + }, 200 ); + + setTimeout( function() { + that.update(); + }, 400 ); + + setTimeout( function() { + var evtData = { + sender: that + }; + + that.responseData = { + response: sampleCloudServicesResponse + }; + + that.editor.fire( 'fileUploadResponse', evtData ); + that.changeStatus( 'uploaded' ); + }, 1000 ); + } + }, CKEDITOR.fileTools.fileLoader.prototype ); + }, + + tearDown: function() { + // Clean up the listeners so it doesn't affect subsequent tests. + CKEDITOR.tools.array.forEach( this.listeners, function( listener ) { + listener.removeListener(); + } ); + + this.editor.widgets.destroyAll( true ); + + this.listeners = []; + this.editor.uploadRepository.loaders = []; + }, + + setUp: function() { + this.editorBot.setHtmlWithSelection( '

^

' ); + }, + + // To test - edge case: changing mode during upload. + 'test nothing explodes when upload finishes in a different mode': function() { + var editor = this.editor, + disposableListeners = this.listeners; + + assertPasteFiles( editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function( widgets ) { + assert.areSame( 1, widgets.length, 'Widgets count' ); + + var doneSpy = sinon.spy(); + disposableListeners.push( widgets[ 0 ].on( 'uploadDone', doneSpy ) ); + + editor.setMode( 'source', function() { + resume( function() { + // It's unlikely, but theoretically possible that upload will complete before + // the mode is changed. Inform about this case. + assert.isFalse( doneSpy.called, 'Race condition didn\'t occur' ); + + disposableListeners.push( widgets[ 0 ].once( 'uploadDone', function() { + resume( function() { + assert.isTrue( true ); + } ); + } ) ); + + wait(); + } ); } ); - } ); - - wait(); - } - } ); - }, - - /* - * Main assertion for pasting files. - * - * @param {CKEDITOR.editor} editor - * @param {Object} options - * @param {File[]} [options.files=[]] Files to be dropped. - * @param {Function} options.callback Function to be called after the paste event. - * Params: - * - * * `CKEDITOR.plugins.widget[]` widgets - Array of widgets in a given editor. - * * `CKEDITOR.eventInfo` evt - Paste event. - */ - _assertPasteFiles: function( editor, options ) { - var files = options.files || [], - callback = options.callback; - - editor.once( 'paste', function( evt ) { - // Unfortunately at the time being we need to do additional timeout here, as - // the paste event gets cancelled. - setTimeout( function() { - resume( function() { - callback( objToArray( editor.widgets.instances ), evt ); - } ); - }, 0 ); - }, null, null, -1 ); - - - pasteFiles( editor, files ); - - wait(); - } - }; + + wait(); + } + } ); + } + }; bender.test( tests ); } )(); diff --git a/tests/plugins/imagebase/features/_helpers/tools.js b/tests/plugins/imagebase/features/_helpers/tools.js new file mode 100644 index 00000000000..f8bc4af30fb --- /dev/null +++ b/tests/plugins/imagebase/features/_helpers/tools.js @@ -0,0 +1,48 @@ +/* exported imageBaseFeaturesTools */ +/* global pasteFiles */ + +( function() { + 'use strict'; + + function objToArray( obj ) { + var tools = CKEDITOR.tools; + + return tools.array.map( tools.objectKeys( obj ), function( key ) { + return obj[ key ]; + } ); + } + + window.imageBaseFeaturesTools = { + /* + * Main assertion for pasting files. + * + * @param {CKEDITOR.editor} editor + * @param {Object} options + * @param {File[]} [options.files=[]] Files to be dropped. + * @param {Function} options.callback Function to be called after the paste event. + * Params: + * + * * `CKEDITOR.plugins.widget[]` widgets - Array of widgets in a given editor. + * * `CKEDITOR.eventInfo` evt - Paste event. + */ + assertPasteFiles: function( editor, options ) { + var files = options.files || [], + callback = options.callback; + + editor.once( 'paste', function( evt ) { + // Unfortunately at the time being we need to do additional timeout here, as + // the paste event gets cancelled. + setTimeout( function() { + resume( function() { + callback( objToArray( editor.widgets.instances ), evt ); + } ); + }, 0 ); + }, null, null, -1 ); + + // pasteFiles is defined in filetools plugin helper. + pasteFiles( editor, files ); + + wait(); + } + }; +} )(); diff --git a/tests/plugins/imagebase/features/upload.js b/tests/plugins/imagebase/features/upload.js index fc64a6d3e84..16c7caff741 100644 --- a/tests/plugins/imagebase/features/upload.js +++ b/tests/plugins/imagebase/features/upload.js @@ -1,21 +1,13 @@ /* bender-tags: editor, clipboard, upload */ /* bender-ckeditor-plugins: imagebase */ -/* bender-include: %BASE_PATH%/plugins/clipboard/_helpers/pasting.js */ -/* global pasteFiles */ +/* bender-include: %BASE_PATH%/plugins/clipboard/_helpers/pasting.js, _helpers/tools.js */ +/* global imageBaseFeaturesTools */ ( function() { 'use strict'; bender.editor = true; - function objToArray( obj ) { - var tools = CKEDITOR.tools; - - return tools.array.map( tools.objectKeys( obj ), function( key ) { - return obj[ key ]; - } ); - } - function getTestHtmlFile( fileName ) { var file = bender.tools.srcToFile( 'data:text/html;base64,Zm9v' ); file.name = fileName ? fileName : 'name.html'; @@ -32,365 +24,334 @@ CKEDITOR.fileTools.fileLoader.call( this, editor, fileOrData, fileName ); } - var tests = { - init: function() { - var plugin = CKEDITOR.plugins.imagebase, - editor = this.editor, - imageWidgetDef = { - name: 'testImageWidget', - supportedTypes: /image\/(jpeg|png)/, - loaderType: SuccessFileLoader - }, - textWidgetDef = { - name: 'testTextWidget', - supportedTypes: /text\/plain/, - loaderType: SuccessFileLoader - }; - - // Array of listeners to be cleared after each TC. - this.listeners = []; - this.sandbox = sinon.sandbox.create(); - - plugin.addImageWidget( editor, imageWidgetDef.name, plugin.addFeature( editor, 'upload', imageWidgetDef ) ); - - plugin.addImageWidget( editor, textWidgetDef.name, plugin.addFeature( editor, 'upload', textWidgetDef ) ); - - SuccessFileLoader.prototype = CKEDITOR.tools.extend( { - upload: function() { - this.changeStatus( 'uploaded' ); - } - }, CKEDITOR.fileTools.fileLoader.prototype ); - - FailFileLoader.prototype = CKEDITOR.tools.extend( { - upload: function() { - this.changeStatus( 'error' ); - } - }, CKEDITOR.fileTools.fileLoader.prototype ); - }, - - tearDown: function() { - // Clean up the listeners so it doesn't affect subsequent tests. - CKEDITOR.tools.array.forEach( this.listeners, function( listener ) { - listener.removeListener(); - } ); - - this.sandbox.restore(); - this.editor.widgets.destroyAll( true ); - - this.listeners = []; - this.editor.uploadRepository.loaders = []; - }, - - setUp: function() { - this.editorBot.setHtmlWithSelection( '

^

' ); - }, - - // To test - test mixed dropped files types (e.g. 1 image, 1 text, 1 unsupported). - - 'test dropping supported file type creates a widget': function() { - var editor = this.editor; - - this._assertPasteFiles( editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function( widgets ) { - assert.areSame( 1, widgets.length, 'Widgets count' ); - assert.areSame( widgets[ 0 ].name, 'testImageWidget', 'Widget name' ); - } - } ); - }, - - 'test dropping unsupported file does not create a widget': function() { - var editor = this.editor; - - this._assertPasteFiles( editor, { - files: [ getTestHtmlFile() ], - callback: function( widgets ) { - assert.areSame( 0, widgets.length, 'Widgets count' ); - } - } ); - }, - - 'test multiple files create multiple widgets': function() { - var editor = this.editor; - - this._assertPasteFiles( editor, { - files: [ - bender.tools.getTestPngFile(), - bender.tools.getTestPngFile(), - bender.tools.getTestPngFile() - ], - callback: function( widgets ) { - assert.areSame( 3, widgets.length, 'Widgets count' ); - assert.areSame( widgets[ 0 ].name, 'testImageWidget', 'Widget 0 name' ); - assert.areSame( widgets[ 1 ].name, 'testImageWidget', 'Widget 1 name' ); - assert.areSame( widgets[ 2 ].name, 'testImageWidget', 'Widget 2 name' ); - } - } ); - }, - - 'test loader can be customized': function() { - var editor = this.editor, - originalLoader = editor.widgets.registered.testImageWidget.loaderType, - CustomDummyType = sinon.spy( SuccessFileLoader ); - - // Force a dummy loader. - editor.widgets.registered.testImageWidget.loaderType = CustomDummyType; - - this._assertPasteFiles( editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function() { - // Restore original loader. - editor.widgets.registered.testImageWidget.loaderType = originalLoader; - - assert.areSame( 1, CustomDummyType.callCount, 'CustomDummyType constructor calls' ); - } - } ); - }, - - 'test events': function() { - var editor = this.editor, - stubs = { - uploadBegan: sinon.stub(), - uploadDone: sinon.stub(), - uploadFailed: sinon.stub() - }, - that = this; - - this.listeners.push( editor.widgets.on( 'instanceCreated', function( evt ) { - // Add spies to the widget. - for ( var i in stubs ) { - that.listeners.push( evt.data.on( i, stubs[ i ] ) ); - } - } ) ); - - this._assertPasteFiles( editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function() { - assert.areSame( 1, stubs.uploadBegan.callCount, 'uploadBegan event count' ); - sinon.assert.calledWithExactly( stubs.uploadBegan, sinon.match.has( 'data', editor.uploadRepository.loaders[ 0 ] ) ); - - assert.areSame( 1, stubs.uploadDone.callCount, 'uploadDone event count' ); - - assert.areSame( 0, stubs.uploadFailed.callCount, 'uploadFailed event count' ); - } - } ); - }, - - 'test upload error': function() { - var editor = this.editor, - stubs = { - uploadBegan: sinon.stub(), - uploadDone: sinon.stub(), - uploadFailed: sinon.stub() - }, - that = this, - originalLoader = editor.widgets.registered.testImageWidget.loaderType; - - // Force a loader that will fail. - editor.widgets.registered.testImageWidget.loaderType = FailFileLoader; - - this.listeners.push( editor.widgets.on( 'instanceCreated', function( evt ) { - // Add spies to the widget. - for ( var i in stubs ) { - that.listeners.push( evt.data.on( i, stubs[ i ] ) ); - } - } ) ); - - this._assertPasteFiles( editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function( widgets ) { - // Restore original loader. - editor.widgets.registered.testImageWidget.loaderType = originalLoader; - - var loaderInstance = editor.uploadRepository.loaders[ 0 ]; - - assert.areSame( 1, stubs.uploadBegan.callCount, 'uploadBegan event count' ); - sinon.assert.calledWithExactly( stubs.uploadBegan, sinon.match.has( 'data', loaderInstance ) ); - - assert.areSame( 0, stubs.uploadDone.callCount, 'uploadDone event count' ); - - assert.areSame( 1, stubs.uploadFailed.callCount, 'uploadFailed event count' ); - assert.areSame( loaderInstance, stubs.uploadFailed.args[ 0 ][ 0 ].data.sender, 'Event data.sender' ); - - assert.areSame( 0, widgets.length, 'Widget count' ); - } - } ); - }, - - 'test upload error event is cancelable': function() { - var editor = this.editor, - that = this, - originalLoader = editor.widgets.registered.testImageWidget.loaderType; - - // Force a loader that will fail. - editor.widgets.registered.testImageWidget.loaderType = FailFileLoader; - - this.listeners.push( editor.widgets.on( 'instanceCreated', function( evt ) { - that.listeners.push( evt.data.on( 'uploadFailed', function( evt ) { - evt.cancel(); + var assertPasteFiles = imageBaseFeaturesTools.assertPasteFiles, + tests = { + init: function() { + var plugin = CKEDITOR.plugins.imagebase, + editor = this.editor, + imageWidgetDef = { + name: 'testImageWidget', + supportedTypes: /image\/(jpeg|png)/, + loaderType: SuccessFileLoader + }, + textWidgetDef = { + name: 'testTextWidget', + supportedTypes: /text\/plain/, + loaderType: SuccessFileLoader + }; + + // Array of listeners to be cleared after each TC. + this.listeners = []; + this.sandbox = sinon.sandbox.create(); + + plugin.addImageWidget( editor, imageWidgetDef.name, plugin.addFeature( editor, 'upload', imageWidgetDef ) ); + + plugin.addImageWidget( editor, textWidgetDef.name, plugin.addFeature( editor, 'upload', textWidgetDef ) ); + + SuccessFileLoader.prototype = CKEDITOR.tools.extend( { + upload: function() { + this.changeStatus( 'uploaded' ); + } + }, CKEDITOR.fileTools.fileLoader.prototype ); + + FailFileLoader.prototype = CKEDITOR.tools.extend( { + upload: function() { + this.changeStatus( 'error' ); + } + }, CKEDITOR.fileTools.fileLoader.prototype ); + }, + + tearDown: function() { + // Clean up the listeners so it doesn't affect subsequent tests. + CKEDITOR.tools.array.forEach( this.listeners, function( listener ) { + listener.removeListener(); + } ); + + this.sandbox.restore(); + this.editor.widgets.destroyAll( true ); + + this.listeners = []; + this.editor.uploadRepository.loaders = []; + }, + + setUp: function() { + this.editorBot.setHtmlWithSelection( '

^

' ); + }, + + // To test - test mixed dropped files types (e.g. 1 image, 1 text, 1 unsupported). + + 'test dropping supported file type creates a widget': function() { + var editor = this.editor; + + assertPasteFiles( editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function( widgets ) { + assert.areSame( 1, widgets.length, 'Widgets count' ); + assert.areSame( widgets[ 0 ].name, 'testImageWidget', 'Widget name' ); + } + } ); + }, + + 'test dropping unsupported file does not create a widget': function() { + var editor = this.editor; + + assertPasteFiles( editor, { + files: [ getTestHtmlFile() ], + callback: function( widgets ) { + assert.areSame( 0, widgets.length, 'Widgets count' ); + } + } ); + }, + + 'test multiple files create multiple widgets': function() { + var editor = this.editor; + + assertPasteFiles( editor, { + files: [ + bender.tools.getTestPngFile(), + bender.tools.getTestPngFile(), + bender.tools.getTestPngFile() + ], + callback: function( widgets ) { + assert.areSame( 3, widgets.length, 'Widgets count' ); + assert.areSame( widgets[ 0 ].name, 'testImageWidget', 'Widget 0 name' ); + assert.areSame( widgets[ 1 ].name, 'testImageWidget', 'Widget 1 name' ); + assert.areSame( widgets[ 2 ].name, 'testImageWidget', 'Widget 2 name' ); + } + } ); + }, + + 'test loader can be customized': function() { + var editor = this.editor, + originalLoader = editor.widgets.registered.testImageWidget.loaderType, + CustomDummyType = sinon.spy( SuccessFileLoader ); + + // Force a dummy loader. + editor.widgets.registered.testImageWidget.loaderType = CustomDummyType; + + assertPasteFiles( editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function() { + // Restore original loader. + editor.widgets.registered.testImageWidget.loaderType = originalLoader; + + assert.areSame( 1, CustomDummyType.callCount, 'CustomDummyType constructor calls' ); + } + } ); + }, + + 'test events': function() { + var editor = this.editor, + stubs = { + uploadBegan: sinon.stub(), + uploadDone: sinon.stub(), + uploadFailed: sinon.stub() + }, + that = this; + + this.listeners.push( editor.widgets.on( 'instanceCreated', function( evt ) { + // Add spies to the widget. + for ( var i in stubs ) { + that.listeners.push( evt.data.on( i, stubs[ i ] ) ); + } } ) ); - } ) ); - - this._assertPasteFiles( editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function( widgets ) { - editor.widgets.registered.testImageWidget.loaderType = originalLoader; - - assert.areSame( 1, widgets.length, 'Widget was not removed' ); - } - } ); - }, - - 'test widgets can coexist side by side': function() { - // In other tests we're using image widget, if text widget works it means they work side - // by side just fine. - var editor = this.editor; - - this._assertPasteFiles( editor, { - files: [ bender.tools.getTestTxtFile() ], - callback: function( widgets ) { - assert.areSame( 1, widgets.length, 'Widgets count' ); - assert.areSame( 'testTextWidget', widgets[ 0 ].name, 'Widget name' ); - } - } ); - }, - - 'test default progress reporter': function() { - var imageBase = CKEDITOR.plugins.imagebase, - createForElementSpy = this.sandbox.spy( imageBase.progressBar, 'createForElement' ), - bindLoaderSpy = this.sandbox.spy( imageBase.progressBar.prototype, 'bindLoader' ), - editor = this.editor; - - this._assertPasteFiles( this.editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function( widgets ) { - assert.areSame( 1, createForElementSpy.callCount, 'ProgressBar.createForElement call count' ); - - sinon.assert.calledWithExactly( createForElementSpy, widgets[ 0 ].element ); - - sinon.assert.calledWithExactly( bindLoaderSpy, editor.uploadRepository.loaders[ 0 ] ); - } - } ); - }, - - 'test progress reporter customization': function() { - var CustomProgress = this.sandbox.spy( CKEDITOR.plugins.imagebase, 'progressBar' ), - widgetDefinition = this.editor.widgets.registered.testImageWidget, - originalProgress = widgetDefinition.progressIndicatorType; - - CustomProgress.createForElement = sinon.spy( function() { - return new CustomProgress(); - } ); - - widgetDefinition.progressIndicatorType = CustomProgress; - - - this._assertPasteFiles( this.editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function() { - widgetDefinition.progressIndicatorType = originalProgress; - - assert.areSame( 1, CustomProgress.callCount, 'ProgressBar.createForElement call count' ); - } - } ); - }, - - 'test preventing default progress reporter with event': function() { - // This test is a bit tricky, we need to add a new widget so that we can hook to widget.init method because - // widget#instanceCreated event is fired too late for this assertion. - var imageBase = CKEDITOR.plugins.imagebase, - createForElementSpy = this.sandbox.spy( imageBase.progressBar, 'createForElement' ), - editor = this.editor, - disposableListeners = this.listeners, - def = { - name: 'progressPrevent', - supportedTypes: /text\/html/, - init: function() { - disposableListeners.push( this.on( 'uploadBegan', function( evt ) { - evt.cancel(); - } ) ); + + assertPasteFiles( editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function() { + assert.areSame( 1, stubs.uploadBegan.callCount, 'uploadBegan event count' ); + sinon.assert.calledWithExactly( stubs.uploadBegan, sinon.match.has( 'data', editor.uploadRepository.loaders[ 0 ] ) ); + + assert.areSame( 1, stubs.uploadDone.callCount, 'uploadDone event count' ); + + assert.areSame( 0, stubs.uploadFailed.callCount, 'uploadFailed event count' ); + } + } ); + }, + + 'test upload error': function() { + var editor = this.editor, + stubs = { + uploadBegan: sinon.stub(), + uploadDone: sinon.stub(), + uploadFailed: sinon.stub() + }, + that = this, + originalLoader = editor.widgets.registered.testImageWidget.loaderType; + + // Force a loader that will fail. + editor.widgets.registered.testImageWidget.loaderType = FailFileLoader; + + this.listeners.push( editor.widgets.on( 'instanceCreated', function( evt ) { + // Add spies to the widget. + for ( var i in stubs ) { + that.listeners.push( evt.data.on( i, stubs[ i ] ) ); + } + } ) ); + + assertPasteFiles( editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function( widgets ) { + // Restore original loader. + editor.widgets.registered.testImageWidget.loaderType = originalLoader; + + var loaderInstance = editor.uploadRepository.loaders[ 0 ]; + + assert.areSame( 1, stubs.uploadBegan.callCount, 'uploadBegan event count' ); + sinon.assert.calledWithExactly( stubs.uploadBegan, sinon.match.has( 'data', loaderInstance ) ); + + assert.areSame( 0, stubs.uploadDone.callCount, 'uploadDone event count' ); + + assert.areSame( 1, stubs.uploadFailed.callCount, 'uploadFailed event count' ); + assert.areSame( loaderInstance, stubs.uploadFailed.args[ 0 ][ 0 ].data.sender, 'Event data.sender' ); + + assert.areSame( 0, widgets.length, 'Widget count' ); + } + } ); + }, + + 'test upload error event is cancelable': function() { + var editor = this.editor, + that = this, + originalLoader = editor.widgets.registered.testImageWidget.loaderType; + + // Force a loader that will fail. + editor.widgets.registered.testImageWidget.loaderType = FailFileLoader; + + this.listeners.push( editor.widgets.on( 'instanceCreated', function( evt ) { + that.listeners.push( evt.data.on( 'uploadFailed', function( evt ) { + evt.cancel(); + } ) ); + } ) ); + + assertPasteFiles( editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function( widgets ) { + editor.widgets.registered.testImageWidget.loaderType = originalLoader; + + assert.areSame( 1, widgets.length, 'Widget was not removed' ); + } + } ); + }, + + 'test widgets can coexist side by side': function() { + // In other tests we're using image widget, if text widget works it means they work side + // by side just fine. + var editor = this.editor; + + assertPasteFiles( editor, { + files: [ bender.tools.getTestTxtFile() ], + callback: function( widgets ) { + assert.areSame( 1, widgets.length, 'Widgets count' ); + assert.areSame( 'testTextWidget', widgets[ 0 ].name, 'Widget name' ); + } + } ); + }, + + 'test default progress reporter': function() { + var imageBase = CKEDITOR.plugins.imagebase, + createForElementSpy = this.sandbox.spy( imageBase.progressBar, 'createForElement' ), + bindLoaderSpy = this.sandbox.spy( imageBase.progressBar.prototype, 'bindLoader' ), + editor = this.editor; + + assertPasteFiles( this.editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function( widgets ) { + assert.areSame( 1, createForElementSpy.callCount, 'ProgressBar.createForElement call count' ); + + sinon.assert.calledWithExactly( createForElementSpy, widgets[ 0 ].element ); + + sinon.assert.calledWithExactly( bindLoaderSpy, editor.uploadRepository.loaders[ 0 ] ); + } + } ); + }, + + 'test progress reporter customization': function() { + var CustomProgress = this.sandbox.spy( CKEDITOR.plugins.imagebase, 'progressBar' ), + widgetDefinition = this.editor.widgets.registered.testImageWidget, + originalProgress = widgetDefinition.progressIndicatorType; + + CustomProgress.createForElement = sinon.spy( function() { + return new CustomProgress(); + } ); + + widgetDefinition.progressIndicatorType = CustomProgress; + + + assertPasteFiles( this.editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function() { + widgetDefinition.progressIndicatorType = originalProgress; + + assert.areSame( 1, CustomProgress.callCount, 'ProgressBar.createForElement call count' ); + } + } ); + }, + + 'test preventing default progress reporter with event': function() { + // This test is a bit tricky, we need to add a new widget so that we can hook to widget.init method because + // widget#instanceCreated event is fired too late for this assertion. + var imageBase = CKEDITOR.plugins.imagebase, + createForElementSpy = this.sandbox.spy( imageBase.progressBar, 'createForElement' ), + editor = this.editor, + disposableListeners = this.listeners, + def = { + name: 'progressPrevent', + supportedTypes: /text\/html/, + init: function() { + disposableListeners.push( this.on( 'uploadBegan', function( evt ) { + evt.cancel(); + } ) ); + } + }; + + imageBase.addImageWidget( editor, def.name, imageBase.addFeature( editor, 'upload', def ) ); + + assertPasteFiles( editor, { + files: [ getTestHtmlFile() ], + callback: function() { + // Remove progressPrevent widget, not to affect other test cases. + editor.widgets.destroyAll( true ); + delete editor.widgets.registered.progressPrevent; + + assert.areSame( 0, createForElementSpy.callCount, 'ProgressBar.createForElement call count' ); + } + } ); + }, + + 'test preventing default progress reporter with widgetDefinition.progressIndicatorType': function() { + var createForElementSpy = this.sandbox.spy( CKEDITOR.plugins.imagebase.progressBar, 'createForElement' ), + widgetDefinition = this.editor.widgets.registered.testImageWidget, + originalProgress = widgetDefinition.progressIndicatorType; + + // Setting to null will disable loading bar. + widgetDefinition.progressIndicatorType = null; + + assertPasteFiles( this.editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function() { + widgetDefinition.progressIndicatorType = originalProgress; + + assert.areSame( 0, createForElementSpy.callCount, 'ProgressBar.createForElement call count' ); + } + } ); + }, + + 'test dropping file into a readonly does not create a widget': function() { + var editor = this.editor; + + editor.setReadOnly( true ); + + assertPasteFiles( editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function( widgets ) { + editor.setReadOnly( false ); + + assert.areSame( 0, widgets.length, 'Widgets count' ); } - }; - - imageBase.addImageWidget( editor, def.name, imageBase.addFeature( editor, 'upload', def ) ); - - this._assertPasteFiles( editor, { - files: [ getTestHtmlFile() ], - callback: function() { - // Remove progressPrevent widget, not to affect other test cases. - editor.widgets.destroyAll( true ); - delete editor.widgets.registered.progressPrevent; - - assert.areSame( 0, createForElementSpy.callCount, 'ProgressBar.createForElement call count' ); - } - } ); - }, - - 'test preventing default progress reporter with widgetDefinition.progressIndicatorType': function() { - var createForElementSpy = this.sandbox.spy( CKEDITOR.plugins.imagebase.progressBar, 'createForElement' ), - widgetDefinition = this.editor.widgets.registered.testImageWidget, - originalProgress = widgetDefinition.progressIndicatorType; - - // Setting to null will disable loading bar. - widgetDefinition.progressIndicatorType = null; - - this._assertPasteFiles( this.editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function() { - widgetDefinition.progressIndicatorType = originalProgress; - - assert.areSame( 0, createForElementSpy.callCount, 'ProgressBar.createForElement call count' ); - } - } ); - }, - - 'test dropping file into a readonly does not create a widget': function() { - var editor = this.editor; - - editor.setReadOnly( true ); - - this._assertPasteFiles( editor, { - files: [ bender.tools.getTestPngFile() ], - callback: function( widgets ) { - editor.setReadOnly( false ); - - assert.areSame( 0, widgets.length, 'Widgets count' ); - } - } ); - }, - - /* - * Main assertion for pasting files. - * - * @param {CKEDITOR.editor} editor - * @param {Object} options - * @param {File[]} [options.files=[]] Files to be dropped. - * @param {Function} options.callback Function to be called after the paste event. - * Params: - * - * * `CKEDITOR.plugins.widget[]` widgets - Array of widgets in a given editor. - * * `CKEDITOR.eventInfo` evt - Paste event. - */ - _assertPasteFiles: function( editor, options ) { - var files = options.files || [], - callback = options.callback; - - editor.once( 'paste', function( evt ) { - // Unfortunately at the time being we need to do additional timeout here, as - // the paste event gets cancelled. - setTimeout( function() { - resume( function() { - callback( objToArray( editor.widgets.instances ), evt ); - } ); - }, 0 ); - }, null, null, -1 ); - - - pasteFiles( editor, files ); - - wait(); - } - }; + } ); + } + }; bender.test( tests ); } )(); From 253751f99d7d708b755a1fe7c5b3177a391c82e9 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Sun, 21 Jan 2018 21:57:48 +0100 Subject: [PATCH 34/78] Major API change in ProgressReporter types: removed static createForElement(), progress bar is now stored directly in the widget wrapper (so that it doesnt leak to output). First: static createForElement() was not really a greatest idea in terms of DX. When extending the ProgressReporter type it required to reimplement it - mostly without any change other than changing the returned type. Instead one should simply create it using constructor, and then manually put the wrapper where they see it fit. Second: Placing inside a wrapper rather than element makes more sense, so it's being placed where the widget "guts" are. However this caused some styling issues it looks like people implementing the progress bars will have to pay attention to styling. --- plugins/easyimage/styles/easyimage.css | 13 +++++ plugins/imagebase/plugin.js | 16 +----- .../easyimage/manual/_helpers/tools.js | 36 ++++--------- .../plugins/easyimage/uploadintegrations.html | 7 +++ tests/plugins/easyimage/uploadintegrations.js | 28 ++++++++-- tests/plugins/imagebase/features/upload.js | 30 +++++------ .../plugins/imagebase/manual/progressbar.html | 3 +- tests/plugins/imagebase/progressbar.html | 18 ------- tests/plugins/imagebase/progressbar.js | 51 +++++++++---------- 9 files changed, 96 insertions(+), 106 deletions(-) create mode 100644 tests/plugins/easyimage/uploadintegrations.html diff --git a/plugins/easyimage/styles/easyimage.css b/plugins/easyimage/styles/easyimage.css index 3942117c690..dfe0832f714 100644 --- a/plugins/easyimage/styles/easyimage.css +++ b/plugins/easyimage/styles/easyimage.css @@ -21,6 +21,12 @@ The outline is not a part of the element's dimensions, we have to use a border a padding: 1px; } +.cke_widget_wrapper_easyimage figure { + margin-top: 0px; + margin-left: 40px; + margin-right: 40px; +} + .easyimage img, .cke_widget_uploadeasyimage img { display: block; height: auto; @@ -63,6 +69,7 @@ The outline is not a part of the element's dimensions, we have to use a border a .cke_loader { position: absolute; + top: 0px; left: 0px; right: 0px; } @@ -72,3 +79,9 @@ The outline is not a part of the element's dimensions, we have to use a border a background: #6a9ed1; width: 0; } + +/* It's important to have top, left, right in sync with figure margins, to algin the loader nicely. */ +.cke_widget_wrapper_easyimage .cke_loader { + left: 40px; + right: 40px; +} diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 575c6d0bd85..0445a58abaa 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -267,7 +267,8 @@ loader[ loadMethod ]( def.uploadUrl, def.additionalRequestParameters ); if ( widget.fire( 'uploadBegan', loader ) !== false && widget.progressIndicatorType ) { - var progress = widget.progressIndicatorType.createForElement( widget.element ); + var progress = new widget.progressIndicatorType(); + widget.wrapper.append( progress.wrapper ); progress.bindLoader( loader ); } @@ -527,19 +528,6 @@ this.bar = this.wrapper.getFirst(); } - /** - * @static - * @param {CKEDITOR.dom.element} wrapper Element where the progress bar will be **prepended**. - * @returns {CKEDITOR.plugins.imagebase.progressBar} - */ - ProgressBar.createForElement = function( wrapper ) { - var ret = new ProgressBar(); - - wrapper.append( ret.wrapper, true ); - - return ret; - }; - ProgressBar.prototype = new ProgressReporter(); ProgressReporter.prototype.updated = function( progress ) { diff --git a/tests/plugins/easyimage/manual/_helpers/tools.js b/tests/plugins/easyimage/manual/_helpers/tools.js index bfd1dccc471..cc189e9ba7a 100644 --- a/tests/plugins/easyimage/manual/_helpers/tools.js +++ b/tests/plugins/easyimage/manual/_helpers/tools.js @@ -50,37 +50,27 @@ var easyImageTools = { * on a image, and removes it indicating the progress. * * @param {CKEDITOR.editor} editor - * @param {Function} ProgressBar Base type for progress indicator - * {@link CKEDITOR.plugins.imageBase.progressBar}. + * @param {Function} ProgressReporter Base type for progress indicator + * {@link CKEDITOR.plugins.imageBase.progressReporter}. * @returns {Function} */ - getProgressOverlapType: function( editor, ProgressBar ) { + getProgressOverlapType: function( editor, ProgressReporter ) { if ( this._cache.ProgressOverlap ) { return this._cache.ProgressOverlap; } - function ProgressOverlap( totalHeight ) { - ProgressBar.call( this ); + function ProgressOverlap() { + ProgressReporter.call( this ); this.wrapper.addClass( 'cke_progress_overlap' ); - - this.totalHeight = totalHeight; } - ProgressOverlap.prototype = new ProgressBar(); + ProgressOverlap.prototype = new ProgressReporter(); ProgressOverlap.prototype.updated = function( progress ) { this.wrapper.setStyle( 'height', 100 - Math.round( progress * 100 ) + '%' ); }; - ProgressOverlap.createForElement = function( wrapper ) { - var ret = new ProgressOverlap( wrapper.getClientRect().height ); - - wrapper.append( ret.wrapper, true ); - - return ret; - }; - this._cache.ProgressOverlap = ProgressOverlap; return ProgressOverlap; @@ -92,7 +82,7 @@ var easyImageTools = { * * @returns {Function} */ - getProgressCircleType: function( editor, ProgressBar ) { + getProgressCircleType: function( editor, ProgressReporter ) { if ( this._cache.ProgressCircle ) { return this._cache.ProgressCircle; } @@ -117,7 +107,7 @@ var easyImageTools = { ' ' + '
' ); - ProgressBar.call( this ); + ProgressReporter.call( this ); this.wrapper.addClass( 'cke_progress_circle' ); @@ -134,7 +124,7 @@ var easyImageTools = { this.wrapper.append( this.circle ); } - ProgressCircle.prototype = new ProgressBar(); + ProgressCircle.prototype = new ProgressReporter(); ProgressCircle.prototype.updated = function( progress ) { var percentage = Math.round( progress * 100 ), @@ -146,14 +136,6 @@ var easyImageTools = { this.circle.data( 'pct', percentage ); }; - ProgressCircle.createForElement = function( wrapper ) { - var ret = new ProgressCircle(); - - wrapper.append( ret.wrapper, true ); - - return ret; - }; - this._cache.ProgressCircle = ProgressCircle; return ProgressCircle; diff --git a/tests/plugins/easyimage/uploadintegrations.html b/tests/plugins/easyimage/uploadintegrations.html new file mode 100644 index 00000000000..07008a87af6 --- /dev/null +++ b/tests/plugins/easyimage/uploadintegrations.html @@ -0,0 +1,7 @@ +
+
+ +
+
+

 

+
\ No newline at end of file diff --git a/tests/plugins/easyimage/uploadintegrations.js b/tests/plugins/easyimage/uploadintegrations.js index de3698ba287..819f15559aa 100644 --- a/tests/plugins/easyimage/uploadintegrations.js +++ b/tests/plugins/easyimage/uploadintegrations.js @@ -6,7 +6,12 @@ ( function() { 'use strict'; - bender.editor = true; + bender.editor = { + config: { + // Disable ACF, we want to catch any uncontrolled junk. + allwoedContent: true + } + }; // Loader mock that successes asynchronously. function AsyncSuccessFileLoader( editor, fileOrData, fileName ) { @@ -32,6 +37,7 @@ // Array of listeners to be cleared after each TC. this.listeners = []; + this.sandbox = sinon.sandbox.create(); this.editor.widgets.registered.easyimage.loaderType = AsyncSuccessFileLoader; @@ -79,6 +85,7 @@ listener.removeListener(); } ); + this.sandbox.restore(); this.editor.widgets.destroyAll( true ); this.listeners = []; @@ -89,6 +96,19 @@ this.editorBot.setHtmlWithSelection( '

^

' ); }, + 'test downcast does not include progress bar': function() { + var editor = this.editor; + + this.sandbox.stub( URL, 'createObjectURL' ).returns( '%BASE_PATH%_assets/logo.png' ); + + assertPasteFiles( editor, { + files: [ bender.tools.getTestPngFile() ], + callback: function() { + assert.beautified.html( CKEDITOR.document.getById( 'expected-progress-bar-downcast' ).getHtml(), editor.getData() ); + } + } ); + }, + // To test - edge case: changing mode during upload. 'test nothing explodes when upload finishes in a different mode': function() { var editor = this.editor, @@ -109,8 +129,10 @@ assert.isFalse( doneSpy.called, 'Race condition didn\'t occur' ); disposableListeners.push( widgets[ 0 ].once( 'uploadDone', function() { - resume( function() { - assert.isTrue( true ); + editor.setMode( 'wysiwyg', function() { + resume( function() { + assert.isTrue( true ); + } ); } ); } ) ); diff --git a/tests/plugins/imagebase/features/upload.js b/tests/plugins/imagebase/features/upload.js index 16c7caff741..eac1c4b1b40 100644 --- a/tests/plugins/imagebase/features/upload.js +++ b/tests/plugins/imagebase/features/upload.js @@ -44,6 +44,8 @@ this.listeners = []; this.sandbox = sinon.sandbox.create(); + this.ProgressBarSpy = sinon.spy( plugin, 'progressBar' ); + plugin.addImageWidget( editor, imageWidgetDef.name, plugin.addFeature( editor, 'upload', imageWidgetDef ) ); plugin.addImageWidget( editor, textWidgetDef.name, plugin.addFeature( editor, 'upload', textWidgetDef ) ); @@ -68,6 +70,7 @@ } ); this.sandbox.restore(); + this.ProgressBarSpy.reset(); this.editor.widgets.destroyAll( true ); this.listeners = []; @@ -250,16 +253,14 @@ 'test default progress reporter': function() { var imageBase = CKEDITOR.plugins.imagebase, - createForElementSpy = this.sandbox.spy( imageBase.progressBar, 'createForElement' ), + ProgressBarSpy = this.sandbox.spy( this.editor.widgets.registered.testImageWidget, 'progressIndicatorType' ), bindLoaderSpy = this.sandbox.spy( imageBase.progressBar.prototype, 'bindLoader' ), editor = this.editor; assertPasteFiles( this.editor, { files: [ bender.tools.getTestPngFile() ], - callback: function( widgets ) { - assert.areSame( 1, createForElementSpy.callCount, 'ProgressBar.createForElement call count' ); - - sinon.assert.calledWithExactly( createForElementSpy, widgets[ 0 ].element ); + callback: function() { + assert.areSame( 1, ProgressBarSpy.callCount, 'ProgressBar constructor call count' ); sinon.assert.calledWithExactly( bindLoaderSpy, editor.uploadRepository.loaders[ 0 ] ); } @@ -267,23 +268,20 @@ }, 'test progress reporter customization': function() { - var CustomProgress = this.sandbox.spy( CKEDITOR.plugins.imagebase, 'progressBar' ), + var CustomProgress = sinon.spy( CKEDITOR.plugins.imagebase.progressBar ), widgetDefinition = this.editor.widgets.registered.testImageWidget, originalProgress = widgetDefinition.progressIndicatorType; - CustomProgress.createForElement = sinon.spy( function() { - return new CustomProgress(); - } ); + CustomProgress.prototype = new CKEDITOR.plugins.imagebase.progressBar(); widgetDefinition.progressIndicatorType = CustomProgress; - assertPasteFiles( this.editor, { files: [ bender.tools.getTestPngFile() ], callback: function() { widgetDefinition.progressIndicatorType = originalProgress; - assert.areSame( 1, CustomProgress.callCount, 'ProgressBar.createForElement call count' ); + assert.areSame( 1, CustomProgress.callCount, 'CustomProgress constructor call count' ); } } ); }, @@ -292,7 +290,7 @@ // This test is a bit tricky, we need to add a new widget so that we can hook to widget.init method because // widget#instanceCreated event is fired too late for this assertion. var imageBase = CKEDITOR.plugins.imagebase, - createForElementSpy = this.sandbox.spy( imageBase.progressBar, 'createForElement' ), + ProgressBarSpy = this.ProgressBarSpy, editor = this.editor, disposableListeners = this.listeners, def = { @@ -307,6 +305,8 @@ imageBase.addImageWidget( editor, def.name, imageBase.addFeature( editor, 'upload', def ) ); + editor.widgets.registered.progressPrevent.progressIndicatorType = ProgressBarSpy; + assertPasteFiles( editor, { files: [ getTestHtmlFile() ], callback: function() { @@ -314,13 +314,13 @@ editor.widgets.destroyAll( true ); delete editor.widgets.registered.progressPrevent; - assert.areSame( 0, createForElementSpy.callCount, 'ProgressBar.createForElement call count' ); + assert.areSame( 0, ProgressBarSpy.callCount, 'ProgressBar constructor call count' ); } } ); }, 'test preventing default progress reporter with widgetDefinition.progressIndicatorType': function() { - var createForElementSpy = this.sandbox.spy( CKEDITOR.plugins.imagebase.progressBar, 'createForElement' ), + var ProgressBarSpy = this.ProgressBarSpy, widgetDefinition = this.editor.widgets.registered.testImageWidget, originalProgress = widgetDefinition.progressIndicatorType; @@ -332,7 +332,7 @@ callback: function() { widgetDefinition.progressIndicatorType = originalProgress; - assert.areSame( 0, createForElementSpy.callCount, 'ProgressBar.createForElement call count' ); + assert.areSame( 0, ProgressBarSpy.callCount, 'ProgressBar constructor call count' ); } } ); }, diff --git a/tests/plugins/imagebase/manual/progressbar.html b/tests/plugins/imagebase/manual/progressbar.html index 41fa288e02b..f85b259bbed 100644 --- a/tests/plugins/imagebase/manual/progressbar.html +++ b/tests/plugins/imagebase/manual/progressbar.html @@ -83,7 +83,8 @@ loader.remove(); } - loader = Constructor.createForElement( editor.editable().findOne( 'div' ) ); + loader = new Constructor(); + editor.editable().findOne( 'div' ).append( loader.wrapper, true ); loader.updated( CKEDITOR.document.getById( 'progress-range' ).getValue() ); window.gLoader = loader; diff --git a/tests/plugins/imagebase/progressbar.html b/tests/plugins/imagebase/progressbar.html index d1224b35e35..9f58ea28e8c 100644 --- a/tests/plugins/imagebase/progressbar.html +++ b/tests/plugins/imagebase/progressbar.html @@ -1,26 +1,8 @@
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
diff --git a/tests/plugins/imagebase/progressbar.js b/tests/plugins/imagebase/progressbar.js index 052bd9c911b..f87d7d02669 100644 --- a/tests/plugins/imagebase/progressbar.js +++ b/tests/plugins/imagebase/progressbar.js @@ -13,14 +13,11 @@ CKEDITOR.event.implementOn( loaderMock ); - // Store the content of #nested-sandbox - it will be used to restore original HTML - // before each test case. - this.nestedSandbox = doc.getById( 'nested-sandbox' ); - this._nestedSandboxContent = this.nestedSandbox.getHtml(); + this.sandbox = doc.getById( 'sandbox' ); }, setUp: function() { - this.nestedSandbox.setHtml( this._nestedSandboxContent ); + this.sandbox.setHtml( '' ); this.dummyProgress = new ProgressBar(); @@ -30,29 +27,20 @@ sinon.stub( this.dummyProgress, 'updated' ); }, - 'test createForElement()': function() { - var ret = ProgressBar.createForElement( this.nestedSandbox.findOne( '.nested2' ) ); + 'test constructor creates proper elements': function() { + var ret = new ProgressBar(); + this.sandbox.append( ret.wrapper, true ); - assert.isInstanceOf( ProgressBar, ret, 'Returned type' ); + assert.areSame( this.sandbox.findOne( '.cke_loader' ), ret.wrapper, 'ret.wrapper' ); + assert.areSame( this.sandbox.findOne( '.cke_bar' ), ret.bar, 'ret.bar' ); - assert.beautified.html( doc.getById( 'expected-create-from-element' ).getHtml(), this.nestedSandbox.getHtml() ); - }, - - 'test createForElement() creates proper elements': function() { - var ret = ProgressBar.createForElement( this.nestedSandbox.findOne( '.nested2' ) ); - - assert.areSame( this.nestedSandbox.findOne( '.cke_loader' ), ret.wrapper, 'ret.wrapper' ); - assert.areSame( this.nestedSandbox.findOne( '.cke_bar' ), ret.bar, 'ret.bar' ); - }, - - 'test createForElement() prepends the element': function() { - ProgressBar.createForElement( this.nestedSandbox ); - - assert.beautified.html( doc.getById( 'expected-create-from-element-prepend' ).getHtml(), this.nestedSandbox.getHtml() ); + assert.beautified.html( doc.getById( 'expected-create-from-element' ).getHtml(), this.sandbox.getHtml() ); }, 'test remove()': function() { - var ret = ProgressBar.createForElement( this.nestedSandbox ); + var ret = new ProgressBar( this.sandbox ); + + this.sandbox.append( ret.wrapper ); ret.remove(); @@ -70,7 +58,7 @@ [ -1.0, '0%' ], [ 1.1, '100%' ] ], - ret = ProgressBar.createForElement( this.nestedSandbox ); + ret = new ProgressBar( this.sandbox ); CKEDITOR.tools.array.forEach( values, function( assertSet ) { ret.updated( assertSet[ 0 ] ); @@ -79,11 +67,11 @@ }, // 'test update() locks the snapshot': function() { - // // todo + // @todo: add coverage. // }, 'test failed()': function() { - var ret = ProgressBar.createForElement( this.nestedSandbox ); + var ret = this._createProgressBar(); ret.failed(); @@ -91,7 +79,7 @@ }, 'test aborted()': function() { - var ret = ProgressBar.createForElement( this.nestedSandbox ); + var ret = this._createProgressBar(); ret.aborted(); @@ -99,7 +87,7 @@ }, 'test done()': function() { - var ret = ProgressBar.createForElement( this.nestedSandbox ); + var ret = this._createProgressBar(); ret.done(); @@ -183,6 +171,13 @@ loaderMock.fire( 'error' ); assert.areSame( 1, this.dummyProgress.failed.callCount, 'Failed call count' ); + }, + + // Adds the progress bar straight into DOM and returns ProgressBar instance. + _createProgressBar: function() { + var ret = new ProgressBar(); + this.sandbox.append( ret.wrapper ); + return ret; } }; From 9f3e44fbdd66f3d6a6a862c83502d37817e303d3 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Sun, 21 Jan 2018 22:07:05 +0100 Subject: [PATCH 35/78] Docs: added an API listing for ProgressBar constructor. --- plugins/imagebase/plugin.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 0445a58abaa..6611a4fe1b5 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -511,7 +511,11 @@ }; /** - * Vertical progress bar. + * Type adding a vertical progress bar. + * + * var progress = new CKEDITOR.plugins.imagebase.progressBar(); + * myWrapper.append( progress.wrapper, true ); + * progress.bindLoader( myFileLoader ); * * @class CKEDITOR.plugins.imagebase.progressBar * @extends CKEDITOR.plugins.imagebase.progressReporter From 409a596bdd1c39935f94a4ffbaf88ef77d0483b7 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Sun, 21 Jan 2018 22:14:35 +0100 Subject: [PATCH 36/78] Renamed last remaining "progress indicator" into "progress reporter" which sounds much better for a generic class. --- plugins/easyimage/plugin.js | 2 +- plugins/imagebase/plugin.js | 10 +++++----- .../easyimage/manual/customprogressbar.html | 8 ++++---- .../imagebase/features/manual/upload.html | 2 +- tests/plugins/imagebase/features/upload.js | 18 +++++++++--------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/plugins/easyimage/plugin.js b/plugins/easyimage/plugin.js index 6dc68f9063f..5eacd999096 100644 --- a/plugins/easyimage/plugin.js +++ b/plugins/easyimage/plugin.js @@ -156,7 +156,7 @@ loaderType: CKEDITOR.plugins.cloudservices.cloudServicesLoader, - progressIndicatorType: CKEDITOR.plugins.imagebase.progressBar, + progressReporterType: CKEDITOR.plugins.imagebase.progressBar, upcasts: { figure: function( element ) { diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 6611a4fe1b5..d8900febe45 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -188,7 +188,7 @@ function getUploadFeature() { var ret = { - progressIndicatorType: ProgressBar, + progressReporterType: ProgressBar, setUp: function( editor, definition ) { editor.on( 'paste', function( evt ) { @@ -266,8 +266,8 @@ loader[ loadMethod ]( def.uploadUrl, def.additionalRequestParameters ); - if ( widget.fire( 'uploadBegan', loader ) !== false && widget.progressIndicatorType ) { - var progress = new widget.progressIndicatorType(); + if ( widget.fire( 'uploadBegan', loader ) !== false && widget.progressReporterType ) { + var progress = new widget.progressReporterType(); widget.wrapper.append( progress.wrapper ); progress.bindLoader( loader ); } @@ -462,14 +462,14 @@ }, /** - * Removes the progress indicator from DOM. + * Removes the progress reporter from DOM. */ remove: function() { this.wrapper.remove(); }, /** - * Binds this progress indicator to a given `loader`. + * Binds this progress reporter to a given `loader`. * * It will automatically remove its listeners when the `loader` has triggered one of following events: * diff --git a/tests/plugins/easyimage/manual/customprogressbar.html b/tests/plugins/easyimage/manual/customprogressbar.html index 3b6cf5700d9..3ea0f5c1505 100644 --- a/tests/plugins/easyimage/manual/customprogressbar.html +++ b/tests/plugins/easyimage/manual/customprogressbar.html @@ -7,14 +7,14 @@

Default progress bar

-

Cricular progress indicator

+

Cricular progress reporter

Drop an image below:

-

Overlapping progress indicator

+

Overlapping progress reporter

Drop an image below:

@@ -35,7 +35,7 @@

Overlapping progress indicator

var widgetDef = evt.data, progressHelper = easyImageTools.progress, imageBasePlugin = CKEDITOR.plugins.imagebase, - // Each editor uses a different progress indicator. + // Each editor uses a different progress reporter. mapping = { editor1: imageBasePlugin.progressBar, editor2: progressHelper.getProgressCircleType( this, imageBasePlugin.progressReporter ), @@ -43,7 +43,7 @@

Overlapping progress indicator

}; if ( widgetDef.name === 'easyimage' ) { - widgetDef.progressIndicatorType = mapping[ this.name ]; + widgetDef.progressReporterType = mapping[ this.name ]; } } } diff --git a/tests/plugins/imagebase/features/manual/upload.html b/tests/plugins/imagebase/features/manual/upload.html index b9238a4e03b..58e85cee4ed 100644 --- a/tests/plugins/imagebase/features/manual/upload.html +++ b/tests/plugins/imagebase/features/manual/upload.html @@ -32,7 +32,7 @@ // Match any file type. def.supportedTypes = /^.+$/; - def.progressIndicatorType = CKEDITOR.plugins.imagebase.progressBar; + def.progressReporterType = CKEDITOR.plugins.imagebase.progressBar; } } ); diff --git a/tests/plugins/imagebase/features/upload.js b/tests/plugins/imagebase/features/upload.js index eac1c4b1b40..538bfb469e6 100644 --- a/tests/plugins/imagebase/features/upload.js +++ b/tests/plugins/imagebase/features/upload.js @@ -253,7 +253,7 @@ 'test default progress reporter': function() { var imageBase = CKEDITOR.plugins.imagebase, - ProgressBarSpy = this.sandbox.spy( this.editor.widgets.registered.testImageWidget, 'progressIndicatorType' ), + ProgressBarSpy = this.sandbox.spy( this.editor.widgets.registered.testImageWidget, 'progressReporterType' ), bindLoaderSpy = this.sandbox.spy( imageBase.progressBar.prototype, 'bindLoader' ), editor = this.editor; @@ -270,16 +270,16 @@ 'test progress reporter customization': function() { var CustomProgress = sinon.spy( CKEDITOR.plugins.imagebase.progressBar ), widgetDefinition = this.editor.widgets.registered.testImageWidget, - originalProgress = widgetDefinition.progressIndicatorType; + originalProgress = widgetDefinition.progressReporterType; CustomProgress.prototype = new CKEDITOR.plugins.imagebase.progressBar(); - widgetDefinition.progressIndicatorType = CustomProgress; + widgetDefinition.progressReporterType = CustomProgress; assertPasteFiles( this.editor, { files: [ bender.tools.getTestPngFile() ], callback: function() { - widgetDefinition.progressIndicatorType = originalProgress; + widgetDefinition.progressReporterType = originalProgress; assert.areSame( 1, CustomProgress.callCount, 'CustomProgress constructor call count' ); } @@ -305,7 +305,7 @@ imageBase.addImageWidget( editor, def.name, imageBase.addFeature( editor, 'upload', def ) ); - editor.widgets.registered.progressPrevent.progressIndicatorType = ProgressBarSpy; + editor.widgets.registered.progressPrevent.progressReporterType = ProgressBarSpy; assertPasteFiles( editor, { files: [ getTestHtmlFile() ], @@ -319,18 +319,18 @@ } ); }, - 'test preventing default progress reporter with widgetDefinition.progressIndicatorType': function() { + 'test preventing default progress reporter with widgetDefinition.progressReporterType': function() { var ProgressBarSpy = this.ProgressBarSpy, widgetDefinition = this.editor.widgets.registered.testImageWidget, - originalProgress = widgetDefinition.progressIndicatorType; + originalProgress = widgetDefinition.progressReporterType; // Setting to null will disable loading bar. - widgetDefinition.progressIndicatorType = null; + widgetDefinition.progressReporterType = null; assertPasteFiles( this.editor, { files: [ bender.tools.getTestPngFile() ], callback: function() { - widgetDefinition.progressIndicatorType = originalProgress; + widgetDefinition.progressReporterType = originalProgress; assert.areSame( 0, ProgressBarSpy.callCount, 'ProgressBar constructor call count' ); } From b3ffed80389a4472276d6d4ee580c6bb40f423cf Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Sun, 21 Jan 2018 22:17:05 +0100 Subject: [PATCH 37/78] Tests: fixed failing manual test. --- tests/plugins/easyimage/manual/progressbar.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/plugins/easyimage/manual/progressbar.html b/tests/plugins/easyimage/manual/progressbar.html index 96186f51265..15494d28e34 100644 --- a/tests/plugins/easyimage/manual/progressbar.html +++ b/tests/plugins/easyimage/manual/progressbar.html @@ -30,7 +30,8 @@

Sample editor

var IMG_URL = '%BASE_PATH%_assets/logo.png', commonConfig = { - cloudServices_url: 'https://files.cke-cs.com/upload/' + cloudServices_url: 'https://files.cke-cs.com/upload/', + cloudServices_token: 'dummy-token' }; window.XMLHttpRequest.responseData = { From cfcd9a8c6899d8adfc3449a502dfbc70adbcf299 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Sun, 21 Jan 2018 22:38:10 +0100 Subject: [PATCH 38/78] Tests: added manual test for progress bar being displayed with right-to-left languages. --- .../imagebase/manual/progressbarrtl.html | 28 +++++++++++++++++++ .../imagebase/manual/progressbarrtl.md | 17 +++++++++++ 2 files changed, 45 insertions(+) create mode 100644 tests/plugins/imagebase/manual/progressbarrtl.html create mode 100644 tests/plugins/imagebase/manual/progressbarrtl.md diff --git a/tests/plugins/imagebase/manual/progressbarrtl.html b/tests/plugins/imagebase/manual/progressbarrtl.html new file mode 100644 index 00000000000..d0e26737d13 --- /dev/null +++ b/tests/plugins/imagebase/manual/progressbarrtl.html @@ -0,0 +1,28 @@ +
+
+

line

+

line

+
+

Above div will gain your progress indicator.

+
+ + + diff --git a/tests/plugins/imagebase/manual/progressbarrtl.md b/tests/plugins/imagebase/manual/progressbarrtl.md new file mode 100644 index 00000000000..0e4aea51354 --- /dev/null +++ b/tests/plugins/imagebase/manual/progressbarrtl.md @@ -0,0 +1,17 @@ +@bender-tags: 4.9.0, feature, 932 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, imagebase + +# Progress Reporter RTL + +The editor uses right-to-left language editor, and shows 30% progress. + +1. Take a look at the progress bar. + +## Expected + +Progress bar starts from right side of the screen. + +## Unexpected + +Progress bar starts from left side of the screen. \ No newline at end of file From 7ce4fd59b8557ca3be8e788595917b73d72978b3 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Sun, 21 Jan 2018 22:57:10 +0100 Subject: [PATCH 39/78] Tests: ignore unsupported environments in upload widget feature / progress bar tests. Also spotted two minor CSS code style issues, fixed it together. --- tests/plugins/easyimage/balloontoolbar.js | 4 ++++ tests/plugins/easyimage/uploadintegrations.js | 4 ++++ tests/plugins/imagebase/features/manual/upload.html | 4 ++++ tests/plugins/imagebase/features/upload.js | 4 ++++ tests/plugins/imagebase/manual/progressbar.html | 6 +++++- tests/plugins/imagebase/manual/progressbarrtl.html | 6 +++++- tests/plugins/imagebase/progressbar.js | 4 ++++ 7 files changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/plugins/easyimage/balloontoolbar.js b/tests/plugins/easyimage/balloontoolbar.js index c9813063323..2d51d61b6b1 100644 --- a/tests/plugins/easyimage/balloontoolbar.js +++ b/tests/plugins/easyimage/balloontoolbar.js @@ -61,6 +61,10 @@ initialFrameHeight = testSuiteIframe && testSuiteIframe.getStyle( 'height' ), tests = { setUp: function() { + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 ) { + assert.ignore(); + } + // This test checks real balloon panel positioning. To avoid affecting position with scroll offset, set the parent iframe height // enough to contain entire content. Note that iframe is not present if the test suite is open in a separate window, or ran on IEs. if ( testSuiteIframe ) { diff --git a/tests/plugins/easyimage/uploadintegrations.js b/tests/plugins/easyimage/uploadintegrations.js index 819f15559aa..329b242d22a 100644 --- a/tests/plugins/easyimage/uploadintegrations.js +++ b/tests/plugins/easyimage/uploadintegrations.js @@ -93,6 +93,10 @@ }, setUp: function() { + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 ) { + assert.ignore(); + } + this.editorBot.setHtmlWithSelection( '

^

' ); }, diff --git a/tests/plugins/imagebase/features/manual/upload.html b/tests/plugins/imagebase/features/manual/upload.html index 58e85cee4ed..499fe547eca 100644 --- a/tests/plugins/imagebase/features/manual/upload.html +++ b/tests/plugins/imagebase/features/manual/upload.html @@ -5,6 +5,10 @@ + + + diff --git a/tests/plugins/imagebase/features/manual/uploadsimple.md b/tests/plugins/imagebase/features/manual/uploadsimple.md new file mode 100644 index 00000000000..736c60aca1b --- /dev/null +++ b/tests/plugins/imagebase/features/manual/uploadsimple.md @@ -0,0 +1,13 @@ +@bender-tags: 4.9.0, feature, 932 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, toolbar, elementspath, imagebase, cloudservices + +## Upload Widget + +1. Drop a png/jpg files into the editor. + +## Expected + +A simple widget with the name of a file is created. + +**Note:** at the time of writing Cloud Service accepts only images, and return `400` code for any other resource type. \ No newline at end of file From 61ee1c30e2c39b7f4b750fced3d01ca83b4a5443 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 24 Jan 2018 20:50:59 +0100 Subject: [PATCH 63/78] Added lang meta entry. --- dev/langtool/meta/ckeditor.plugin-easyimage/meta.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/langtool/meta/ckeditor.plugin-easyimage/meta.txt b/dev/langtool/meta/ckeditor.plugin-easyimage/meta.txt index bf239264cd5..cea6e9dc42b 100644 --- a/dev/langtool/meta/ckeditor.plugin-easyimage/meta.txt +++ b/dev/langtool/meta/ckeditor.plugin-easyimage/meta.txt @@ -4,3 +4,4 @@ altText = Label for button converting image to assign an alternative text. fullImage = Label for button converting image to a full width. sideImage = Label for button converting image to be a side image. +uploadFailed = Contents of alert displayed when Easy Image widget could not be uploaded due to network error. From 7081a150ed9cb2f9f5fc8877df1cf009e0d32f40 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 24 Jan 2018 21:07:17 +0100 Subject: [PATCH 64/78] Docs: more API docs for the Widget Upload Feature. --- plugins/imagebase/plugin.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index b0ed1a61f2c..4c9f5cacb8b 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -187,6 +187,15 @@ } function getUploadFeature() { + /** + * Widget feature dedicated for handling seamless file uploads. + * + * This type serves solely as a mixing, and should be added using + * {@link CKEDITOR.plugins.imagebase#addFeature} method. + * + * @class CKEDITOR.plugins.imagebase.featuresDefinitions.upload + * @abstract + */ var ret = { progressReporterType: ProgressBar, @@ -256,7 +265,7 @@ } ); }, - /* + /** * Initiates an upload process on a given widget. It does that by firing a {@link CKEDITOR.fileTools#fileLoader} request. * * @private @@ -276,12 +285,28 @@ this._beginUpload( widget, loader ); }, + /** + * Tells whether the loader is complete. + * + * @private + * @param {CKEDITOR.fileTools.fileLoader} loader + * @returns {Boolean} + */ _isLoaderDone: function( loader ) { var xhr = loader.xhr; return xhr && loader.xhr.readyState === 4; }, + /** + * + * @private + * @param {CKEDITOR.editor} editor + * @param {Blob/String} fileOrData See {@link CKEDITOR.fileTools.fileLoader}. + * @param {String} [fileName] Preferred file name to be passed to the upload process. + * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget definition that the loader is spawned for. + * @returns {CKEDITOR.fileTools.fileLoader} + */ _spawnLoader: function( editor, file, fileName, widgetDef ) { var loadMethod = widgetDef.loadMethod || 'loadAndUpload', loader = editor.uploadRepository.create( file, fileName, widgetDef.loaderType ); @@ -291,6 +316,13 @@ return loader; }, + /** + * Initializes the upload process for given `widget` using `loader`. + * + * @private + * @param {CKEDITOR.plugins.widget} widget + * @param {CKEDITOR.fileTools.fileLoader} loader + */ _beginUpload: function( widget, loader ) { function widgetCleanup() { // Remove upload id so that it's not being re-requested when e.g. some1 copies and pastes From 531335ed403c3409839ea6c260096ae18e350fde Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 24 Jan 2018 21:14:04 +0100 Subject: [PATCH 65/78] Docs: API docs polishing. --- plugins/imagebase/plugin.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 4c9f5cacb8b..59412c2c973 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -193,6 +193,9 @@ * This type serves solely as a mixing, and should be added using * {@link CKEDITOR.plugins.imagebase#addFeature} method. * + * This API is not yet in a final shape, thus marked as a private. It can be changed at any point. + * + * @private * @class CKEDITOR.plugins.imagebase.featuresDefinitions.upload * @abstract */ @@ -266,7 +269,7 @@ }, /** - * Initiates an upload process on a given widget. It does that by firing a {@link CKEDITOR.fileTools#fileLoader} request. + * Initiates an upload process on a given widget. It does that by firing a {@link CKEDITOR.fileTools.fileLoader} request. * * @private * @param {CKEDITOR.editor} editor Editor instance. From 49e5d47a55097e1196d102b5138815a01c6b1b42 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 25 Jan 2018 00:22:22 +0100 Subject: [PATCH 66/78] Changed the order of _spawnLoader method. --- plugins/easyimage/plugin.js | 2 +- plugins/imagebase/plugin.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/easyimage/plugin.js b/plugins/easyimage/plugin.js index 89cbd0f9a0d..e69362d3d7e 100644 --- a/plugins/easyimage/plugin.js +++ b/plugins/easyimage/plugin.js @@ -342,7 +342,7 @@ imgFormat = imgSrc.match( /image\/([a-z]+?);/i ); imgFormat = ( imgFormat && imgFormat[ 1 ] ) || 'jpg'; - var loader = easyImageDef._spawnLoader( editor, imgSrc, undefined, easyImageDef ); + var loader = easyImageDef._spawnLoader( editor, imgSrc, easyImageDef ); widgetElement = easyImageDef._insertWidget( editor, easyImageDef, imgSrc, false, { uploadId: loader.id diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 59412c2c973..be9741f37bc 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -234,7 +234,7 @@ evt.stop(); CKEDITOR.tools.array.forEach( matchedFiles, function( curFile, index ) { - var loader = ret._spawnLoader( editor, curFile, curFile.name, definition ); + var loader = ret._spawnLoader( editor, curFile, definition, curFile.name ); ret._insertWidget( editor, definition, URL.createObjectURL( curFile ), true, { uploadId: loader.id } ); @@ -306,11 +306,11 @@ * @private * @param {CKEDITOR.editor} editor * @param {Blob/String} fileOrData See {@link CKEDITOR.fileTools.fileLoader}. - * @param {String} [fileName] Preferred file name to be passed to the upload process. * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget definition that the loader is spawned for. + * @param {String} [fileName] Preferred file name to be passed to the upload process. * @returns {CKEDITOR.fileTools.fileLoader} */ - _spawnLoader: function( editor, file, fileName, widgetDef ) { + _spawnLoader: function( editor, file, widgetDef, fileName ) { var loadMethod = widgetDef.loadMethod || 'loadAndUpload', loader = editor.uploadRepository.create( file, fileName, widgetDef.loaderType ); From e1c565608a5c35173bc2b29b6020a0aa5d194827 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 25 Jan 2018 00:23:05 +0100 Subject: [PATCH 67/78] Refactoring: _loadWidget is no longer used - removing. --- plugins/imagebase/plugin.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index be9741f37bc..9d5e0a65666 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -268,26 +268,6 @@ } ); }, - /** - * Initiates an upload process on a given widget. It does that by firing a {@link CKEDITOR.fileTools.fileLoader} request. - * - * @private - * @param {CKEDITOR.editor} editor Editor instance. - * @param {CKEDITOR.plugins.widget} widget Widget to be loaded. - * @param {CKEDITOR.plugins.widget.definition} def Loaded widget definition. - * @param {File/String} file File or base64-encoded file string to be uploaded. - * @param {String} [fileName] If `file` is passed as a base64-encoded string, this field should contain a name. - */ - _loadWidget: function( editor, widget, def, file, fileName ) { - var uploads = editor.uploadRepository, - loadMethod = def.loadMethod || 'loadAndUpload', - loader = uploads.create( file, fileName, def.loaderType ); - - loader[ loadMethod ]( def.uploadUrl, def.additionalRequestParameters ); - - this._beginUpload( widget, loader ); - }, - /** * Tells whether the loader is complete. * From c8870aaa73d6ff8ed936cca7f2c0fd71f3507fbd Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 25 Jan 2018 00:37:52 +0100 Subject: [PATCH 68/78] Refactoring: renamed uploadBegan to uploadStarted event. --- plugins/easyimage/plugin.js | 2 +- plugins/imagebase/plugin.js | 6 +++--- .../plugins/imagebase/features/manual/upload.html | 2 +- tests/plugins/imagebase/features/upload.js | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/easyimage/plugin.js b/plugins/easyimage/plugin.js index e69362d3d7e..4ac5a5fd711 100644 --- a/plugins/easyimage/plugin.js +++ b/plugins/easyimage/plugin.js @@ -228,7 +228,7 @@ this.addClass( editor.config.easyimage_class ); } - this.on( 'uploadBegan', function() { + this.on( 'uploadStarted', function() { var widget = this; getNaturalWidth( widget.parts.image, function( width ) { diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 9d5e0a65666..68c14f8c3d7 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -351,7 +351,7 @@ } ); } ); - if ( widget.fire( 'uploadBegan', loader ) !== false && widget.progressReporterType ) { + if ( widget.fire( 'uploadStarted', loader ) !== false && widget.progressReporterType ) { widget.setData( 'uploadId', loader.id ); if ( !widget._isLoaderDone( loader ) ) { @@ -402,7 +402,7 @@ /* * Fired when upload was initiated and before response is fetched. * - * progress.once( 'uploadBegan', function( evt ) { + * progress.once( 'uploadStarted', function( evt ) { * evt.cancel(); * // Implement a custom progress bar. * } ); @@ -412,7 +412,7 @@ * Note that the event will be fired even if the widget was created for a loader that * is already resolved. * - * @evt uploadBegan + * @evt uploadStarted * @param {CKEDITOR.fileTools.fileLoader} data Lader that is used for this widget. */ diff --git a/tests/plugins/imagebase/features/manual/upload.html b/tests/plugins/imagebase/features/manual/upload.html index 7db412fad48..759f9d8495d 100644 --- a/tests/plugins/imagebase/features/manual/upload.html +++ b/tests/plugins/imagebase/features/manual/upload.html @@ -45,7 +45,7 @@ var widget = evt.data; if ( widget.name === 'placeholder' ) { - widget.once( 'uploadBegan', function( uploadEvent ) { + widget.once( 'uploadStarted', function( uploadEvent ) { widget.setData( 'name', 'Uploading...' ); } ); diff --git a/tests/plugins/imagebase/features/upload.js b/tests/plugins/imagebase/features/upload.js index e5d347f68e0..5550a3583ac 100644 --- a/tests/plugins/imagebase/features/upload.js +++ b/tests/plugins/imagebase/features/upload.js @@ -179,7 +179,7 @@ 'test events': function() { var editor = this.editor, stubs = { - uploadBegan: sinon.stub(), + uploadStarted: sinon.stub(), uploadDone: sinon.stub(), uploadFailed: sinon.stub() }, @@ -195,8 +195,8 @@ assertPasteFiles( editor, { files: [ bender.tools.getTestPngFile() ], callback: function() { - assert.areSame( 1, stubs.uploadBegan.callCount, 'uploadBegan event count' ); - sinon.assert.calledWithExactly( stubs.uploadBegan, sinon.match.has( 'data', editor.uploadRepository.loaders[ 0 ] ) ); + assert.areSame( 1, stubs.uploadStarted.callCount, 'uploadStarted event count' ); + sinon.assert.calledWithExactly( stubs.uploadStarted, sinon.match.has( 'data', editor.uploadRepository.loaders[ 0 ] ) ); assert.areSame( 1, stubs.uploadDone.callCount, 'uploadDone event count' ); @@ -208,7 +208,7 @@ 'test upload error': function() { var editor = this.editor, stubs = { - uploadBegan: sinon.stub(), + uploadStarted: sinon.stub(), uploadDone: sinon.stub(), uploadFailed: sinon.stub() }, @@ -233,8 +233,8 @@ var loaderInstance = editor.uploadRepository.loaders[ 0 ]; - assert.areSame( 1, stubs.uploadBegan.callCount, 'uploadBegan event count' ); - sinon.assert.calledWithExactly( stubs.uploadBegan, sinon.match.has( 'data', loaderInstance ) ); + assert.areSame( 1, stubs.uploadStarted.callCount, 'uploadStarted event count' ); + sinon.assert.calledWithExactly( stubs.uploadStarted, sinon.match.has( 'data', loaderInstance ) ); assert.areSame( 0, stubs.uploadDone.callCount, 'uploadDone event count' ); @@ -330,7 +330,7 @@ name: 'progressPrevent', supportedTypes: /text\/html/, init: function() { - disposableListeners.push( this.on( 'uploadBegan', function( evt ) { + disposableListeners.push( this.on( 'uploadStarted', function( evt ) { evt.cancel(); } ) ); } From e4ed3c29235c87014a9eb711571a140561b2b027 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 25 Jan 2018 00:41:13 +0100 Subject: [PATCH 69/78] Tests: lowered fake file loader response timings, so that the test suite passes faster. --- tests/plugins/easyimage/uploadintegrations.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/plugins/easyimage/uploadintegrations.js b/tests/plugins/easyimage/uploadintegrations.js index df53901743e..f7a8a1ec3f0 100644 --- a/tests/plugins/easyimage/uploadintegrations.js +++ b/tests/plugins/easyimage/uploadintegrations.js @@ -67,11 +67,11 @@ setTimeout( function() { that.update(); - }, 200 ); + }, 40 ); setTimeout( function() { that.update(); - }, 400 ); + }, 120 ); setTimeout( function() { var evtData = { @@ -83,7 +83,7 @@ }; that._lastTimeout( evtData ); - }, 1000 ); + }, 350 ); }, _lastTimeout: function( evtData ) { this.responseData = { From 197619fb4040c3d23d5650412d3ff607093cf226 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 25 Jan 2018 10:14:07 +0100 Subject: [PATCH 70/78] Docs: improved API docs in widget upload feature. --- plugins/imagebase/plugin.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 68c14f8c3d7..566466e7710 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -200,6 +200,13 @@ * @abstract */ var ret = { + /** + * Type used for for progress reporting, it has to be a subclass of {@link CKEDITOR.plugins.imagebase.progressReporter}. + * + * Could be set to `false` so that there is no reporter created at all. + * + * @property {Function/Boolean} [progressReporterType=CKEDITOR.plugins.imagebase.progressBar] + */ progressReporterType: ProgressBar, setUp: function( editor, definition ) { @@ -276,6 +283,7 @@ * @returns {Boolean} */ _isLoaderDone: function( loader ) { + // This method should be removed once #1497 is done. var xhr = loader.xhr; return xhr && loader.xhr.readyState === 4; @@ -399,6 +407,12 @@ } } + /** + * Preferred file loader type used for requests. + * + * @property {Function} [loaderType=CKEDITOR.fileTools.fileLoader] + */ + /* * Fired when upload was initiated and before response is fetched. * From 979f111b8ca5c138e7ecf0fa6a694cb6fb7d66a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krzto=C5=84?= Date: Thu, 25 Jan 2018 16:36:17 +0100 Subject: [PATCH 71/78] Fixed typos. --- plugins/easyimage/plugin.js | 2 +- plugins/imagebase/plugin.js | 6 +++--- tests/plugins/easyimage/uploadintegrations.js | 2 +- tests/plugins/imagebase/features/_helpers/tools.js | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/easyimage/plugin.js b/plugins/easyimage/plugin.js index 4ac5a5fd711..7be991bff2b 100644 --- a/plugins/easyimage/plugin.js +++ b/plugins/easyimage/plugin.js @@ -326,7 +326,7 @@ isDataInSrc = imgSrc && imgSrc.substring( 0, 5 ) == 'data:', isRealObject = img.data( 'cke-realelement' ) === null; - // We are not uploading images in non-editable blocs and fake objects (https://dev.ckeditor.com/ticket/13003). + // We are not uploading images in non-editable blocks and fake objects (https://dev.ckeditor.com/ticket/13003). if ( isDataInSrc && isRealObject && !img.isReadOnly( 1 ) ) { widgetsFound++; diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 566466e7710..daf5d755d85 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -224,7 +224,7 @@ curFile; // Refetch the definition... original definition looks like an outdated copy and it doesn't - // include members inherited form imagebase. + // include members inherited from imagebase. definition = editor.widgets.registered[ definition.name ]; for ( var i = 0; i < filesCount; i++ ) { @@ -316,7 +316,7 @@ */ _beginUpload: function( widget, loader ) { function widgetCleanup() { - // Remove upload id so that it's not being re-requested when e.g. some1 copies and pastes + // Remove upload id so that it's not being re-requested when e.g. someone copies and pastes // the widget in other place. if ( widget.isInited() ) { widget.setData( 'uploadId', undefined ); @@ -380,7 +380,7 @@ * @param {CKEDITOR.editor} editor * @param {CKEDITOR.plugins.widget.definition} widgetDef * @param {String} blobUrl Blob URL of an image. - * @param {Boolean} [finalize=true] If `false` widget will not be automatically finalized (added to {@link CKEDITOR.plugins.widget.repository}) + * @param {Boolean} [finalize=true] If `false` widget will not be automatically finalized (added to {@link CKEDITOR.plugins.widget.repository}), * but returned as a {@link CKEDITOR.dom.element} instance. * @returns {CKEDITOR.plugins.widget/CKEDITOR.dom.element} The widget instance or {@link CKEDITOR.dom.element} of a widget wrapper if `finalize` was set to `false`. */ diff --git a/tests/plugins/easyimage/uploadintegrations.js b/tests/plugins/easyimage/uploadintegrations.js index f7a8a1ec3f0..98df914f278 100644 --- a/tests/plugins/easyimage/uploadintegrations.js +++ b/tests/plugins/easyimage/uploadintegrations.js @@ -269,7 +269,7 @@ 'test copy and paste in progress widget while original was loaded': function() { // Yet another tricky variation. // 1. In This case user starts to upload file that takes say 1 sec. - // 2. Copies the file curing the upload. + // 2. Copies the file during the upload. // 3. Wait for the file upload to complete. // 4. Then paste what he has in clipboard (incomplete) widget as a new widget. var editor = this.editor, diff --git a/tests/plugins/imagebase/features/_helpers/tools.js b/tests/plugins/imagebase/features/_helpers/tools.js index 69467866e1d..7e01a55597f 100644 --- a/tests/plugins/imagebase/features/_helpers/tools.js +++ b/tests/plugins/imagebase/features/_helpers/tools.js @@ -42,8 +42,8 @@ listeners = []; function wrappedCallback( uploadEvt ) { - // In case we listen for `upload*` events only first event should be handled (to not cause multiple) - // resume calls. + // In case we listen for `upload*` events only first event should be handled (to not cause multiple + // resume calls). if ( listeners.length ) { listeners = CKEDITOR.tools.array.filter( listeners, function( curListener ) { curListener.removeListener(); @@ -201,7 +201,7 @@ }, /* - * Cache for tye returned types. + * Cache for the returned types. */ _cache: {} } From 0cdb34b94487a5558bfaf15bdf841af04e40075d Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 26 Jan 2018 15:29:34 +0100 Subject: [PATCH 72/78] Tests: Corrected API docs for Upload Widget Feature test helpers. --- tests/plugins/imagebase/features/_helpers/tools.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/plugins/imagebase/features/_helpers/tools.js b/tests/plugins/imagebase/features/_helpers/tools.js index 7e01a55597f..dd27cc831b9 100644 --- a/tests/plugins/imagebase/features/_helpers/tools.js +++ b/tests/plugins/imagebase/features/_helpers/tools.js @@ -18,7 +18,6 @@ * * @param {CKEDITOR.editor} editor * @param {Object} options - * @param {File[]} [options.files=[]] Files to be dropped. * @param {Function} options.callback Function to be called after the paste event. * Params: * @@ -26,6 +25,7 @@ * * `CKEDITOR.eventInfo` evt - Paste event. * * `CKEDITOR.eventInfo/undefined` uploadEvt - Upload event, available ony if `options.fullLoad` was set * to `true` and at least one widget was found. + * @param {File[]} [options.files=[]] Files to be dropped. * @param {String} [options.dataValue=null] HTML data to be put into the clipboard if any. * @param {Boolean} [options.fullLoad=false] If `true` assertion will wait for `uploadDone` of `uploadFailed` * event of the first widget. @@ -79,8 +79,7 @@ * on a image, and removes it indicating the progress. * * @param {CKEDITOR.editor} editor - * @param {Function} ProgressReporter Base type for progress indicator - * {@link CKEDITOR.plugins.imageBase.progressReporter}. + * @param {Function} ProgressReporter Base type for progress indicator {@link CKEDITOR.plugins.imageBase.progressReporter}. * @returns {Function} */ getProgressOverlapType: function( editor, ProgressReporter ) { @@ -109,6 +108,8 @@ * Returns a type that implements a progress indicator that puts a * circle-shaped progress bar. * + * @param {CKEDITOR.editor} editor + * @param {Function} ProgressReporter Base type for progress indicator {@link CKEDITOR.plugins.imageBase.progressReporter}. * @returns {Function} */ getProgressCircleType: function( editor, ProgressReporter ) { From ee185884fd37c4135094012a57982d70d4423044 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 26 Jan 2018 15:40:15 +0100 Subject: [PATCH 73/78] Fixed uploadId not being assigned if uploadStarted was cancelled and minor doc fixes. Docs: simplified reporter methods description and corrected uploadStarted event docs. --- plugins/imagebase/plugin.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index daf5d755d85..42b5b5166fc 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -359,9 +359,9 @@ } ); } ); - if ( widget.fire( 'uploadStarted', loader ) !== false && widget.progressReporterType ) { - widget.setData( 'uploadId', loader.id ); + widget.setData( 'uploadId', loader.id ); + if ( widget.fire( 'uploadStarted', loader ) !== false && widget.progressReporterType ) { if ( !widget._isLoaderDone( loader ) ) { // Progress reporter has only sense if widget is in progress. var progress = new widget.progressReporterType(); @@ -421,7 +421,7 @@ * // Implement a custom progress bar. * } ); * - * This event is cancelable, if canceled it will add a progress bar to the widget. + * This event is cancelable, if canceled, the default progress bar will not be created. * * Note that the event will be fired even if the widget was created for a loader that * is already resolved. @@ -584,21 +584,21 @@ updated: function() {}, /** - * To be called when the progress should be marked as complete. + * Marks the progress reporter as complete. */ done: function() { this.remove(); }, /** - * To be called when the progress should be marked as aborted. + * Marks the progress reporter as aborted. */ aborted: function() { this.remove(); }, /** - * To be called when the progress should be marked as failed. + * Marks the progress reporter as failed. */ failed: function() { this.remove(); From 19263ff4f3c413a534500e35169b1f293af9ecf8 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 26 Jan 2018 15:46:48 +0100 Subject: [PATCH 74/78] Fixed error message when uploading Easy Image widget fails. --- plugins/easyimage/lang/en.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/easyimage/lang/en.js b/plugins/easyimage/lang/en.js index e7562db189c..97f7337e6c3 100644 --- a/plugins/easyimage/lang/en.js +++ b/plugins/easyimage/lang/en.js @@ -9,5 +9,5 @@ CKEDITOR.plugins.setLang( 'easyimage', 'en', { sideImage: 'Side Image', altText: 'Change image alternative text' }, - uploadFailed: 'Your image could not be downloaded due to network error.' + uploadFailed: 'Your image could not be uploaded due to a network error.' } ); From b5cda531f82df1d0a4a85c6d996e1217786bfd4f Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 26 Jan 2018 15:47:27 +0100 Subject: [PATCH 75/78] Docs: API docs for Image Base plugin corrected. --- plugins/imagebase/plugin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 42b5b5166fc..890b3e94c0e 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -190,7 +190,7 @@ /** * Widget feature dedicated for handling seamless file uploads. * - * This type serves solely as a mixing, and should be added using + * This type serves solely as a mixin, and should be added using * {@link CKEDITOR.plugins.imagebase#addFeature} method. * * This API is not yet in a final shape, thus marked as a private. It can be changed at any point. @@ -293,7 +293,7 @@ * * @private * @param {CKEDITOR.editor} editor - * @param {Blob/String} fileOrData See {@link CKEDITOR.fileTools.fileLoader}. + * @param {Blob/String} file See {@link CKEDITOR.fileTools.fileLoader}. * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget definition that the loader is spawned for. * @param {String} [fileName] Preferred file name to be passed to the upload process. * @returns {CKEDITOR.fileTools.fileLoader} From 5fc9b25e6677faeb1652a0154aa2dade7ad4f203 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 26 Jan 2018 16:09:12 +0100 Subject: [PATCH 76/78] Tests: added a manual test for Easy Image and Paste from Word integration. --- .../easyimage/manual/pastefromword.html | 32 +++++++++++++++++++ .../plugins/easyimage/manual/pastefromword.md | 19 +++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/plugins/easyimage/manual/pastefromword.html create mode 100644 tests/plugins/easyimage/manual/pastefromword.md diff --git a/tests/plugins/easyimage/manual/pastefromword.html b/tests/plugins/easyimage/manual/pastefromword.html new file mode 100644 index 00000000000..5b44121b5e2 --- /dev/null +++ b/tests/plugins/easyimage/manual/pastefromword.html @@ -0,0 +1,32 @@ +

Note, this test uses a real Cloud Service connection, so you might want to be on-line 😉.

+ +

Classic editor

+ +
+

Sample editor

+

Go on, put some content here.

+
+ +

Inline editor

+ +
+

Sample editor

+

Go on, put some content here.

+
+ + + diff --git a/tests/plugins/easyimage/manual/pastefromword.md b/tests/plugins/easyimage/manual/pastefromword.md new file mode 100644 index 00000000000..a62b25ed65b --- /dev/null +++ b/tests/plugins/easyimage/manual/pastefromword.md @@ -0,0 +1,19 @@ +@bender-tags: 4.9.0, feature, 932 +@bender-ui: collapsed +@bender-ckeditor-plugins: sourcearea, wysiwygarea, floatingspace, toolbar, easyimage, undo, pastefromword + +## Easy Image PFW Integration + +Upload some images: + +1. Open a document containing an image in MS Word (e.g. [`Image_alternative_text.docx`](https://github.com/ckeditor/ckeditor-dev/blob/7ecc15bc26aef53fadb7f3ec342510ca2d736236/tests/plugins/pastefromword/generated/_fixtures/PFW_image/Image_alternative_text/Image_alternative_text.docx)). +1. Copy whole file contents. +1. Focus the "Classic editor". +1. Paste into the editor. + +### Expected + +* Image is inserted as an Easy Image widget. +* Image gets uploaded (if you didn't see the progress bar you can examine it by checking image URL in devtools). + +Repeat the steps with Inline editor. \ No newline at end of file From 31b0af557de9292993d637189d9e6175935d37fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krzto=C5=84?= Date: Mon, 29 Jan 2018 12:19:15 +0100 Subject: [PATCH 77/78] Tests: adjusted test assert to message change. --- tests/plugins/easyimage/uploadintegrations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/plugins/easyimage/uploadintegrations.js b/tests/plugins/easyimage/uploadintegrations.js index 98df914f278..4fcdce36268 100644 --- a/tests/plugins/easyimage/uploadintegrations.js +++ b/tests/plugins/easyimage/uploadintegrations.js @@ -230,7 +230,7 @@ easyImageDef.loaderType = originalLoader; assert.areSame( 1, window.alert.callCount, 'Alert call count' ); - sinon.assert.alwaysCalledWith( window.alert, 'Your image could not be downloaded due to network error.' ); + sinon.assert.alwaysCalledWith( window.alert, 'Your image could not be uploaded due to a network error.' ); // Widget should be removed. assert.areSame( 0, widgets.length, 'Widgets count' ); From c8447125c8d64489c421f9cb4ad16be71f0f5d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krzto=C5=84?= Date: Mon, 29 Jan 2018 12:59:19 +0100 Subject: [PATCH 78/78] Docs scope adjustments. --- plugins/imagebase/plugin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/imagebase/plugin.js b/plugins/imagebase/plugin.js index 890b3e94c0e..38a543d2150 100644 --- a/plugins/imagebase/plugin.js +++ b/plugins/imagebase/plugin.js @@ -375,7 +375,7 @@ } }, - /* + /** * @private * @param {CKEDITOR.editor} editor * @param {CKEDITOR.plugins.widget.definition} widgetDef @@ -413,7 +413,7 @@ * @property {Function} [loaderType=CKEDITOR.fileTools.fileLoader] */ - /* + /** * Fired when upload was initiated and before response is fetched. * * progress.once( 'uploadStarted', function( evt ) { @@ -430,7 +430,7 @@ * @param {CKEDITOR.fileTools.fileLoader} data Lader that is used for this widget. */ - /* + /** * Fired when upload process succeeded. This is the event where you want apply data * from your response into a widget. * @@ -444,7 +444,7 @@ * @param {CKEDITOR.fileTools.fileLoader} data.loader Loader that caused this event. */ - /* + /** * Fired when upload process {@link CKEDITOR.fileTools.fileLoader#event-error failed} or was * {@link CKEDITOR.fileTools.fileLoader#event-abort aborted}. *