8
0

utils.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* globals document Event */
  6. import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  7. import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
  8. import Model from '../../src/model';
  9. import ButtonView from '../../src/button/buttonview';
  10. import DropdownView from '../../src/dropdown/dropdownview';
  11. import DropdownPanelView from '../../src/dropdown/dropdownpanelview';
  12. import SplitButtonView from '../../src/button/splitbuttonview';
  13. import View from '../../src/view';
  14. import ToolbarView from '../../src/toolbar/toolbarview';
  15. import { createDropdown, createSplitButtonDropdown, addToolbarToDropdown } from '../../src/dropdown/utils';
  16. const assertBinding = utilsTestUtils.assertBinding;
  17. describe( 'utils', () => {
  18. let locale, dropdownView;
  19. beforeEach( () => {
  20. locale = { t() {} };
  21. } );
  22. describe( 'createDropdown()', () => {
  23. let model;
  24. beforeEach( () => {
  25. model = new Model();
  26. dropdownView = createDropdown( model, locale );
  27. } );
  28. it( 'accepts locale', () => {
  29. expect( dropdownView.locale ).to.equal( locale );
  30. expect( dropdownView.panelView.locale ).to.equal( locale );
  31. } );
  32. it( 'returns view', () => {
  33. expect( dropdownView ).to.be.instanceOf( DropdownView );
  34. } );
  35. it( 'creates dropdown#panelView out of DropdownPanelView', () => {
  36. expect( dropdownView.panelView ).to.be.instanceOf( DropdownPanelView );
  37. } );
  38. it( 'creates dropdown#buttonView out of ButtonView', () => {
  39. expect( dropdownView.buttonView ).to.be.instanceOf( ButtonView );
  40. } );
  41. it( 'binds button attributes to the model', () => {
  42. const modelDef = {
  43. label: 'foo',
  44. isOn: false,
  45. isEnabled: true,
  46. withText: false,
  47. tooltip: false
  48. };
  49. model = new Model( modelDef );
  50. dropdownView = createDropdown( model, locale );
  51. assertBinding( dropdownView.buttonView,
  52. modelDef,
  53. [
  54. [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ]
  55. ],
  56. { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true }
  57. );
  58. } );
  59. it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => {
  60. const modelDef = {
  61. label: 'foo',
  62. isOn: false,
  63. isEnabled: true,
  64. withText: false,
  65. tooltip: false
  66. };
  67. model = new Model( modelDef );
  68. dropdownView = createDropdown( model, locale );
  69. dropdownView.isOpen = false;
  70. expect( dropdownView.buttonView.isOn ).to.be.false;
  71. model.isOn = true;
  72. expect( dropdownView.buttonView.isOn ).to.be.true;
  73. dropdownView.isOpen = true;
  74. expect( dropdownView.buttonView.isOn ).to.be.true;
  75. model.isOn = false;
  76. expect( dropdownView.buttonView.isOn ).to.be.true;
  77. } );
  78. it( 'binds dropdown#isEnabled to the model', () => {
  79. const modelDef = {
  80. label: 'foo',
  81. isEnabled: true,
  82. withText: false,
  83. tooltip: false
  84. };
  85. model = new Model( modelDef );
  86. dropdownView = createDropdown( model, locale );
  87. assertBinding( dropdownView,
  88. { isEnabled: true },
  89. [
  90. [ model, { isEnabled: false } ]
  91. ],
  92. { isEnabled: false }
  93. );
  94. } );
  95. describe( '#buttonView', () => {
  96. it( 'accepts locale', () => {
  97. expect( dropdownView.buttonView.locale ).to.equal( locale );
  98. } );
  99. it( 'is a ButtonView instance', () => {
  100. expect( dropdownView.buttonView ).to.be.instanceof( ButtonView );
  101. } );
  102. it( 'delegates "execute" to "select" event', () => {
  103. const spy = sinon.spy();
  104. dropdownView.buttonView.on( 'select', spy );
  105. dropdownView.buttonView.fire( 'execute' );
  106. sinon.assert.calledOnce( spy );
  107. } );
  108. } );
  109. addDefaultBehaviorTests();
  110. } );
  111. describe( 'createSplitButtonDropdown()', () => {
  112. let model;
  113. beforeEach( () => {
  114. model = new Model();
  115. dropdownView = createSplitButtonDropdown( model, locale );
  116. } );
  117. it( 'accepts locale', () => {
  118. expect( dropdownView.locale ).to.equal( locale );
  119. expect( dropdownView.panelView.locale ).to.equal( locale );
  120. } );
  121. it( 'returns view', () => {
  122. expect( dropdownView ).to.be.instanceOf( DropdownView );
  123. } );
  124. it( 'creates dropdown#panelView out of DropdownPanelView', () => {
  125. expect( dropdownView.panelView ).to.be.instanceOf( DropdownPanelView );
  126. } );
  127. it( 'creates dropdown#buttonView out of SplitButtonView', () => {
  128. expect( dropdownView.buttonView ).to.be.instanceOf( SplitButtonView );
  129. } );
  130. it( 'binds button attributes to the model', () => {
  131. const modelDef = {
  132. label: 'foo',
  133. isOn: false,
  134. isEnabled: true,
  135. withText: false,
  136. tooltip: false
  137. };
  138. model = new Model( modelDef );
  139. dropdownView = createDropdown( model, locale );
  140. assertBinding( dropdownView.buttonView,
  141. modelDef,
  142. [
  143. [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ]
  144. ],
  145. { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true }
  146. );
  147. } );
  148. it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => {
  149. const modelDef = {
  150. label: 'foo',
  151. isOn: false,
  152. isEnabled: true,
  153. withText: false,
  154. tooltip: false
  155. };
  156. model = new Model( modelDef );
  157. dropdownView = createDropdown( model, locale );
  158. dropdownView.isOpen = false;
  159. expect( dropdownView.buttonView.isOn ).to.be.false;
  160. model.isOn = true;
  161. expect( dropdownView.buttonView.isOn ).to.be.true;
  162. dropdownView.isOpen = true;
  163. expect( dropdownView.buttonView.isOn ).to.be.true;
  164. model.isOn = false;
  165. expect( dropdownView.buttonView.isOn ).to.be.true;
  166. } );
  167. it( 'binds dropdown#isEnabled to the model', () => {
  168. const modelDef = {
  169. label: 'foo',
  170. isEnabled: true,
  171. withText: false,
  172. tooltip: false
  173. };
  174. model = new Model( modelDef );
  175. dropdownView = createDropdown( model, locale );
  176. assertBinding( dropdownView,
  177. { isEnabled: true },
  178. [
  179. [ model, { isEnabled: false } ]
  180. ],
  181. { isEnabled: false }
  182. );
  183. } );
  184. describe( '#buttonView', () => {
  185. it( 'accepts locale', () => {
  186. expect( dropdownView.buttonView.locale ).to.equal( locale );
  187. } );
  188. it( 'returns SplitButtonView instance', () => {
  189. expect( dropdownView.buttonView ).to.be.instanceof( SplitButtonView );
  190. } );
  191. } );
  192. addDefaultBehaviorTests();
  193. } );
  194. describe( 'addToolbarToDropdown()', () => {
  195. let model, buttons;
  196. beforeEach( () => {
  197. buttons = [ '<svg>foo</svg>', '<svg>bar</svg>' ].map( icon => {
  198. const button = new ButtonView();
  199. button.icon = icon;
  200. return button;
  201. } );
  202. model = new Model( { isVertical: true } );
  203. dropdownView = createDropdown( model, locale );
  204. addToolbarToDropdown( dropdownView, buttons, model );
  205. dropdownView.render();
  206. document.body.appendChild( dropdownView.element );
  207. } );
  208. afterEach( () => {
  209. dropdownView.element.remove();
  210. } );
  211. it( 'sets view#locale', () => {
  212. expect( dropdownView.locale ).to.equal( locale );
  213. } );
  214. it( 'sets view class', () => {
  215. expect( dropdownView.element.classList.contains( 'ck-toolbar-dropdown' ) ).to.be.true;
  216. } );
  217. describe( 'view#toolbarView', () => {
  218. it( 'is created', () => {
  219. const panelChildren = dropdownView.panelView.children;
  220. expect( panelChildren ).to.have.length( 1 );
  221. expect( panelChildren.get( 0 ) ).to.equal( dropdownView.toolbarView );
  222. expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView );
  223. } );
  224. it( 'delegates view.toolbarView.items#execute to the view', done => {
  225. dropdownView.on( 'execute', evt => {
  226. expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) );
  227. expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] );
  228. done();
  229. } );
  230. dropdownView.toolbarView.items.get( 0 ).fire( 'execute' );
  231. } );
  232. it( 'reacts on model#isVertical', () => {
  233. model.isVertical = false;
  234. expect( dropdownView.toolbarView.isVertical ).to.be.false;
  235. model.isVertical = true;
  236. expect( dropdownView.toolbarView.isVertical ).to.be.true;
  237. } );
  238. } );
  239. } );
  240. function addDefaultBehaviorTests() {
  241. describe( 'hasDefaultBehavior', () => {
  242. describe( 'closeDropdownOnBlur()', () => {
  243. beforeEach( () => {
  244. dropdownView.render();
  245. document.body.appendChild( dropdownView.element );
  246. } );
  247. afterEach( () => {
  248. dropdownView.element.remove();
  249. } );
  250. it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => {
  251. // Open the dropdown.
  252. dropdownView.isOpen = true;
  253. // Fire event from outside of the dropdown.
  254. document.body.dispatchEvent( new Event( 'mousedown', {
  255. bubbles: true
  256. } ) );
  257. // Closed the dropdown.
  258. expect( dropdownView.isOpen ).to.be.false;
  259. // Fire event from outside of the dropdown.
  260. document.body.dispatchEvent( new Event( 'mousedown', {
  261. bubbles: true
  262. } ) );
  263. // Dropdown is still closed.
  264. expect( dropdownView.isOpen ).to.be.false;
  265. } );
  266. it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => {
  267. // Open the dropdown.
  268. dropdownView.isOpen = true;
  269. // Event from view.element should be discarded.
  270. dropdownView.element.dispatchEvent( new Event( 'mousedown', {
  271. bubbles: true
  272. } ) );
  273. // Dropdown is still open.
  274. expect( dropdownView.isOpen ).to.be.true;
  275. // Event from within view.element should be discarded.
  276. const child = document.createElement( 'div' );
  277. dropdownView.element.appendChild( child );
  278. child.dispatchEvent( new Event( 'mousedown', {
  279. bubbles: true
  280. } ) );
  281. // Dropdown is still open.
  282. expect( dropdownView.isOpen ).to.be.true;
  283. } );
  284. } );
  285. describe( 'closeDropdownOnExecute()', () => {
  286. beforeEach( () => {
  287. dropdownView.render();
  288. document.body.appendChild( dropdownView.element );
  289. } );
  290. afterEach( () => {
  291. dropdownView.element.remove();
  292. } );
  293. it( 'changes view#isOpen on view#execute', () => {
  294. dropdownView.isOpen = true;
  295. dropdownView.fire( 'execute' );
  296. expect( dropdownView.isOpen ).to.be.false;
  297. dropdownView.fire( 'execute' );
  298. expect( dropdownView.isOpen ).to.be.false;
  299. } );
  300. } );
  301. describe( 'focusDropdownContentsOnArrows()', () => {
  302. let panelChildView;
  303. beforeEach( () => {
  304. panelChildView = new View();
  305. panelChildView.setTemplate( { tag: 'div' } );
  306. panelChildView.focus = () => {};
  307. panelChildView.focusLast = () => {};
  308. // TODO: describe this as #contentView instead of #listView and #toolbarView
  309. dropdownView.panelView.children.add( panelChildView );
  310. dropdownView.render();
  311. document.body.appendChild( dropdownView.element );
  312. } );
  313. afterEach( () => {
  314. dropdownView.element.remove();
  315. } );
  316. it( '"arrowdown" focuses the #innerPanelView if dropdown is open', () => {
  317. const keyEvtData = {
  318. keyCode: keyCodes.arrowdown,
  319. preventDefault: sinon.spy(),
  320. stopPropagation: sinon.spy()
  321. };
  322. const spy = sinon.spy( panelChildView, 'focus' );
  323. dropdownView.isOpen = false;
  324. dropdownView.keystrokes.press( keyEvtData );
  325. sinon.assert.notCalled( spy );
  326. dropdownView.isOpen = true;
  327. dropdownView.keystrokes.press( keyEvtData );
  328. sinon.assert.calledOnce( spy );
  329. } );
  330. it( '"arrowup" focuses the last #item in #innerPanelView if dropdown is open', () => {
  331. const keyEvtData = {
  332. keyCode: keyCodes.arrowup,
  333. preventDefault: sinon.spy(),
  334. stopPropagation: sinon.spy()
  335. };
  336. const spy = sinon.spy( panelChildView, 'focusLast' );
  337. dropdownView.isOpen = false;
  338. dropdownView.keystrokes.press( keyEvtData );
  339. sinon.assert.notCalled( spy );
  340. dropdownView.isOpen = true;
  341. dropdownView.keystrokes.press( keyEvtData );
  342. sinon.assert.calledOnce( spy );
  343. } );
  344. } );
  345. } );
  346. }
  347. } );