text: /**
   * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
   * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
   */
  
  /* global document */
  
  import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  import EventInfo from '@ckeditor/ckeditor5-utils/src/eventinfo';
  import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  import SpecialCharacters from '../src/specialcharacters';
  import SpecialCharactersMathematical from '../src/specialcharactersmathematical';
  import SpecialCharactersArrows from '../src/specialcharactersarrows';
  import SpecialCharactersNavigationView from '../src/ui/specialcharactersnavigationview';
  import CharacterGridView from '../src/ui/charactergridview';
  import CharacterInfoView from '../src/ui/characterinfoview';
  import specialCharactersIcon from '../theme/icons/specialcharacters.svg';
  import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  
  describe( 'SpecialCharacters', () => {
  	let plugin;
  
  	beforeEach( () => {
  		plugin = new SpecialCharacters( {} );
  	} );
  
  	it( 'should require proper plugins', () => {
  		expect( SpecialCharacters.requires ).to.deep.equal( [ Typing ] );
  	} );
  
  	it( 'should be named', () => {
  		expect( SpecialCharacters.pluginName ).to.equal( 'SpecialCharacters' );
  	} );
  
  	describe( 'init()', () => {
  		let editor, command, element;
  
  		beforeEach( () => {
  			element = document.createElement( 'div' );
  			document.body.appendChild( element );
  
  			return ClassicTestEditor
  				.create( element, {
  					plugins: [
  						SpecialCharacters,
  						SpecialCharactersMathematical,
  						SpecialCharactersArrows
  					]
  				} )
  				.then( newEditor => {
  					editor = newEditor;
  					command = editor.commands.get( 'input' );
  				} );
  		} );
  
  		afterEach( () => {
  			element.remove();
  
  			return editor.destroy();
  		} );
  
  		describe( '"specialCharacters" dropdown', () => {
  			let dropdown;
  
  			beforeEach( () => {
  				dropdown = editor.ui.componentFactory.create( 'specialCharacters' );
  				dropdown.isOpen = true; // Dropdown is lazy loaded, so needs to be open to be verified (#6175).
  			} );
  
  			afterEach( () => {
  				dropdown.destroy();
  			} );
  
  			it( 'has a navigation view', () => {
  				expect( dropdown.panelView.children.first ).to.be.instanceOf( SpecialCharactersNavigationView );
  			} );
  
  			it( 'a navigation contains the "All" special category', () => {
  				const listView = dropdown.panelView.children.first.groupDropdownView.panelView.children.first;
  
  				// "Mathematical" and "Arrows" are provided by other plugins. "All" is being added by SpecialCharacters itself.
  				expect( listView.items.length ).to.equal( 3 );
  				expect( listView.items.first.children.first.label ).to.equal( 'All' );
  			} );
  
  			it( 'has a grid view', () => {
  				expect( dropdown.panelView.children.get( 1 ) ).to.be.instanceOf( CharacterGridView );
  			} );
  
  			it( 'has a character info view', () => {
  				expect( dropdown.panelView.children.last ).to.be.instanceOf( CharacterInfoView );
  			} );
  
  			describe( '#buttonView', () => {
  				it( 'should get basic properties', () => {
  					expect( dropdown.buttonView.label ).to.equal( 'Special characters' );
  					expect( dropdown.buttonView.icon ).to.equal( specialCharactersIcon );
  					expect( dropdown.buttonView.tooltip ).to.be.true;
  				} );
  
  				it( 'should bind #isEnabled to the command', () => {
  					expect( dropdown.isEnabled ).to.be.true;
  
  					command.isEnabled = false;
  					expect( dropdown.isEnabled ).to.be.false;
  					command.isEnabled = true;
  				} );
  			} );
  
  			it( 'executes a command and focuses the editing view', () => {
  				const grid = dropdown.panelView.children.get( 1 );
  				const executeSpy = sinon.stub( editor, 'execute' );
  				const focusSpy = sinon.stub( editor.editing.view, 'focus' );
  
  				grid.tiles.get( 2 ).fire( 'execute' );
  
  				sinon.assert.calledOnce( executeSpy );
  				sinon.assert.calledOnce( focusSpy );
  				sinon.assert.calledWithExactly( executeSpy.firstCall, 'input', {
  					text: '≤'
  				} );
  			} );
  
  			describe( 'grid view', () => {
  				let grid;
  
  				beforeEach( () => {
  					grid = dropdown.panelView.children.get( 1 );
  				} );
  
  				it( 'delegates #execute to the dropdown', () => {
  					const spy = sinon.spy();
  
  					dropdown.on( 'execute', spy );
  					grid.fire( 'execute', { name: 'foo' } );
  
  					sinon.assert.calledOnce( spy );
  				} );
  
  				it( 'has default contents', () => {
  					expect( grid.tiles ).to.have.length.greaterThan( 10 );
  				} );
  
  				it( 'is updated when navigation view fires #execute', () => {
  					const navigation = dropdown.panelView.children.first;
  
  					expect( grid.tiles.get( 0 ).label ).to.equal( '<' );
  					navigation.groupDropdownView.fire( new EventInfo( { label: 'Arrows' }, 'execute' ) );
  
  					expect( grid.tiles.get( 0 ).label ).to.equal( '⇐' );
  				} );
  			} );
  
  			describe( 'character info view', () => {
  				let grid, characterInfo;
  
  				beforeEach( () => {
  					grid = dropdown.panelView.children.get( 1 );
  					characterInfo = dropdown.panelView.children.last;
  				} );
  
  				it( 'is empty when the dropdown was shown', () => {
  					dropdown.fire( 'change:isOpen' );
  
  					expect( characterInfo.character ).to.equal( null );
  					expect( characterInfo.name ).to.equal( null );
  					expect( characterInfo.code ).to.equal( '' );
  				} );
  
  				it( 'is updated when the tile fires #mouseover', () => {
  					const tile = grid.tiles.get( 0 );
  
  					tile.fire( 'mouseover' );
  
  					expect( tile.label ).to.equal( '<' );
  					expect( characterInfo.character ).to.equal( '<' );
  					expect( characterInfo.name ).to.equal( 'Less-than sign' );
  					expect( characterInfo.code ).to.equal( 'U+003c' );
  				} );
  			} );
  
  			it( 'is not fully initialized when not open', () => {
  				// (#6175)
  				const uninitializedDropdown = editor.ui.componentFactory.create( 'specialCharacters' );
  				expect( uninitializedDropdown.panelView.children.length ).to.be.equal( 0 );
  			} );
  		} );
  	} );
  
  	describe( 'addItems()', () => {
  		it( 'adds special characters to the available symbols', () => {
  			plugin.addItems( 'Arrows', [
  				{ title: 'arrow left', character: '←' },
  				{ title: 'arrow right', character: '→' }
  			] );
  
  			expect( plugin._groups.size ).to.equal( 1 );
  			expect( plugin._groups.has( 'Arrows' ) ).to.equal( true );
  
  			expect( plugin._characters.size ).to.equal( 2 );
  			expect( plugin._characters.has( 'arrow left' ) ).to.equal( true );
  			expect( plugin._characters.has( 'arrow right' ) ).to.equal( true );
  		} );
  
  		it( 'works with subsequent calls to the same group', () => {
  			plugin.addItems( 'Mathematical', [
  				{
  					title: 'dot',
  					character: '.'
  				}
  			] );
  
  			plugin.addItems( 'Mathematical', [
  				{
  					title: ',',
  					character: 'comma'
  				}
  			] );
  
  			const groups = [ ...plugin.getGroups() ];
  			expect( groups ).to.deep.equal( [ 'Mathematical' ] );
  		} );
  
  		it( 'does not accept "All" as a group name', () => {
  			expectToThrowCKEditorError( () => {
  				plugin.addItems( 'All', [] );
  			}, /special-character-invalid-group-name/ );
  		} );
  	} );
  
  	describe( 'getGroups()', () => {
  		it( 'returns iterator of defined groups', () => {
  			plugin.addItems( 'Arrows', [
  				{ title: 'arrow left', character: '←' }
  			] );
  
  			plugin.addItems( 'Mathematical', [
  				{ title: 'precedes', character: '≺' },
  				{ title: 'succeeds', character: '≻' }
  			] );
  
  			const groups = [ ...plugin.getGroups() ];
  			expect( groups ).to.deep.equal( [ 'Arrows', 'Mathematical' ] );
  		} );
  	} );
  
  	describe( 'getCharactersForGroup()', () => {
  		it( 'returns a collection of defined special characters names', () => {
  			plugin.addItems( 'Mathematical', [
  				{ title: 'precedes', character: '≺' },
  				{ title: 'succeeds', character: '≻' }
  			] );
  
  			const characters = plugin.getCharactersForGroup( 'Mathematical' );
  
  			expect( characters.size ).to.equal( 2 );
  			expect( characters.has( 'precedes' ) ).to.equal( true );
  			expect( characters.has( 'succeeds' ) ).to.equal( true );
  		} );
  
  		it( 'returns a collection of all special characters for "All" group', () => {
  			plugin.addItems( 'Arrows', [
  				{ title: 'arrow left', character: '←' },
  				{ title: 'arrow right', character: '→' }
  			] );
  
  			plugin.addItems( 'Mathematical', [
  				{ title: 'precedes', character: '≺' },
  				{ title: 'succeeds', character: '≻' }
  			] );
  
  			const characters = plugin.getCharactersForGroup( 'All' );
  
  			expect( characters.size ).to.equal( 4 );
  			expect( characters.has( 'arrow left' ) ).to.equal( true );
  			expect( characters.has( 'arrow right' ) ).to.equal( true );
  			expect( characters.has( 'precedes' ) ).to.equal( true );
  			expect( characters.has( 'succeeds' ) ).to.equal( true );
  		} );
  
  		it( 'returns undefined for non-existing group', () => {
  			plugin.addItems( 'Mathematical', [
  				{ title: 'precedes', character: '≺' },
  				{ title: 'succeeds', character: '≻' }
  			] );
  
  			const characters = plugin.getCharactersForGroup( 'Foo' );
  
  			expect( characters ).to.be.undefined;
  		} );
  	} );
  
  	describe( 'getCharacter()', () => {
  		it( 'returns a collection of defined special characters names', () => {
  			plugin.addItems( 'Mathematical', [
  				{ title: 'precedes', character: '≺' },
  				{ title: 'succeeds', character: '≻' }
  			] );
  
  			expect( plugin.getCharacter( 'succeeds' ) ).to.equal( '≻' );
  		} );
  
  		it( 'returns undefined for non-existing character', () => {
  			expect( plugin.getCharacter( 'succeeds' ) ).to.be.undefined;
  		} );
  	} );
  } );
  