|
|
@@ -8,8 +8,6 @@
|
|
|
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
|
|
|
import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
|
|
|
import bindTwoStepCaretToAttribute from '../../src/utils/bindtwostepcarettoattribute';
|
|
|
-import Position from '../../src/model/position';
|
|
|
-import Range from '../../src/model/range';
|
|
|
import { upcastElementToAttribute } from '../../src/conversion/upcast-converters';
|
|
|
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
|
|
|
|
|
|
@@ -49,98 +47,62 @@ describe( 'bindTwoStepCaretToAttribute()', () => {
|
|
|
it( 'should "enter" the text with attribute in two steps', () => {
|
|
|
setData( model, '<$text c="true">foo[]</$text><$text a="true" b="true">bar</$text>' );
|
|
|
|
|
|
- // Gravity is not overridden, caret is at the beginning of the text but is "outside" of the text.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'c' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
-
|
|
|
- // Press right key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowright,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Gravity is overridden, caret movement is blocked, selection at the beginning but "inside" the text.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'a', 'b' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.true;
|
|
|
- sinon.assert.calledOnce( preventDefaultSpy );
|
|
|
-
|
|
|
- // Press right key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowright,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Caret movement was not blocked this time (still once) so everything works normally.
|
|
|
- sinon.assert.calledOnce( preventDefaultSpy );
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ // Gravity is not overridden, caret is at the beginning of the text but is "outside" of the text.
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '→',
|
|
|
+ // Gravity is overridden, caret movement is blocked, selection at the beginning but "inside" the text.
|
|
|
+ { selectionAttributes: [ 'a', 'b' ], isGravityOverridden: true, preventDefault: 1 },
|
|
|
+ '→',
|
|
|
+ // Caret movement was not blocked this time (still once) so everything works normally.
|
|
|
+ { selectionAttributes: [ 'a', 'b' ], isGravityOverridden: false, preventDefault: 1 },
|
|
|
+ ] );
|
|
|
} );
|
|
|
|
|
|
it( 'should "leave" the text with attribute in two steps', () => {
|
|
|
setData( model, '<$text a="true" b="true">bar[]</$text><$text c="true">foo</$text>' );
|
|
|
|
|
|
- // Gravity is not overridden, caret is at the end of the text but is "inside" of the text.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'a', 'b' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
-
|
|
|
- // Press right key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowright,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Gravity is overridden, caret movement is blocked, selection at the end but "outside" the text.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'c' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.true;
|
|
|
- sinon.assert.calledOnce( preventDefaultSpy );
|
|
|
-
|
|
|
- // Press right key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowright,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Caret movement was not blocked this time (still once) so everything works normally.
|
|
|
- sinon.assert.calledOnce( preventDefaultSpy );
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ // Gravity is not overridden, caret is at the end of the text but is "inside" of the text.
|
|
|
+ { selectionAttributes: [ 'a', 'b' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '→',
|
|
|
+ // Gravity is overridden, caret movement is blocked, selection at the end but "outside" the text.
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: true, preventDefault: 1 },
|
|
|
+ '→',
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 1 },
|
|
|
+ ] );
|
|
|
} );
|
|
|
|
|
|
it( 'should do nothing for not bound attribute (at the beginning)', () => {
|
|
|
setData( model, '[]<$text c="true">foo</$text>' );
|
|
|
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowright,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- sinon.assert.notCalled( preventDefaultSpy );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '→',
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ ] );
|
|
|
} );
|
|
|
|
|
|
it( 'should do nothing for not bound attribute (at the end)', () => {
|
|
|
setData( model, '<$text c="true">foo[]</$text>' );
|
|
|
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowright,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- sinon.assert.notCalled( preventDefaultSpy );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '→',
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ ] );
|
|
|
} );
|
|
|
|
|
|
it( 'should require two-steps movement when caret goes between text node with the same attribute but different value', () => {
|
|
|
setData( model, '<$text a="1">bar[]</$text><$text a="2">foo</$text>' );
|
|
|
|
|
|
- // Gravity is not overridden.
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
-
|
|
|
- // Press right key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowright,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Gravity is overridden, caret movement is blocked.
|
|
|
- expect( selection.isGravityOverridden ).to.true;
|
|
|
- sinon.assert.calledOnce( preventDefaultSpy );
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ { selectionAttributes: [ 'a' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '→',
|
|
|
+ { selectionAttributes: [ 'a' ], isGravityOverridden: true, preventDefault: 1 },
|
|
|
+ '→',
|
|
|
+ { selectionAttributes: [ 'a' ], isGravityOverridden: false, preventDefault: 1 },
|
|
|
+ ] );
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
@@ -148,144 +110,81 @@ describe( 'bindTwoStepCaretToAttribute()', () => {
|
|
|
it( 'should "enter" the text with attribute in two steps', () => {
|
|
|
setData( model, '<$text>foo</$text><$text a="true" b="true">bar</$text><$text c="true">b[]iz</$text>' );
|
|
|
|
|
|
- // Gravity is not overridden, caret is a one character after the and of the text.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'c' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
-
|
|
|
- // Press left key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowleft,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Caret movement was not blocked.
|
|
|
- sinon.assert.notCalled( preventDefaultSpy );
|
|
|
-
|
|
|
- // So we need to move caret one character left like it should be done in the real world.
|
|
|
- // Caret should ends up at the end of text with attribute but still outside of it.
|
|
|
- model.change( writer => writer.setSelection( new Range( new Position( model.document.getRoot(), [ 6 ] ) ) ) );
|
|
|
-
|
|
|
- // Gravity is overridden.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'c' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.true;
|
|
|
-
|
|
|
- // Press left key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowleft,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Caret movement was blocked but now is "inside" the text.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'a', 'b' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
- sinon.assert.calledOnce( preventDefaultSpy );
|
|
|
-
|
|
|
- // Press left key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowleft,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Caret movement was not blocked this time (still once) so everything works normally.
|
|
|
- sinon.assert.calledOnce( preventDefaultSpy );
|
|
|
-
|
|
|
- // And again we need to move the caret like it should be done in the real world to be shure that everything is
|
|
|
- // like it should to be.
|
|
|
- model.change( writer => writer.setSelection( new Range( new Position( model.document.getRoot(), [ 5 ] ) ) ) );
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ // Gravity is not overridden, caret is a one character after the and of the text.
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ // Caret movement was not blocked but the gravity is overridden.
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: true, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'a', 'b' ], isGravityOverridden: false, preventDefault: 1 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'a', 'b' ], isGravityOverridden: false, preventDefault: 1 },
|
|
|
+ ] );
|
|
|
} );
|
|
|
|
|
|
it( 'should "leave" the text with attribute in two steps', () => {
|
|
|
setData( model, '<$text c="true">foo</$text><$text a="true" b="true">b[]ar</$text>' );
|
|
|
|
|
|
- // Gravity is not overridden, caret is a one character after the beginning of the text.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'a', 'b' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
-
|
|
|
- // Press left key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowleft,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Caret movement was not blocked.
|
|
|
- sinon.assert.notCalled( preventDefaultSpy );
|
|
|
-
|
|
|
- // So we need to move caret one character left like it should be done in the real world.
|
|
|
- // Caret should ends up at the beginning of text with attribute but still inside of it.
|
|
|
- model.change( writer => writer.setSelection( new Range( new Position( model.document.getRoot(), [ 3 ] ) ) ) );
|
|
|
-
|
|
|
- // Gravity is overridden, caret is at the beginning of the text and is "inside" of the text.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'a', 'b' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.true;
|
|
|
-
|
|
|
- // Press left key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowleft,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Gravity is not overridden, caret movement was blocked but now is "outside" the text.
|
|
|
- expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'c' ] );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
- sinon.assert.calledOnce( preventDefaultSpy );
|
|
|
-
|
|
|
- // Press left key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowleft,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Caret movement was not blocked this time (still once) so everything works normally.
|
|
|
- sinon.assert.calledOnce( preventDefaultSpy );
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ // Gravity is not overridden, caret is a one character after the beginning of the text.
|
|
|
+ { selectionAttributes: [ 'a', 'b' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ // Caret movement was not blocked.
|
|
|
+ { selectionAttributes: [ 'a', 'b' ], isGravityOverridden: true, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 1 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 1 }
|
|
|
+ ] );
|
|
|
} );
|
|
|
|
|
|
it( 'should do nothing for not bound attribute (at the beginning)', () => {
|
|
|
setData( model, '<$text c="true">[]foo</$text>' );
|
|
|
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowright,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- sinon.assert.notCalled( preventDefaultSpy );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 }
|
|
|
+ ] );
|
|
|
} );
|
|
|
|
|
|
it( 'should do nothing for not bound attribute (at the end)', () => {
|
|
|
setData( model, '<$text c="true">foo</$text>[]' );
|
|
|
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowright,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- sinon.assert.notCalled( preventDefaultSpy );
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'c' ], isGravityOverridden: false, preventDefault: 0 }
|
|
|
+ ] );
|
|
|
} );
|
|
|
|
|
|
it( 'should do nothing when caret is at the beginning of block element', () => {
|
|
|
setData( model, '[]foo', { lastRangeBackward: true } );
|
|
|
|
|
|
- expect( () => {
|
|
|
- fireKeyDownEvent( { keyCode: keyCodes.arrowleft } );
|
|
|
- } ).to.not.throw();
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ { selectionAttributes: [], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ ] );
|
|
|
} );
|
|
|
|
|
|
it( 'should require two-steps movement when caret goes between text node with the same attribute but different value', () => {
|
|
|
setData( model, '<$text a="2">foo</$text><$text a="1">b[]ar</$text>' );
|
|
|
|
|
|
- // Gravity is not overridden.
|
|
|
- expect( selection.isGravityOverridden ).to.false;
|
|
|
-
|
|
|
- // Press left key.
|
|
|
- fireKeyDownEvent( {
|
|
|
- keyCode: keyCodes.arrowleft,
|
|
|
- preventDefault: preventDefaultSpy
|
|
|
- } );
|
|
|
-
|
|
|
- // Gravity is overridden, caret movement was not blocked.
|
|
|
- sinon.assert.notCalled( preventDefaultSpy );
|
|
|
- expect( selection.isGravityOverridden ).to.true;
|
|
|
+ testTwoStepCaretMovement( [
|
|
|
+ { selectionAttributes: [ 'a' ], isGravityOverridden: false, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'a' ], isGravityOverridden: true, preventDefault: 0 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'a' ], isGravityOverridden: false, preventDefault: 1 },
|
|
|
+ '←',
|
|
|
+ { selectionAttributes: [ 'a' ], isGravityOverridden: false, preventDefault: 1 }
|
|
|
+ ] );
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
@@ -352,9 +251,66 @@ describe( 'bindTwoStepCaretToAttribute()', () => {
|
|
|
expect( selection.isGravityOverridden ).to.false;
|
|
|
} );
|
|
|
|
|
|
+ const keyMap = {
|
|
|
+ '→': 'arrowright',
|
|
|
+ '←': 'arrowleft'
|
|
|
+ };
|
|
|
+
|
|
|
function fireKeyDownEvent( options ) {
|
|
|
const eventData = Object.assign( { domTarget: document.body }, options );
|
|
|
|
|
|
viewDoc.fire( 'keydown', eventData );
|
|
|
}
|
|
|
+
|
|
|
+ function getSelectionAttributesArray( selection ) {
|
|
|
+ return Array.from( selection.getAttributeKeys() );
|
|
|
+ }
|
|
|
+
|
|
|
+ function testTwoStepCaretMovement( scenario ) {
|
|
|
+ for ( const step of scenario ) {
|
|
|
+ if ( typeof step == 'string' ) {
|
|
|
+ let preventDefaultCalled;
|
|
|
+
|
|
|
+ fireKeyDownEvent( {
|
|
|
+ keyCode: keyCodes[ keyMap[ step ] ],
|
|
|
+ preventDefault: () => {
|
|
|
+ preventDefaultSpy();
|
|
|
+
|
|
|
+ preventDefaultCalled = true;
|
|
|
+ }
|
|
|
+ } );
|
|
|
+
|
|
|
+ if ( !preventDefaultCalled ) {
|
|
|
+ const position = selection.getFirstPosition();
|
|
|
+ let shift;
|
|
|
+
|
|
|
+ if ( step == '→' ) {
|
|
|
+ if ( position.isAtEnd ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ shift = 1;
|
|
|
+ } else if ( step == '←' ) {
|
|
|
+ if ( position.isAtStart ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ shift = -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ model.change( writer => {
|
|
|
+ writer.setSelection( selection.getFirstPosition().getShiftedBy( shift ) );
|
|
|
+ } );
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const stepIndex = scenario.indexOf( step );
|
|
|
+ const stepString = `in step #${ stepIndex } (${ JSON.stringify( step ) })`;
|
|
|
+
|
|
|
+ expect( getSelectionAttributesArray( selection ) ).to.have.members( step.selectionAttributes, '#attributes ' + stepString );
|
|
|
+ expect( selection.isGravityOverridden ).to.equal( step.isGravityOverridden, '#isGravityOverridden ' + stepString );
|
|
|
+ expect( preventDefaultSpy.callCount ).to.equal( step.preventDefault, '#preventDefault ' + stepString );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
} );
|
|
|
+
|