fakeselectionobserver.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /* globals document */
  6. import createElement from '@ckeditor/ckeditor5-utils/src/dom/createelement';
  7. import FakeSelectionObserver from '../../../src/view/observer/fakeselectionobserver';
  8. import View from '../../../src/view/view';
  9. import DomEventData from '../../../src/view/observer/domeventdata';
  10. import createViewRoot from '../_utils/createroot';
  11. import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
  12. import { setData, stringify } from '../../../src/dev-utils/view';
  13. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  14. import { StylesProcessor } from '../../../src/view/stylesmap';
  15. describe( 'FakeSelectionObserver', () => {
  16. let observer, view, viewDocument, root, domRoot;
  17. testUtils.createSinonSandbox();
  18. before( () => {
  19. domRoot = createElement( document, 'div', {
  20. contenteditable: 'true'
  21. } );
  22. document.body.appendChild( domRoot );
  23. } );
  24. after( () => {
  25. domRoot.parentElement.removeChild( domRoot );
  26. } );
  27. beforeEach( () => {
  28. view = new View( new StylesProcessor() );
  29. viewDocument = view.document;
  30. root = createViewRoot( viewDocument );
  31. view.attachDomRoot( domRoot );
  32. observer = view.getObserver( FakeSelectionObserver );
  33. viewDocument.selection._setTo( null, { fake: true } );
  34. } );
  35. afterEach( () => {
  36. view.destroy();
  37. } );
  38. it( 'should do nothing if selection is not fake', () => {
  39. viewDocument.selection._setTo( null, { fake: false } );
  40. return checkEventPrevention( keyCodes.arrowleft, false );
  41. } );
  42. it( 'should do nothing if is disabled', () => {
  43. observer.disable();
  44. return checkEventPrevention( keyCodes.arrowleft, false );
  45. } );
  46. it( 'should prevent default for left arrow key', ( ) => {
  47. return checkEventPrevention( keyCodes.arrowleft );
  48. } );
  49. it( 'should prevent default for right arrow key', ( ) => {
  50. return checkEventPrevention( keyCodes.arrowright );
  51. } );
  52. it( 'should prevent default for up arrow key', ( ) => {
  53. return checkEventPrevention( keyCodes.arrowup );
  54. } );
  55. it( 'should prevent default for down arrow key', ( ) => {
  56. return checkEventPrevention( keyCodes.arrowdown );
  57. } );
  58. it( 'should fire selectionChange event with new selection when left arrow key is pressed', () => {
  59. return checkSelectionChange(
  60. '<container:p>foo[<strong>bar</strong>]baz</container:p>',
  61. keyCodes.arrowleft,
  62. '<container:p>foo[]<strong>bar</strong>baz</container:p>'
  63. );
  64. } );
  65. it( 'should fire selectionChange event with new selection when right arrow key is pressed', () => {
  66. return checkSelectionChange(
  67. '<container:p>foo[<strong>bar</strong>]baz</container:p>',
  68. keyCodes.arrowright,
  69. '<container:p>foo<strong>bar</strong>[]baz</container:p>'
  70. );
  71. } );
  72. it( 'should fire selectionChange event with new selection when up arrow key is pressed', () => {
  73. return checkSelectionChange(
  74. '<container:p>foo[<strong>bar</strong>]baz</container:p>',
  75. keyCodes.arrowup,
  76. '<container:p>foo[]<strong>bar</strong>baz</container:p>'
  77. );
  78. } );
  79. it( 'should fire selectionChange event with new selection when down arrow key is pressed', () => {
  80. return checkSelectionChange(
  81. '<container:p>foo[<strong>bar</strong>]baz</container:p>',
  82. keyCodes.arrowdown,
  83. '<container:p>foo<strong>bar</strong>[]baz</container:p>'
  84. );
  85. } );
  86. it( 'should fire `selectionChangeDone` event after selection stop changing', () => {
  87. const clock = testUtils.sinon.useFakeTimers();
  88. const spy = sinon.spy();
  89. viewDocument.on( 'selectionChangeDone', spy );
  90. // Change selection.
  91. changeFakeSelectionPressing( keyCodes.arrowdown );
  92. // Wait 100ms.
  93. clock.tick( 100 );
  94. // Check if spy was called.
  95. sinon.assert.notCalled( spy );
  96. // Change selection one more time.
  97. changeFakeSelectionPressing( keyCodes.arrowdown );
  98. // Wait 210ms (debounced function should be called).
  99. clock.tick( 210 );
  100. sinon.assert.calledOnce( spy );
  101. } );
  102. it( 'should not fire `selectionChangeDone` event when observer will be destroyed', () => {
  103. const clock = testUtils.sinon.useFakeTimers();
  104. const spy = sinon.spy();
  105. viewDocument.on( 'selectionChangeDone', spy );
  106. // Change selection.
  107. changeFakeSelectionPressing( keyCodes.arrowdown );
  108. // Wait 100ms.
  109. clock.tick( 100 );
  110. // And destroy observer.
  111. observer.destroy();
  112. // Wait another 110ms.
  113. clock.tick( 110 );
  114. // Check that event won't be called.
  115. sinon.assert.notCalled( spy );
  116. } );
  117. // Checks if preventDefault method was called by FakeSelectionObserver for specified key code.
  118. //
  119. // @param {Number} keyCode
  120. // @param {Boolean} shouldPrevent If set to true method checks if event was prevented.
  121. // @returns {Promise}
  122. function checkEventPrevention( keyCode, shouldPrevent = true ) {
  123. return new Promise( resolve => {
  124. const data = {
  125. keyCode,
  126. preventDefault: sinon.spy()
  127. };
  128. viewDocument.once( 'keydown', () => {
  129. if ( shouldPrevent ) {
  130. sinon.assert.calledOnce( data.preventDefault );
  131. } else {
  132. sinon.assert.notCalled( data.preventDefault );
  133. }
  134. resolve();
  135. }, { priority: 'lowest' } );
  136. viewDocument.fire( 'keydown', new DomEventData( viewDocument, { target: document.body }, data ) );
  137. } );
  138. }
  139. // Checks if proper selectionChange event is fired by FakeSelectionObserver for specified key.
  140. //
  141. // @param {String} initialData
  142. // @param {Number} keyCode
  143. // @param {String} output
  144. // @returns {Promise}
  145. function checkSelectionChange( initialData, keyCode, output ) {
  146. return new Promise( resolve => {
  147. viewDocument.once( 'selectionChange', ( eventInfo, data ) => {
  148. expect( stringify( root.getChild( 0 ), data.newSelection, { showType: true } ) ).to.equal( output );
  149. resolve();
  150. } );
  151. setData( view, initialData );
  152. changeFakeSelectionPressing( keyCode );
  153. } );
  154. }
  155. // Sets fake selection to the document and fire `keydown` event what cause `selectionChange` event.
  156. //
  157. // @param {Number} keyCode
  158. function changeFakeSelectionPressing( keyCode ) {
  159. viewDocument.selection._setTo( viewDocument.selection.getRanges(), {
  160. backward: viewDocument.selection.isBackward,
  161. fake: true
  162. } );
  163. const data = {
  164. keyCode,
  165. preventDefault: sinon.spy()
  166. };
  167. viewDocument.fire( 'keydown', new DomEventData( viewDocument, { target: document.body }, data ) );
  168. }
  169. } );