8
0

fakeselectionobserver.js 6.1 KB

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