bogusbr-integration.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  7. import MutationObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mutationobserver';
  8. import Typing from '../src/typing';
  9. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  10. import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
  11. import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  12. describe( 'Typing – bogus BR integration', () => {
  13. let editor, domRoot, mutationObserver, editorElement;
  14. beforeEach( () => {
  15. editorElement = document.createElement( 'div' );
  16. document.body.appendChild( editorElement );
  17. return ClassicTestEditor
  18. .create( editorElement, {
  19. plugins: [ Typing, Paragraph, Bold ]
  20. } )
  21. .then( newEditor => {
  22. editor = newEditor;
  23. domRoot = editor.editing.view.getDomRoot();
  24. mutationObserver = editor.editing.view.getObserver( MutationObserver );
  25. } );
  26. } );
  27. afterEach( () => {
  28. editorElement.remove();
  29. return editor.destroy();
  30. } );
  31. // Part of tests used the native insertText command to generate mutations which would be generated
  32. // by real typing. Unfortunately, the command is not reliable (due to focus) and we needed
  33. // to stop using it. Instead we use a direct DOM modifications and completely fake mutations.
  34. describe( 'direct DOM modifications', () => {
  35. it( 'space is inserted on the end of the line (paragraph)', done => {
  36. editor.model.change( () => {
  37. setData( editor.model, '<paragraph>Foo[]</paragraph>' );
  38. } );
  39. editor.model.document.once( 'change', () => {
  40. expect( editor.getData() ).to.equal( '<p>Foo&nbsp;</p>' );
  41. done();
  42. }, { priority: 'low' } );
  43. const p = editor.editing.view.getDomRoot().firstChild;
  44. p.firstChild.data = 'Foo\u00a0';
  45. } );
  46. it( 'space is inserted at the end of the line (empty paragraph)', done => {
  47. editor.model.change( () => {
  48. setData( editor.model, '<paragraph>[]</paragraph>' );
  49. } );
  50. editor.model.document.once( 'change', () => {
  51. expect( editor.getData( { trim: 'none' } ) ).to.equal( '<p>&nbsp;</p>' );
  52. done();
  53. }, { priority: 'low' } );
  54. const p = editor.editing.view.getDomRoot().firstChild;
  55. const bogusBr = p.firstChild;
  56. const text = document.createTextNode( '\u00a0' );
  57. p.insertBefore( text, bogusBr );
  58. } );
  59. // We sometimes observed that browsers remove the bogus br automatically.
  60. it( 'space is inserted at the end of the line (empty paragraph; remove <br> when inserting space)', done => {
  61. editor.model.change( () => {
  62. setData( editor.model, '<paragraph>[]</paragraph>' );
  63. } );
  64. editor.model.document.once( 'change', () => {
  65. expect( editor.getData( { trim: 'none' } ) ).to.equal( '<p>&nbsp;</p>' );
  66. done();
  67. }, { priority: 'low' } );
  68. const p = editor.editing.view.getDomRoot().firstChild;
  69. const bogusBr = p.firstChild;
  70. const text = document.createTextNode( '\u00a0' );
  71. p.insertBefore( text, bogusBr );
  72. bogusBr.remove();
  73. } );
  74. it( 'space is inserted on the end of the line (bold)', done => {
  75. editor.model.change( () => {
  76. setData( editor.model, '<paragraph><$text bold="true">Foo[]</$text></paragraph>' );
  77. } );
  78. editor.model.document.once( 'change', () => {
  79. expect( editor.getData() ).to.equal( '<p><strong>Foo&nbsp;</strong></p>' );
  80. done();
  81. }, { priority: 'low' } );
  82. const p = editor.editing.view.getDomRoot().firstChild;
  83. p.firstChild.firstChild.data = 'Foo\u00a0';
  84. } );
  85. } );
  86. describe( 'mutations', () => {
  87. // This tests use fixed list of mutation mocks to simulate specific browser behaviours.
  88. it( 'space is inserted on the end of the paragraph', done => {
  89. editor.model.change( () => {
  90. setData( editor.model, '<paragraph>Foo[]</paragraph>' );
  91. } );
  92. editor.model.document.once( 'change', () => {
  93. expect( editor.getData() ).to.equal( '<p>Foo&nbsp;</p>' );
  94. done();
  95. }, { priority: 'low' } );
  96. const mutationTarget = domRoot.childNodes[ 0 ].childNodes[ 0 ];
  97. mutationObserver.disable();
  98. mutationTarget.data = 'Foo ';
  99. mutationObserver.enable();
  100. mutationObserver._onMutations( [
  101. generateMutationMock( 'characterData', mutationTarget ),
  102. generateMutationMock( 'characterData', mutationTarget ),
  103. generateMutationMock( 'characterData', mutationTarget )
  104. ] );
  105. } );
  106. it( 'space is inserted on the end of the line paragraph (with bogus br)', done => {
  107. editor.model.change( () => {
  108. editor.editing.view.getDomRoot().focus();
  109. setData( editor.model, '<paragraph>Foo[]</paragraph>' );
  110. } );
  111. editor.model.document.once( 'change', () => {
  112. expect( editor.getData() ).to.equal( '<p>Foo&nbsp;</p>' );
  113. done();
  114. }, { priority: 'low' } );
  115. const paragraph = domRoot.childNodes[ 0 ];
  116. const text = paragraph.childNodes[ 0 ];
  117. const br = document.createElement( 'br' );
  118. mutationObserver.disable();
  119. text.data = 'Foo ';
  120. mutationObserver.enable();
  121. mutationObserver._onMutations( [
  122. generateMutationMock( 'characterData', text ),
  123. generateMutationMock( 'childList', paragraph, text, [ br ] ),
  124. generateMutationMock( 'characterData', text ),
  125. generateMutationMock( 'characterData', text )
  126. ] );
  127. } );
  128. it( 'word is properly corrected on the end of the block element (with bogus br)', done => {
  129. editor.model.change( () => {
  130. editor.editing.view.getDomRoot().focus();
  131. setData( editor.model, '<paragraph>Foo hous[]</paragraph>' );
  132. } );
  133. editor.model.document.once( 'change', () => {
  134. expect( editor.getData() ).to.equal( '<p>Foo house</p>' );
  135. done();
  136. }, { priority: 'low' } );
  137. const paragraph = domRoot.childNodes[ 0 ];
  138. const text = paragraph.childNodes[ 0 ];
  139. const br = document.createElement( 'br' );
  140. mutationObserver.disable();
  141. text.data = 'Foo house';
  142. mutationObserver.enable();
  143. mutationObserver._onMutations( [
  144. generateMutationMock( 'characterData', text ),
  145. generateMutationMock( 'characterData', text ),
  146. generateMutationMock( 'characterData', text ),
  147. generateMutationMock( 'childList', paragraph, text, [ br ] ),
  148. generateMutationMock( 'characterData', text ),
  149. generateMutationMock( 'characterData', text ),
  150. generateMutationMock( 'characterData', text )
  151. ] );
  152. } );
  153. } );
  154. function generateMutationMock( type, target, previousSibling, addedNodes ) {
  155. return {
  156. addedNodes: addedNodes || [],
  157. attributeName: null,
  158. attributeNamespace: null,
  159. nextSibling: null,
  160. oldValue: null,
  161. previousSibling: previousSibling || null,
  162. removedNodes: [],
  163. target,
  164. type
  165. };
  166. }
  167. } );