listview.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global document */
  6. import ViewCollection from '../../src/viewcollection';
  7. import ListView from '../../src/list/listview';
  8. import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
  9. import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
  10. import FocusCycler from '../../src/focuscycler';
  11. import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
  12. import View from '../../src/view';
  13. describe( 'ListView', () => {
  14. let view;
  15. beforeEach( () => {
  16. return ( view = new ListView() ).init();
  17. } );
  18. describe( 'constructor()', () => {
  19. it( 'creates element from template', () => {
  20. expect( view.element.classList.contains( 'ck-reset' ) ).to.be.true;
  21. expect( view.element.classList.contains( 'ck-list' ) ).to.be.true;
  22. } );
  23. it( 'creates view#children collection', () => {
  24. expect( view.items ).to.be.instanceOf( ViewCollection );
  25. expect( view.template.children ).to.have.length( 1 );
  26. expect( view.template.children.get( 0 ) ).to.equal( view.items );
  27. } );
  28. it( 'creates #focusTracker instance', () => {
  29. expect( view.focusTracker ).to.be.instanceOf( FocusTracker );
  30. } );
  31. it( 'creates #keystrokeHandler instance', () => {
  32. expect( view.keystrokes ).to.be.instanceOf( KeystrokeHandler );
  33. } );
  34. it( 'creates #_focusCycler instance', () => {
  35. expect( view._focusCycler ).to.be.instanceOf( FocusCycler );
  36. } );
  37. it( 'registers #items in #focusTracker', () => {
  38. const spyAdd = sinon.spy( view.focusTracker, 'add' );
  39. const spyRemove = sinon.spy( view.focusTracker, 'remove' );
  40. view.items.add( focusable() );
  41. view.items.add( focusable() );
  42. sinon.assert.calledTwice( spyAdd );
  43. view.items.remove( 1 );
  44. sinon.assert.calledOnce( spyRemove );
  45. } );
  46. } );
  47. describe( 'init()', () => {
  48. it( 'starts listening for #keystrokes coming from #element', () => {
  49. view = new ListView();
  50. const spy = sinon.spy( view.keystrokes, 'listenTo' );
  51. view.init();
  52. sinon.assert.calledOnce( spy );
  53. sinon.assert.calledWithExactly( spy, view.element );
  54. } );
  55. describe( 'activates keyboard navigation for the list', () => {
  56. it( 'so "arrowup" focuses previous focusable item', () => {
  57. const keyEvtData = {
  58. keyCode: keyCodes.arrowup,
  59. preventDefault: sinon.spy(),
  60. stopPropagation: sinon.spy()
  61. };
  62. // No children to focus.
  63. view.keystrokes.press( keyEvtData );
  64. sinon.assert.calledOnce( keyEvtData.preventDefault );
  65. sinon.assert.calledOnce( keyEvtData.stopPropagation );
  66. view.items.add( nonFocusable() );
  67. view.items.add( nonFocusable() );
  68. // No focusable children.
  69. view.keystrokes.press( keyEvtData );
  70. sinon.assert.calledTwice( keyEvtData.preventDefault );
  71. sinon.assert.calledTwice( keyEvtData.stopPropagation );
  72. view.items.add( focusable() );
  73. view.items.add( nonFocusable() );
  74. view.items.add( focusable() );
  75. // Mock the last item is focused.
  76. view.focusTracker.isFocused = true;
  77. view.focusTracker.focusedElement = view.items.get( 4 ).element;
  78. const spy = sinon.spy( view.items.get( 2 ), 'focus' );
  79. view.keystrokes.press( keyEvtData );
  80. sinon.assert.calledThrice( keyEvtData.preventDefault );
  81. sinon.assert.calledThrice( keyEvtData.stopPropagation );
  82. sinon.assert.calledOnce( spy );
  83. } );
  84. it( 'so "arrowdown" focuses next focusable item', () => {
  85. const keyEvtData = {
  86. keyCode: keyCodes.arrowdown,
  87. preventDefault: sinon.spy(),
  88. stopPropagation: sinon.spy()
  89. };
  90. // No children to focus.
  91. view.keystrokes.press( keyEvtData );
  92. sinon.assert.calledOnce( keyEvtData.preventDefault );
  93. sinon.assert.calledOnce( keyEvtData.stopPropagation );
  94. view.items.add( nonFocusable() );
  95. view.items.add( nonFocusable() );
  96. // No focusable children.
  97. view.keystrokes.press( keyEvtData );
  98. sinon.assert.calledTwice( keyEvtData.preventDefault );
  99. sinon.assert.calledTwice( keyEvtData.stopPropagation );
  100. view.items.add( focusable() );
  101. view.items.add( nonFocusable() );
  102. view.items.add( focusable() );
  103. // Mock the last item is focused.
  104. view.focusTracker.isFocused = true;
  105. view.focusTracker.focusedElement = view.items.get( 4 ).element;
  106. const spy = sinon.spy( view.items.get( 2 ), 'focus' );
  107. view.keystrokes.press( keyEvtData );
  108. sinon.assert.calledThrice( keyEvtData.preventDefault );
  109. sinon.assert.calledThrice( keyEvtData.stopPropagation );
  110. sinon.assert.calledOnce( spy );
  111. } );
  112. } );
  113. } );
  114. describe( 'focus()', () => {
  115. it( 'focuses the first focusable item in DOM', () => {
  116. // No children to focus.
  117. view.focus();
  118. // The second child is focusable.
  119. view.items.add( nonFocusable() );
  120. view.items.add( focusable() );
  121. view.items.add( nonFocusable() );
  122. const spy = sinon.spy( view.items.get( 1 ), 'focus' );
  123. view.focus();
  124. sinon.assert.calledOnce( spy );
  125. } );
  126. } );
  127. describe( 'focusLast()', () => {
  128. it( 'focuses the last focusable item in DOM', () => {
  129. // No children to focus.
  130. view.focusLast();
  131. // The second child is focusable.
  132. view.items.add( nonFocusable() );
  133. view.items.add( focusable() );
  134. view.items.add( nonFocusable() );
  135. const spy = sinon.spy( view.items.get( 1 ), 'focus' );
  136. view.focusLast();
  137. sinon.assert.calledOnce( spy );
  138. } );
  139. } );
  140. } );
  141. function focusable() {
  142. const view = nonFocusable();
  143. view.focus = () => {};
  144. return view;
  145. }
  146. function nonFocusable() {
  147. const view = new View();
  148. view.element = document.createElement( 'li' );
  149. return view;
  150. }