diff --git a/packages/blocks/src/api/raw-handling/html-to-blocks.js b/packages/blocks/src/api/raw-handling/html-to-blocks.js
index 18630a9abdce4..1ee2bdc263126 100644
--- a/packages/blocks/src/api/raw-handling/html-to-blocks.js
+++ b/packages/blocks/src/api/raw-handling/html-to-blocks.js
@@ -1,7 +1,13 @@
+/**
+ * WordPress dependencies
+ */
+import { Platform } from '@wordpress/element';
+
/**
* Internal dependencies
*/
import { createBlock, findTransform } from '../factory';
+import parse from '../parser';
import { getBlockAttributes } from '../parser/get-block-attributes';
import { getRawTransforms } from './get-raw-transforms';
@@ -28,6 +34,13 @@ export function htmlToBlocks( html, handler ) {
);
if ( ! rawTransform ) {
+ // Until the HTML block is supported in the native version, we'll parse it
+ // instead of creating the block to generate it as an unsupported block.
+ if ( Platform.isNative ) {
+ return parse(
+ `${ node.outerHTML }`
+ );
+ }
return createBlock(
// Should not be hardcoded.
'core/html',
diff --git a/test/native/integration/__snapshots__/blocks-raw-handling.native.js.snap b/test/native/integration/__snapshots__/blocks-raw-handling.native.js.snap
new file mode 100644
index 0000000000000..75d8caebbe31e
--- /dev/null
+++ b/test/native/integration/__snapshots__/blocks-raw-handling.native.js.snap
@@ -0,0 +1,212 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+/*
+exports[`Blocks raw handling pasteHandler apple 1`] = `"This is a title
This is a heading
This is a paragraph with a link.
A
Bulleted
Indented
List
One
Two
Three
One
Two
Three
1
2
3
I
II
III
An image:
"`;
+
+exports[`Blocks raw handling pasteHandler classic 1`] = `"First paragraph
Second paragraph
Third paragraph
Fourth paragraph
Fifth paragraph
Sixth paragraph"`;
+
+exports[`Blocks raw handling pasteHandler evernote 1`] = `"This is a paragraph.
This is a link.
An
Unordered
Indented
List
One
Two
Indented
Three
One
Two
Three
Four
Five
Six
"`;
+
+exports[`Blocks raw handling pasteHandler google-docs 1`] = `"This is a title
This is a heading
Formatting test: bold, italic, link, strikethrough, superscript, subscript, nested.
A
Bulleted
Indented
List
One
Two
Three
One
Two
Three
1
2
3
I
II
III
An image:
"`;
+
+exports[`Blocks raw handling pasteHandler google-docs-list-only 1`] = `"My first list item
A sub list item
A second sub list item
My second list item
My third list item"`;
+
+exports[`Blocks raw handling pasteHandler google-docs-table 1`] = `"
One
Two
Three
1
2
3
I
II
III"`;
+
+exports[`Blocks raw handling pasteHandler google-docs-table-with-colspan 1`] = `"
Test colspan
"`;
+
+exports[`Blocks raw handling pasteHandler google-docs-table-with-comments 1`] = `"
One
Two
Three
1
2
3
I
II
III"`;
+
+exports[`Blocks raw handling pasteHandler google-docs-table-with-rowspan 1`] = `"
Test rowspan
"`;
+
+exports[`Blocks raw handling pasteHandler google-docs-with-comments 1`] = `"This is a title
This is a heading
Formatting test: bold, italic, link, strikethrough, superscript, subscript, nested.
A
Bulleted
Indented
List
One
Two
Three
One
Two
Three
1
2
3
I
II
III
An image:
"`;
+*/
+
+exports[`Blocks raw handling pasteHandler gutenberg 1`] = `"Test"`;
+
+exports[`Blocks raw handling pasteHandler iframe-embed 1`] = `""`;
+
+/*
+exports[`Blocks raw handling pasteHandler markdown 1`] = `"This is a heading with italic
This is a paragraph with a link, bold, and strikethrough.
Preserve
line breaks please.
Lists
A
Bulleted Indented
List
One
Two
Three
Table
First Header
Second Header
Content from cell 1
Content from cell 2
Content in the first column
Content in the second column
Table with empty cells.
Quote
First
Second
Code
Inline code
tags should work.This is a code block.
"`;
+
+exports[`Blocks raw handling pasteHandler ms-word 1`] = `"This is a title
This is a subtitle
This is a heading level 1
This is a heading level 2
This is a paragraph with a link.
A
Bulleted
Indented
List
One
Two
Three
One
Two
Three
1
2
3
I
II
III
An image:
This is an anchor link that leads to the next paragraph.
This is the paragraph with the anchor.
This is an anchor link that leads nowhere.
This is a paragraph with an anchor with no link pointing to it.
This is a reference to a footnote[1].
This is a reference to an endnote[i].
[1] This is a footnote.
[i] This is an endnote."`;
+
+exports[`Blocks raw handling pasteHandler ms-word-list 1`] = `"This is a headline?
This is a text:
One
Two
Three
Lorem Ipsum.
"`;
+
+exports[`Blocks raw handling pasteHandler ms-word-online 1`] = `"This is a heading
This is a paragraph with a link.
A
Bulleted
Indented
List
One
Two
Three
One
Two
Three
1
2
3
I
II
III
An image:
"`;
+
+exports[`Blocks raw handling pasteHandler ms-word-styled 1`] = `"
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque aliquet hendrerit auctor. Nam lobortis, est vel lacinia tincidunt, purus tellus vehicula ex, nec pharetra justo dui sed lorem. Nam congue laoreet massa, quis varius est tincidunt ut."`;
+
+exports[`Blocks raw handling pasteHandler nested-divs 1`] = `"First paragraph
Second paragraph
Third paragraph
Fourth paragraph
Fifth paragraph
Sixth paragraph"`;
+
+exports[`Blocks raw handling pasteHandler one-image 1`] = `""`;
+
+exports[`Blocks raw handling pasteHandler plain 1`] = `"test
test
test"`;
+
+exports[`Blocks raw handling pasteHandler shortcode-matching 1`] = `"[gallery ids="40,41,42"]
[gallery ids="1000"]
[gallery ids="42"]"`;
+*/
+
+exports[`Blocks raw handling pasteHandler should remove extra blank lines 1`] = `
+"
+
1
+ + + +2
+" +`; + +exports[`Blocks raw handling pasteHandler should strip HTML formatting space from inline text 1`] = `"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent a elit eget tortor molestie egestas. Donec pretium urna vitae mattis imperdiet. Praesent et lorem iaculis, volutpat odio vitae, ornare lacus. Donec ut felis tristique, pharetra erat id, viverra justo. Integer sit amet elementum arcu, eget pharetra felis. In malesuada enim est, sed placerat nulla feugiat at. Vestibulum feugiat vitae elit sit amet tincidunt. Pellentesque finibus sed dolor non facilisis. Curabitur accumsan ante ac hendrerit vestibulum."`; + +exports[`Blocks raw handling pasteHandler should strip some text-level elements 1`] = ` +" +This is ncorect
+" +`; + +exports[`Blocks raw handling pasteHandler should strip windows data 1`] = ` +" +Paragraph Win
+" +`; + +/* +exports[`Blocks raw handling pasteHandler slack-paragraphs 1`] = `"test with link++" +`; + +exports[`rawHandler should convert HTML post to blocks with minimal content changes 1`] = ` +" +chicken
+ + + +ribs
+
This is a paragraph.
+ + + +Preserve me!
+ + + +++ + + +Text.
+
++" +`; + +exports[`rawHandler should convert a caption shortcode 1`] = ` +" +Heading
+ + + +Text.
+
Hello world!
This is ncorect
+" +`; + +exports[`rawHandler should preserve alignment 1`] = ` +" +center
+" +`; diff --git a/test/native/integration/blocks-raw-handling.native.js b/test/native/integration/blocks-raw-handling.native.js new file mode 100644 index 0000000000000..5f21ca035fbf9 --- /dev/null +++ b/test/native/integration/blocks-raw-handling.native.js @@ -0,0 +1,587 @@ +/** + * External dependencies + */ +import fs from 'fs'; +import path from 'path'; + +/** + * WordPress dependencies + */ +import { + createBlock, + getBlockContent, + pasteHandler, + rawHandler, + registerBlockType, + serialize, +} from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; + +function readFile( filePath ) { + return fs.existsSync( filePath ) + ? fs.readFileSync( filePath, 'utf8' ).trim() + : ''; +} + +// Path to the fixtures provided in `gutenberg/test/integration`. +const fixturesPath = `${ __dirname }/../../integration`; + +// NOTE: This file is a clone of the same `blocks-raw-handling.js` file located in +// `gutenberg/test/integration`. The reason for the separation is that several of +// the test cases fail in the native version. For now, we are going to skip them, but +// we'd need to work on them in the future. +// +// Once all issues in tests are addressed, we'll remove this file in favor of the +// original one. +describe( 'Blocks raw handling', () => { + beforeAll( () => { + // Load all hooks that modify blocks. + require( '../../../packages/editor/src/hooks' ); + registerCoreBlocks(); + registerBlockType( 'test/gallery', { + title: 'Test Gallery', + category: 'text', + attributes: { + ids: { + type: 'array', + default: [], + }, + }, + transforms: { + from: [ + { + type: 'shortcode', + tag: 'gallery', + isMatch( { named: { ids } } ) { + return ids.indexOf( 42 ) > -1; + }, + attributes: { + ids: { + type: 'array', + shortcode: ( { named: { ids } } ) => + ids + .split( ',' ) + .map( ( id ) => parseInt( id, 10 ) ), + }, + }, + priority: 9, + }, + ], + }, + save: () => null, + } ); + + registerBlockType( 'test/non-inline-block', { + title: 'Test Non Inline Block', + category: 'text', + supports: { + pasteTextInline: false, + }, + transforms: { + from: [ + { + type: 'raw', + isMatch: ( node ) => { + return ( + 'words to live by' === node.textContent.trim() + ); + }, + transform: () => { + return createBlock( 'core/embed', { + url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', + } ); + }, + }, + ], + }, + save: () => null, + } ); + + registerBlockType( 'test/transform-to-multiple-blocks', { + title: 'Test Transform to Multiple Blocks', + category: 'text', + transforms: { + from: [ + { + type: 'raw', + isMatch: ( node ) => { + return node.textContent + .split( ' ' ) + .every( ( chunk ) => /^P\S+?/.test( chunk ) ); + }, + transform: ( node ) => { + return node.textContent + .split( ' ' ) + .map( ( chunk ) => + createBlock( 'core/paragraph', { + content: chunk.substring( 1 ), + } ) + ); + }, + }, + ], + }, + save: () => null, + } ); + } ); + + it( 'should filter inline content', () => { + const filtered = pasteHandler( { + HTML: '', + plainText: ' ', + mode: 'AUTO', + } ); + + expect( console ).toHaveLogged(); + expect( filtered ).toBe( ' ' ); + } ); + + it( 'should paste special whitespace in plain text only', () => { + const filtered = pasteHandler( { + HTML: '', + plainText: ' ', + mode: 'AUTO', + } ); + + expect( console ).toHaveLogged(); + expect( filtered ).toBe( ' ' ); + } ); + + it( 'should parse Markdown', () => { + const filtered = pasteHandler( { + HTML: '* one
Some bold text.
', + mode: 'AUTO', + } ); + + expect( filtered ).toBe( 'Some bold text.' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should parse Markdown with HTML', () => { + const filtered = pasteHandler( { + HTML: '', + plainText: '# Some heading\n\nA paragraph.', + mode: 'AUTO', + } ) + .map( getBlockContent ) + .join( '' ); + + expect( filtered ).toBe( + 'A paragraph.
' + ); + expect( console ).toHaveLogged(); + } ); + + it.skip( 'should break up forced inline content', () => { + const filtered = pasteHandler( { + HTML: 'test
test
', + mode: 'INLINE', + } ); + + expect( filtered ).toBe( 'testwords to live by
', + plainText: 'words to live by\n', + mode: 'AUTO', + } ); + + expect( filtered ).toHaveLength( 1 ); + expect( filtered[ 0 ].name ).toBe( 'core/embed' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should treat single heading as inline text', () => { + const filtered = pasteHandler( { + HTML: '', + mode: 'AUTO', + } ) + ); + + expect( filtered ).toMatchSnapshot(); + expect( console ).toHaveLogged(); + } ); + + it( 'should paste gutenberg content from plain text', () => { + const block = ''; + expect( + serialize( + pasteHandler( { + plainText: block, + mode: 'AUTO', + } ) + ) + ).toBe( block ); + } ); + + it.skip( 'should handle transforms that return an array of blocks', () => { + const transformed = pasteHandler( { + HTML: 'chicken
ribs
P1 P2
', + plainText: 'P1 P2\n', + } ) + .map( getBlockContent ) + .join( '' ); + + expect( transformed ).toBe( '1
2
' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should convert pre', () => { + const transformed = pasteHandler( { + HTML: '1\n2', + plainText: '1\n2', + } ) + .map( getBlockContent ) + .join( '' ); + + expect( transformed ).toBe( + '
1\n2' + ); + expect( console ).toHaveLogged(); + } ); + + it( 'should convert code', () => { + const transformed = pasteHandler( { + HTML: '
1\n2
',
+ plainText: '1\n2',
+ } )
+ .map( getBlockContent )
+ .join( '' );
+
+ expect( transformed ).toBe(
+ '1\n2
'
+ );
+ expect( console ).toHaveLogged();
+ } );
+
+ describe( 'pasteHandler', () => {
+ // TODO: The cases commented should be eventually addressed and restored.
+ [
+ // 'plain',
+ // 'classic',
+ // 'nested-divs',
+ // 'apple',
+ // 'google-docs',
+ // 'google-docs-list-only',
+ // 'google-docs-table',
+ // 'google-docs-table-with-colspan',
+ // 'google-docs-table-with-rowspan',
+ // 'google-docs-table-with-comments',
+ // 'google-docs-with-comments',
+ // 'ms-word',
+ // 'ms-word-list',
+ // 'ms-word-styled',
+ // 'ms-word-online',
+ // 'evernote',
+ 'iframe-embed',
+ // 'one-image',
+ // 'two-images',
+ // 'markdown',
+ // 'wordpress',
+ 'gutenberg',
+ // 'shortcode-matching',
+ // 'slack-quote',
+ // 'slack-paragraphs',
+ ].forEach( ( type ) => {
+ // eslint-disable-next-line jest/valid-title
+ it( type, () => {
+ const HTML = readFile(
+ path.join(
+ fixturesPath,
+ `fixtures/documents/${ type }-in.html`
+ )
+ );
+ const plainText = readFile(
+ path.join(
+ fixturesPath,
+ `fixtures/documents/${ type }-in.txt`
+ )
+ );
+ const output = readFile(
+ path.join(
+ fixturesPath,
+ `fixtures/documents/${ type }-out.html`
+ )
+ );
+
+ if ( ! ( HTML || plainText ) || ! output ) {
+ throw new Error( `Expected fixtures for type ${ type }` );
+ }
+
+ const converted = pasteHandler( { HTML, plainText } );
+ const serialized =
+ typeof converted === 'string'
+ ? converted
+ : serialize( converted );
+
+ expect( serialized ).toBe( output );
+
+ const convertedInline = pasteHandler( {
+ HTML,
+ plainText,
+ mode: 'INLINE',
+ } );
+
+ expect( convertedInline ).toMatchSnapshot();
+ expect( console ).toHaveLogged();
+ } );
+ } );
+
+ it( 'should strip some text-level elements', () => {
+ const HTML = 'This is ncorect
'; + expect( serialize( pasteHandler( { HTML } ) ) ).toMatchSnapshot(); + expect( console ).toHaveLogged(); + } ); + + it( 'should remove extra blank lines', () => { + const HTML = readFile( + path.join( + fixturesPath, + 'fixtures/documents/google-docs-blank-lines.html' + ) + ); + expect( serialize( pasteHandler( { HTML } ) ) ).toMatchSnapshot(); + expect( console ).toHaveLogged(); + } ); + + it( 'should strip windows data', () => { + const HTML = readFile( + path.join( fixturesPath, 'fixtures/documents/windows.html' ) + ); + expect( serialize( pasteHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it.skip( 'should strip HTML formatting space from inline text', () => { + const HTML = readFile( + path.join( + fixturesPath, + 'fixtures/documents/inline-with-html-formatting-space.html' + ) + ); + expect( pasteHandler( { HTML } ) ).toMatchSnapshot(); + expect( console ).toHaveLogged(); + } ); + } ); +} ); + +describe( 'rawHandler', () => { + it.skip( 'should convert HTML post to blocks with minimal content changes', () => { + const HTML = readFile( + path.join( + fixturesPath, + 'fixtures/documents/wordpress-convert.html' + ) + ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it.skip( 'should convert a caption shortcode', () => { + const HTML = readFile( + path.join( + fixturesPath, + 'fixtures/documents/shortcode-caption.html' + ) + ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it.skip( 'should convert a caption shortcode with link', () => { + const HTML = readFile( + path.join( + fixturesPath, + 'fixtures/documents/shortcode-caption-with-link.html' + ) + ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it.skip( 'should convert a caption shortcode with caption', () => { + const HTML = readFile( + path.join( + fixturesPath, + 'fixtures/documents/shortcode-caption-with-caption-link.html' + ) + ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it.skip( 'should convert a list with attributes', () => { + const HTML = readFile( + path.join( + fixturesPath, + 'fixtures/documents/list-with-attributes.html' + ) + ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it.skip( 'should not strip any text-level elements', () => { + const HTML = 'This is ncorect
'; + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it.skip( 'should preserve alignment', () => { + const HTML = 'center
'; + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + // This is an extra test added to cover the case fixed in: + // `rnmobile/fix/div-tag-convert-to-blocks`. + it( 'should convert to unsupported HTML block when no transformation is available', () => { + const HTML = 'Hello world!