diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js
index 6eb76ddafb3193..e7bf658f683d73 100644
--- a/packages/editor/src/components/rich-text/index.js
+++ b/packages/editor/src/components/rich-text/index.js
@@ -592,6 +592,8 @@ export class RichText extends Component {
// If we click shift+Enter on inline RichTexts, we avoid creating two contenteditables
// We also split the content and call the onSplit prop if provided.
if ( keyCode === ENTER ) {
+ event.preventDefault();
+
if ( this.props.onReplace ) {
const text = getTextContent( this.getRecord() );
const transformation = findTransform( this.enterPatterns, ( item ) => {
@@ -603,7 +605,6 @@ export class RichText extends Component {
// important that we stop other handlers (e.g. ones
// registered by TinyMCE) from also handling this event.
event.stopImmediatePropagation();
- event.preventDefault();
this.props.onReplace( [
transformation.transform( { content: text } ),
] );
@@ -612,27 +613,33 @@ export class RichText extends Component {
}
if ( this.props.multiline ) {
- if ( ! this.props.onSplit ) {
- return;
- }
-
const record = this.getRecord();
- if ( ! isEmptyLine( record ) ) {
- return;
+ if ( this.props.onSplit && isEmptyLine( record ) ) {
+ this.props.onSplit( ...split( record ).map( this.valueToFormat ) );
+ } else {
+ // Character is used to separate lines in multiline values.
+ this.onChange( insert( record, '\u2028' ) );
+ }
+ } else if ( event.shiftKey || ! this.props.onSplit ) {
+ const record = this.getRecord();
+ const text = getTextContent( record );
+ const length = text.length;
+ let toInsert = '\n';
+
+ // If the caret is at the end of the text, and there is no
+ // trailing line break or no text at all, we have to insert two
+ // line breaks in order to create a new line visually and place
+ // the caret there.
+ if ( record.end === length && (
+ text.charAt( length - 1 ) !== '\n' || length === 0
+ ) ) {
+ toInsert = '\n\n';
}
- event.preventDefault();
-
- this.props.onSplit( ...split( record ).map( this.valueToFormat ) );
+ this.onChange( insert( this.getRecord(), toInsert ) );
} else {
- event.preventDefault();
-
- if ( event.shiftKey || ! this.props.onSplit ) {
- this.editor.execCommand( 'InsertLineBreak', false, event );
- } else {
- this.splitContent();
- }
+ this.splitContent();
}
}
}
diff --git a/packages/rich-text/src/create-element.js b/packages/rich-text/src/create-element.js
new file mode 100644
index 00000000000000..0e2f6f3572fdf8
--- /dev/null
+++ b/packages/rich-text/src/create-element.js
@@ -0,0 +1,13 @@
+/**
+ * Parse the given HTML into a body element.
+ *
+ * @param {HTMLDocument} document The HTML document to use to parse.
+ * @param {string} html The HTML to parse.
+ *
+ * @return {HTMLBodyElement} Body element with parsed HTML.
+ */
+export function createElement( { implementation }, html ) {
+ const { body } = implementation.createHTMLDocument( '' );
+ body.innerHTML = html;
+ return body;
+}
diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js
index 5048f355bfeca4..8998ca7199f2a5 100644
--- a/packages/rich-text/src/create.js
+++ b/packages/rich-text/src/create.js
@@ -4,6 +4,7 @@
import { isEmpty } from './is-empty';
import { isFormatEqual } from './is-format-equal';
+import { createElement } from './create-element';
/**
* Browser dependencies
@@ -11,21 +12,6 @@ import { isFormatEqual } from './is-format-equal';
const { TEXT_NODE, ELEMENT_NODE } = window.Node;
-/**
- * Parse the given HTML into a body element.
- *
- * @param {string} html The HTML to parse.
- *
- * @return {HTMLBodyElement} Body element with parsed HTML.
- */
-function createElement( html ) {
- const htmlDocument = document.implementation.createHTMLDocument( '' );
-
- htmlDocument.body.innerHTML = html;
-
- return htmlDocument.body;
-}
-
function createEmptyValue() {
return { formats: [], text: '' };
}
@@ -74,7 +60,7 @@ export function create( {
}
if ( typeof html === 'string' && html.length > 0 ) {
- element = createElement( html );
+ element = createElement( document, html );
}
if ( typeof element !== 'object' ) {
@@ -147,6 +133,12 @@ function accumulateSelection( accumulator, node, range, value ) {
node === endContainer.childNodes[ endOffset - 1 ]
) {
accumulator.end = currentLength + value.text.length;
+ // Range indicates that the selection is before the current node.
+ } else if (
+ parentNode === endContainer &&
+ node === endContainer.childNodes[ endOffset ]
+ ) {
+ accumulator.end = currentLength;
}
}
diff --git a/packages/rich-text/src/split.js b/packages/rich-text/src/split.js
index f757186b25152e..d8b40b5aafb75b 100644
--- a/packages/rich-text/src/split.js
+++ b/packages/rich-text/src/split.js
@@ -32,13 +32,13 @@ export function split( { formats, text, start, end }, string ) {
nextStart += string.length + substring.length;
if ( start !== undefined && end !== undefined ) {
- if ( start > startIndex && start < nextStart ) {
+ if ( start >= startIndex && start < nextStart ) {
value.start = start - startIndex;
} else if ( start < startIndex && end > startIndex ) {
value.start = 0;
}
- if ( end > startIndex && end < nextStart ) {
+ if ( end >= startIndex && end < nextStart ) {
value.end = end - startIndex;
} else if ( start < nextStart && end > nextStart ) {
value.end = substring.length;
diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap
new file mode 100644
index 00000000000000..77cfe39a7be885
--- /dev/null
+++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap
@@ -0,0 +1,301 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`recordToDom should create a value with formatting 1`] = `
+
+
+ test
+
+
+
+`;
+
+exports[`recordToDom should create a value with formatting for split tags 1`] = `
+
+
+ test
+
+
+
+`;
+
+exports[`recordToDom should create a value with formatting with attributes 1`] = `
+
+
+ test
+
+
+
+`;
+
+exports[`recordToDom should create a value with image object 1`] = `
+
+
+
+
+`;
+
+exports[`recordToDom should create a value with image object and formatting 1`] = `
+
+
+
+
+
+
+
+`;
+
+exports[`recordToDom should create a value with image object and text after 1`] = `
+
+
+
+ te
+
+ st
+
+`;
+
+exports[`recordToDom should create a value with image object and text before 1`] = `
+
+ te
+
+ st
+
+
+
+
+
+`;
+
+exports[`recordToDom should create a value with nested formatting 1`] = `
+
+
+
+ test
+
+
+
+
+`;
+
+exports[`recordToDom should create a value without formatting 1`] = `
+
+ test
+
+`;
+
+exports[`recordToDom should create an empty value 1`] = `
+
+
+
+
+`;
+
+exports[`recordToDom should create an empty value from empty tags 1`] = `
+
+
+
+
+`;
+
+exports[`recordToDom should filter format attributes with settings 1`] = `
+
+
+ test
+
+
+
+`;
+
+exports[`recordToDom should filter text at end with settings 1`] = `
+
+ test
+
+`;
+
+exports[`recordToDom should filter text in format with settings 1`] = `
+
+
+ test
+
+
+
+`;
+
+exports[`recordToDom should filter text outside format with settings 1`] = `
+
+
+ test
+
+
+
+`;
+
+exports[`recordToDom should filter text with settings 1`] = `
+
+
+
+
+`;
+
+exports[`recordToDom should handle br 1`] = `
+
+
+
+
+
+`;
+
+exports[`recordToDom should handle br with formatting 1`] = `
+
+
+
+
+
+
+
+
+`;
+
+exports[`recordToDom should handle br with text 1`] = `
+
+ te
+
+ st
+
+`;
+
+exports[`recordToDom should handle double br 1`] = `
+
+ a
+
+
+
+ b
+
+`;
+
+exports[`recordToDom should handle multiline list value 1`] = `
+
+
+ one
+
+
+
+
+ three
+
+
+`;
+
+exports[`recordToDom should handle multiline value 1`] = `
+
+
+ one
+
+
+ two
+
+
+`;
+
+exports[`recordToDom should handle multiline value with empty 1`] = `
+
+
+ one
+
+
+
+
+
+
+`;
+
+exports[`recordToDom should handle selection before br 1`] = `
+
+ a
+
+
+
+ b
+
+`;
+
+exports[`recordToDom should ignore line breaks to format HTML 1`] = `
+
+
+
+
+`;
+
+exports[`recordToDom should preserve emoji 1`] = `
+
+ 🍒
+
+`;
+
+exports[`recordToDom should preserve emoji in formatting 1`] = `
+
+
+ 🍒
+
+
+
+`;
+
+exports[`recordToDom should remove br with settings 1`] = `
+
+
+
+
+`;
+
+exports[`recordToDom should remove with children with settings 1`] = `
+
+ two
+
+`;
+
+exports[`recordToDom should remove with settings 1`] = `
+
+
+
+
+`;
+
+exports[`recordToDom should unwrap with settings 1`] = `
+
+ te
+
+ st
+
+
+
+`;
diff --git a/packages/rich-text/src/test/create.js b/packages/rich-text/src/test/create.js
index 200f3e6470c1d7..619c38826b67dc 100644
--- a/packages/rich-text/src/test/create.js
+++ b/packages/rich-text/src/test/create.js
@@ -9,526 +9,19 @@ import { JSDOM } from 'jsdom';
*/
import { create } from '../create';
-import { getSparseArrayLength } from './helpers';
+import { createElement } from '../create-element';
+import { getSparseArrayLength, spec } from './helpers';
const { window } = new JSDOM();
const { document } = window;
-function createElement( html ) {
- const htmlDocument = document.implementation.createHTMLDocument( '' );
-
- htmlDocument.body.innerHTML = html;
-
- return htmlDocument.body;
-}
-
describe( 'create', () => {
const em = { type: 'em' };
const strong = { type: 'strong' };
- const img = { type: 'img', attributes: { src: '' }, object: true };
- const a = { type: 'a', attributes: { href: '#' } };
- const list = [ { type: 'ul' }, { type: 'li' } ];
-
- const spec = [
- {
- description: 'should create an empty value',
- html: '',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 0,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 0,
- formats: [],
- text: '',
- },
- },
- {
- description: 'should ignore line breaks to format HTML',
- html: '\n\n\r\n',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 0,
- formats: [],
- text: '',
- },
- },
- {
- description: 'should create an empty value from empty tags',
- html: '',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 0,
- formats: [],
- text: '',
- },
- },
- {
- description: 'should create a value without formatting',
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.firstChild,
- endOffset: 4,
- endContainer: element.firstChild,
- } ),
- record: {
- start: 0,
- end: 4,
- formats: [ , , , , ],
- text: 'test',
- },
- },
- {
- description: 'should preserve emoji',
- html: '🍒',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 2,
- formats: [ , , ],
- text: '🍒',
- },
- },
- {
- description: 'should preserve emoji in formatting',
- html: '🍒',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 2,
- formats: [ [ em ], [ em ] ],
- text: '🍒',
- },
- },
- {
- description: 'should create a value with formatting',
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.firstChild,
- endOffset: 1,
- endContainer: element.firstChild,
- } ),
- record: {
- start: 0,
- end: 4,
- formats: [ [ em ], [ em ], [ em ], [ em ] ],
- text: 'test',
- },
- },
- {
- description: 'should create a value with nested formatting',
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 4,
- formats: [ [ em, strong ], [ em, strong ], [ em, strong ], [ em, strong ] ],
- text: 'test',
- },
- },
- {
- description: 'should create a value with formatting for split tags',
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.querySelector( 'em' ),
- endOffset: 1,
- endContainer: element.querySelector( 'em' ),
- } ),
- record: {
- start: 0,
- end: 2,
- formats: [ [ em ], [ em ], [ em ], [ em ] ],
- text: 'test',
- },
- },
- {
- description: 'should create a value with formatting with attributes',
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 4,
- formats: [ [ a ], [ a ], [ a ], [ a ] ],
- text: 'test',
- },
- },
- {
- description: 'should create a value with image object',
- html: '
',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 0,
- formats: [ [ img ] ],
- text: '\ufffc',
- },
- },
- {
- description: 'should create a value with image object and formatting',
- html: '
',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.querySelector( 'img' ),
- endOffset: 1,
- endContainer: element.querySelector( 'img' ),
- } ),
- record: {
- start: 0,
- end: 1,
- formats: [ [ em, img ] ],
- text: '\ufffc',
- },
- },
- {
- description: 'should create a value with image object and text before',
- html: 'test
',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 2,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 5,
- formats: [ , , [ em ], [ em ], [ em, img ] ],
- text: 'test\ufffc',
- },
- },
- {
- description: 'should create a value with image object and text after',
- html: '
test',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 2,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 5,
- formats: [ [ em, img ], [ em ], [ em ], , , ],
- text: '\ufffctest',
- },
- },
- {
- description: 'should handle br',
- html: '
',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 0,
- formats: [ , ],
- text: '\n',
- },
- },
- {
- description: 'should handle br with text',
- html: 'te
st',
- createRange: ( element ) => ( {
- startOffset: 1,
- startContainer: element,
- endOffset: 2,
- endContainer: element,
- } ),
- record: {
- start: 2,
- end: 2,
- formats: [ , , , , , ],
- text: 'te\nst',
- },
- },
- {
- description: 'should handle br with formatting',
- html: '
',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 1,
- formats: [ [ em ] ],
- text: '\n',
- },
- },
- {
- description: 'should handle multiline value',
- multilineTag: 'p',
- html: 'one
two
',
- createRange: ( element ) => ( {
- startOffset: 1,
- startContainer: element.querySelector( 'p' ).firstChild,
- endOffset: 0,
- endContainer: element.lastChild,
- } ),
- record: {
- start: 1,
- end: 4,
- formats: [ , , , , , , , ],
- text: 'one\u2028two',
- },
- },
- {
- description: 'should handle multiline list value',
- multilineTag: 'li',
- html: 'onethree',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 6,
- formats: [ , , , list, list, list, , , , , , , ],
- text: 'onetwo\u2028three',
- },
- },
- {
- description: 'should handle multiline value with empty',
- multilineTag: 'p',
- html: 'one
',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.lastChild,
- endOffset: 0,
- endContainer: element.lastChild,
- } ),
- record: {
- start: 4,
- end: 4,
- formats: [ , , , , ],
- text: 'one\u2028',
- },
- },
- {
- description: 'should remove with settings',
- settings: {
- unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ),
- },
- html: '',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 0,
- formats: [],
- text: '',
- },
- },
- {
- description: 'should remove br with settings',
- settings: {
- unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ),
- },
- html: '
',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 0,
- formats: [],
- text: '',
- },
- },
- {
- description: 'should unwrap with settings',
- settings: {
- unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ),
- },
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 4,
- formats: [ , , [ em ], [ em ] ],
- text: 'test',
- },
- },
- {
- description: 'should remove with children with settings',
- settings: {
- removeNode: ( node ) => node.getAttribute( 'data-mce-bogus' ) === 'all',
- },
- html: 'onetwo',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.lastChild,
- endOffset: 1,
- endContainer: element.lastChild,
- } ),
- record: {
- start: 0,
- end: 1,
- formats: [ , , , ],
- text: 'two',
- },
- },
- {
- description: 'should filter format attributes with settings',
- settings: {
- removeAttribute: ( attribute ) => attribute.indexOf( 'data-mce-' ) === 0,
- },
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 4,
- formats: [ [ strong ], [ strong ], [ strong ], [ strong ] ],
- text: 'test',
- },
- },
- {
- description: 'should filter text with settings',
- settings: {
- filterString: ( string ) => string.replace( '\uFEFF', '' ),
- },
- html: '',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- } ),
- record: {
- start: 0,
- end: 0,
- formats: [],
- text: '',
- },
- },
- {
- description: 'should filter text at end with settings',
- settings: {
- filterString: ( string ) => string.replace( '\uFEFF', '' ),
- },
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 4,
- startContainer: element.firstChild,
- endOffset: 4,
- endContainer: element.firstChild,
- } ),
- record: {
- start: 4,
- end: 4,
- formats: [ , , , , ],
- text: 'test',
- },
- },
- {
- description: 'should filter text in format with settings',
- settings: {
- filterString: ( string ) => string.replace( '\uFEFF', '' ),
- },
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 5,
- startContainer: element.querySelector( 'em' ).firstChild,
- endOffset: 5,
- endContainer: element.querySelector( 'em' ).firstChild,
- } ),
- record: {
- start: 4,
- end: 4,
- formats: [ [ em ], [ em ], [ em ], [ em ] ],
- text: 'test',
- },
- },
- {
- description: 'should filter text outside format with settings',
- settings: {
- filterString: ( string ) => string.replace( '\uFEFF', '' ),
- },
- html: 'test',
- createRange: ( element ) => ( {
- startOffset: 1,
- startContainer: element.lastChild,
- endOffset: 1,
- endContainer: element.lastChild,
- } ),
- record: {
- start: 4,
- end: 4,
- formats: [ [ em ], [ em ], [ em ], [ em ] ],
- text: 'test',
- },
- },
- ];
spec.forEach( ( { description, multilineTag, settings, html, createRange, record } ) => {
it( description, () => {
- const element = createElement( html );
+ const element = createElement( document, html );
const range = createRange( element );
const createdRecord = create( { element, range, multilineTag, ...settings } );
const formatsLength = getSparseArrayLength( record.formats );
diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js
index 836641c8af015d..f1d5b021a34863 100644
--- a/packages/rich-text/src/test/helpers/index.js
+++ b/packages/rich-text/src/test/helpers/index.js
@@ -1,3 +1,600 @@
export function getSparseArrayLength( array ) {
return array.reduce( ( i ) => i + 1, 0 );
}
+
+const em = { type: 'em' };
+const strong = { type: 'strong' };
+const img = { type: 'img', attributes: { src: '' }, object: true };
+const a = { type: 'a', attributes: { href: '#' } };
+const list = [ { type: 'ul' }, { type: 'li' } ];
+
+export const spec = [
+ {
+ description: 'should create an empty value',
+ html: '',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 0,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 0 ],
+ record: {
+ start: 0,
+ end: 0,
+ formats: [],
+ text: '',
+ },
+ },
+ {
+ description: 'should ignore line breaks to format HTML',
+ html: '\n\n\r\n',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 0 ],
+ record: {
+ start: 0,
+ end: 0,
+ formats: [],
+ text: '',
+ },
+ },
+ {
+ description: 'should create an empty value from empty tags',
+ html: '',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 0 ],
+ record: {
+ start: 0,
+ end: 0,
+ formats: [],
+ text: '',
+ },
+ },
+ {
+ description: 'should create a value without formatting',
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element.firstChild,
+ endOffset: 4,
+ endContainer: element.firstChild,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 4 ],
+ record: {
+ start: 0,
+ end: 4,
+ formats: [ , , , , ],
+ text: 'test',
+ },
+ },
+ {
+ description: 'should preserve emoji',
+ html: '🍒',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 2 ],
+ record: {
+ start: 0,
+ end: 2,
+ formats: [ , , ],
+ text: '🍒',
+ },
+ },
+ {
+ description: 'should preserve emoji in formatting',
+ html: '🍒',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0, 0 ],
+ endPath: [ 0, 0, 2 ],
+ record: {
+ start: 0,
+ end: 2,
+ formats: [ [ em ], [ em ] ],
+ text: '🍒',
+ },
+ },
+ {
+ description: 'should create a value with formatting',
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element.firstChild,
+ endOffset: 1,
+ endContainer: element.firstChild,
+ } ),
+ startPath: [ 0, 0, 0 ],
+ endPath: [ 0, 0, 4 ],
+ record: {
+ start: 0,
+ end: 4,
+ formats: [ [ em ], [ em ], [ em ], [ em ] ],
+ text: 'test',
+ },
+ },
+ {
+ description: 'should create a value with nested formatting',
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0, 0, 0 ],
+ endPath: [ 0, 0, 0, 4 ],
+ record: {
+ start: 0,
+ end: 4,
+ formats: [ [ em, strong ], [ em, strong ], [ em, strong ], [ em, strong ] ],
+ text: 'test',
+ },
+ },
+ {
+ description: 'should create a value with formatting for split tags',
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element.querySelector( 'em' ),
+ endOffset: 1,
+ endContainer: element.querySelector( 'em' ),
+ } ),
+ startPath: [ 0, 0, 0 ],
+ endPath: [ 0, 0, 2 ],
+ record: {
+ start: 0,
+ end: 2,
+ formats: [ [ em ], [ em ], [ em ], [ em ] ],
+ text: 'test',
+ },
+ },
+ {
+ description: 'should create a value with formatting with attributes',
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0, 0 ],
+ endPath: [ 0, 0, 4 ],
+ record: {
+ start: 0,
+ end: 4,
+ formats: [ [ a ], [ a ], [ a ], [ a ] ],
+ text: 'test',
+ },
+ },
+ {
+ description: 'should create a value with image object',
+ html: '
',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 1, 0 ],
+ endPath: [ 1, 0 ],
+ record: {
+ start: 0,
+ end: 0,
+ formats: [ [ img ] ],
+ text: '\ufffc',
+ },
+ },
+ {
+ description: 'should create a value with image object and formatting',
+ html: '
',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element.querySelector( 'img' ),
+ endOffset: 1,
+ endContainer: element.querySelector( 'img' ),
+ } ),
+ startPath: [ 0, 1, 0 ],
+ endPath: [ 0, 1, 0 ],
+ record: {
+ start: 0,
+ end: 1,
+ formats: [ [ em, img ] ],
+ text: '\ufffc',
+ },
+ },
+ {
+ description: 'should create a value with image object and text before',
+ html: 'test
',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 2,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 1, 2, 0 ],
+ record: {
+ start: 0,
+ end: 5,
+ formats: [ , , [ em ], [ em ], [ em, img ] ],
+ text: 'test\ufffc',
+ },
+ },
+ {
+ description: 'should create a value with image object and text after',
+ html: '
test',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 2,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 1, 0 ],
+ endPath: [ 1, 2 ],
+ record: {
+ start: 0,
+ end: 5,
+ formats: [ [ em, img ], [ em ], [ em ], , , ],
+ text: '\ufffctest',
+ },
+ },
+ {
+ description: 'should handle br',
+ html: '
',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 0 ],
+ record: {
+ start: 0,
+ end: 0,
+ formats: [ , ],
+ text: '\n',
+ },
+ },
+ {
+ description: 'should handle br with text',
+ html: 'te
st',
+ createRange: ( element ) => ( {
+ startOffset: 1,
+ startContainer: element,
+ endOffset: 2,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 2 ],
+ endPath: [ 2, 0 ],
+ record: {
+ start: 2,
+ end: 3,
+ formats: [ , , , , , ],
+ text: 'te\nst',
+ },
+ },
+ {
+ description: 'should handle br with formatting',
+ html: '
',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0, 0 ],
+ endPath: [ 0, 2, 0 ],
+ record: {
+ start: 0,
+ end: 1,
+ formats: [ [ em ] ],
+ text: '\n',
+ },
+ },
+ {
+ description: 'should handle double br',
+ html: 'a
b',
+ createRange: ( element ) => ( {
+ startOffset: 2,
+ startContainer: element,
+ endOffset: 3,
+ endContainer: element,
+ } ),
+ startPath: [ 2, 0 ],
+ endPath: [ 4, 0 ],
+ record: {
+ formats: [ , , , , ],
+ text: 'a\n\nb',
+ start: 2,
+ end: 3,
+ },
+ },
+ {
+ description: 'should handle selection before br',
+ html: 'a
b',
+ createRange: ( element ) => ( {
+ startOffset: 2,
+ startContainer: element,
+ endOffset: 2,
+ endContainer: element,
+ } ),
+ startPath: [ 2, 0 ],
+ endPath: [ 2, 0 ],
+ record: {
+ formats: [ , , , , ],
+ text: 'a\n\nb',
+ start: 2,
+ end: 2,
+ },
+ },
+ {
+ description: 'should handle multiline value',
+ multilineTag: 'p',
+ html: 'one
two
',
+ createRange: ( element ) => ( {
+ startOffset: 1,
+ startContainer: element.querySelector( 'p' ).firstChild,
+ endOffset: 0,
+ endContainer: element.lastChild,
+ } ),
+ startPath: [ 0, 0, 1 ],
+ endPath: [ 1, 0, 0 ],
+ record: {
+ start: 1,
+ end: 4,
+ formats: [ , , , , , , , ],
+ text: 'one\u2028two',
+ },
+ },
+ {
+ description: 'should handle multiline list value',
+ multilineTag: 'li',
+ html: 'onethree',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0, 0 ],
+ endPath: [ 1, 0, 0 ],
+ record: {
+ start: 0,
+ end: 7,
+ formats: [ , , , list, list, list, , , , , , , ],
+ text: 'onetwo\u2028three',
+ },
+ },
+ {
+ description: 'should handle multiline value with empty',
+ multilineTag: 'p',
+ html: 'one
',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element.lastChild,
+ endOffset: 0,
+ endContainer: element.lastChild,
+ } ),
+ startPath: [ 1, 0, 0 ],
+ endPath: [ 1, 0, 0 ],
+ record: {
+ start: 4,
+ end: 4,
+ formats: [ , , , , ],
+ text: 'one\u2028',
+ },
+ },
+ {
+ description: 'should remove with settings',
+ settings: {
+ unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ),
+ },
+ html: '',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 0 ],
+ record: {
+ start: 0,
+ end: 0,
+ formats: [],
+ text: '',
+ },
+ },
+ {
+ description: 'should remove br with settings',
+ settings: {
+ unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ),
+ },
+ html: '
',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 0 ],
+ record: {
+ start: 0,
+ end: 0,
+ formats: [],
+ text: '',
+ },
+ },
+ {
+ description: 'should unwrap with settings',
+ settings: {
+ unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ),
+ },
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 1, 0, 2 ],
+ record: {
+ start: 0,
+ end: 4,
+ formats: [ , , [ em ], [ em ] ],
+ text: 'test',
+ },
+ },
+ {
+ description: 'should remove with children with settings',
+ settings: {
+ removeNode: ( node ) => node.getAttribute( 'data-mce-bogus' ) === 'all',
+ },
+ html: 'onetwo',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element.lastChild,
+ endOffset: 1,
+ endContainer: element.lastChild,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 1 ],
+ record: {
+ start: 0,
+ end: 1,
+ formats: [ , , , ],
+ text: 'two',
+ },
+ },
+ {
+ description: 'should filter format attributes with settings',
+ settings: {
+ removeAttribute: ( attribute ) => attribute.indexOf( 'data-mce-' ) === 0,
+ },
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0, 0 ],
+ endPath: [ 0, 0, 4 ],
+ record: {
+ start: 0,
+ end: 4,
+ formats: [ [ strong ], [ strong ], [ strong ], [ strong ] ],
+ text: 'test',
+ },
+ },
+ {
+ description: 'should filter text with settings',
+ settings: {
+ filterString: ( string ) => string.replace( '\uFEFF', '' ),
+ },
+ html: '',
+ createRange: ( element ) => ( {
+ startOffset: 0,
+ startContainer: element,
+ endOffset: 1,
+ endContainer: element,
+ } ),
+ startPath: [ 0, 0 ],
+ endPath: [ 0, 0 ],
+ record: {
+ start: 0,
+ end: 0,
+ formats: [],
+ text: '',
+ },
+ },
+ {
+ description: 'should filter text at end with settings',
+ settings: {
+ filterString: ( string ) => string.replace( '\uFEFF', '' ),
+ },
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 4,
+ startContainer: element.firstChild,
+ endOffset: 4,
+ endContainer: element.firstChild,
+ } ),
+ startPath: [ 0, 4 ],
+ endPath: [ 0, 4 ],
+ record: {
+ start: 4,
+ end: 4,
+ formats: [ , , , , ],
+ text: 'test',
+ },
+ },
+ {
+ description: 'should filter text in format with settings',
+ settings: {
+ filterString: ( string ) => string.replace( '\uFEFF', '' ),
+ },
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 5,
+ startContainer: element.querySelector( 'em' ).firstChild,
+ endOffset: 5,
+ endContainer: element.querySelector( 'em' ).firstChild,
+ } ),
+ startPath: [ 0, 0, 4 ],
+ endPath: [ 0, 0, 4 ],
+ record: {
+ start: 4,
+ end: 4,
+ formats: [ [ em ], [ em ], [ em ], [ em ] ],
+ text: 'test',
+ },
+ },
+ {
+ description: 'should filter text outside format with settings',
+ settings: {
+ filterString: ( string ) => string.replace( '\uFEFF', '' ),
+ },
+ html: 'test',
+ createRange: ( element ) => ( {
+ startOffset: 1,
+ startContainer: element.lastChild,
+ endOffset: 1,
+ endContainer: element.lastChild,
+ } ),
+ startPath: [ 0, 0, 4 ],
+ endPath: [ 0, 0, 4 ],
+ record: {
+ start: 4,
+ end: 4,
+ formats: [ [ em ], [ em ], [ em ], [ em ] ],
+ text: 'test',
+ },
+ },
+];
diff --git a/packages/rich-text/src/test/to-dom.js b/packages/rich-text/src/test/to-dom.js
index b641085831980f..f68a3dda498980 100644
--- a/packages/rich-text/src/test/to-dom.js
+++ b/packages/rich-text/src/test/to-dom.js
@@ -8,149 +8,19 @@ import { JSDOM } from 'jsdom';
* Internal dependencies
*/
-import { create } from '../create';
import { toDom, applyValue } from '../to-dom';
+import { createElement } from '../create-element';
+import { spec } from './helpers';
const { window } = new JSDOM();
const { document } = window;
-function createNode( HTML ) {
- const doc = document.implementation.createHTMLDocument( '' );
- doc.body.innerHTML = HTML;
- return doc.body.firstChild;
-}
-
-function createElement( html ) {
- const htmlDocument = document.implementation.createHTMLDocument( '' );
- htmlDocument.body.innerHTML = html;
- return htmlDocument.body;
-}
-
describe( 'recordToDom', () => {
- it( 'should extract recreate HTML 1', () => {
- const HTML = 'one two 🍒
three
';
- const element = createNode( `${ HTML }
` );
- const range = {
- startOffset: 1,
- startContainer: element.querySelector( 'em' ).firstChild,
- endOffset: 1,
- endContainer: element.querySelector( 'strong' ).firstChild,
- };
- const { body, selection } = toDom( create( { element, range } ) );
-
- expect( body.innerHTML ).toEqual( element.innerHTML );
- expect( selection ).toEqual( {
- startPath: [ 1, 0, 1 ],
- endPath: [ 3, 1, 0, 1 ],
- } );
- } );
-
- it( 'should extract recreate HTML 2', () => {
- const HTML = 'one two 🍒 test
three
';
- const element = createNode( `${ HTML }
` );
- const range = {
- startOffset: 1,
- startContainer: element.querySelector( 'em' ).firstChild,
- endOffset: 0,
- endContainer: element.querySelector( 'strong' ).firstChild,
- };
- const { body, selection } = toDom( create( { element, range } ) );
-
- expect( body.innerHTML ).toEqual( element.innerHTML );
- expect( selection ).toEqual( {
- startPath: [ 1, 0, 1 ],
- endPath: [ 3, 2, 0 ],
- } );
- } );
-
- it( 'should extract recreate HTML 3', () => {
- const HTML = '
';
- const element = createNode( `${ HTML }
` );
- const range = {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element,
- };
- const { body, selection } = toDom( create( { element, range } ) );
-
- expect( body.innerHTML ).toEqual( element.innerHTML );
- expect( selection ).toEqual( {
- startPath: [],
- endPath: [],
- } );
- } );
-
- it( 'should extract recreate HTML 4', () => {
- const HTML = 'two 🍒';
- const element = createNode( `${ HTML }
` );
- const range = {
- startOffset: 1,
- startContainer: element.querySelector( 'em' ).firstChild,
- endOffset: 2,
- endContainer: element.querySelector( 'em' ).firstChild,
- };
- const { body, selection } = toDom( create( { element, range } ) );
-
- expect( body.innerHTML ).toEqual( element.innerHTML );
- expect( selection ).toEqual( {
- startPath: [ 0, 0, 1 ],
- endPath: [ 0, 0, 2 ],
- } );
- } );
-
- it( 'should extract recreate HTML 5', () => {
- const HTML = 'If you want to learn more about how to build additional blocks, or if you are interested in helping with the project, head over to the GitHub repository.';
- const element = createNode( `${ HTML }
` );
- const range = {
- startOffset: 1,
- startContainer: element.querySelector( 'em' ).firstChild,
- endOffset: 0,
- endContainer: element.querySelector( 'a' ).firstChild,
- };
- const { body, selection } = toDom( create( { element, range } ) );
-
- expect( body.innerHTML ).toEqual( element.innerHTML );
- expect( selection ).toEqual( {
- startPath: [ 0, 0, 1 ],
- endPath: [ 0, 0, 135 ],
- } );
- } );
-
- it( 'should create correct selection path ', () => {
- const HTML = 'test italic';
- const element = createNode( `${ HTML }
` );
- const range = {
- startOffset: 1,
- startContainer: element,
- endOffset: 2,
- endContainer: element,
- };
- const { body, selection } = toDom( create( { element, range } ) );
-
- expect( body.innerHTML ).toEqual( element.innerHTML );
- expect( selection ).toEqual( {
- startPath: [ 0, 5 ],
- endPath: [ 1, 0, 6 ],
- } );
- } );
-
- it( 'should extract recreate HTML 6', () => {
- const HTML = 'onethree';
- const element = createNode( `` );
- const range = {
- startOffset: 1,
- startContainer: element.querySelector( 'li' ).firstChild,
- endOffset: 2,
- endContainer: element.querySelector( 'li' ).firstChild,
- };
- const multilineTag = 'li';
- const { body, selection } = toDom( create( { element, range, multilineTag } ), 'li' );
-
- expect( body.innerHTML ).toEqual( element.innerHTML );
- expect( selection ).toEqual( {
- startPath: [ 0, 0, 1 ],
- endPath: [ 0, 0, 2 ],
+ spec.forEach( ( { description, multilineTag, record, startPath, endPath } ) => {
+ it( description, () => {
+ const { body, selection } = toDom( record, multilineTag );
+ expect( body ).toMatchSnapshot();
+ expect( selection ).toEqual( { startPath, endPath } );
} );
} );
} );
@@ -179,8 +49,8 @@ describe( 'applyValue', () => {
cases.forEach( ( { current, future, description, movedCount } ) => {
it( description, () => {
- const body = createElement( current );
- const futureBody = createElement( future );
+ const body = createElement( document, current );
+ const futureBody = createElement( document, future );
const childNodes = Array.from( futureBody.childNodes );
applyValue( futureBody, body );
const count = childNodes.reduce( ( acc, { parentNode } ) => {
diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js
index ec8c5a610b4323..018869337c533c 100644
--- a/packages/rich-text/src/to-dom.js
+++ b/packages/rich-text/src/to-dom.js
@@ -137,6 +137,11 @@ export function toDom( value, multilineTag ) {
endPath = [ multilineIndex, ...endPath ];
}
},
+ onEmpty( body ) {
+ const br = body.ownerDocument.createElement( 'br' );
+ br.setAttribute( 'data-mce-bogus', '1' );
+ body.appendChild( br );
+ },
} );
return {
diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js
index 8f228654c74bbb..c1ced44a16c6b1 100644
--- a/packages/rich-text/src/to-tree.js
+++ b/packages/rich-text/src/to-tree.js
@@ -33,6 +33,7 @@ export function toTree( value, multilineTag, settings ) {
appendText,
onStartIndex,
onEndIndex,
+ onEmpty,
} = settings;
const { formats, text, start, end } = value;
const formatsLength = formats.length + 1;
@@ -70,9 +71,21 @@ export function toTree( value, multilineTag, settings ) {
} );
}
+ // If there is selection at 0, handle it before characters are inserted.
+
+ if ( onStartIndex && start === 0 && i === 0 ) {
+ onStartIndex( tree, pointer, multilineIndex );
+ }
+
+ if ( onEndIndex && end === 0 && i === 0 ) {
+ onEndIndex( tree, pointer, multilineIndex );
+ }
+
if ( character !== '\ufffc' ) {
if ( character === '\n' ) {
pointer = append( getParent( pointer ), { type: 'br', object: true } );
+ // Ensure pointer is text node.
+ pointer = append( getParent( pointer ), '' );
} else if ( ! isText( pointer ) ) {
pointer = append( getParent( pointer ), character );
} else {
@@ -89,5 +102,9 @@ export function toTree( value, multilineTag, settings ) {
}
}
+ if ( onEmpty && text.length === 0 ) {
+ onEmpty( tree );
+ }
+
return tree;
}
diff --git a/test/e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap b/test/e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap
index 7ceacf3ab3ee69..a0cc53a6c18113 100644
--- a/test/e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap
+++ b/test/e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap
@@ -2,12 +2,12 @@
exports[`Deprecated Node Matcher should insert block with children source 1`] = `
"
-test
test
+test
a
"
`;
exports[`Deprecated Node Matcher should insert block with node source 1`] = `
"
-test
+test
"
`;
diff --git a/test/e2e/specs/__snapshots__/writing-flow.test.js.snap b/test/e2e/specs/__snapshots__/writing-flow.test.js.snap
index 70d32a6440c4b9..ee6bcfc536604e 100644
--- a/test/e2e/specs/__snapshots__/writing-flow.test.js.snap
+++ b/test/e2e/specs/__snapshots__/writing-flow.test.js.snap
@@ -32,6 +32,36 @@ exports[`adding blocks should clean TinyMCE content 2`] = `
"
`;
+exports[`adding blocks should insert line break at end 1`] = `
+"
+a
+"
+`;
+
+exports[`adding blocks should insert line break at end and continue writing 1`] = `
+"
+a
b
+"
+`;
+
+exports[`adding blocks should insert line break at start 1`] = `
+"
+
a
+"
+`;
+
+exports[`adding blocks should insert line break in empty container 1`] = `
+"
+
+"
+`;
+
+exports[`adding blocks should insert line break mid text 1`] = `
+"
+a
b
+"
+`;
+
exports[`adding blocks should navigate around inline boundaries 1`] = `
"
FirstAfter
diff --git a/test/e2e/specs/deprecated-node-matcher.test.js b/test/e2e/specs/deprecated-node-matcher.test.js
index b78de5e4e61f5a..e9de62e3e207bb 100644
--- a/test/e2e/specs/deprecated-node-matcher.test.js
+++ b/test/e2e/specs/deprecated-node-matcher.test.js
@@ -35,8 +35,11 @@ describe( 'Deprecated Node Matcher', () => {
await insertBlock( 'Deprecated Children Matcher' );
await page.keyboard.type( 'test' );
await page.keyboard.press( 'Enter' );
+ await page.keyboard.type( 'a' );
+ await page.keyboard.down( 'Shift' );
+ await page.keyboard.press( 'ArrowLeft' );
+ await page.keyboard.up( 'Shift' );
await pressWithModifier( META_KEY, 'b' );
- await page.keyboard.type( 'test' );
expect( console ).toHaveWarned();
expect( await getEditedPostContent() ).toMatchSnapshot();
} );
diff --git a/test/e2e/specs/writing-flow.test.js b/test/e2e/specs/writing-flow.test.js
index dfc6bf625c7a6d..b5a088d112f860 100644
--- a/test/e2e/specs/writing-flow.test.js
+++ b/test/e2e/specs/writing-flow.test.js
@@ -160,4 +160,41 @@ describe( 'adding blocks', () => {
await page.keyboard.type( 'Inside' );
expect( await getEditedPostContent() ).toMatchSnapshot();
} );
+
+ it( 'should insert line break at end', async () => {
+ await clickBlockAppender();
+ await page.keyboard.type( 'a' );
+ await pressWithModifier( 'Shift', 'Enter' );
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+ } );
+
+ it( 'should insert line break at end and continue writing', async () => {
+ await clickBlockAppender();
+ await page.keyboard.type( 'a' );
+ await pressWithModifier( 'Shift', 'Enter' );
+ await page.keyboard.type( 'b' );
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+ } );
+
+ it( 'should insert line break mid text', async () => {
+ await clickBlockAppender();
+ await page.keyboard.type( 'ab' );
+ await page.keyboard.press( 'ArrowLeft' );
+ await pressWithModifier( 'Shift', 'Enter' );
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+ } );
+
+ it( 'should insert line break at start', async () => {
+ await clickBlockAppender();
+ await page.keyboard.type( 'a' );
+ await page.keyboard.press( 'ArrowLeft' );
+ await pressWithModifier( 'Shift', 'Enter' );
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+ } );
+
+ it( 'should insert line break in empty container', async () => {
+ await clickBlockAppender();
+ await pressWithModifier( 'Shift', 'Enter' );
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+ } );
} );