fakeselectionobserver.js 6.2 KB

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