8
0

placeholder.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import {
  6. enablePlaceholder,
  7. disablePlaceholder,
  8. showPlaceholder,
  9. hidePlaceholder,
  10. needsPlaceholder
  11. } from '../../src/view/placeholder';
  12. import createViewRoot from './_utils/createroot';
  13. import View from '../../src/view/view';
  14. import ViewRange from '../../src/view/range';
  15. import { setData } from '../../src/dev-utils/view';
  16. describe( 'placeholder', () => {
  17. let view, viewDocument, viewRoot;
  18. beforeEach( () => {
  19. view = new View();
  20. viewDocument = view.document;
  21. viewRoot = createViewRoot( viewDocument );
  22. viewDocument.isFocused = true;
  23. } );
  24. describe( 'enablePlaceholder', () => {
  25. it( 'should attach proper CSS class and data attribute', () => {
  26. setData( view, '<div></div><div>{another div}</div>' );
  27. const element = viewRoot.getChild( 0 );
  28. enablePlaceholder( {
  29. view,
  30. element,
  31. text: 'foo bar baz'
  32. } );
  33. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  34. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  35. } );
  36. it( 'should attach proper CSS class and data attribute (isDirectHost=false)', () => {
  37. setData( view, '<p></p>' );
  38. viewDocument.isFocused = false;
  39. enablePlaceholder( {
  40. view,
  41. element: viewRoot,
  42. text: 'foo bar baz',
  43. isDirectHost: false
  44. } );
  45. expect( viewRoot.getChild( 0 ).getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  46. expect( viewRoot.getChild( 0 ).hasClass( 'ck-placeholder' ) ).to.be.true;
  47. } );
  48. it( 'if element has children set only data attribute', () => {
  49. setData( view, '<div>first div</div><div>{another div}</div>' );
  50. const element = viewRoot.getChild( 0 );
  51. enablePlaceholder( {
  52. view,
  53. element,
  54. text: 'foo bar baz'
  55. } );
  56. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  57. expect( element.hasClass( 'ck-placeholder' ) ).to.be.false;
  58. } );
  59. it( 'if element has only ui elements, set CSS class and data attribute', () => {
  60. setData( view, '<div><ui:span></ui:span><ui:span></ui:span></div><div>{another div}</div>' );
  61. const element = viewRoot.getChild( 0 );
  62. enablePlaceholder( {
  63. view,
  64. element,
  65. text: 'foo bar baz'
  66. } );
  67. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  68. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  69. } );
  70. it( 'if element has selection inside set only data attribute', () => {
  71. setData( view, '<div>[]</div><div>another div</div>' );
  72. const element = viewRoot.getChild( 0 );
  73. enablePlaceholder( {
  74. view,
  75. element,
  76. text: 'foo bar baz'
  77. } );
  78. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  79. expect( element.hasClass( 'ck-placeholder' ) ).to.be.false;
  80. } );
  81. it( 'if element has selection inside but document is blurred should contain placeholder CSS class', () => {
  82. setData( view, '<div>[]</div><div>another div</div>' );
  83. const element = viewRoot.getChild( 0 );
  84. viewDocument.isFocused = false;
  85. enablePlaceholder( {
  86. view,
  87. element,
  88. text: 'foo bar baz'
  89. } );
  90. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  91. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  92. } );
  93. it( 'should remove CSS class if selection is moved inside', () => {
  94. setData( view, '<div></div><div>{another div}</div>' );
  95. const element = viewRoot.getChild( 0 );
  96. enablePlaceholder( {
  97. view,
  98. element,
  99. text: 'foo bar baz'
  100. } );
  101. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  102. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  103. view.change( writer => {
  104. writer.setSelection( ViewRange._createIn( element ) );
  105. } );
  106. expect( element.hasClass( 'ck-placeholder' ) ).to.be.false;
  107. } );
  108. it( 'should change placeholder settings when called twice', () => {
  109. setData( view, '<div></div><div>{another div}</div>' );
  110. const element = viewRoot.getChild( 0 );
  111. enablePlaceholder( {
  112. view,
  113. element,
  114. text: 'foo bar baz'
  115. } );
  116. enablePlaceholder( {
  117. view,
  118. element,
  119. text: 'new text'
  120. } );
  121. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'new text' );
  122. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  123. } );
  124. it( 'should not throw when element is no longer in document', () => {
  125. setData( view, '<div></div><div>{another div}</div>' );
  126. const element = viewRoot.getChild( 0 );
  127. enablePlaceholder( {
  128. view,
  129. element,
  130. text: 'foo bar baz'
  131. } );
  132. setData( view, '<p>paragraph</p>' );
  133. view.render();
  134. } );
  135. it( 'should allow to add placeholder to elements from different documents', () => {
  136. setData( view, '<div></div><div>{another div}</div>' );
  137. const element = viewRoot.getChild( 0 );
  138. const secondView = new View();
  139. const secondDocument = secondView.document;
  140. secondDocument.isFocused = true;
  141. const secondRoot = createViewRoot( secondDocument );
  142. setData( secondView, '<div></div><div>{another div}</div>' );
  143. const secondElement = secondRoot.getChild( 0 );
  144. enablePlaceholder( {
  145. view,
  146. element,
  147. text: 'first placeholder'
  148. } );
  149. enablePlaceholder( {
  150. view: secondView,
  151. element: secondElement,
  152. text: 'second placeholder'
  153. } );
  154. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'first placeholder' );
  155. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  156. expect( secondElement.getAttribute( 'data-placeholder' ) ).to.equal( 'second placeholder' );
  157. expect( secondElement.hasClass( 'ck-placeholder' ) ).to.be.true;
  158. // Move selection to the elements with placeholders.
  159. view.change( writer => {
  160. writer.setSelection( ViewRange._createIn( element ) );
  161. } );
  162. secondView.change( writer => {
  163. writer.setSelection( ViewRange._createIn( secondElement ) );
  164. } );
  165. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'first placeholder' );
  166. expect( element.hasClass( 'ck-placeholder' ) ).to.be.false;
  167. expect( secondElement.getAttribute( 'data-placeholder' ) ).to.equal( 'second placeholder' );
  168. expect( secondElement.hasClass( 'ck-placeholder' ) ).to.be.false;
  169. } );
  170. it( 'should update placeholder before rendering', () => {
  171. setData( view, '<div></div><div>{another div}</div>' );
  172. const element = viewRoot.getChild( 0 );
  173. enablePlaceholder( {
  174. view,
  175. element,
  176. text: 'foo bar baz'
  177. } );
  178. view.change( writer => {
  179. writer.setSelection( ViewRange._createIn( element ) );
  180. // Here we are before rendering - placeholder is visible in first element;
  181. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  182. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  183. } );
  184. // After rendering - placeholder should be invisible since selection is moved there.
  185. expect( element.hasClass( 'ck-placeholder' ) ).to.be.false;
  186. } );
  187. it( 'should not set attributes/class when multiple children (isDirectHost=false)', () => {
  188. setData( view, '<p></p><p></p>' );
  189. viewDocument.isFocused = false;
  190. enablePlaceholder( {
  191. view,
  192. element: viewRoot,
  193. text: 'foo bar baz',
  194. isDirectHost: false
  195. } );
  196. expect( viewRoot.getChild( 0 ).hasAttribute( 'data-placeholder' ) ).to.be.false;
  197. expect( viewRoot.getChild( 0 ).hasClass( 'ck-placeholder' ) ).to.be.false;
  198. } );
  199. it( 'should not set attributes/class when first child is not element (isDirectHost=false)', () => {
  200. setData( view, '<ui:span></ui:span>' );
  201. viewDocument.isFocused = false;
  202. enablePlaceholder( {
  203. view,
  204. element: viewRoot,
  205. text: 'foo bar baz',
  206. isDirectHost: false
  207. } );
  208. expect( viewRoot.getChild( 0 ).hasAttribute( 'data-placeholder' ) ).to.be.false;
  209. expect( viewRoot.getChild( 0 ).hasClass( 'ck-placeholder' ) ).to.be.false;
  210. } );
  211. } );
  212. describe( 'disablePlaceholder', () => {
  213. it( 'should remove placeholder from element', () => {
  214. setData( view, '<div></div><div>{another div}</div>' );
  215. const element = viewRoot.getChild( 0 );
  216. enablePlaceholder( {
  217. view,
  218. element,
  219. text: 'foo bar baz'
  220. } );
  221. expect( element.getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  222. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  223. disablePlaceholder( view, element );
  224. expect( element.hasAttribute( 'data-placeholder' ) ).to.be.false;
  225. expect( element.hasClass( 'ck-placeholder' ) ).to.be.false;
  226. } );
  227. it( 'should not blow up when called on element without placeholder', () => {
  228. setData( view, '<div></div><div>{another div}</div>' );
  229. const element = viewRoot.getChild( 0 );
  230. disablePlaceholder( view, element );
  231. expect( element.hasAttribute( 'data-placeholder' ) ).to.be.false;
  232. expect( element.hasClass( 'ck-placeholder' ) ).to.be.false;
  233. } );
  234. it( 'should remove placeholder from element (isDirectHost=false)', () => {
  235. setData( view, '<p></p>' );
  236. viewDocument.isFocused = false;
  237. enablePlaceholder( {
  238. view,
  239. element: viewRoot,
  240. text: 'foo bar baz',
  241. isDirectHost: false
  242. } );
  243. expect( viewRoot.getChild( 0 ).getAttribute( 'data-placeholder' ) ).to.equal( 'foo bar baz' );
  244. expect( viewRoot.getChild( 0 ).hasClass( 'ck-placeholder' ) ).to.be.true;
  245. disablePlaceholder( view, viewRoot );
  246. expect( viewRoot.getChild( 0 ).hasAttribute( 'data-placeholder' ) ).to.be.false;
  247. expect( viewRoot.getChild( 0 ).hasClass( 'ck-placeholder' ) ).to.be.false;
  248. } );
  249. } );
  250. describe( 'showPlaceholder', () => {
  251. it( 'should add the ck-placholder class if an element does not have it', () => {
  252. setData( view, '<div></div>' );
  253. const element = viewRoot.getChild( 0 );
  254. const result = view.change( writer => showPlaceholder( writer, element ) );
  255. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  256. expect( result ).to.be.true;
  257. } );
  258. it( 'should do nothing if an element already has the ck-placeholder class', () => {
  259. setData( view, '<div class="ck-placeholder"></div>' );
  260. const element = viewRoot.getChild( 0 );
  261. let spy;
  262. const result = view.change( writer => {
  263. spy = sinon.spy( writer, 'addClass' );
  264. return showPlaceholder( writer, element );
  265. } );
  266. expect( element.hasClass( 'ck-placeholder' ) ).to.be.true;
  267. expect( result ).to.be.false;
  268. sinon.assert.notCalled( spy );
  269. } );
  270. } );
  271. describe( 'hidePlaceholder', () => {
  272. it( 'should remove the ck-placholder class if an element has it', () => {
  273. setData( view, '<div class="ck-placeholder"></div>' );
  274. const element = viewRoot.getChild( 0 );
  275. const result = view.change( writer => hidePlaceholder( writer, element ) );
  276. expect( element.hasClass( 'ck-placeholder' ) ).to.be.false;
  277. expect( result ).to.be.true;
  278. } );
  279. it( 'should do nothing if an element has no ck-placeholder class', () => {
  280. setData( view, '<div></div>' );
  281. const element = viewRoot.getChild( 0 );
  282. let spy;
  283. const result = view.change( writer => {
  284. spy = sinon.spy( writer, 'removeClass' );
  285. return hidePlaceholder( writer, element );
  286. } );
  287. expect( element.hasClass( 'ck-placeholder' ) ).to.be.false;
  288. expect( result ).to.be.false;
  289. sinon.assert.notCalled( spy );
  290. } );
  291. } );
  292. describe( 'needsPlaceholder', () => {
  293. it( 'should return false if element was removed from the document', () => {
  294. setData( view, '<p></p>' );
  295. viewDocument.isFocused = false;
  296. const element = viewRoot.getChild( 0 );
  297. expect( needsPlaceholder( element ) ).to.be.true;
  298. view.change( writer => {
  299. writer.remove( element );
  300. } );
  301. expect( needsPlaceholder( element ) ).to.be.false;
  302. } );
  303. it( 'should return true if element is empty and document is blurred', () => {
  304. setData( view, '<p></p>' );
  305. viewDocument.isFocused = false;
  306. const element = viewRoot.getChild( 0 );
  307. expect( needsPlaceholder( element ) ).to.be.true;
  308. } );
  309. it( 'should return true if element hosts UI elements only and document is blurred', () => {
  310. setData( view, '<p><ui:span></ui:span></p>' );
  311. viewDocument.isFocused = false;
  312. const element = viewRoot.getChild( 0 );
  313. expect( needsPlaceholder( element ) ).to.be.true;
  314. } );
  315. it( 'should return true when document is focused but selection anchored somewhere else', () => {
  316. setData( view, '<p></p><p>{moo}</p>' );
  317. viewDocument.isFocused = true;
  318. const element = viewRoot.getChild( 0 );
  319. expect( needsPlaceholder( element ) ).to.be.true;
  320. } );
  321. } );
  322. } );