Explorar o código

Added missing tests for ContentualBalloon.

Oskar Wróbel %!s(int64=6) %!d(string=hai) anos
pai
achega
74b9e6d357

+ 1 - 9
packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js

@@ -226,7 +226,7 @@ export default class ContextualBalloon extends Plugin {
 				if ( this._panels.length > 1 ) {
 					this._showNextPanel();
 				} else {
-					this.view.unpin();
+					this.view.hide();
 					this.visibleView = null;
 					this._visiblePanel = null;
 					this._rotatorView.hideView();
@@ -292,10 +292,6 @@ export default class ContextualBalloon extends Plugin {
 	 * @private
 	 */
 	_showNextPanel() {
-		if ( this._panels.length === 1 ) {
-			return;
-		}
-
 		const panel = this._visiblePanel;
 
 		let nextIndex = this._panels.getIndex( panel ) + 1;
@@ -314,10 +310,6 @@ export default class ContextualBalloon extends Plugin {
 	 * @private
 	 */
 	_showPrevPanel() {
-		if ( this._panels.length === 1 ) {
-			return;
-		}
-
 		const panel = this._visiblePanel;
 
 		let nextIndex = this._panels.getIndex( panel ) - 1;

+ 363 - 30
packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js

@@ -15,7 +15,7 @@ import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-util
 /* global document, Event */
 
 describe( 'ContextualBalloon', () => {
-	let editor, editorElement, balloon, viewA, viewB;
+	let editor, editorElement, balloon, viewA, viewB, viewC;
 
 	beforeEach( () => {
 		editorElement = document.createElement( 'div' );
@@ -36,6 +36,7 @@ describe( 'ContextualBalloon', () => {
 
 				viewA = new View();
 				viewB = new View();
+				viewC = new View();
 
 				// Add viewA to the pane and init viewB.
 				balloon.add( {
@@ -65,7 +66,7 @@ describe( 'ContextualBalloon', () => {
 		} );
 	} );
 
-	describe( 'init()', () => {
+	describe( 'constructor()', () => {
 		it( 'should create a plugin instance with properties', () => {
 			expect( balloon.view ).to.instanceof( BalloonPanelView );
 		} );
@@ -161,9 +162,11 @@ describe( 'ContextualBalloon', () => {
 	} );
 
 	describe( 'add()', () => {
-		it( 'should add view to the stack and display in balloon attached using given position options', () => {
-			expect( balloon.view.content.length ).to.equal( 1 );
-			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewA );
+		it( 'should add view to the `main` stack and display in balloon attached using given position options', () => {
+			const content = balloon.view.content.get( 0 ).content;
+
+			expect( content.length ).to.equal( 1 );
+			expect( content.get( 0 ) ).to.deep.equal( viewA );
 			expect( balloon.view.pin.calledOnce ).to.true;
 			sinon.assert.calledWithMatch( balloon.view.pin.firstCall, {
 				target: 'fake',
@@ -171,6 +174,58 @@ describe( 'ContextualBalloon', () => {
 			} );
 		} );
 
+		it( 'should add view to the custom stack but not display it when other panel is already visible', () => {
+			balloon.add( {
+				view: viewB,
+				panelId: 'second',
+				position: {
+					target: 'fake'
+				}
+			} );
+
+			balloon.add( {
+				view: viewC,
+				panelId: 'second',
+				position: {
+					target: 'fake'
+				}
+			} );
+
+			const content = balloon.view.content.get( 0 ).content;
+
+			expect( content.length ).to.equal( 1 );
+			expect( content.get( 0 ) ).to.deep.equal( viewA );
+			expect( balloon.hasView( viewB ) );
+			expect( balloon.hasView( viewC ) );
+		} );
+
+		it( 'should add multiple views to he stack and display last one', () => {
+			balloon.add( {
+				view: viewB,
+				position: {
+					target: 'fake',
+					limiter: balloon.positionLimiter
+				}
+			} );
+
+			const content = balloon.view.content.get( 0 ).content;
+
+			expect( content.length ).to.equal( 1 );
+			expect( content.get( 0 ) ).to.deep.equal( viewB );
+		} );
+
+		it( 'should throw an error when try to add the same view more than once', () => {
+			expect( () => {
+				balloon.add( {
+					view: viewA,
+					position: {
+						target: 'fake',
+						limiter: balloon.positionLimiter
+					}
+				} );
+			} ).to.throw( CKEditorError, /^contextualballoon-add-view-exist/ );
+		} );
+
 		it( 'should use a provided limiter instead of #positionLimiter', () => {
 			balloon.remove( viewA );
 			balloon.view.pin.resetHistory();
@@ -230,31 +285,6 @@ describe( 'ContextualBalloon', () => {
 			sinon.assert.calledOnce( balloon.view.pin );
 		} );
 
-		it( 'should throw an error when try to add the same view more than once', () => {
-			expect( () => {
-				balloon.add( {
-					view: viewA,
-					position: {
-						target: 'fake',
-						limiter: balloon.positionLimiter
-					}
-				} );
-			} ).to.throw( CKEditorError, /^contextualballoon-add-view-exist/ );
-		} );
-
-		it( 'should add multiple views to he stack and display last one', () => {
-			balloon.add( {
-				view: viewB,
-				position: {
-					target: 'fake',
-					limiter: balloon.positionLimiter
-				}
-			} );
-
-			expect( balloon.view.content.length ).to.equal( 1 );
-			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewB );
-		} );
-
 		it( 'should use the position of the last view in the stack', () => {
 			balloon.add( {
 				view: viewB,
@@ -365,6 +395,42 @@ describe( 'ContextualBalloon', () => {
 		} );
 	} );
 
+	describe( 'showPanel()', () => {
+		it( 'should hide current view and display last view from the given panel stack', () => {
+			balloon.add( {
+				panelId: 'second',
+				view: viewB
+			} );
+
+			balloon.add( {
+				panelId: 'second',
+				view: viewC
+			} );
+
+			expect( balloon.visibleView ).to.equal( viewA );
+
+			balloon.showPanel( 'second' );
+
+			expect( balloon.visibleView ).to.equal( viewC );
+
+			balloon.showPanel( 'main' );
+
+			expect( balloon.visibleView ).to.equal( viewA );
+		} );
+
+		it( 'should do nothing when given panel is already visible', () => {
+			expect( () => {
+				balloon.showPanel( 'main' );
+			} ).to.not.throw();
+		} );
+
+		it( 'should throw an error when there is no panel of given id', () => {
+			expect( () => {
+				balloon.showPanel( 'second' );
+			} ).to.throw( CKEditorError, 'contextualballoon-showpanel-panel-not-exist: Cannot show not existing panel.' );
+		} );
+	} );
+
 	describe( 'remove()', () => {
 		it( 'should remove given view and hide balloon when there is no other view to display', () => {
 			balloon.view.isVisible = true;
@@ -375,6 +441,53 @@ describe( 'ContextualBalloon', () => {
 			expect( balloon.view.isVisible ).to.false;
 		} );
 
+		it( 'should remove given view from not displayed panel', () => {
+			balloon.add( {
+				panelId: 'second',
+				view: viewB
+			} );
+
+			balloon.add( {
+				panelId: 'second',
+				view: viewC
+			} );
+
+			balloon.remove( viewB );
+
+			expect( balloon.visibleView ).to.equal( viewA );
+			expect( () => {
+				balloon.showPanel( 'second' );
+			} ).to.not.throw();
+		} );
+
+		it( 'should remove not displayed panel if a removed view was the only view in this panel', () => {
+			balloon.add( {
+				panelId: 'second',
+				view: viewB
+			} );
+
+			balloon.remove( viewB );
+
+			expect( balloon.visibleView ).to.equal( viewA );
+			expect( () => {
+				balloon.showPanel( 'second' );
+			} ).to.throw();
+		} );
+
+		it( 'should switch panel to the next one when removed view was the last one in the visible panel', () => {
+			balloon.add( {
+				panelId: 'second',
+				view: viewB
+			} );
+
+			balloon.remove( viewA );
+
+			expect( balloon.visibleView ).to.equal( viewB );
+			expect( () => {
+				balloon.showPanel( 'main' );
+			} ).to.throw();
+		} );
+
 		it( 'should remove given view and set preceding in the stack as visible when removed view was visible', () => {
 			balloon.add( {
 				view: viewB,
@@ -403,6 +516,53 @@ describe( 'ContextualBalloon', () => {
 			expect( balloon.visibleView ).to.equal( viewB );
 		} );
 
+		it( 'should remove given view from a not currently visible stack', () => {
+			balloon.add( {
+				view: viewB,
+				panelId: 'second',
+				position: {
+					target: 'fake'
+				}
+			} );
+
+			balloon.add( {
+				view: viewC,
+				panelId: 'second',
+				position: {
+					target: 'fake'
+				}
+			} );
+
+			balloon.remove( viewB );
+
+			expect( balloon.hasView( viewB ) ).to.false;
+			expect( balloon.hasView( viewC ) ).to.true;
+
+			// Does not throw, so the panel is there.
+			expect( () => {
+				balloon.showPanel( 'second' );
+			} ).to.not.throw();
+		} );
+
+		it( 'should remove not displayed stack when removied view was the last one in the stack', () => {
+			balloon.add( {
+				view: viewB,
+				panelId: 'second',
+				position: {
+					target: 'fake'
+				}
+			} );
+
+			balloon.remove( viewB );
+
+			expect( balloon.hasView( viewB ) ).to.false;
+
+			// Does throw, so the panel is not there.
+			expect( () => {
+				balloon.showPanel( 'second' );
+			} ).to.throw();
+		} );
+
 		it( 'should throw an error when there is no given view in the stack', () => {
 			expect( () => {
 				balloon.remove( viewB );
@@ -533,4 +693,177 @@ describe( 'ContextualBalloon', () => {
 			expect( editor.ui.view.body.getIndex( balloon.view ) ).to.not.equal( -1 );
 		} );
 	} );
+
+	describe( 'rotator view', () => {
+		let rotatorView;
+
+		beforeEach( () => {
+			rotatorView = balloon.view.content.get( 0 );
+		} );
+
+		it( 'should display navigation when there is more than one panel', () => {
+			const navigationElement = rotatorView.element.querySelector( '.ck-balloon-rotator__navigation' );
+
+			expect( navigationElement.classList.contains( 'ck-hidden' ) ).to.equal( true );
+
+			balloon.add( {
+				view: viewB,
+				panelId: 'second'
+			} );
+
+			expect( navigationElement.classList.contains( 'ck-hidden' ) ).to.equal( false );
+		} );
+
+		it( 'should display counter', () => {
+			const counterElement = rotatorView.element.querySelector( '.ck-balloon-rotator__counter' );
+
+			expect( counterElement.textContent ).to.equal( '1 of 1' );
+
+			balloon.add( {
+				view: viewB,
+				panelId: 'second'
+			} );
+
+			expect( counterElement.textContent ).to.equal( '1 of 2' );
+
+			balloon.showPanel( 'second' );
+
+			expect( counterElement.textContent ).to.equal( '2 of 2' );
+		} );
+
+		it( 'should hide counter when element width is less then 200px', () => {
+			// To be sure navigation is displayed.
+			balloon.add( {
+				view: viewB,
+				panelId: 'second'
+			} );
+
+			const counterElement = rotatorView.element.querySelector( '.ck-balloon-rotator__counter' );
+			let mockedWidth = 201;
+
+			sinon.stub( rotatorView.element, 'clientWidth' ).get( () => mockedWidth );
+
+			balloon.showPanel( 'second' );
+
+			expect( counterElement.classList.contains( 'ck-hidden' ) ).to.equal( false );
+
+			mockedWidth = 200;
+			balloon.showPanel( 'main' );
+
+			expect( counterElement.classList.contains( 'ck-hidden' ) ).to.equal( true );
+
+			mockedWidth = 201;
+			balloon.updatePosition();
+
+			expect( counterElement.classList.contains( 'ck-hidden' ) ).to.equal( false );
+		} );
+
+		it( 'should switch panel to the next one after clicking next button', () => {
+			balloon.add( {
+				view: viewB,
+				panelId: 'second'
+			} );
+
+			balloon.add( {
+				view: viewC,
+				panelId: 'third'
+			} );
+
+			expect( balloon.visibleView ).to.equal( viewA );
+
+			rotatorView.buttonNextView.fire( 'execute' );
+
+			expect( balloon.visibleView ).to.equal( viewB );
+
+			rotatorView.buttonNextView.fire( 'execute' );
+
+			expect( balloon.visibleView ).to.equal( viewC );
+
+			rotatorView.buttonNextView.fire( 'execute' );
+
+			expect( balloon.visibleView ).to.equal( viewA );
+		} );
+
+		it( 'should not move focus to the editable when switching not focused view to the next one', () => {
+			const editableFocusSpy = sinon.spy( editor.editing.view, 'focus' );
+
+			balloon.add( {
+				view: viewB,
+				panelId: 'second'
+			} );
+
+			rotatorView.buttonNextView.fire( 'execute' );
+
+			sinon.assert.notCalled( editableFocusSpy );
+		} );
+
+		it( 'should move focus to the editable when switching focused view to the next one', () => {
+			const editableFocusSpy = sinon.spy( editor.editing.view, 'focus' );
+
+			balloon.add( {
+				view: viewB,
+				panelId: 'second'
+			} );
+
+			rotatorView.focusTracker.isFocused = true;
+
+			rotatorView.buttonNextView.fire( 'execute' );
+
+			sinon.assert.calledOnce( editableFocusSpy );
+		} );
+
+		it( 'should switch panel to the prev one after clicking prev button', () => {
+			balloon.add( {
+				view: viewB,
+				panelId: 'second'
+			} );
+
+			balloon.add( {
+				view: viewC,
+				panelId: 'third'
+			} );
+
+			expect( balloon.visibleView ).to.equal( viewA );
+
+			rotatorView.buttonPrevView.fire( 'execute' );
+
+			expect( balloon.visibleView ).to.equal( viewC );
+
+			rotatorView.buttonPrevView.fire( 'execute' );
+
+			expect( balloon.visibleView ).to.equal( viewB );
+
+			rotatorView.buttonPrevView.fire( 'execute' );
+
+			expect( balloon.visibleView ).to.equal( viewA );
+		} );
+
+		it( 'should not move focus to the editable when switching not focused view to the prev one', () => {
+			const editableFocusSpy = sinon.spy( editor.editing.view, 'focus' );
+
+			balloon.add( {
+				view: viewB,
+				panelId: 'second'
+			} );
+
+			rotatorView.buttonPrevView.fire( 'execute' );
+
+			sinon.assert.notCalled( editableFocusSpy );
+		} );
+
+		it( 'should move focus to the editable when switching focused view to the prev one', () => {
+			const editableFocusSpy = sinon.spy( editor.editing.view, 'focus' );
+
+			balloon.add( {
+				view: viewB,
+				panelId: 'second'
+			} );
+
+			rotatorView.focusTracker.isFocused = true;
+
+			rotatorView.buttonPrevView.fire( 'execute' );
+
+			sinon.assert.calledOnce( editableFocusSpy );
+		} );
+	} );
 } );