Преглед на файлове

Tests: updated tests for Selection, LiveSelection and Document.

Szymon Cofalik преди 9 години
родител
ревизия
54a9ce75e3

+ 3 - 3
packages/ckeditor5-engine/tests/model/document/document.js

@@ -228,15 +228,15 @@ describe( 'Document', () => {
 		it( 'should get updated attributes whenever selection gets updated', () => {
 			sinon.spy( doc.selection, '_updateAttributes' );
 
-			doc.selection.fire( 'change:range' );
+			doc.selection.fire( 'change:range', { directChange: true } );
 
 			expect( doc.selection._updateAttributes.called ).to.be.true;
 		} );
 
-		it( 'should get updated attributes whenever changes to the document are applied', () => {
+		it( 'should get updated attributes whenever attribute operation is applied', () => {
 			sinon.spy( doc.selection, '_updateAttributes' );
 
-			doc.fire( 'changesDone' );
+			doc.fire( 'change', 'addAttribute' );
 
 			expect( doc.selection._updateAttributes.called ).to.be.true;
 		} );

+ 168 - 22
packages/ckeditor5-engine/tests/model/liveselection.js

@@ -14,6 +14,7 @@ import LiveRange from '/ckeditor5/engine/model/liverange.js';
 import LiveSelection from '/ckeditor5/engine/model/liveselection.js';
 import InsertOperation from '/ckeditor5/engine/model/operation/insertoperation.js';
 import MoveOperation from '/ckeditor5/engine/model/operation/moveoperation.js';
+import AttributeOperation from '/ckeditor5/engine/model/operation/attributeoperation.js';
 import count from '/ckeditor5/utils/count.js';
 import testUtils from '/tests/core/_utils/utils.js';
 import { wrapInDelta } from '/tests/engine/model/_utils/utils.js';
@@ -196,7 +197,7 @@ describe( 'LiveSelection', () => {
 			spy = sinon.spy();
 			selection.on( 'change:range', spy );
 
-			ranges = selection._ranges;
+			ranges = Array.from( selection._ranges );
 
 			sinon.spy( ranges[ 0 ], 'detach' );
 			sinon.spy( ranges[ 1 ], 'detach' );
@@ -238,7 +239,7 @@ describe( 'LiveSelection', () => {
 			spy = sinon.spy();
 			selection.on( 'change:range', spy );
 
-			oldRanges = selection._ranges;
+			oldRanges = Array.from( selection._ranges );
 
 			sinon.spy( oldRanges[ 0 ], 'detach' );
 			sinon.spy( oldRanges[ 1 ], 'detach' );
@@ -284,8 +285,8 @@ describe( 'LiveSelection', () => {
 
 	// LiveSelection uses LiveRanges so here are only simple test to see if integration is
 	// working well, without getting into complicated corner cases.
-	describe( 'after applying an operation should get updated and not fire change:range event', () => {
-		let spy;
+	describe( 'after applying an operation should get updated and fire events', () => {
+		let spyRange;
 
 		beforeEach( () => {
 			root.insertChildren( 0, [
@@ -296,12 +297,16 @@ describe( 'LiveSelection', () => {
 
 			selection.addRange( new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 1, 4 ] ) ) );
 
-			spy = sinon.spy();
-			selection.on( 'change:range', spy );
+			spyRange = sinon.spy();
+			selection.on( 'change:range', spyRange );
 		} );
 
 		describe( 'InsertOperation', () => {
 			it( 'before selection', () => {
+				selection.on( 'change:range', ( evt, data ) => {
+					expect( data.directChange ).to.be.false;
+				} );
+
 				doc.applyOperation( wrapInDelta(
 					new InsertOperation(
 						new Position( root, [ 0, 1 ] ),
@@ -310,14 +315,18 @@ describe( 'LiveSelection', () => {
 					)
 				) );
 
-				let range = selection._ranges[ 0 ];
+				let range = selection.getFirstRange();
 
 				expect( range.start.path ).to.deep.equal( [ 0, 5 ] );
 				expect( range.end.path ).to.deep.equal( [ 1, 4 ] );
-				expect( spy.called ).to.be.false;
+				expect( spyRange.calledOnce ).to.be.true;
 			} );
 
 			it( 'inside selection', () => {
+				selection.on( 'change:range', ( evt, data ) => {
+					expect( data.directChange ).to.be.false;
+				} );
+
 				doc.applyOperation( wrapInDelta(
 					new InsertOperation(
 						new Position( root, [ 1, 0 ] ),
@@ -326,16 +335,20 @@ describe( 'LiveSelection', () => {
 					)
 				) );
 
-				let range = selection._ranges[ 0 ];
+				let range = selection.getFirstRange();
 
 				expect( range.start.path ).to.deep.equal( [ 0, 2 ] );
 				expect( range.end.path ).to.deep.equal( [ 1, 7 ] );
-				expect( spy.called ).to.be.false;
+				expect( spyRange.calledOnce ).to.be.true;
 			} );
 		} );
 
 		describe( 'MoveOperation', () => {
 			it( 'move range from before a selection', () => {
+				selection.on( 'change:range', ( evt, data ) => {
+					expect( data.directChange ).to.be.false;
+				} );
+
 				doc.applyOperation( wrapInDelta(
 					new MoveOperation(
 						new Position( root, [ 0, 0 ] ),
@@ -345,14 +358,18 @@ describe( 'LiveSelection', () => {
 					)
 				) );
 
-				let range = selection._ranges[ 0 ];
+				let range = selection.getFirstRange();
 
 				expect( range.start.path ).to.deep.equal( [ 0, 0 ] );
 				expect( range.end.path ).to.deep.equal( [ 1, 4 ] );
-				expect( spy.called ).to.be.false;
+				expect( spyRange.calledOnce ).to.be.true;
 			} );
 
 			it( 'moved into before a selection', () => {
+				selection.on( 'change:range', ( evt, data ) => {
+					expect( data.directChange ).to.be.false;
+				} );
+
 				doc.applyOperation( wrapInDelta(
 					new MoveOperation(
 						new Position( root, [ 2 ] ),
@@ -362,14 +379,18 @@ describe( 'LiveSelection', () => {
 					)
 				) );
 
-				let range = selection._ranges[ 0 ];
+				let range = selection.getFirstRange();
 
 				expect( range.start.path ).to.deep.equal( [ 0, 4 ] );
 				expect( range.end.path ).to.deep.equal( [ 1, 4 ] );
-				expect( spy.called ).to.be.false;
+				expect( spyRange.calledOnce ).to.be.true;
 			} );
 
 			it( 'move range from inside of selection', () => {
+				selection.on( 'change:range', ( evt, data ) => {
+					expect( data.directChange ).to.be.false;
+				} );
+
 				doc.applyOperation( wrapInDelta(
 					new MoveOperation(
 						new Position( root, [ 1, 0 ] ),
@@ -379,14 +400,18 @@ describe( 'LiveSelection', () => {
 					)
 				) );
 
-				let range = selection._ranges[ 0 ];
+				let range = selection.getFirstRange();
 
 				expect( range.start.path ).to.deep.equal( [ 0, 2 ] );
 				expect( range.end.path ).to.deep.equal( [ 1, 2 ] );
-				expect( spy.called ).to.be.false;
+				expect( spyRange.calledOnce ).to.be.true;
 			} );
 
 			it( 'moved range intersects with selection', () => {
+				selection.on( 'change:range', ( evt, data ) => {
+					expect( data.directChange ).to.be.false;
+				} );
+
 				doc.applyOperation( wrapInDelta(
 					new MoveOperation(
 						new Position( root, [ 1, 3 ] ),
@@ -396,14 +421,18 @@ describe( 'LiveSelection', () => {
 					)
 				) );
 
-				let range = selection._ranges[ 0 ];
+				let range = selection.getFirstRange();
 
 				expect( range.start.path ).to.deep.equal( [ 0, 2 ] );
 				expect( range.end.path ).to.deep.equal( [ 1, 3 ] );
-				expect( spy.called ).to.be.false;
+				expect( spyRange.calledOnce ).to.be.true;
 			} );
 
 			it( 'split inside selection (do not break selection)', () => {
+				selection.on( 'change:range', ( evt, data ) => {
+					expect( data.directChange ).to.be.false;
+				} );
+
 				doc.applyOperation( wrapInDelta(
 					new InsertOperation(
 						new Position( root, [ 2 ] ),
@@ -421,11 +450,77 @@ describe( 'LiveSelection', () => {
 					)
 				) );
 
-				let range = selection._ranges[ 0 ];
+				let range = selection.getFirstRange();
 
 				expect( range.start.path ).to.deep.equal( [ 0, 2 ] );
 				expect( range.end.path ).to.deep.equal( [ 2, 2 ] );
-				expect( spy.called ).to.be.false;
+				expect( spyRange.calledOnce ).to.be.true;
+			} );
+		} );
+
+		describe( 'AttributeOperation', () => {
+			it( 'changed range includes selection anchor', () => {
+				const spyAttribute = sinon.spy();
+				selection.on( 'change:attribute', spyAttribute );
+
+				selection.on( 'change:attribute', ( evt, data ) => {
+					expect( data.directChange ).to.be.false;
+					expect( data.attributeKeys ).to.deep.equal( [ 'foo' ] );
+				} );
+
+				doc.applyOperation( wrapInDelta(
+					new AttributeOperation(
+						new Range( new Position( root, [ 0, 1 ] ), new Position( root, [ 0, 5 ] ) ),
+						'foo',
+						null,
+						'bar',
+						doc.version
+					)
+				) );
+
+				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
+				expect( spyAttribute.calledOnce ).to.be.true;
+			} );
+
+			it( 'should not overwrite previously set attributes', () => {
+				selection.setAttribute( 'foo', 'xyz' );
+
+				const spyAttribute = sinon.spy();
+				selection.on( 'change:attribute', spyAttribute );
+
+				doc.applyOperation( wrapInDelta(
+					new AttributeOperation(
+						new Range( new Position( root, [ 0, 1 ] ), new Position( root, [ 0, 5 ] ) ),
+						'foo',
+						null,
+						'bar',
+						doc.version
+					)
+				) );
+
+				expect( selection.getAttribute( 'foo' ) ).to.equal( 'xyz' );
+				expect( spyAttribute.called ).to.be.false;
+			} );
+
+			it( 'should not overwrite previously removed attributes', () => {
+				selection.setAttribute( 'foo', 'xyz' );
+				selection.removeAttribute( 'foo' );
+
+				const spyAttribute = sinon.spy();
+				selection.on( 'change:attribute', spyAttribute );
+
+				doc.applyOperation( wrapInDelta(
+					new AttributeOperation(
+						new Range( new Position( root, [ 0, 1 ] ), new Position( root, [ 0, 5 ] ) ),
+						'foo',
+						null,
+						'bar',
+						doc.version
+					)
+				) );
+
+				expect( selection.hasAttribute( 'foo' ) ).to.be.false;
+				expect( spyAttribute.called ).to.be.false;
 			} );
 		} );
 	} );
@@ -458,6 +553,30 @@ describe( 'LiveSelection', () => {
 		} );
 
 		describe( 'setAttributesTo', () => {
+			it( 'should fire change:attribute event with correct parameters', ( done ) => {
+				selection.setAttributesTo( { foo: 'bar', abc: 'def' } );
+
+				selection.on( 'change:attribute', ( evt, data ) => {
+					expect( data.directChange ).to.be.true;
+					expect( data.attributeKeys ).to.deep.equal( [ 'abc', 'xxx' ] );
+
+					done();
+				} );
+
+				selection.setAttributesTo( { foo: 'bar', xxx: 'yyy' } );
+			} );
+
+			it( 'should not fire change:attribute event if same attributes are set', () => {
+				selection.setAttributesTo( { foo: 'bar', abc: 'def' } );
+
+				const spy = sinon.spy();
+				selection.on( 'change:attribute', spy );
+
+				selection.setAttributesTo( { foo: 'bar', abc: 'def' } );
+
+				expect( spy.called ).to.be.false;
+			} );
+
 			it( 'should remove all stored attributes and store the given ones if the selection is in empty node', () => {
 				selection.setRanges( [ rangeInEmptyP ] );
 				selection.setAttribute( 'abc', 'xyz' );
@@ -510,7 +629,7 @@ describe( 'LiveSelection', () => {
 		} );
 	} );
 
-	describe( '_updateAttributes', () => {
+	describe( 'update attributes on direct range change', () => {
 		beforeEach( () => {
 			root.insertChildren( 0, [
 				new Element( 'p', { p: true } ),
@@ -562,13 +681,40 @@ describe( 'LiveSelection', () => {
 			expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [] );
 		} );
 
+		it( 'should overwrite any previously set attributes', () => {
+			selection.collapse( new Position( root, [ 5, 0 ] ) );
+
+			selection.setAttribute( 'x', true );
+			selection.setAttribute( 'y', true );
+
+			expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'd', true ], [ 'x', true ], [ 'y', true ] ] );
+
+			selection.collapse( new Position( root, [ 1 ] ) );
+
+			expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'a', true ] ] );
+		} );
+
 		it( 'should fire change:attribute event', () => {
 			let spy = sinon.spy();
 			selection.on( 'change:attribute', spy );
 
 			selection.setRanges( [ new Range( new Position( root, [ 2 ] ), new Position( root, [ 5 ] ) ) ] );
 
-			expect( spy.called ).to.be.true;
+			expect( spy.calledOnce ).to.be.true;
+		} );
+
+		it( 'should not fire change:attribute event if attributes did not change', () => {
+			selection.collapse( new Position( root, [ 5, 0 ] ) );
+
+			expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'd', true ] ] );
+
+			let spy = sinon.spy();
+			selection.on( 'change:attribute', spy );
+
+			selection.collapse( new Position( root, [ 5, 1 ] ) );
+
+			expect( Array.from( selection.getAttributes() ) ).to.deep.equal( [ [ 'd', true ] ] );
+			expect( spy.called ).to.be.false;
 		} );
 	} );
 

+ 0 - 6
packages/ckeditor5-engine/tests/model/selection.js

@@ -106,12 +106,6 @@ describe( 'Selection', () => {
 	} );
 
 	describe( 'addRange', () => {
-		it( 'should throw an error when range is invalid', () => {
-			expect( () => {
-				selection.addRange( { invalid: 'Range' } );
-			} ).to.throw( CKEditorError, /model-selection-added-not-range/ );
-		} );
-
 		it( 'should copy added ranges and store multiple ranges', () => {
 			selection.addRange( liveRange );
 			selection.addRange( range );

+ 8 - 0
packages/ckeditor5-engine/tests/tickets/462/1.html

@@ -0,0 +1,8 @@
+<head>
+	<meta charset="utf-8">
+	<link rel="stylesheet" href="%APPS_DIR%ckeditor/build/modules/amd/theme/ckeditor.css">
+</head>
+
+<div id="editor">
+	<p>Lorem <strong>ipsum.</strong></p>
+</div>

+ 19 - 0
packages/ckeditor5-engine/tests/tickets/462/1.js

@@ -0,0 +1,19 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals console:false, window, document */
+
+import ClassicEditor from '/ckeditor5/editor-classic/classic.js';
+
+ClassicEditor.create( document.querySelector( '#editor' ), {
+	features: [ 'enter', 'typing', 'paragraph', 'basic-styles/bold', 'basic-styles/italic' ],
+	toolbar: [ 'bold', 'italic' ]
+} )
+.then( editor => {
+	window.editor = editor;
+} )
+.catch( err => {
+	console.error( err.stack );
+} );

+ 34 - 0
packages/ckeditor5-engine/tests/tickets/462/1.md

@@ -0,0 +1,34 @@
+@bender-ui: collapsed
+@bender-tags: ticket, 462, 0.4.0, iteration4
+
+### Selection attributes conversion test [#462](https://github.com/ckeditor/ckeditor5-engine/issues/462)
+
+Open console and inspect a paragraph in editable. Remember to not blur editor after setting selection.
+
+#### Test 1
+
+1. Put caret inside unstyled word.
+2. Press italic key in toolbar.
+
+Expected results:
+
+`<em></em>` was inserted into editable (along with filler characters).
+
+`editor.document.selection.hasAttribute( 'italic' )` returns `true`.
+
+`document.getSelection().anchorNode.parentNode` returns `<em>` element.
+
+#### Test 2
+
+1. Put caret inside bold word.
+2. Press bold key in toolbar.
+
+Expected results:
+
+`<strong>` element from previous test got removed.
+
+`<strong>` element got broken.
+
+`editor.document.selection.hasAttribute( 'bold' )` returns `false`.
+
+`document.getSelection().anchorNode` returns `<p>` element.