| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- /* globals document */
- import ViewDocument from '../../../src/view/document';
- import UIElement from '../../../src/view/uielement';
- import ViewContainerElement from '../../../src/view/containerelement';
- import ViewAttribtueElement from '../../../src/view/attributeelement';
- import ViewText from '../../../src/view/text';
- import ViewRange from '../../../src/view/range';
- import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
- import createElement from '@ckeditor/ckeditor5-utils/src/dom/createelement';
- import createViewRoot from '../_utils/createroot';
- import { setData as setViewData } from '../../../src/dev-utils/view';
- import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
- describe( 'Document', () => {
- let viewDocument, domRoot, domSelection, viewRoot, foo, bar, ui, ui2;
- function createUIElement( name, contents ) {
- const element = new UIElement( name );
- element.render = function( domDocument ) {
- const domElement = this.toDomElement( domDocument );
- domElement.innerText = contents;
- return domElement;
- };
- return element;
- }
- beforeEach( () => {
- domRoot = createElement( document, 'div', {
- contenteditable: 'true'
- } );
- document.body.appendChild( domRoot );
- viewDocument = new ViewDocument();
- viewRoot = createViewRoot( viewDocument );
- viewDocument.attachDomRoot( domRoot );
- domSelection = document.getSelection();
- domSelection.removeAllRanges();
- viewDocument.isFocused = true;
- foo = new ViewText( 'foo' );
- bar = new ViewText( 'bar' );
- ui = createUIElement( 'span', 'xxx' );
- ui2 = createUIElement( 'span', 'yyy' );
- } );
- afterEach( () => {
- viewDocument.destroy();
- domRoot.parentElement.removeChild( domRoot );
- } );
- function renderAndFireKeydownEvent( options ) {
- viewDocument.render();
- const eventData = Object.assign( { keyCode: keyCodes.arrowright, domTarget: viewDocument.domRoots.get( 'main' ) }, options );
- viewDocument.fire( 'keydown', eventData );
- }
- function check( anchorNode, anchorOffset, focusNode, focusOffset ) {
- const anchor = domSelection.anchorNode.data ? domSelection.anchorNode.data : domSelection.anchorNode.nodeName.toUpperCase();
- expect( anchor, 'anchorNode' ).to.equal( anchorNode );
- expect( domSelection.anchorOffset, 'anchorOffset' ).to.equal( anchorOffset );
- if ( focusNode ) {
- const focus = domSelection.focusNode.data ? domSelection.focusNode.data : domSelection.focusNode.nodeName.toUpperCase();
- expect( focus, 'focusNode' ).to.equal( focusNode );
- expect( domSelection.focusOffset, 'focusOffset' ).to.equal( focusOffset );
- } else {
- expect( domSelection.isCollapsed, 'isCollapsed' ).to.be.true;
- }
- }
- describe( 'jump over ui element handler', () => {
- describe( 'collapsed selection', () => {
- it( 'do nothing when another key is pressed', () => {
- // <container:p>foo<ui:span>xxx</ui:span>{}bar</container:p>
- const p = new ViewContainerElement( 'p', null, [ foo, ui, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( bar, 0, bar, 0 ) ] );
- renderAndFireKeydownEvent( { keyCode: keyCodes.arrowleft } );
- testUtils.checkAssertions(
- () => check( 'bar', 0 ),
- // Safari renders selection at the end of the text node.
- () => check( 'xxx', 3 )
- );
- } );
- it( 'jump over ui element when right arrow is pressed before ui element - directly before ui element', () => {
- // <container:p>foo[]<ui:span>xxx</ui:span>bar</container:p>
- const p = new ViewContainerElement( 'p', null, [ foo, ui, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( p, 1, p, 1 ) ] );
- renderAndFireKeydownEvent();
- testUtils.checkAssertions(
- // <p>foo<span>xxx</span>[]bar</p>
- () => check( 'P', 2 ),
- // Safari renders selection at the end of the text node.
- // <p>foo<span>xxx{}</span>bar</p>
- () => check( 'xxx', 3 )
- );
- } );
- it( 'jump over ui element when right arrow is pressed before ui element - not directly before ui element', () => {
- // <container:p>foo{}<ui:span>xxx</ui:span>bar</container:p>
- const p = new ViewContainerElement( 'p', null, [ foo, ui, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 3, foo, 3 ) ] );
- renderAndFireKeydownEvent();
- testUtils.checkAssertions(
- // <p>foo<span>xxx</span>[]bar</p>
- () => check( 'P', 2 ),
- // Safari renders selection at the end of the text node.
- // <p>foo<span>xxx{}</span>bar</p>
- () => check( 'xxx', 3 )
- );
- } );
- it( 'jump over multiple ui elements when right arrow is pressed before ui element', () => {
- // <container:p>foo{}<ui:span>xxx</ui:span><ui:span>yyy</ui:span>bar</container:p>'
- const p = new ViewContainerElement( 'p', null, [ foo, ui, ui2, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 3, foo, 3 ) ] );
- renderAndFireKeydownEvent();
- testUtils.checkAssertions(
- // <p>foo<span>xxx</span><span>yyy</span>[]bar</p>
- () => check( 'P', 3 ),
- // Safari renders selection at the end of the text node.
- // <p>foo<span>xxx</span><span>yyy{}</span>bar</p>
- () => check( 'yyy', 3 )
- );
- } );
- it( 'jump over ui elements at the end of container element', () => {
- // <container:p>foo{}<ui:span>xxx</ui:span><ui:span>yyy</ui:span></container:p><container:div></container:div>
- const p = new ViewContainerElement( 'p', null, [ foo, ui, ui2 ] );
- const div = new ViewContainerElement( 'div' );
- viewRoot.appendChildren( p );
- viewRoot.appendChildren( div );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 3, foo, 3 ) ] );
- renderAndFireKeydownEvent();
- testUtils.checkAssertions(
- // <p>foo<span>xxx</span><span>yyy</span>[]</p><div></div>
- () => check( 'P', 3 ),
- // Safari renders selection at the end of the text node.
- // <p>foo<span>xxx</span><span>yyy{}</span></p><div></div>
- () => check( 'yyy', 3 )
- );
- } );
- it( 'jump over ui element if selection is in attribute element - case 1', () => {
- // <container:p><attribute:b>foo{}</attribute:b><ui:span>xxx</ui:span>bar</container:p>
- const b = new ViewAttribtueElement( 'b', null, foo );
- const p = new ViewContainerElement( 'p', null, [ b, ui, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 3, foo, 3 ) ] );
- renderAndFireKeydownEvent();
- testUtils.checkAssertions(
- // <p><b>foo</b><span>xxx</span>[]bar</p>
- () => check( 'P', 2 ),
- // Safari renders selection at the end of the text node.
- // <p><b>foo</b><span>xxx{}</span>bar</p>
- () => check( 'xxx', 3 )
- );
- } );
- it( 'jump over ui element if selection is in attribute element - case 2', () => {
- // <container:p><attribute:b>foo[]</attribute:b><ui:span>xxx</ui:span>bar</container:p>
- const b = new ViewAttribtueElement( 'b', null, foo );
- const p = new ViewContainerElement( 'p', null, [ b, ui, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( b, 1, b, 1 ) ] );
- renderAndFireKeydownEvent();
- testUtils.checkAssertions(
- // <p><b>foo</b><span>xxx</span>[]bar</p>
- () => check( 'P', 2 ),
- // Safari renders selection at the end of the text node.
- // <p><b>foo</b><span>xxx{}</span>bar</p>
- () => check( 'xxx', 3 )
- );
- } );
- it( 'jump over ui element if selection is in multiple attribute elements', () => {
- // <container:p>
- // <attribute:i>
- // <attribute:b>foo{}</attribute:b>
- // </attribute:i>
- // <ui:span>
- // xxx
- // </ui:span>
- // bar
- // </container:p>
- const b = new ViewAttribtueElement( 'b', null, foo );
- const i = new ViewAttribtueElement( 'i', null, b );
- const p = new ViewContainerElement( 'p', null, [ i, ui, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 3, foo, 3 ) ] );
- renderAndFireKeydownEvent();
- testUtils.checkAssertions(
- // <p><i><b>foo</b></i><span>xxx</span>[]bar</p>
- () => check( 'P', 2 ),
- // Safari renders selection at the end of the text node.
- // <p><i><b>foo</b></i><span>xxx{}</span>bar</p>
- () => check( 'xxx', 3 )
- );
- } );
- it( 'jump over empty attribute elements and ui elements', () => {
- // <container:p>' +
- // foo{}
- // <attribute:b></attribute:b>
- // <ui:span>xxx</ui:span>
- // <ui:span>yyy</ui:span>
- // <attribute:b></attribute:b>
- // bar
- // </container:p>
- const b1 = new ViewAttribtueElement( 'b' );
- const b2 = new ViewAttribtueElement( 'b' );
- const p = new ViewContainerElement( 'p', null, [ foo, b1, ui, ui2, b2, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 3, foo, 3 ) ] );
- renderAndFireKeydownEvent();
- testUtils.checkAssertions(
- // <p>foo<b></b><span>xxx</span><span>yyy</span>[]bar</p>
- () => check( 'P', 5 ),
- // Safari renders selection at the end of the text node.
- // <p>foo<b></b><span>xxx</span><span>yyy{}</span>bar</p>
- () => check( 'yyy', 3 )
- );
- } );
- it( 'jump over empty attribute elements and ui elements if shift key is pressed', () => {
- // <container:p>
- // foo{}
- // <attribute:b></attribute:b>
- // <ui:span>xxx</ui:span>
- // <ui:span>yyy</ui:span>
- // <attribute:b></attribute:b>
- // bar
- // </container:p>
- const b1 = new ViewAttribtueElement( 'b' );
- const b2 = new ViewAttribtueElement( 'b' );
- const p = new ViewContainerElement( 'p', null, [ foo, b1, ui, ui2, b2, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 3, foo, 3 ) ] );
- renderAndFireKeydownEvent( { shiftKey: true } );
- testUtils.checkAssertions(
- // <p>foo<b></b><span>xxx</span><span>yyy</span><b><b>[]bar</p>
- () => check( 'P', 5 ),
- // Safari renders selection at the end of the text node.
- // <p>foo<b></b><span>xxx</span><span>yyy{}</span><b><b>bar</p>
- () => check( 'yyy', 3 )
- );
- } );
- it( 'do nothing if selection is not directly before ui element', () => {
- setViewData( viewDocument, '<container:p>fo{}o<ui:span></ui:span>bar</container:p>' );
- renderAndFireKeydownEvent();
- check( 'foo', 2 );
- } );
- it( 'do nothing if selection is in attribute element but not before ui element', () => {
- setViewData( viewDocument, '<container:p><attribute:b>foo{}</attribute:b>bar</container:p>' );
- renderAndFireKeydownEvent();
- check( 'foo', 3 );
- } );
- it( 'do nothing if selection is before non-empty attribute element', () => {
- setViewData( viewDocument, '<container:p>fo{}<attribute:b>o</attribute:b><ui:span></ui:span>bar</container:p>' );
- renderAndFireKeydownEvent();
- check( 'fo', 2 );
- } );
- it( 'do nothing if selection is before container element - case 1', () => {
- setViewData( viewDocument, '<container:p>foo{}</container:p><ui:span></ui:span><container:div>bar</container:div>' );
- renderAndFireKeydownEvent();
- check( 'foo', 3 );
- } );
- it( 'do nothing if selection is before container element - case 2', () => {
- setViewData( viewDocument, '<container:div>foo{}<container:p></container:p><ui:span></ui:span></container:div>' );
- renderAndFireKeydownEvent();
- check( 'foo', 3 );
- } );
- it( 'do nothing if selection is at the end of last container element', () => {
- setViewData( viewDocument, '<container:p>foo{}</container:p>' );
- renderAndFireKeydownEvent();
- check( 'foo', 3 );
- } );
- } );
- describe( 'non-collapsed selection', () => {
- it( 'should do nothing', () => {
- setViewData( viewDocument, '<container:p>f{oo}<ui:span></ui:span>bar</container:p>' );
- renderAndFireKeydownEvent();
- check( 'foo', 1, 'foo', 3 );
- } );
- it( 'should do nothing if selection is not before ui element - shift key pressed', () => {
- setViewData( viewDocument, '<container:p>f{o}o<ui:span></ui:span>bar</container:p>' );
- renderAndFireKeydownEvent( { shiftKey: true } );
- check( 'foo', 1, 'foo', 2 );
- } );
- it( 'jump over ui element if shift key is pressed', () => {
- // <container:p>fo{o}<ui:span>xxx</ui:span>bar</container:p>
- const p = new ViewContainerElement( 'p', null, [ foo, ui, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 2, foo, 3 ) ] );
- renderAndFireKeydownEvent( { shiftKey: true } );
- testUtils.checkAssertions(
- // <p>fo{o<span>xxx</span>]bar</p>
- () => check( 'foo', 2, 'P', 2 ),
- // Safari renders selection at the end of the previous text node.
- // <p>fo{o<span>xxx}</span>bar</p>
- () => check( 'foo', 2, 'xxx', 3 )
- );
- } );
- it( 'jump over ui element if selection is in multiple attribute elements', () => {
- // <container:p>
- // <attribute:i>
- // <attribute:b>fo{o}</attribute:b>
- // </attribute:i>
- // <ui:span>xxx</ui:span>
- // bar
- // </container:p>
- const b = new ViewAttribtueElement( 'b', null, foo );
- const i = new ViewAttribtueElement( 'i', null, b );
- const p = new ViewContainerElement( 'p', null, [ i, ui, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 2, foo, 3 ) ] );
- renderAndFireKeydownEvent( { shiftKey: true } );
- testUtils.checkAssertions(
- // <p><i><b>fo{o</b></i><span>xxx</span>]bar</p>
- () => check( 'foo', 2, 'P', 2 ),
- // Safari renders selection at the end of the previous text node.
- // <p><i><b>fo{o</b></i><span>xxx}</span>bar</p>
- () => check( 'foo', 2, 'xxx', 3 )
- );
- } );
- it( 'jump over empty attribute elements and ui elements if shift key is pressed', () => {
- // <container:p>
- // fo{o}
- // <attribute:b></attribute:b>
- // <ui:span>xxx</ui:span>
- // <ui:span>yyy</ui:span>
- // <attribute:b></attribute:b>
- // bar
- // </container:p>
- const b1 = new ViewAttribtueElement( 'b' );
- const b2 = new ViewAttribtueElement( 'b' );
- const p = new ViewContainerElement( 'p', null, [ foo, b1, ui, ui2, b2, bar ] );
- viewRoot.appendChildren( p );
- viewDocument.selection.setRanges( [ ViewRange.createFromParentsAndOffsets( foo, 2, foo, 3 ) ] );
- renderAndFireKeydownEvent( { shiftKey: true } );
- testUtils.checkAssertions(
- // <p>fo{o<b></b><span>xxx</span><span>yyy</span><b></b>]bar</p>
- () => check( 'foo', 2, 'P', 5 ),
- // Safari renders selection at the end of the previous text node.
- // <p>fo{o<b></b><span>xxx</span><span>yyy}</span><b></b>bar</p>
- () => check( 'foo', 2, 'yyy', 3 )
- );
- } );
- } );
- it( 'should do nothing if dom position cannot be converted to view position', () => {
- const newDiv = document.createElement( 'div' );
- const domSelection = document.getSelection();
- document.body.appendChild( newDiv );
- domSelection.collapse( newDiv, 0 );
- viewDocument.fire( 'keydown', { keyCode: keyCodes.arrowright, domTarget: viewDocument.domRoots.get( 'main' ) } );
- } );
- } );
- } );
|