text: /**
   * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
   * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
   */
  
  /* global document */
  
  import ViewCollection from '../../src/viewcollection';
  import ListView from '../../src/list/listview';
  import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
  import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
  import FocusCycler from '../../src/focuscycler';
  import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
  import View from '../../src/view';
  
  describe( 'ListView', () => {
  	let view;
  
  	beforeEach( () => {
  		view = new ListView();
  		view.render();
  	} );
  
  	afterEach( () => {
  		view.destroy();
  	} );
  
  	describe( 'constructor()', () => {
  		it( 'creates element from template', () => {
  			expect( view.element.classList.contains( 'ck' ) ).to.be.true;
  			expect( view.element.classList.contains( 'ck-reset' ) ).to.be.true;
  			expect( view.element.classList.contains( 'ck-list' ) ).to.be.true;
  		} );
  
  		it( 'creates view#items collection', () => {
  			expect( view.items ).to.be.instanceOf( ViewCollection );
  			expect( view.template.children ).to.have.length( 1 );
  			expect( view.template.children[ 0 ] ).to.equal( view.items );
  		} );
  
  		it( 'creates #focusTracker instance', () => {
  			expect( view.focusTracker ).to.be.instanceOf( FocusTracker );
  		} );
  
  		it( 'creates #keystrokeHandler instance', () => {
  			expect( view.keystrokes ).to.be.instanceOf( KeystrokeHandler );
  		} );
  
  		it( 'creates #_focusCycler instance', () => {
  			expect( view._focusCycler ).to.be.instanceOf( FocusCycler );
  		} );
  	} );
  
  	describe( 'render()', () => {
  		it( 'registers #items in #focusTracker', () => {
  			const view = new ListView();
  			const spyAdd = sinon.spy( view.focusTracker, 'add' );
  			const spyRemove = sinon.spy( view.focusTracker, 'remove' );
  
  			sinon.assert.notCalled( spyAdd );
  			view.items.add( focusable() );
  			view.items.add( focusable() );
  
  			view.render();
  			sinon.assert.calledTwice( spyAdd );
  
  			view.items.remove( 1 );
  			sinon.assert.calledOnce( spyRemove );
  
  			view.destroy();
  		} );
  
  		it( 'starts listening for #keystrokes coming from #element', () => {
  			const view = new ListView();
  			const spy = sinon.spy( view.keystrokes, 'listenTo' );
  
  			view.render();
  			sinon.assert.calledOnce( spy );
  			sinon.assert.calledWithExactly( spy, view.element );
  
  			view.destroy();
  		} );
  
  		describe( 'activates keyboard navigation for the list', () => {
  			it( 'so "arrowup" focuses previous focusable item', () => {
  				const keyEvtData = {
  					keyCode: keyCodes.arrowup,
  					preventDefault: sinon.spy(),
  					stopPropagation: sinon.spy()
  				};
  
  				// No children to focus.
  				view.keystrokes.press( keyEvtData );
  				sinon.assert.calledOnce( keyEvtData.preventDefault );
  				sinon.assert.calledOnce( keyEvtData.stopPropagation );
  
  				view.items.add( nonFocusable() );
  				view.items.add( nonFocusable() );
  
  				// No focusable children.
  				view.keystrokes.press( keyEvtData );
  				sinon.assert.calledTwice( keyEvtData.preventDefault );
  				sinon.assert.calledTwice( keyEvtData.stopPropagation );
  
  				view.items.add( focusable() );
  				view.items.add( nonFocusable() );
  				view.items.add( focusable() );
  
  				// Mock the last item is focused.
  				view.focusTracker.isFocused = true;
  				view.focusTracker.focusedElement = view.items.get( 4 ).element;
  
  				const spy = sinon.spy( view.items.get( 2 ), 'focus' );
  				view.keystrokes.press( keyEvtData );
  
  				sinon.assert.calledThrice( keyEvtData.preventDefault );
  				sinon.assert.calledThrice( keyEvtData.stopPropagation );
  				sinon.assert.calledOnce( spy );
  			} );
  
  			it( 'so "arrowdown" focuses next focusable item', () => {
  				const keyEvtData = {
  					keyCode: keyCodes.arrowdown,
  					preventDefault: sinon.spy(),
  					stopPropagation: sinon.spy()
  				};
  
  				// No children to focus.
  				view.keystrokes.press( keyEvtData );
  				sinon.assert.calledOnce( keyEvtData.preventDefault );
  				sinon.assert.calledOnce( keyEvtData.stopPropagation );
  
  				view.items.add( nonFocusable() );
  				view.items.add( nonFocusable() );
  
  				// No focusable children.
  				view.keystrokes.press( keyEvtData );
  				sinon.assert.calledTwice( keyEvtData.preventDefault );
  				sinon.assert.calledTwice( keyEvtData.stopPropagation );
  
  				view.items.add( focusable() );
  				view.items.add( nonFocusable() );
  				view.items.add( focusable() );
  
  				// Mock the last item is focused.
  				view.focusTracker.isFocused = true;
  				view.focusTracker.focusedElement = view.items.get( 4 ).element;
  
  				const spy = sinon.spy( view.items.get( 2 ), 'focus' );
  				view.keystrokes.press( keyEvtData );
  
  				sinon.assert.calledThrice( keyEvtData.preventDefault );
  				sinon.assert.calledThrice( keyEvtData.stopPropagation );
  				sinon.assert.calledOnce( spy );
  			} );
  		} );
  	} );
  
  	describe( 'focus()', () => {
  		it( 'focuses the first focusable item in DOM', () => {
  			// No children to focus.
  			view.focus();
  
  			// The second child is focusable.
  			view.items.add( nonFocusable() );
  			view.items.add( focusable() );
  			view.items.add( nonFocusable() );
  
  			const spy = sinon.spy( view.items.get( 1 ), 'focus' );
  			view.focus();
  
  			sinon.assert.calledOnce( spy );
  		} );
  	} );
  
  	describe( 'focusLast()', () => {
  		it( 'focuses the last focusable item in DOM', () => {
  			// No children to focus.
  			view.focusLast();
  
  			// The second child is focusable.
  			view.items.add( nonFocusable() );
  			view.items.add( focusable() );
  			view.items.add( nonFocusable() );
  
  			const spy = sinon.spy( view.items.get( 1 ), 'focus' );
  			view.focusLast();
  
  			sinon.assert.calledOnce( spy );
  		} );
  	} );
  } );
  
  function focusable() {
  	const view = nonFocusable();
  
  	view.focus = () => {};
  
  	return view;
  }
  
  function nonFocusable() {
  	const view = new View();
  	view.element = document.createElement( 'li' );
  
  	return view;
  }
  