8
0

bindtwostepcarettoattribute.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global document */
  6. import bindTwoStepCaretToAttribute from '../../src/utils/bindtwostepcarettoattribute';
  7. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  8. import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
  9. import { upcastElementToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
  10. import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
  11. import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
  12. import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  13. describe( 'bindTwoStepCaretToAttribute()', () => {
  14. let editor, model, emitter, selection, viewDoc, preventDefaultSpy;
  15. beforeEach( () => {
  16. emitter = Object.create( DomEmitterMixin );
  17. return VirtualTestEditor.create().then( newEditor => {
  18. editor = newEditor;
  19. model = editor.model;
  20. selection = model.document.selection;
  21. viewDoc = editor.editing.view;
  22. preventDefaultSpy = sinon.spy();
  23. editor.model.schema.extend( '$text', {
  24. allowAttributes: [ 'a', 'b', 'c' ],
  25. allowIn: '$root'
  26. } );
  27. editor.conversion.for( 'upcast' ).add( upcastElementToAttribute( { view: 'a', model: 'a' } ) );
  28. editor.conversion.for( 'upcast' ).add( upcastElementToAttribute( { view: 'b', model: 'b' } ) );
  29. editor.conversion.for( 'upcast' ).add( upcastElementToAttribute( { view: 'c', model: 'c' } ) );
  30. bindTwoStepCaretToAttribute( editor, emitter, 'a' );
  31. } );
  32. } );
  33. afterEach( () => {
  34. return editor.destroy();
  35. } );
  36. describe( 'moving right', () => {
  37. it( 'should "enter" the text with attribute in two steps', () => {
  38. setData( model, '<$text c="true">foo[]</$text><$text a="true" b="true">bar</$text>' );
  39. // Firing keyup event will simulate that caret is here as a result of right arrow key press.
  40. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowright } ) );
  41. // Gravity is not overridden, caret is at the beginning of the text but is "outside" of the text.
  42. expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'c' ] );
  43. expect( selection.isGravityOverridden ).to.false;
  44. // Press right key.
  45. viewDoc.fire( 'keydown', getEventData( {
  46. keyCode: keyCodes.arrowright,
  47. preventDefault: preventDefaultSpy
  48. } ) );
  49. // Gravity is overridden, caret movement is blocked, selection at the beginning but "inside" the text.
  50. expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'a', 'b' ] );
  51. expect( selection.isGravityOverridden ).to.true;
  52. sinon.assert.calledOnce( preventDefaultSpy );
  53. // Press right key.
  54. viewDoc.fire( 'keydown', getEventData( {
  55. keyCode: keyCodes.arrowright,
  56. preventDefault: preventDefaultSpy
  57. } ) );
  58. // Caret movement was not blocked this time (still once) so everything works normally.
  59. sinon.assert.calledOnce( preventDefaultSpy );
  60. } );
  61. it( 'should "leave" the text with attribute in two steps', () => {
  62. setData( model, '<$text a="true" b="true">bar[]</$text><$text c="true">foo</$text>' );
  63. // Firing keyup event will simulate that caret is here as a result of right arrow key press.
  64. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowright } ) );
  65. // Gravity is not overridden, caret is at the end of the text but is "inside" of the text.
  66. expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'a', 'b' ] );
  67. expect( selection.isGravityOverridden ).to.false;
  68. // Press right key.
  69. viewDoc.fire( 'keydown', getEventData( {
  70. keyCode: keyCodes.arrowright,
  71. preventDefault: preventDefaultSpy
  72. } ) );
  73. // Gravity is overridden, caret movement is blocked, selection at the beginning but "outside" the text.
  74. expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'c' ] );
  75. expect( selection.isGravityOverridden ).to.true;
  76. sinon.assert.calledOnce( preventDefaultSpy );
  77. // Press right key.
  78. viewDoc.fire( 'keydown', getEventData( {
  79. keyCode: keyCodes.arrowright,
  80. preventDefault: preventDefaultSpy
  81. } ) );
  82. // Caret movement was not blocked this time (still once) so everything works normally.
  83. sinon.assert.calledOnce( preventDefaultSpy );
  84. } );
  85. it( 'should do nothing for not bound attribute (at the beginning)', () => {
  86. setData( model, '[]<$text c="true">foo</$text>' );
  87. // Firing keyup event will simulate that caret is here as a result of right arrow key press.
  88. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowright } ) );
  89. viewDoc.fire( 'keydown', getEventData( {
  90. keyCode: keyCodes.arrowright,
  91. preventDefault: preventDefaultSpy
  92. } ) );
  93. sinon.assert.notCalled( preventDefaultSpy );
  94. expect( selection.isGravityOverridden ).to.false;
  95. } );
  96. it( 'should do nothing for not bound attribute (at the end)', () => {
  97. setData( model, '<$text c="true">foo[]</$text>' );
  98. // Firing keyup event will simulate that caret is here as a result of right arrow key press.
  99. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowright } ) );
  100. viewDoc.fire( 'keydown', getEventData( {
  101. keyCode: keyCodes.arrowright,
  102. preventDefault: preventDefaultSpy
  103. } ) );
  104. sinon.assert.notCalled( preventDefaultSpy );
  105. expect( selection.isGravityOverridden ).to.false;
  106. } );
  107. } );
  108. describe( 'moving left', () => {
  109. it( 'should "enter" the text with attribute in two steps', () => {
  110. setData( model, '<$text>foo</$text><$text a="true" b="true">bar[]</$text><$text c="true">biz</$text>' );
  111. // Firing keyup event will simulate that caret is here as a result of left arrow key press.
  112. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  113. // Gravity is overridden, caret is at the end of the text but is "outside" of the text.
  114. expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'c' ] );
  115. expect( selection.isGravityOverridden ).to.true;
  116. // Press left key.
  117. viewDoc.fire( 'keydown', getEventData( {
  118. keyCode: keyCodes.arrowleft,
  119. preventDefault: preventDefaultSpy
  120. } ) );
  121. // Moving left needs additional keyup event to check that everything is right.
  122. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  123. // Caret movement was blocked but now is "inside" the text.
  124. expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'a', 'b' ] );
  125. expect( selection.isGravityOverridden ).to.false;
  126. sinon.assert.calledOnce( preventDefaultSpy );
  127. // Press left key.
  128. viewDoc.fire( 'keydown', getEventData( {
  129. keyCode: keyCodes.arrowleft,
  130. preventDefault: preventDefaultSpy
  131. } ) );
  132. // Moving left needs additional keyup event to check that everything is right.
  133. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  134. // Caret movement was not blocked this time (still once) so everything works normally.
  135. sinon.assert.calledOnce( preventDefaultSpy );
  136. } );
  137. it( 'should "leave" the text with attribute in two steps', () => {
  138. setData( model, '<$text c="true">foo</$text><$text a="true" b="true">[]bar</$text>' );
  139. // Firing keyup event will simulate that caret is here as a result of left arrow key press.
  140. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  141. // Gravity is overridden, caret is at the beginning of the text and is "inside" of the text.
  142. expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'a', 'b' ] );
  143. expect( selection.isGravityOverridden ).to.true;
  144. // Press left key.
  145. viewDoc.fire( 'keydown', getEventData( {
  146. keyCode: keyCodes.arrowleft,
  147. preventDefault: preventDefaultSpy
  148. } ) );
  149. // Moving left needs additional keyup event to check that everything is right.
  150. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  151. // Gravity is not overridden, caret movement was blocked but now is "outside" the text.
  152. expect( Array.from( selection.getAttributeKeys() ) ).to.have.members( [ 'c' ] );
  153. expect( selection.isGravityOverridden ).to.false;
  154. sinon.assert.calledOnce( preventDefaultSpy );
  155. // Press left key.
  156. viewDoc.fire( 'keydown', getEventData( {
  157. keyCode: keyCodes.arrowleft,
  158. preventDefault: preventDefaultSpy
  159. } ) );
  160. // Moving left needs additional keyup event to check that everything is right.
  161. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  162. // Caret movement was not blocked this time (still once) so everything works normally.
  163. sinon.assert.calledOnce( preventDefaultSpy );
  164. } );
  165. it( 'should do nothing for not bound attribute (at the beginning)', () => {
  166. setData( model, '<$text c="true">[]foo</$text>' );
  167. // Firing keyup event will simulate that caret is here as a result of left arrow key press.
  168. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  169. viewDoc.fire( 'keydown', getEventData( {
  170. keyCode: keyCodes.arrowright,
  171. preventDefault: preventDefaultSpy
  172. } ) );
  173. // Moving left needs additional keyup event to check that everything is right.
  174. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  175. sinon.assert.notCalled( preventDefaultSpy );
  176. expect( selection.isGravityOverridden ).to.false;
  177. } );
  178. it( 'should do nothing for not bound attribute (at the end)', () => {
  179. setData( model, '<$text c="true">foo</$text>[]' );
  180. // Firing keyup event will simulate that caret is here as a result of left arrow key press.
  181. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  182. viewDoc.fire( 'keydown', getEventData( {
  183. keyCode: keyCodes.arrowright,
  184. preventDefault: preventDefaultSpy
  185. } ) );
  186. // Moving left needs additional keyup event to check that everything is right.
  187. viewDoc.fire( 'keyup', getEventData( { keyCode: keyCodes.arrowleft } ) );
  188. sinon.assert.notCalled( preventDefaultSpy );
  189. expect( selection.isGravityOverridden ).to.false;
  190. } );
  191. it( 'mouse', () => {
  192. it( 'should not override gravity when selection is placed at the beginning of text', () => {
  193. setData( model, '<$text a="true">[]foo</$text>' );
  194. expect( selection.isGravityOverridden ).to.false;
  195. } );
  196. it( 'should not override gravity when selection is placed at the end of text', () => {
  197. setData( model, '<$text a="true">foo[]</$text>' );
  198. expect( selection.isGravityOverridden ).to.false;
  199. } );
  200. } );
  201. } );
  202. function getEventData( data ) {
  203. data.target = document.body;
  204. return new DomEventData( viewDoc, data, { keyCode: data.keyCode } );
  205. }
  206. } );