diff --git a/src/view/domconverter.js b/src/view/domconverter.js
index 95058b7c7..104b32a85 100644
--- a/src/view/domconverter.js
+++ b/src/view/domconverter.js
@@ -23,6 +23,7 @@ import indexOf from '@ckeditor/ckeditor5-utils/src/dom/indexof';
import getAncestors from '@ckeditor/ckeditor5-utils/src/dom/getancestors';
import getCommonAncestor from '@ckeditor/ckeditor5-utils/src/dom/getcommonancestor';
import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
+import isElement from '@ckeditor/ckeditor5-utils/src/lib/lodash/isElement';
/**
* DomConverter is a set of tools to do transformations between DOM nodes and view nodes. It also handles
@@ -948,10 +949,11 @@ export default class DomConverter {
* Takes text data from native `Text` node and processes it to a correct {@link module:engine/view/text~Text view text node} data.
*
* Following changes are done:
+ *
* * multiple whitespaces are replaced to a single space,
- * * space at the beginning of the text node is removed, if it is a first text node in it's container
- * element or if previous text node ends by space character,
- * * space at the end of the text node is removed, if it is a last text node in it's container.
+ * * space at the beginning of a text node is removed if it is the first text node in its container
+ * element or if the previous text node ends with a space character,
+ * * space at the end of the text node is removed, if it is the last text node in its container.
*
* @param {Node} node DOM text node to process.
* @returns {String} Processed data.
@@ -970,17 +972,20 @@ export default class DomConverter {
// We're replacing 1+ (and not 2+) to also normalize singular \n\t\r characters (#822).
data = data.replace( /[ \n\t\r]{1,}/g, ' ' );
- const prevNode = this._getTouchingDomTextNode( node, false );
- const nextNode = this._getTouchingDomTextNode( node, true );
+ const prevNode = this._getTouchingInlineDomNode( node, false );
+ const nextNode = this._getTouchingInlineDomNode( node, true );
+
+ const shouldLeftTrim = this._checkShouldLeftTrimDomText( prevNode );
+ const shouldRightTrim = this._checkShouldRightTrimDomText( node, nextNode );
- // If previous dom text node does not exist or it ends by whitespace character, remove space character from the beginning
+ // If the previous dom text node does not exist or it ends by whitespace character, remove space character from the beginning
// of this text node. Such space character is treated as a whitespace.
- if ( !prevNode || /[^\S\u00A0]/.test( prevNode.data.charAt( prevNode.data.length - 1 ) ) ) {
+ if ( shouldLeftTrim ) {
data = data.replace( /^ /, '' );
}
- // If next text node does not exist remove space character from the end of this text node.
- if ( !nextNode && !startsWithFiller( node ) ) {
+ // If the next text node does not exist remove space character from the end of this text node.
+ if ( shouldRightTrim ) {
data = data.replace( / $/, '' );
}
@@ -1001,15 +1006,15 @@ export default class DomConverter {
// Then, change character that is at the beginning of the text node to space character.
// As above, that was created for rendering reasons but it's real meaning is just a space character.
// We do that replacement only if this is the first node or the previous node ends on whitespace character.
- if ( !prevNode || /[^\S\u00A0]/.test( prevNode.data.charAt( prevNode.data.length - 1 ) ) ) {
+ if ( shouldLeftTrim ) {
data = data.replace( /^\u00A0/, ' ' );
}
// Since input text data could be: `x_ _`, we would not replace the first after `x` character.
// We have to fix it. Since we already change all ` `, we will have something like this at the end of text data:
// `x_ _ _` -> `x_ `. Find at the end of string (can be followed only by spaces).
- // We do that replacement only if this is the last node or the next node starts by .
- if ( !nextNode || nextNode.data.charAt( 0 ) == '\u00A0' ) {
+ // We do that replacement only if this is the last node or the next node starts with or is a
.
+ if ( isText( nextNode ) ? nextNode.data.charAt( 0 ) == '\u00A0' : true ) {
data = data.replace( /\u00A0( *)$/, ' $1' );
}
@@ -1018,6 +1023,39 @@ export default class DomConverter {
return data;
}
+ /**
+ * Helper function which checks if a DOM text node, preceded by the given `prevNode` should
+ * be trimmed from the left side.
+ *
+ * @param {Node} prevNode
+ */
+ _checkShouldLeftTrimDomText( prevNode ) {
+ if ( !prevNode ) {
+ return true;
+ }
+
+ if ( isElement( prevNode ) ) {
+ return true;
+ }
+
+ return /[^\S\u00A0]/.test( prevNode.data.charAt( prevNode.data.length - 1 ) );
+ }
+
+ /**
+ * Helper function which checks if a DOM text node, succeeded by the given `nextNode` should
+ * be trimmed from the right side.
+ *
+ * @param {Node} node
+ * @param {Node} prevNode
+ */
+ _checkShouldRightTrimDomText( node, nextNode ) {
+ if ( nextNode ) {
+ return false;
+ }
+
+ return !startsWithFiller( node );
+ }
+
/**
* Helper function. For given {@link module:engine/view/text~Text view text node}, it finds previous or next sibling
* that is contained in the same container element. If there is no such sibling, `null` is returned.
@@ -1033,12 +1071,17 @@ export default class DomConverter {
} );
for ( const value of treeWalker ) {
+ // ViewContainerElement is found on a way to next ViewText node, so given `node` was first/last
+ // text node in its container element.
if ( value.item.is( 'containerElement' ) ) {
- // ViewContainerElement is found on a way to next ViewText node, so given `node` was first/last
- // text node in its container element.
return null;
- } else if ( value.item.is( 'textProxy' ) ) {
- // Found a text node in the same container element.
+ }
+ //
found – it works like a block boundary, so do not scan further.
+ else if ( value.item.is( 'br' ) ) {
+ return null;
+ }
+ // Found a text node in the same container element.
+ else if ( value.item.is( 'textProxy' ) ) {
return value.item;
}
}
@@ -1047,15 +1090,27 @@ export default class DomConverter {
}
/**
- * Helper function. For given `Text` node, it finds previous or next sibling that is contained in the same block element.
- * If there is no such sibling, `null` is returned.
+ * Helper function. For the given text node, it finds the closest touching node which is either
+ * a text node or a `
`. The search is terminated at block element boundaries and if a matching node
+ * wasn't found so far, `null` is returned.
+ *
+ * In the following DOM structure:
+ *
+ *
foobar
bom
+ *
+ * * `foo` doesn't have its previous touching inline node (`null` is returned),
+ * * `foo`'s next touching inline node is `bar`
+ * * `bar`'s next touching inline node is `
`
+ *
+ * This method returns text nodes and `
` elements because these types of nodes affect how
+ * spaces in the given text node need to be converted.
*
* @private
* @param {Text} node
* @param {Boolean} getNext
- * @returns {Text|null}
+ * @returns {Text|Element|null}
*/
- _getTouchingDomTextNode( node, getNext ) {
+ _getTouchingInlineDomNode( node, getNext ) {
if ( !node.parentNode ) {
return null;
}
@@ -1064,7 +1119,19 @@ export default class DomConverter {
const document = node.ownerDocument;
const topmostParent = getAncestors( node )[ 0 ];
- const treeWalker = document.createTreeWalker( topmostParent, NodeFilter.SHOW_TEXT );
+ const treeWalker = document.createTreeWalker( topmostParent, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, {
+ acceptNode( node ) {
+ if ( isText( node ) ) {
+ return NodeFilter.FILTER_ACCEPT;
+ }
+
+ if ( node.tagName == 'BR' ) {
+ return NodeFilter.FILTER_ACCEPT;
+ }
+
+ return NodeFilter.FILTER_SKIP;
+ }
+ } );
treeWalker.currentNode = node;
diff --git a/tests/view/domconverter/dom-to-view.js b/tests/view/domconverter/dom-to-view.js
index d01ec1df9..c3bf0cd76 100644
--- a/tests/view/domconverter/dom-to-view.js
+++ b/tests/view/domconverter/dom-to-view.js
@@ -316,6 +316,77 @@ describe( 'DomConverter', () => {
expect( viewDiv.getChild( 0 ).getChild( 0 ).data ).to.equal( 'foo' );
} );
+ it( 'after a
', () => {
+ const domP = createElement( document, 'p', {}, [
+ document.createTextNode( 'foo' ),
+ createElement( document, 'br' ),
+ document.createTextNode( ' bar' )
+ ] );
+
+ const viewP = converter.domToView( domP );
+
+ expect( viewP.childCount ).to.equal( 3 );
+ expect( viewP.getChild( 2 ).data ).to.equal( 'bar' );
+ } );
+
+ it( 'after a
– two spaces', () => {
+ const domP = createElement( document, 'p', {}, [
+ document.createTextNode( 'foo' ),
+ createElement( document, 'br' ),
+ document.createTextNode( ' \u00a0bar' )
+ ] );
+
+ const viewP = converter.domToView( domP );
+
+ expect( viewP.childCount ).to.equal( 3 );
+ expect( viewP.getChild( 2 ).data ).to.equal( ' bar' );
+ } );
+
+ // This TC ensures that the algorithm stops on
.
+ // If not, situations like https://github.com/ckeditor/ckeditor5/issues/1024#issuecomment-393109558 might occur.
+ it( 'after a
– when
is preceeded with a nbsp', () => {
+ const domP = createElement( document, 'p', {}, [
+ document.createTextNode( 'foo\u00a0' ),
+ createElement( document, 'br' ),
+ document.createTextNode( ' bar' )
+ ] );
+
+ const viewP = converter.domToView( domP );
+
+ expect( viewP.childCount ).to.equal( 3 );
+ expect( viewP.getChild( 2 ).data ).to.equal( 'bar' );
+ } );
+
+ it( 'after a
– when text after that
is nested', () => {
+ const domP = createElement( document, 'p', {}, [
+ document.createTextNode( 'foo' ),
+ createElement( document, 'br' ),
+ createElement( document, 'b', {}, [
+ document.createTextNode( ' bar' )
+ ] )
+ ] );
+
+ const viewP = converter.domToView( domP );
+
+ expect( viewP.childCount ).to.equal( 3 );
+ expect( viewP.getChild( 2 ).getChild( 0 ).data ).to.equal( 'bar' );
+ } );
+
+ it( 'between
s - trim only the left boundary', () => {
+ const domP = createElement( document, 'p', {}, [
+ document.createTextNode( 'x' ),
+ createElement( document, 'br' ),
+ document.createTextNode( ' foo ' ),
+ createElement( document, 'br' ),
+ document.createTextNode( 'x' )
+ ] );
+
+ const viewP = converter.domToView( domP );
+
+ expect( viewP.childCount ).to.equal( 5 );
+ expect( viewP.getChild( 2 ).data ).to.equal( 'foo ' );
+ } );
+
it( 'multiple consecutive whitespaces changed to one', () => {
const domDiv = createElement( document, 'div', {}, [
createElement( document, 'p', {}, [
@@ -521,6 +592,57 @@ describe( 'DomConverter', () => {
expect( viewDiv.getChild( 0 ).getChild( 1 ).getChild( 0 ).data ).to.equal( '\u00a0' );
} );
+ // While we render `X
X`, `X
X` is ok too – the space needs to be preserved.
+ it( 'not before a
', () => {
+ const domP = createElement( document, 'p', {}, [
+ document.createTextNode( 'foo ' ),
+ createElement( document, 'br' )
+ ] );
+
+ const viewP = converter.domToView( domP );
+
+ expect( viewP.childCount ).to.equal( 2 );
+ expect( viewP.getChild( 0 ).data ).to.equal( 'foo ' );
+ } );
+
+ it( 'not before a
(nbsp+space)', () => {
+ const domP = createElement( document, 'p', {}, [
+ document.createTextNode( 'foo\u00a0 ' ),
+ createElement( document, 'br' )
+ ] );
+
+ const viewP = converter.domToView( domP );
+
+ expect( viewP.childCount ).to.equal( 2 );
+ expect( viewP.getChild( 0 ).data ).to.equal( 'foo ' );
+ } );
+
+ it( 'before a
(space+space=>space)', () => {
+ const domP = createElement( document, 'p', {}, [
+ document.createTextNode( 'foo ' ),
+ createElement( document, 'br' )
+ ] );
+
+ const viewP = converter.domToView( domP );
+
+ expect( viewP.childCount ).to.equal( 2 );
+ expect( viewP.getChild( 0 ).data ).to.equal( 'foo ' );
+ } );
+
+ it( 'not before a
– when text before that
is nested', () => {
+ const domP = createElement( document, 'p', {}, [
+ createElement( document, 'b', {}, [
+ document.createTextNode( 'foo ' )
+ ] ),
+ createElement( document, 'br' )
+ ] );
+
+ const viewP = converter.domToView( domP );
+
+ expect( viewP.childCount ).to.equal( 2 );
+ expect( viewP.getChild( 0 ).getChild( 0 ).data ).to.equal( 'foo ' );
+ } );
+
//
// See also whitespace-handling-integration.js.
//
diff --git a/tests/view/domconverter/view-to-dom.js b/tests/view/domconverter/view-to-dom.js
index 3933ae17d..3f41b81d4 100644
--- a/tests/view/domconverter/view-to-dom.js
+++ b/tests/view/domconverter/view-to-dom.js
@@ -10,6 +10,7 @@ import ViewElement from '../../../src/view/element';
import ViewPosition from '../../../src/view/position';
import ViewContainerElement from '../../../src/view/containerelement';
import ViewAttributeElement from '../../../src/view/attributeelement';
+import ViewEmptyElement from '../../../src/view/emptyelement';
import DomConverter from '../../../src/view/domconverter';
import ViewDocumentFragment from '../../../src/view/documentfragment';
import { INLINE_FILLER, INLINE_FILLER_LENGTH, isBlockFiller } from '../../../src/view/filler';
@@ -248,12 +249,16 @@ describe( 'DomConverter', () => {
}
const domElement = converter.viewToDom( viewElement, document );
- const data = domElement.innerHTML.replace( / /g, '_' );
+ const data = showNbsp( domElement.innerHTML );
expect( data ).to.equal( output );
} );
}
+ function showNbsp( html ) {
+ return html.replace( / /g, '_' );
+ }
+
// At the beginning.
test( ' x', '_x' );
test( ' x', '_ x' );
@@ -400,19 +405,213 @@ describe( 'DomConverter', () => {
test( [ ' ', ' ' ], '_ _ __' );
it( 'not in preformatted blocks', () => {
- const viewDiv = new ViewContainerElement( 'pre', null, [ new ViewText( ' foo ' ), new ViewText( ' bar ' ) ] );
- const domDiv = converter.viewToDom( viewDiv, document );
+ const viewPre = new ViewContainerElement( 'pre', null, [ new ViewText( ' foo ' ), new ViewText( ' bar ' ) ] );
+ const domPre = converter.viewToDom( viewPre, document );
- expect( domDiv.innerHTML ).to.equal( ' foo bar ' );
+ expect( domPre.innerHTML ).to.equal( ' foo bar ' );
} );
- it( 'text node before in a preformatted node', () => {
- const viewCode = new ViewAttributeElement( 'pre', null, new ViewText( 'foo ' ) );
- const viewDiv = new ViewContainerElement( 'div', null, [ viewCode, new ViewText( ' bar' ) ] );
+ it( 'not in a preformatted block followed by a text', () => {
+ const viewPre = new ViewAttributeElement( 'pre', null, new ViewText( 'foo ' ) );
+ const viewDiv = new ViewContainerElement( 'div', null, [ viewPre, new ViewText( ' bar' ) ] );
const domDiv = converter.viewToDom( viewDiv, document );
expect( domDiv.innerHTML ).to.equal( 'foo
bar' );
} );
+
+ describe( 'around
s', () => {
+ it( 'before
– a single space', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( 'foo ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'bar' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( 'foo_
bar' );
+ } );
+
+ it( 'before
– two spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( 'foo ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'bar' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( 'foo _
bar' );
+ } );
+
+ it( 'before
– three spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( 'foo ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'bar' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( 'foo __
bar' );
+ } );
+
+ it( 'before
– only a space', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( ' ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'bar' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( '_
bar' );
+ } );
+
+ it( 'before
– only two spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( ' ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'bar' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( '__
bar' );
+ } );
+
+ it( 'before
– only three spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( ' ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'bar' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( '_ _
bar' );
+ } );
+
+ it( 'after
– a single space', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( 'foo' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' bar' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( 'foo
_bar' );
+ } );
+
+ it( 'after
– two spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( 'foo' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' bar' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( 'foo
_ bar' );
+ } );
+
+ it( 'after
– three spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( 'foo' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' bar' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( 'foo
_ _bar' );
+ } );
+
+ it( 'after
– only a space', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( 'foo' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' ' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( 'foo
_' );
+ } );
+
+ it( 'after
– only two spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( 'foo' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' ' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( 'foo
__' );
+ } );
+
+ it( 'after
– only three spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewText( 'foo' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' ' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( 'foo
_ _' );
+ } );
+
+ it( 'between
s – a single space', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'foo' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( '
_
foo' );
+ } );
+
+ it( 'between
s – only two spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'foo' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( '
__
foo' );
+ } );
+
+ it( 'between
s – only three spaces', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'foo' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( '
_ _
foo' );
+ } );
+
+ it( 'between
s – space and text', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewEmptyElement( 'br' ),
+ new ViewText( ' foo' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'foo' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( '
_foo
foo' );
+ } );
+
+ it( 'between
s – text and space', () => {
+ const viewDiv = new ViewContainerElement( 'div', null, [
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'foo ' ),
+ new ViewEmptyElement( 'br' ),
+ new ViewText( 'foo' )
+ ] );
+ const domDiv = converter.viewToDom( viewDiv, document );
+
+ expect( showNbsp( domDiv.innerHTML ) ).to.equal( '
foo_
foo' );
+ } );
+ } );
} );
} );
diff --git a/tests/view/domconverter/whitespace-handling-integration.js b/tests/view/domconverter/whitespace-handling-integration.js
index e29ce0400..47fe30e35 100644
--- a/tests/view/domconverter/whitespace-handling-integration.js
+++ b/tests/view/domconverter/whitespace-handling-integration.js
@@ -5,14 +5,19 @@
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
import { getData } from '../../../src/dev-utils/model';
+// NOTE:
+// dev utils' setData() loses white spaces so don't use it for tests here!!!
+// https://github.com/ckeditor/ckeditor5-engine/issues/1428
+
describe( 'DomConverter – whitespace handling – integration', () => {
let editor;
// See https://github.com/ckeditor/ckeditor5-engine/issues/822.
- describe( 'data loading', () => {
+ describe( 'normalizing whitespaces around block boundaries (#822)', () => {
beforeEach( () => {
return VirtualTestEditor
.create( { plugins: [ Paragraph ] } )
@@ -145,4 +150,137 @@ describe( 'DomConverter – whitespace handling – integration', () => {
expect( editor.getData() ).to.equal( 'foo
bar
' );
} );
} );
+
+ // https://github.com/ckeditor/ckeditor5/issues/1024
+ describe( 'whitespaces around
s', () => {
+ beforeEach( () => {
+ return VirtualTestEditor
+ .create( { plugins: [ Paragraph, ShiftEnter ] } )
+ .then( newEditor => {
+ editor = newEditor;
+ } );
+ } );
+
+ afterEach( () => {
+ return editor.destroy();
+ } );
+
+ it( 'single spaces around
', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'single spaces around
(normalization)', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'two spaces before a
', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'two spaces before a
(normalization)', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'two spaces before a
(normalization to a model nbsp)', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo\u00a0 bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'single space after a
', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'single space after a
(normalization)', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foobar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'two spaces after a
', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'two spaces after a
(normalization)', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'two spaces after a
(normalization to a model nbsp)', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo \u00a0bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ // https://github.com/ckeditor/ckeditor5-engine/issues/1429
+ // it( 'space between
s', () => {
+ // editor.setData( 'foo
bar
' );
+
+ // expect( getData( editor.model, { withoutSelection: true } ) )
+ // .to.equal( 'foo bar' );
+
+ // expect( editor.getData() ).to.equal( 'foo
bar
' );
+ // } );
+
+ it( 'space between
s (normalization)', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foobar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+
+ it( 'two spaces between
s', () => {
+ editor.setData( 'foo
bar
' );
+
+ expect( getData( editor.model, { withoutSelection: true } ) )
+ .to.equal( 'foo bar' );
+
+ expect( editor.getData() ).to.equal( 'foo
bar
' );
+ } );
+ } );
} );