|
@@ -3,7 +3,7 @@
|
|
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
-/* global window, document, setTimeout, Event */
|
|
|
|
|
|
|
+/* global document, setTimeout, Event */
|
|
|
|
|
|
|
|
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
|
|
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
|
|
|
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
|
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
|
@@ -15,11 +15,12 @@ import global from '@ckeditor/ckeditor5-utils/src/dom/global';
|
|
|
import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
|
|
import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
|
|
|
import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
|
|
import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
|
|
|
import EventInfo from '@ckeditor/ckeditor5-utils/src/eventinfo';
|
|
import EventInfo from '@ckeditor/ckeditor5-utils/src/eventinfo';
|
|
|
|
|
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
|
|
|
+import env from '@ckeditor/ckeditor5-utils/src/env';
|
|
|
|
|
|
|
|
import MentionUI from '../src/mentionui';
|
|
import MentionUI from '../src/mentionui';
|
|
|
import MentionEditing from '../src/mentionediting';
|
|
import MentionEditing from '../src/mentionediting';
|
|
|
import MentionsView from '../src/ui/mentionsview';
|
|
import MentionsView from '../src/ui/mentionsview';
|
|
|
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
|
|
|
|
|
|
|
|
describe( 'MentionUI', () => {
|
|
describe( 'MentionUI', () => {
|
|
|
let editor, model, doc, editingView, mentionUI, editorElement, mentionsView, panelView;
|
|
let editor, model, doc, editingView, mentionUI, editorElement, mentionsView, panelView;
|
|
@@ -151,9 +152,8 @@ describe( 'MentionUI', () => {
|
|
|
return waitForDebounce()
|
|
return waitForDebounce()
|
|
|
.then( () => {
|
|
.then( () => {
|
|
|
const pinArgument = pinSpy.firstCall.args[ 0 ];
|
|
const pinArgument = pinSpy.firstCall.args[ 0 ];
|
|
|
- const { target, positions, limiter, fitInViewport } = pinArgument;
|
|
|
|
|
|
|
+ const { target, positions, limiter } = pinArgument;
|
|
|
|
|
|
|
|
- expect( fitInViewport ).to.be.true;
|
|
|
|
|
expect( positions ).to.have.length( 4 );
|
|
expect( positions ).to.have.length( 4 );
|
|
|
|
|
|
|
|
// Mention UI should set limiter to the editable area.
|
|
// Mention UI should set limiter to the editable area.
|
|
@@ -177,8 +177,8 @@ describe( 'MentionUI', () => {
|
|
|
expect( mentionMarker.getRange().isEqual( range ), 'Should position to mention marker.' );
|
|
expect( mentionMarker.getRange().isEqual( range ), 'Should position to mention marker.' );
|
|
|
|
|
|
|
|
const caretSouthEast = positions[ 0 ];
|
|
const caretSouthEast = positions[ 0 ];
|
|
|
- const caretNorthEast = positions[ 1 ];
|
|
|
|
|
- const caretSouthWest = positions[ 2 ];
|
|
|
|
|
|
|
+ const caretSouthWest = positions[ 1 ];
|
|
|
|
|
+ const caretNorthEast = positions[ 2 ];
|
|
|
const caretNorthWest = positions[ 3 ];
|
|
const caretNorthWest = positions[ 3 ];
|
|
|
|
|
|
|
|
expect( caretSouthEast( caretRect, balloonRect ) ).to.deep.equal( {
|
|
expect( caretSouthEast( caretRect, balloonRect ) ).to.deep.equal( {
|
|
@@ -187,18 +187,18 @@ describe( 'MentionUI', () => {
|
|
|
top: 121
|
|
top: 121
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
- expect( caretNorthEast( caretRect, balloonRect ) ).to.deep.equal( {
|
|
|
|
|
- left: 501,
|
|
|
|
|
- name: 'caret_ne',
|
|
|
|
|
- top: -53
|
|
|
|
|
- } );
|
|
|
|
|
-
|
|
|
|
|
expect( caretSouthWest( caretRect, balloonRect ) ).to.deep.equal( {
|
|
expect( caretSouthWest( caretRect, balloonRect ) ).to.deep.equal( {
|
|
|
left: 301,
|
|
left: 301,
|
|
|
name: 'caret_sw',
|
|
name: 'caret_sw',
|
|
|
top: 121
|
|
top: 121
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
|
|
+ expect( caretNorthEast( caretRect, balloonRect ) ).to.deep.equal( {
|
|
|
|
|
+ left: 501,
|
|
|
|
|
+ name: 'caret_ne',
|
|
|
|
|
+ top: -53
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
expect( caretNorthWest( caretRect, balloonRect ) ).to.deep.equal( {
|
|
expect( caretNorthWest( caretRect, balloonRect ) ).to.deep.equal( {
|
|
|
left: 301,
|
|
left: 301,
|
|
|
name: 'caret_nw',
|
|
name: 'caret_nw',
|
|
@@ -344,38 +344,91 @@ describe( 'MentionUI', () => {
|
|
|
writer.insertText( '@', doc.selection.getFirstPosition() );
|
|
writer.insertText( '@', doc.selection.getFirstPosition() );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
|
|
+ const arrowDownEvtData = {
|
|
|
|
|
+ keyCode: keyCodes.arrowdown,
|
|
|
|
|
+ preventDefault: sinon.spy(),
|
|
|
|
|
+ stopPropagation: sinon.spy()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const arrowUpEvtData = {
|
|
|
|
|
+ keyCode: keyCodes.arrowup,
|
|
|
|
|
+ preventDefault: sinon.spy(),
|
|
|
|
|
+ stopPropagation: sinon.spy()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const mentionElementSpy = testUtils.sinon.spy( mentionsView.element, 'scrollTop', [ 'set' ] );
|
|
|
|
|
+
|
|
|
return waitForDebounce()
|
|
return waitForDebounce()
|
|
|
.then( () => {
|
|
.then( () => {
|
|
|
- expect( panelView.isVisible ).to.be.true;
|
|
|
|
|
|
|
+ // The scroll test highly depends on browser styles.
|
|
|
|
|
+ // Some CI test environments might not load theme which will result that tests will not render on CI as locally.
|
|
|
|
|
+ // To make this test repeatable across different environments it enforces mentions view size to 100px...
|
|
|
|
|
+ const reset = 'padding:0px;margin:0px;border:0 none;line-height: 1em;';
|
|
|
|
|
|
|
|
- expectChildViewsIsOnState( [ true, false, false, false, false, false, false, false, false, false ] );
|
|
|
|
|
|
|
+ mentionsView.element.style = `min-height:100px;height:100px;max-height:100px;${ reset };`;
|
|
|
|
|
|
|
|
- const arrowDownEvtData = {
|
|
|
|
|
- keyCode: keyCodes.arrowdown,
|
|
|
|
|
- preventDefault: sinon.spy(),
|
|
|
|
|
- stopPropagation: sinon.spy()
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ // ...and each list view item size to 25px...
|
|
|
|
|
+ Array.from( mentionsView.items ).forEach( item => {
|
|
|
|
|
+ const listItemElement = item.children.get( 0 ).element;
|
|
|
|
|
|
|
|
- const arrowUpEvtData = {
|
|
|
|
|
- keyCode: keyCodes.arrowup,
|
|
|
|
|
- preventDefault: sinon.spy(),
|
|
|
|
|
- stopPropagation: sinon.spy()
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ listItemElement.style = `min-height:unset;height:25px;max-height:25px;${ reset };min-width:12em;`;
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // ...so after those changes it is safe to assume that:
|
|
|
|
|
+ // - base offset is 0
|
|
|
|
|
+ // - only 4 items are visible at once
|
|
|
|
|
+ // - if scrolled to the last element scrollTop will be set to 150px. The 150px is the offset of the 7th item in the
|
|
|
|
|
+ // list as last four (7, 8, 9 & 10) will be visible.
|
|
|
|
|
+ expect( panelView.isVisible ).to.be.true;
|
|
|
|
|
+ expectChildViewsIsOnState( [ true, false, false, false, false, false, false, false, false, false ] );
|
|
|
|
|
+
|
|
|
|
|
+ // Edge browser always tries to scroll in tests environment: See ckeditor5-utils#282.
|
|
|
|
|
+ if ( !env.isEdge ) {
|
|
|
|
|
+ sinon.assert.callCount( mentionElementSpy.set, 0 );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
fireKeyDownEvent( arrowDownEvtData );
|
|
fireKeyDownEvent( arrowDownEvtData );
|
|
|
- expect( mentionsView.element.scrollTop ).to.equal( 0 );
|
|
|
|
|
|
|
|
|
|
expectChildViewsIsOnState( [ false, true, false, false, false, false, false, false, false, false ] );
|
|
expectChildViewsIsOnState( [ false, true, false, false, false, false, false, false, false, false ] );
|
|
|
|
|
+ // Edge browser always tries to scroll in tests environment: See ckeditor5-utils#282.
|
|
|
|
|
+ if ( !env.isEdge ) {
|
|
|
|
|
+ expect( mentionsView.element.scrollTop ).to.equal( 0 );
|
|
|
|
|
+ sinon.assert.callCount( mentionElementSpy.set, 0 );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
fireKeyDownEvent( arrowUpEvtData );
|
|
fireKeyDownEvent( arrowUpEvtData );
|
|
|
|
|
+
|
|
|
|
|
+ expectChildViewsIsOnState( [ true, false, false, false, false, false, false, false, false, false ] );
|
|
|
|
|
+ expect( mentionsView.element.scrollTop ).to.equal( 0 );
|
|
|
|
|
+
|
|
|
|
|
+ // Edge browser always tries to scroll in tests environment: See ckeditor5-utils#282.
|
|
|
|
|
+ if ( !env.isEdge ) {
|
|
|
|
|
+ sinon.assert.callCount( mentionElementSpy.set, 0 );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
fireKeyDownEvent( arrowUpEvtData );
|
|
fireKeyDownEvent( arrowUpEvtData );
|
|
|
|
|
|
|
|
expectChildViewsIsOnState( [ false, false, false, false, false, false, false, false, false, true ] );
|
|
expectChildViewsIsOnState( [ false, false, false, false, false, false, false, false, false, true ] );
|
|
|
- expect( mentionsView.element.scrollTop ).to.be.not.equal( 0 );
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // We want 150, but sometimes we get e.g. 151.
|
|
|
|
|
+ expect( mentionsView.element.scrollTop ).to.be.within( 140, 160, 'last item highlighted' );
|
|
|
|
|
+
|
|
|
|
|
+ // Edge browser always tries to scroll in tests environment: See ckeditor5-utils#282.
|
|
|
|
|
+ if ( !env.isEdge ) {
|
|
|
|
|
+ sinon.assert.callCount( mentionElementSpy.set, 1 );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
fireKeyDownEvent( arrowDownEvtData );
|
|
fireKeyDownEvent( arrowDownEvtData );
|
|
|
|
|
+
|
|
|
expectChildViewsIsOnState( [ true, false, false, false, false, false, false, false, false, false ] );
|
|
expectChildViewsIsOnState( [ true, false, false, false, false, false, false, false, false, false ] );
|
|
|
- expect( mentionsView.element.scrollTop ).to.equal( 0 );
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // We want 0, but sometimes we get e.g. 1. (Firefox)
|
|
|
|
|
+ expect( mentionsView.element.scrollTop ).to.be.within( 0, 10 );
|
|
|
|
|
+
|
|
|
|
|
+ // Edge browser always tries to scroll in tests environment: See ckeditor5-utils#282.
|
|
|
|
|
+ if ( !env.isEdge ) {
|
|
|
|
|
+ sinon.assert.callCount( mentionElementSpy.set, 2 );
|
|
|
|
|
+ }
|
|
|
} );
|
|
} );
|
|
|
} );
|
|
} );
|
|
|
} );
|
|
} );
|
|
@@ -432,9 +485,9 @@ describe( 'MentionUI', () => {
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
it( 'should not show panel when selection is inside a mention', () => {
|
|
it( 'should not show panel when selection is inside a mention', () => {
|
|
|
- setData( model, '<paragraph>foo [@John] bar</paragraph>' );
|
|
|
|
|
|
|
+ setData( model, '<paragraph>foo [@Lily] bar</paragraph>' );
|
|
|
model.change( writer => {
|
|
model.change( writer => {
|
|
|
- writer.setAttribute( 'mention', { id: '@John', _uid: 1234 }, doc.selection.getFirstRange() );
|
|
|
|
|
|
|
+ writer.setAttribute( 'mention', { id: '@Lily', _uid: 1234 }, doc.selection.getFirstRange() );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
model.change( writer => {
|
|
model.change( writer => {
|
|
@@ -449,9 +502,9 @@ describe( 'MentionUI', () => {
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
it( 'should not show panel when selection is at the end of a mention', () => {
|
|
it( 'should not show panel when selection is at the end of a mention', () => {
|
|
|
- setData( model, '<paragraph>foo [@John] bar</paragraph>' );
|
|
|
|
|
|
|
+ setData( model, '<paragraph>foo [@Lily] bar</paragraph>' );
|
|
|
model.change( writer => {
|
|
model.change( writer => {
|
|
|
- writer.setAttribute( 'mention', { id: '@John', _uid: 1234 }, doc.selection.getFirstRange() );
|
|
|
|
|
|
|
+ writer.setAttribute( 'mention', { id: '@Lily', _uid: 1234 }, doc.selection.getFirstRange() );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
model.change( writer => {
|
|
model.change( writer => {
|
|
@@ -522,9 +575,9 @@ describe( 'MentionUI', () => {
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
it( 'should not show panel when selection is after existing mention', () => {
|
|
it( 'should not show panel when selection is after existing mention', () => {
|
|
|
- setData( model, '<paragraph>foo [@John] bar[]</paragraph>' );
|
|
|
|
|
|
|
+ setData( model, '<paragraph>foo [@Lily] bar[]</paragraph>' );
|
|
|
model.change( writer => {
|
|
model.change( writer => {
|
|
|
- writer.setAttribute( 'mention', { id: '@John', _uid: 1234 }, doc.selection.getFirstRange() );
|
|
|
|
|
|
|
+ writer.setAttribute( 'mention', { id: '@Lily', _uid: 1234 }, doc.selection.getFirstRange() );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
return waitForDebounce()
|
|
return waitForDebounce()
|
|
@@ -541,6 +594,33 @@ describe( 'MentionUI', () => {
|
|
|
} );
|
|
} );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
|
|
+ it( 'should not show panel when selection moves inside existing mention', () => {
|
|
|
|
|
+ setData( model, '<paragraph>foo [@Lily] bar</paragraph>' );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setAttribute( 'mention', { id: '@Lily', _uid: 1234 }, doc.selection.getFirstRange() );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ return waitForDebounce()
|
|
|
|
|
+ .then( () => {
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setSelection( doc.getRoot().getChild( 0 ), 9 );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } )
|
|
|
|
|
+ .then( waitForDebounce )
|
|
|
|
|
+ .then( () => {
|
|
|
|
|
+ expect( panelView.isVisible ).to.be.false;
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setSelection( doc.getRoot().getChild( 0 ), 8 );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } )
|
|
|
|
|
+ .then( waitForDebounce )
|
|
|
|
|
+ .then( () => {
|
|
|
|
|
+ expect( panelView.isVisible ).to.be.false;
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
it( 'should show filtered results for matched text', () => {
|
|
it( 'should show filtered results for matched text', () => {
|
|
|
setData( model, '<paragraph>foo []</paragraph>' );
|
|
setData( model, '<paragraph>foo []</paragraph>' );
|
|
|
|
|
|
|
@@ -897,6 +977,39 @@ describe( 'MentionUI', () => {
|
|
|
expectChildViewsIsOnState( [ true, false, false, false, false ] );
|
|
expectChildViewsIsOnState( [ true, false, false, false, false ] );
|
|
|
} );
|
|
} );
|
|
|
} );
|
|
} );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'should not cycle with only one item in the list', () => {
|
|
|
|
|
+ setData( model, '<paragraph>foo []</paragraph>' );
|
|
|
|
|
+
|
|
|
|
|
+ const keyDownEvtData = {
|
|
|
|
|
+ keyCode: keyCodes.arrowdown,
|
|
|
|
|
+ preventDefault: sinon.spy(),
|
|
|
|
|
+ stopPropagation: sinon.spy()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const keyUpEvtData = {
|
|
|
|
|
+ keyCode: keyCodes.arrowdown,
|
|
|
|
|
+ preventDefault: sinon.spy(),
|
|
|
|
|
+ stopPropagation: sinon.spy()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.insertText( '@T', doc.selection.getFirstPosition() );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ return waitForDebounce()
|
|
|
|
|
+ .then( () => {
|
|
|
|
|
+ expectChildViewsIsOnState( [ true ] );
|
|
|
|
|
+
|
|
|
|
|
+ fireKeyDownEvent( keyDownEvtData );
|
|
|
|
|
+
|
|
|
|
|
+ expectChildViewsIsOnState( [ true ] );
|
|
|
|
|
+
|
|
|
|
|
+ fireKeyDownEvent( keyUpEvtData );
|
|
|
|
|
+
|
|
|
|
|
+ expectChildViewsIsOnState( [ true ] );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
describe( 'on "execute" keys', () => {
|
|
describe( 'on "execute" keys', () => {
|
|
@@ -1446,15 +1559,6 @@ describe( 'MentionUI', () => {
|
|
|
mentionUI = editor.plugins.get( MentionUI );
|
|
mentionUI = editor.plugins.get( MentionUI );
|
|
|
panelView = mentionUI.panelView;
|
|
panelView = mentionUI.panelView;
|
|
|
mentionsView = mentionUI._mentionsView;
|
|
mentionsView = mentionUI._mentionsView;
|
|
|
-
|
|
|
|
|
- editingView.attachDomRoot( editorElement );
|
|
|
|
|
-
|
|
|
|
|
- // Focus the engine.
|
|
|
|
|
- editingView.document.isFocused = true;
|
|
|
|
|
- editingView.getDomRoot().focus();
|
|
|
|
|
-
|
|
|
|
|
- // Remove all selection ranges from DOM before testing.
|
|
|
|
|
- window.getSelection().removeAllRanges();
|
|
|
|
|
} );
|
|
} );
|
|
|
}
|
|
}
|
|
|
|
|
|