Skip to content

Commit

Permalink
[Mobile] - Add E2E tests for the Drag & Drop blocks feature (#41368)
Browse files Browse the repository at this point in the history
* Mobile - Add E2E tests for the Drag & Drop blocks feature

* Update longPress action

* Use clickIfClickable

* Use longPress instead of press

* Fix clipboard typo

* Add setClipboard and clearClipboard helpers
  • Loading branch information
Gerardo Pacheco authored May 31, 2022
1 parent 29a170b commit c032635
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* Internal dependencies
*/
import { blockNames } from './pages/editor-page';
import {
clearClipboard,
clickElementOutsideOfTextInput,
dragAndDropAfterElement,
isAndroid,
setClipboard,
tapPasteAboveElement,
} from './helpers/utils';
import testData from './helpers/test-data';

describe( 'Gutenberg Editor Drag & Drop blocks tests', () => {
beforeEach( async () => {
await clearClipboard( editorPage.driver );
} );

it( 'should be able to drag & drop a block', async () => {
// Initialize the editor with a Spacer and Paragraph block
await editorPage.setHtmlContent(
[ testData.spacerBlock, testData.paragraphBlockShortText ].join(
'\n\n'
)
);

// Get elements for both blocks
const spacerBlock = await editorPage.getBlockAtPosition(
blockNames.spacer
);
const paragraphBlock = await editorPage.getParagraphBlockWrapperAtPosition(
2
);

// Drag & drop the Spacer block after the Paragraph block
await dragAndDropAfterElement(
editorPage.driver,
spacerBlock,
paragraphBlock
);

// Get the first block, in this case the Paragraph block
// and check the text value is the expected one
const firstBlockText = await editorPage.getTextForParagraphBlockAtPosition(
1
);
expect( firstBlockText ).toMatch( testData.shortText );

// Remove the blocks
await spacerBlock.click();
await editorPage.removeBlockAtPosition( blockNames.spacer, 2 );
await editorPage.removeBlockAtPosition( blockNames.paragraph, 1 );
} );

it( 'should be able to long-press on a text-based block to paste a text in a focused textinput', async () => {
// Add a Paragraph block
await editorPage.addNewBlock( blockNames.paragraph );
const paragraphBlockElement = await editorPage.getTextBlockAtPosition(
blockNames.paragraph
);

// Set clipboard text
await setClipboard( editorPage.driver, testData.shortText );

// Dismiss auto-suggestion popup
if ( isAndroid() ) {
// On Andrdoid 10 a new auto-suggestion popup is appearing to let the user paste text recently put in the clipboard. Let's dismiss it.
await editorPage.dismissAndroidClipboardSmartSuggestion();
}

// Paste into the Paragraph block
await tapPasteAboveElement( editorPage.driver, paragraphBlockElement );
const paragraphText = await editorPage.getTextForParagraphBlockAtPosition(
1
);

// Expect to have the pasted text in the Paragraph block
expect( paragraphText ).toMatch( testData.shortText );

// Remove the block
await editorPage.removeBlockAtPosition( blockNames.paragraph );
} );

it( 'should be able to long-press on a text-based block using the PlainText component to paste a text in a focused textinput', async () => {
// Add a Shortcode block
await editorPage.addNewBlock( blockNames.shortcode );
const shortcodeBlockElement = await editorPage.getShortBlockTextInputAtPosition(
blockNames.shortcode
);

// Set clipboard text
await setClipboard( editorPage.driver, testData.shortText );

// Dismiss auto-suggestion popup
if ( isAndroid() ) {
// On Andrdoid 10 a new auto-suggestion popup is appearing to let the user paste text recently put in the clipboard. Let's dismiss it.
await editorPage.dismissAndroidClipboardSmartSuggestion();
}

// Paste into the Shortcode block
await tapPasteAboveElement( editorPage.driver, shortcodeBlockElement );
const shortcodeText = await shortcodeBlockElement.text();

// Expect to have the pasted text in the Shortcode block
expect( shortcodeText ).toMatch( testData.shortText );

// Remove the block
await editorPage.removeBlockAtPosition( blockNames.shortcode );
} );

it( 'should be able to drag & drop a text-based block when the textinput is not focused', async () => {
// Initialize the editor with two Paragraph blocks
await editorPage.setHtmlContent(
[
testData.paragraphBlockShortText,
testData.paragraphBlockEmpty,
].join( '\n\n' )
);

// Get elements for both blocks
const firstParagraphBlock = await editorPage.getParagraphBlockWrapperAtPosition(
1
);
const secondParagraphBlock = await editorPage.getParagraphBlockWrapperAtPosition(
2
);

// Tap on the first Paragraph block outside of the textinput
await clickElementOutsideOfTextInput(
editorPage.driver,
firstParagraphBlock
);

// Drag & drop the first Paragraph block after the second Paragraph block
await dragAndDropAfterElement(
editorPage.driver,
firstParagraphBlock,
secondParagraphBlock
);

// Get the current second Paragraph block in the editor after dragging & dropping
const secondBlockText = await editorPage.getTextForParagraphBlockAtPosition(
2
);

// Expect the second Paragraph block to have the expected content
expect( secondBlockText ).toMatch( testData.shortText );

// Remove the block
await editorPage.removeBlockAtPosition( blockNames.paragraph );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { blockNames } from './pages/editor-page';
import {
clearClipboard,
longPressMiddleOfElement,
tapSelectAllAboveElement,
tapCopyAboveElement,
Expand All @@ -21,7 +22,7 @@ describe( 'Gutenberg Editor paste tests', () => {
}

beforeAll( async () => {
await editorPage.driver.setClipboard( '', 'plaintext' );
await clearClipboard( editorPage.driver );
} );

it( 'copies plain text from one paragraph block and pastes in another', async () => {
Expand Down Expand Up @@ -58,10 +59,6 @@ describe( 'Gutenberg Editor paste tests', () => {
);

// Paste into second paragraph block.
await longPressMiddleOfElement(
editorPage.driver,
paragraphBlockElement2
);
await tapPasteAboveElement( editorPage.driver, paragraphBlockElement2 );

const text = await editorPage.getTextForParagraphBlockAtPosition( 2 );
Expand Down Expand Up @@ -101,10 +98,6 @@ describe( 'Gutenberg Editor paste tests', () => {
);

// Paste into second paragraph block.
await longPressMiddleOfElement(
editorPage.driver,
paragraphBlockElement2
);
await tapPasteAboveElement( editorPage.driver, paragraphBlockElement2 );

// Check styled text by verifying html contents.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ exports.paragraphBlockEmpty = `<!-- wp:paragraph -->
<p></p>
<!-- /wp:paragraph -->`;

exports.paragraphBlockShortText = `<!-- wp:paragraph -->
<p>Rock music approaches at high velocity.</p>
<!-- /wp:paragraph -->`;

exports.multiLinesParagraphBlock = `<!-- wp:paragraph -->
<p>multiple lines<br>multiple lines<br>multiple lines</p>
<!-- /wp:paragraph -->`;
Expand All @@ -177,3 +181,7 @@ exports.unknownElementParagraphBlock = `<!-- wp:paragraph -->
exports.lettersInParagraphBlock = `<!-- wp:paragraph -->
<p>ABCD</p>
<!-- /wp:paragraph -->`;

exports.spacerBlock = `<!-- wp:spacer -->
<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->`;
100 changes: 88 additions & 12 deletions packages/react-native-editor/__device-tests__/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const strToKeycode = {
[ backspace ]: 67,
};

// $block-edge-to-content value
const blockEdgeToContent = 16;

const timer = ( ms ) => new Promise( ( res ) => setTimeout( res, ms ) );

const isAndroid = () => {
Expand Down Expand Up @@ -301,18 +304,27 @@ const clickBeginningOfElement = async ( driver, element ) => {
await action.perform();
};

// Clicks in the top left of a text-based element outside of the TextInput
const clickElementOutsideOfTextInput = async ( driver, element ) => {
const location = await element.getLocation();
const y = isAndroid() ? location.y - blockEdgeToContent : location.y;
const x = isAndroid() ? location.x - blockEdgeToContent : location.x;

const action = new wd.TouchAction( driver ).press( { x, y } ).release();
await action.perform();
};

// Long press to activate context menu.
const longPressMiddleOfElement = async ( driver, element ) => {
const location = await element.getLocation();
const size = await element.getSize();

const action = await new wd.TouchAction( driver );
const x = location.x + size.width / 2;
const y = location.y + size.height / 2;
action.press( { x, y } );
// Setting to wait a bit longer because this is failing more frequently on the CI
action.wait( 5000 );
action.release();
const action = new wd.TouchAction( driver )
.longPress( { x, y } )
.wait( 5000 ) // Setting to wait a bit longer because this is failing more frequently on the CI
.release();
await action.perform();
};

Expand Down Expand Up @@ -342,13 +354,21 @@ const tapCopyAboveElement = async ( driver, element ) => {

// Press "Paste" in floating context menu.
const tapPasteAboveElement = async ( driver, element ) => {
const location = await element.getLocation();
const action = await new wd.TouchAction( driver );
action.wait( 2000 );
action.press( { x: location.x + 100, y: location.y - 50 } );
action.wait( 2000 );
action.release();
await action.perform();
await longPressMiddleOfElement( driver, element );

if ( isAndroid() ) {
const location = await element.getLocation();
const action = await new wd.TouchAction( driver );
action.wait( 2000 );
action.press( { x: location.x + 100, y: location.y - 50 } );
action.wait( 2000 );
action.release();
await action.perform();
} else {
const pasteButtonLocator = '//XCUIElementTypeMenuItem[@name="Paste"]';
await clickIfClickable( driver, pasteButtonLocator );
await driver.sleep( 3000 ); // Wait for paste notification to disappear.
}
};

// Starts from the middle of the screen or the element(if specified)
Expand Down Expand Up @@ -413,6 +433,29 @@ const swipeDown = async ( driver, delay = 3000 ) => {
);
};

// Drag & Drop after element
const dragAndDropAfterElement = async ( driver, element, nextElement ) => {
// Element to drag & drop
const elementLocation = await element.getLocation();
const elementSize = await element.getSize();
const x = elementLocation.x + elementSize.width / 2;
const y = elementLocation.y + elementSize.height / 2;

// Element to drag & drop to
const nextElementLocation = await nextElement.getLocation();
const nextElementSize = await nextElement.getSize();
const nextYPosition = isAndroid()
? elementLocation.y + nextElementLocation.y + nextElementSize.height
: nextElementLocation.y + nextElementSize.height;

const action = new wd.TouchAction( driver )
.press( { x, y } )
.wait( 5000 )
.moveTo( { x, y: nextYPosition } )
.release();
await action.perform();
};

const toggleHtmlMode = async ( driver, toggleOn ) => {
if ( isAndroid() ) {
// Hit the "Menu" key.
Expand Down Expand Up @@ -575,17 +618,50 @@ const waitIfAndroid = async () => {
}
};

/**
* Content type definitions.
* Note: Android only supports plaintext.
*
* @typedef {"plaintext" | "image" | "url"} ClipboardContentType
*/

/**
* Helper to set content in the clipboard.
*
* @param {Object} driver Driver
* @param {string} content Content to set in the clipboard
* @param {ClipboardContentType} contentType Type of the content
*/
const setClipboard = async ( driver, content, contentType = 'plaintext' ) => {
const base64String = Buffer.from( content ).toString( 'base64' );
await driver.setClipboard( base64String, contentType );
};

/**
* Helper to clear the clipboard
*
* @param {Object} driver Driver
* @param {ClipboardContentType} contentType Type of the content
*/
const clearClipboard = async ( driver, contentType = 'plaintext' ) => {
await driver.setClipboard( '', contentType );
};

module.exports = {
backspace,
clearClipboard,
clickBeginningOfElement,
clickElementOutsideOfTextInput,
clickIfClickable,
clickMiddleOfElement,
doubleTap,
dragAndDropAfterElement,
isAndroid,
isEditorVisible,
isElementVisible,
isLocalEnvironment,
longPressMiddleOfElement,
setClipboard,
setupDriver,
stopDriver,
swipeDown,
Expand Down
Loading

0 comments on commit c032635

Please sign in to comment.