Selaa lähdekoodia

Added basic implementation.

Maciej Bukowski 7 vuotta sitten
vanhempi
commit
0c7b3a2728

+ 1 - 0
packages/ckeditor5-autosave/package.json

@@ -14,6 +14,7 @@
   },
   "devDependencies": {
 	"@ckeditor/ckeditor5-engine": "^10.0.0",
+	"@ckeditor/ckeditor5-paragraph": "^10.0.0",
     "eslint": "^4.15.0",
     "eslint-config-ckeditor5": "^1.0.7",
     "husky": "^0.14.3",

+ 18 - 13
packages/ckeditor5-autosave/src/autosave.js

@@ -28,13 +28,12 @@ export default class Autosave extends Plugin {
 		super( editor );
 
 		/**
-		 * @public
 		 * @member {Provider}
 		 */
 		this.provider = undefined;
 
 		/**
-		 * @private
+		 * @protected
 		 * @type {Function}
 		 */
 		this._throttledSave = throttle( this._save.bind( this ), 500 );
@@ -81,29 +80,35 @@ export default class Autosave extends Plugin {
 	}
 
 	/**
-	 * TODO: Docs.
+	 * @protected
 	 */
-	save() {
-		this._throttledSave.cancel();
+	_flush() {
+		this._throttledSave.flush();
+	}
+
+	/**
+	 * @protected
+	 */
+	_save() {
+		if ( !this.provider ) {
+			return;
+		}
 
 		const version = this.editor.model.document.version;
 
 		if ( version <= this._lastDocumentVersion ) {
-			return Promise.resolve();
+			return;
 		}
 
 		this._lastDocumentVersion = version;
 
-		if ( !this.provider ) {
-			return Promise.resolve();
-		}
-
-		// TODO: add pending action.
+		const pendingActions = this.editor.plugins.get( PendingActions );
+		const action = pendingActions.add( 'Saving in progress.' );
 
-		return Promise.resolve()
+		Promise.resolve()
 			.then( () => this.provider.save() )
 			.then( () => {
-				// TODO: remove pending action.
+				pendingActions.remove( action );
 			} );
 	}
 }

+ 130 - 4
packages/ckeditor5-autosave/tests/autosave.js

@@ -2,12 +2,14 @@
  * Copyright (c) 2016 - 2017, CKSource - Frederico Knabben. All rights reserved.
  */
 
-/* globals document */
+/* globals document, window */
 
-// import ModelText from '@ckeditor/ckeditor5-engine/src/model/text';
-// import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
+import ModelText from '@ckeditor/ckeditor5-engine/src/model/text';
+import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import Autosave from '../src/autosave';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import PendingActions from '@ckeditor/ckeditor5-core/src/pendingactions';
 
 describe( 'Autosave', () => {
 	const sandbox = sinon.sandbox.create();
@@ -19,11 +21,17 @@ describe( 'Autosave', () => {
 
 		return ClassicTestEditor
 			.create( element, {
-				plugins: []
+				plugins: [ Autosave, Paragraph ]
 			} )
 			.then( _editor => {
+				const data = '<p>paragraph1</p><p>paragraph2</p>';
+
 				editor = _editor;
+				editor.setData( data );
 				autosave = editor.plugins.get( Autosave );
+
+				// Clean autosave's state after setting data.
+				autosave._throttledSave.cancel();
 			} );
 	} );
 
@@ -42,5 +50,123 @@ describe( 'Autosave', () => {
 		it( 'should initialize provider with an undefined value', () => {
 			expect( autosave.provider ).to.be.undefined;
 		} );
+
+		it( 'should allow plugin to work without any defined provider', () => {
+			editor.model.change( writer => {
+				writer.setSelection( ModelRange.createIn( editor.model.document.getRoot().getChild( 0 ) ) );
+				editor.model.insertContent( new ModelText( 'foo' ), editor.model.document.selection );
+			} );
+
+			// Go to the next cycle to because synchronization of CS documentVersion is async.
+			return Promise.resolve().then( () => {
+				autosave._flush();
+			} );
+		} );
+	} );
+
+	describe( 'autosaving', () => {
+		it( 'should run provider\'s save method when the editor\'s change event is fired', () => {
+			autosave.provider = {
+				save: sinon.spy()
+			};
+
+			editor.model.change( writer => {
+				writer.setSelection( ModelRange.createIn( editor.model.document.getRoot().getChild( 0 ) ) );
+				editor.model.insertContent( new ModelText( 'foo' ), editor.model.document.selection );
+			} );
+
+			// Go to the next cycle to because synchronization of CS documentVersion is async.
+			return Promise.resolve()
+				.then( () => autosave._flush() )
+				.then( () => {
+					sinon.assert.calledOnce( autosave.provider.save );
+				} );
+		} );
+
+		it( 'should throttle editor\'s change event', () => {
+			const spy = sinon.spy();
+			const savedStates = [];
+
+			autosave.provider = {
+				save() {
+					spy();
+
+					savedStates.push( editor.getData() );
+				}
+			};
+
+			// Leading (will fire change).
+			const change1 = () => {
+				editor.model.change( writer => {
+					writer.setSelection( ModelRange.createIn( editor.model.document.getRoot().getChild( 1 ) ) );
+					editor.model.insertContent( new ModelText( 'foo' ), editor.model.document.selection );
+				} );
+			};
+
+			const change2 = () => {
+				// Throttled (won't fire change).
+				editor.model.change( writer => {
+					writer.setSelection( ModelRange.createIn( editor.model.document.getRoot().getChild( 1 ) ) );
+					editor.model.insertContent( new ModelText( 'bar' ), editor.model.document.selection );
+				} );
+			};
+
+			const change3 = () => {
+				// Flushed (will fire change).
+				editor.model.change( writer => {
+					writer.setSelection( ModelRange.createIn( editor.model.document.getRoot().getChild( 1 ) ) );
+					editor.model.insertContent( new ModelText( 'biz' ), editor.model.document.selection );
+				} );
+			};
+
+			// Provider.save is done asynchronously, so all changes are run in different cycles.
+			return Promise.resolve()
+				.then( () => change1() )
+				.then( () => change2() )
+				.then( () => change3() )
+				.then( () => autosave._flush() )
+				.then( () => {
+					expect( spy.callCount ).to.equal( 2 );
+					expect( savedStates ).to.deep.equal( [
+						'<p>paragraph1</p><p>foo</p>',
+						'<p>paragraph1</p><p>biz</p>'
+					] );
+				} );
+		} );
+
+		it( 'should add a pending action during the saving.', () => {
+			const wait5msSpy = sinon.spy();
+			const pendingActions = editor.plugins.get( PendingActions );
+
+			autosave.provider = {
+				save() {
+					return wait5ms().then( wait5ms );
+				}
+			};
+
+			editor.model.change( writer => {
+				writer.setSelection( ModelRange.createIn( editor.model.document.getRoot().getChild( 0 ) ) );
+				editor.model.insertContent( new ModelText( 'foo' ), editor.model.document.selection );
+			} );
+
+			// Go to the next cycle to because synchronization of CS documentVersion is async.
+			return Promise.resolve()
+				.then( () => autosave._flush() )
+				.then( () => {
+					sinon.assert.notCalled( wait5msSpy );
+					expect( pendingActions.isPending ).to.be.true;
+					expect( pendingActions.first.message ).to.equal( 'Saving in progress.' );
+				} )
+				.then( wait5ms )
+				.then( () => {
+
+				} );
+		} );
 	} );
+
+	function wait5ms() {
+		return new Promise( res => {
+			window.setTimeout( res, 5 );
+		} );
+	}
 } );