utils.js 13 KB

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