widget-integration.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
  6. import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  7. import Widget from '../src/widget';
  8. import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
  9. import { toWidget } from '../src/utils';
  10. import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  11. import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  12. import env from '@ckeditor/ckeditor5-utils/src/env';
  13. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  14. /* global document */
  15. describe( 'Widget - integration', () => {
  16. let editor, model, view, viewDocument, editorElement;
  17. testUtils.createSinonSandbox();
  18. beforeEach( () => {
  19. // Most tests assume non-edge environment but we do not set `contenteditable=false` on Edge so stub `env.isEdge`.
  20. testUtils.sinon.stub( env, 'isEdge' ).get( () => false );
  21. editorElement = document.createElement( 'div' );
  22. document.body.appendChild( editorElement );
  23. return ClassicEditor.create( editorElement, { plugins: [ Widget, Typing ] } )
  24. .then( newEditor => {
  25. editor = newEditor;
  26. model = editor.model;
  27. view = editor.editing.view;
  28. viewDocument = view.document;
  29. model.schema.register( 'widget', {
  30. inheritAllFrom: '$block',
  31. isObject: true
  32. } );
  33. model.schema.register( 'paragraph', {
  34. inheritAllFrom: '$block',
  35. allowIn: '$root'
  36. } );
  37. model.schema.register( 'nested', {
  38. allowIn: 'widget',
  39. isLimit: true
  40. } );
  41. model.schema.extend( '$text', {
  42. allowIn: [ 'nested', 'editable', 'inline-widget' ]
  43. } );
  44. model.schema.register( 'editable', {
  45. allowIn: [ 'widget', '$root' ]
  46. } );
  47. model.schema.register( 'inline-widget', {
  48. allowWhere: '$text',
  49. isObject: true,
  50. isInline: true
  51. } );
  52. editor.conversion.for( 'downcast' )
  53. .elementToElement( { model: 'paragraph', view: 'p' } )
  54. .elementToElement( { model: 'inline', view: 'figure' } )
  55. .elementToElement( { model: 'image', view: 'img' } )
  56. .elementToElement( {
  57. model: 'widget',
  58. view: ( modelItem, viewWriter ) => {
  59. const div = viewWriter.createContainerElement( 'div' );
  60. return toWidget( div, viewWriter, { label: 'element label' } );
  61. }
  62. } )
  63. .elementToElement( {
  64. model: 'inline-widget',
  65. view: ( modelItem, viewWriter ) => {
  66. const span = viewWriter.createContainerElement( 'span' );
  67. return toWidget( span, viewWriter );
  68. }
  69. } )
  70. .elementToElement( {
  71. model: 'nested',
  72. view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'figcaption', { contenteditable: true } )
  73. } )
  74. .elementToElement( {
  75. model: 'editable',
  76. view: ( modelItem, viewWriter ) => viewWriter.createEditableElement( 'figcaption', { contenteditable: true } )
  77. } );
  78. } );
  79. } );
  80. afterEach( () => {
  81. editorElement.remove();
  82. } );
  83. it( 'should do nothing if clicked inside nested editable', () => {
  84. setModelData( model, '[]<widget><nested>foo bar</nested></widget>' );
  85. const viewDiv = viewDocument.getRoot().getChild( 0 );
  86. const viewFigcaption = viewDiv.getChild( 0 );
  87. const preventDefault = sinon.spy();
  88. const domEventDataMock = new DomEventData( view, {
  89. target: view.domConverter.mapViewToDom( viewFigcaption ),
  90. preventDefault,
  91. detail: 1
  92. } );
  93. viewDocument.fire( 'mousedown', domEventDataMock );
  94. sinon.assert.notCalled( preventDefault );
  95. expect( getViewData( view ) ).to.equal(
  96. '[]<div class="ck-widget" contenteditable="false"><figcaption contenteditable="true">foo bar</figcaption></div>'
  97. );
  98. } );
  99. it( 'should select the entire nested editable if triple clicked', () => {
  100. setModelData( model, '[]<widget><nested>foo bar</nested></widget>' );
  101. const viewDiv = viewDocument.getRoot().getChild( 0 );
  102. const viewFigcaption = viewDiv.getChild( 0 );
  103. const preventDefault = sinon.spy();
  104. const domEventDataMock = new DomEventData( view, {
  105. target: view.domConverter.mapViewToDom( viewFigcaption ),
  106. preventDefault,
  107. detail: 3
  108. } );
  109. viewDocument.fire( 'mousedown', domEventDataMock );
  110. sinon.assert.called( preventDefault );
  111. expect( getViewData( view ) ).to.equal(
  112. '<div class="ck-widget" contenteditable="false"><figcaption contenteditable="true">[foo bar]</figcaption></div>'
  113. );
  114. } );
  115. it( 'should select proper nested editable if triple clicked', () => {
  116. setModelData( model, '[]<widget><nested>foo</nested><nested>bar</nested></widget>' );
  117. const viewDiv = viewDocument.getRoot().getChild( 0 );
  118. const secondViewFigcaption = viewDiv.getChild( 1 );
  119. const preventDefault = sinon.spy();
  120. const domEventDataMock = new DomEventData( view, {
  121. target: view.domConverter.mapViewToDom( secondViewFigcaption ),
  122. preventDefault,
  123. detail: 3
  124. } );
  125. viewDocument.fire( 'mousedown', domEventDataMock );
  126. sinon.assert.called( preventDefault );
  127. expect( getViewData( view ) ).to.equal(
  128. '<div class="ck-widget" contenteditable="false">' +
  129. '<figcaption contenteditable="true">foo</figcaption>' +
  130. '<figcaption contenteditable="true">[bar]</figcaption>' +
  131. '</div>'
  132. );
  133. } );
  134. it( 'should select the entire nested editable if quadra clicked', () => {
  135. setModelData( model, '[]<widget><nested>foo bar</nested></widget>' );
  136. const viewDiv = viewDocument.getRoot().getChild( 0 );
  137. const viewFigcaption = viewDiv.getChild( 0 );
  138. const preventDefault = sinon.spy();
  139. const domEventDataMock = new DomEventData( view, {
  140. target: view.domConverter.mapViewToDom( viewFigcaption ),
  141. preventDefault,
  142. detail: 4
  143. } );
  144. viewDocument.fire( 'mousedown', domEventDataMock );
  145. sinon.assert.called( preventDefault );
  146. expect( getViewData( view ) ).to.equal(
  147. '<div class="ck-widget" contenteditable="false"><figcaption contenteditable="true">[foo bar]</figcaption></div>'
  148. );
  149. } );
  150. it( 'should select the inline widget if triple clicked', () => {
  151. setModelData( model, '<paragraph>Foo<inline-widget>foo bar</inline-widget>Bar</paragraph>' );
  152. const viewParagraph = viewDocument.getRoot().getChild( 0 );
  153. const viewInlineWidget = viewParagraph.getChild( 1 );
  154. const preventDefault = sinon.spy();
  155. const domEventDataMock = new DomEventData( view, {
  156. target: view.domConverter.mapViewToDom( viewInlineWidget ),
  157. preventDefault,
  158. detail: 3
  159. } );
  160. viewDocument.fire( 'mousedown', domEventDataMock );
  161. sinon.assert.called( preventDefault );
  162. expect( getViewData( view ) ).to.equal(
  163. '<p>Foo{<span class="ck-widget ck-widget_selected" contenteditable="false">foo bar</span>}Bar</p>'
  164. );
  165. } );
  166. } );