diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt index ce427be2ad09b0..ec847d71bf51c9 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt @@ -92,7 +92,6 @@ data class GutenbergProps @JvmOverloads constructor( content?.let { putString(PROP_INITIAL_DATA, it) } } - private const val PROP_INITIAL_TITLE = "initialTitle" private const val PROP_INITIAL_HTML_MODE_ENABLED = "initialHtmlModeEnabled" private const val PROP_POST_TYPE = "postType" private const val PROP_HOST_APP_NAMESPACE = "hostAppNamespace" @@ -105,6 +104,7 @@ data class GutenbergProps @JvmOverloads constructor( private const val PROP_QUOTE_BLOCK_V2 = "quoteBlockV2" private const val PROP_LIST_BLOCK_V2 = "listBlockV2" + const val PROP_INITIAL_TITLE = "initialTitle" const val PROP_INITIAL_DATA = "initialData" const val PROP_STYLES = "rawStyles" const val PROP_FEATURES = "rawFeatures" diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-device-actions.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-device-actions.test.js index e7ee4a20df03f2..230c844491d282 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-device-actions.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-device-actions.test.js @@ -27,6 +27,10 @@ describe( 'Gutenberg Editor Rotation tests', () => { await editorPage.addNewBlock( blockNames.paragraph ); if ( isAndroid() ) { + // Waits until the keyboard is visible + await editorPage.driver.waitUntil( + editorPage.driver.isKeyboardShown + ); await editorPage.dismissKeyboard(); } diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-heading-@canary.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-heading-@canary.test.js deleted file mode 100644 index 50a2a3ee8fd640..00000000000000 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-heading-@canary.test.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Internal dependencies - */ -import { blockNames } from './pages/editor-page'; -import testData from './helpers/test-data'; - -describe( 'Gutenberg Editor tests', () => { - it( 'should be able to create a post with heading and paragraph blocks', async () => { - await editorPage.initializeEditor(); - await editorPage.addNewBlock( blockNames.heading ); - let headingBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.heading - ); - - await editorPage.typeTextToTextBlock( - headingBlockElement, - testData.heading - ); - - await editorPage.addNewBlock( blockNames.paragraph ); - let paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph, - 2 - ); - await editorPage.typeTextToTextBlock( - paragraphBlockElement, - testData.mediumText - ); - - await editorPage.addNewBlock( blockNames.paragraph ); - paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph, - 3 - ); - await editorPage.typeTextToTextBlock( - paragraphBlockElement, - testData.mediumText - ); - - await editorPage.addNewBlock( blockNames.heading ); - headingBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.heading, - 4 - ); - await editorPage.typeTextToTextBlock( - headingBlockElement, - testData.heading - ); - - await editorPage.addNewBlock( blockNames.paragraph ); - paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph, - 5 - ); - await editorPage.typeTextToTextBlock( - paragraphBlockElement, - testData.mediumText - ); - - // Assert that even though there are 5 blocks, there should only be 3 paragraph blocks - expect( await editorPage.getNumberOfParagraphBlocks() ).toEqual( 3 ); - } ); -} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js deleted file mode 100644 index 8f21ef04858fb6..00000000000000 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Internal dependencies - */ -import { blockNames } from './pages/editor-page'; -import { - backspace, - clickMiddleOfElement, - clickBeginningOfElement, -} from './helpers/utils'; -import testData from './helpers/test-data'; - -describe( 'Gutenberg Editor tests for Paragraph Block', () => { - it( 'should be able to split one paragraph block into two', async () => { - await editorPage.initializeEditor(); - await editorPage.addNewBlock( blockNames.paragraph ); - const paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph - ); - await editorPage.typeTextToTextBlock( - paragraphBlockElement, - testData.shortText - ); - await clickMiddleOfElement( editorPage.driver, paragraphBlockElement ); - await editorPage.typeTextToTextBlock( - paragraphBlockElement, - '\n', - false - ); - const text0 = await editorPage.getTextForParagraphBlockAtPosition( 1 ); - const text1 = await editorPage.getTextForParagraphBlockAtPosition( 2 ); - expect( await editorPage.getNumberOfParagraphBlocks() ).toEqual( 2 ); - expect( text0 ).not.toBe( '' ); - expect( text1 ).not.toBe( '' ); - expect( testData.shortText ).toMatch( - new RegExp( `${ text0 + text1 }|${ text0 } ${ text1 }` ) - ); - } ); - - it( 'should be able to merge 2 paragraph blocks into 1', async () => { - await editorPage.initializeEditor(); - await editorPage.addNewBlock( blockNames.paragraph ); - let paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph - ); - - await editorPage.typeTextToTextBlock( - paragraphBlockElement, - testData.shortText - ); - await clickMiddleOfElement( editorPage.driver, paragraphBlockElement ); - await editorPage.typeTextToTextBlock( paragraphBlockElement, '\n' ); - - const text0 = await editorPage.getTextForParagraphBlockAtPosition( 1 ); - const text1 = await editorPage.getTextForParagraphBlockAtPosition( 2 ); - expect( await editorPage.getNumberOfParagraphBlocks() ).toEqual( 2 ); - paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph, - 2 - ); - - await clickBeginningOfElement( - editorPage.driver, - paragraphBlockElement - ); - - await editorPage.typeTextToTextBlock( - paragraphBlockElement, - backspace - ); - - const text = await editorPage.getTextForParagraphBlockAtPosition( 1 ); - expect( text0 + text1 ).toMatch( text ); - paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph, - 1 - ); - await paragraphBlockElement.click(); - expect( await editorPage.getNumberOfParagraphBlocks() ).toEqual( 1 ); - } ); - - it( 'should be able to create a post with multiple paragraph blocks', async () => { - await editorPage.initializeEditor(); - await editorPage.addNewBlock( blockNames.paragraph ); - await editorPage.sendTextToParagraphBlock( 1, testData.longText ); - expect( await editorPage.getNumberOfParagraphBlocks() ).toEqual( 3 ); - } ); - - it( 'should be able to merge blocks with unknown html elements', async () => { - await editorPage.initializeEditor( { - initialData: [ - testData.unknownElementParagraphBlock, - testData.lettersInParagraphBlock, - ].join( '\n\n' ), - } ); - - // Merge paragraphs. - const paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph, - 2 - ); - - const text0 = await editorPage.getTextForParagraphBlockAtPosition( 1 ); - const text1 = await editorPage.getTextForParagraphBlockAtPosition( 2 ); - - await clickBeginningOfElement( - editorPage.driver, - paragraphBlockElement - ); - await editorPage.typeTextToTextBlock( - paragraphBlockElement, - backspace - ); - - // Verify the editor has not crashed. - const mergedBlockText = - await editorPage.getTextForParagraphBlockAtPosition( 1 ); - expect( text0 + text1 ).toMatch( mergedBlockText ); - } ); - - // Based on https://github.com/wordpress-mobile/gutenberg-mobile/pull/1507 - it( 'should handle multiline paragraphs from web', async () => { - await editorPage.initializeEditor( { - initialData: [ - testData.multiLinesParagraphBlock, - testData.paragraphBlockEmpty, - ].join( '\n\n' ), - } ); - - // Merge paragraphs. - const paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph, - 2 - ); - await editorPage.typeTextToTextBlock( - paragraphBlockElement, - backspace - ); - - // Verify the editor has not crashed. - const text = await editorPage.getTextForParagraphBlockAtPosition( 1 ); - expect( text.length ).not.toEqual( 0 ); - } ); -} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-writing-flow-@canary.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-writing-flow-@canary.test.js new file mode 100644 index 00000000000000..3a12bf5d13345b --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-writing-flow-@canary.test.js @@ -0,0 +1,333 @@ +/** + * Internal dependencies + */ +import { blockNames } from './pages/editor-page'; +import { + backspace, + clickBeginningOfElement, + waitForMediaLibrary, +} from './helpers/utils'; +import testData from './helpers/test-data'; + +describe( 'Gutenberg Editor Writing flow tests', () => { + it( 'should be able to write a post title', async () => { + await editorPage.initializeEditor( { initialTitle: '' } ); + + const titleInput = await editorPage.getEmptyTitleTextInputElement(); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + await editorPage.typeTextToTextBlock( titleInput, testData.shortText ); + + // Trigger the return key to go to the first Paragraph + await editorPage.typeTextToTextBlock( titleInput, '\n' ); + + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph + ); + expect( paragraphBlockElement ).toBeTruthy(); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + + // Trigger the return key to delete the Paragraph block + await editorPage.typeTextToTextBlock( + paragraphBlockElement, + backspace + ); + // Expect to have an empty Paragraph block and the keyboard visible + expect( + await editorPage.getTextBlockAtPosition( blockNames.paragraph ) + ).toBeTruthy(); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + } ); + + it( 'should be able to create a new Paragraph block when pressing the enter key', async () => { + await editorPage.initializeEditor( { initialTitle: '' } ); + + const defaultBlockAppenderElement = + await editorPage.getDefaultBlockAppenderElement(); + await defaultBlockAppenderElement.click(); + + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph + ); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + + await editorPage.typeTextToTextBlock( + paragraphBlockElement, + testData.shortText + ); + await editorPage.typeTextToTextBlock( paragraphBlockElement, '\n' ); + + // Expect to have a new Paragraph block and the keyboard visible + expect( + await editorPage.getTextBlockAtPosition( blockNames.paragraph, 2 ) + ).toBeTruthy(); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + } ); + + it( 'should automatically dismiss the keyboard when selecting non-text-based-blocks', async () => { + await editorPage.initializeEditor( { initialTitle: '' } ); + + await editorPage.addNewBlock( blockNames.image ); + // Wait for the Media picker to show up + await waitForMediaLibrary( editorPage.driver ); + + // Select the WordPress Media Library option + await editorPage.chooseMediaLibrary(); + + // Wait until the media is added + await editorPage.driver.pause( 500 ); + + const captionElement = await editorPage.getImageBlockCaptionButton(); + await captionElement.click(); + const captionInput = + await editorPage.getImageBlockCaptionInput( captionElement ); + + expect( captionInput ).toBeTruthy(); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + + // Sets a new caption + await editorPage.typeTextToTextBlock( + captionInput, + testData.listItem2, + true + ); + + // Trigger the return key to exit the caption and create a new Paragraph block + await editorPage.typeTextToTextBlock( captionInput, '\n' ); + + // Expect to have an empty Paragraph block and the keyboard visible + let paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph, + 2 + ); + expect( paragraphBlockElement ).toBeTruthy(); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + await editorPage.typeTextToTextBlock( + paragraphBlockElement, + backspace + ); + + // When deleting the Paragraph block, the keyboard should be hidden and + // the image block should be focused. + await editorPage.driver.waitUntil( async function () { + return ! ( await editorPage.driver.isKeyboardShown() ); + } ); + expect( await editorPage.isImageBlockSelected() ).toBe( true ); + + // Adding a new Paragraph block + await editorPage.addNewBlock( blockNames.paragraph ); + paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph, + 2 + ); + + // It should be focused and the keyboard should be visible + expect( paragraphBlockElement ).toBeTruthy(); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + + await editorPage.typeTextToTextBlock( + paragraphBlockElement, + testData.shortText + ); + + const imageBlockElement = await editorPage.getBlockAtPosition( + blockNames.image + ); + await imageBlockElement.click(); + + await editorPage.driver.waitUntil( async function () { + return ! ( await editorPage.driver.isKeyboardShown() ); + } ); + expect( await editorPage.isImageBlockSelected() ).toBe( true ); + } ); + + it( 'should manually dismiss the keyboard', async () => { + await editorPage.initializeEditor( { initialTitle: '' } ); + + const defaultBlockAppenderElement = + await editorPage.getDefaultBlockAppenderElement(); + await defaultBlockAppenderElement.click(); + + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph + ); + expect( paragraphBlockElement ).toBeTruthy(); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + + await editorPage.dismissKeyboard(); + + // Checks that no block is selected by looking for the block menu actions button + expect( await editorPage.isBlockActionsMenuButtonDisplayed() ).toBe( + false + ); + expect( await editorPage.driver.isKeyboardShown() ).toBe( false ); + } ); + + it( 'should dismiss the keyboard and show it back when opening modals', async () => { + await editorPage.initializeEditor( { initialTitle: '' } ); + + const defaultBlockAppenderElement = + await editorPage.getDefaultBlockAppenderElement(); + await defaultBlockAppenderElement.click(); + + await editorPage.openBlockSettings(); + await editorPage.driver.waitUntil( async function () { + return ! ( await editorPage.driver.isKeyboardShown() ); + } ); + + await editorPage.dismissBottomSheet(); + + await editorPage.driver.waitUntil( editorPage.driver.isKeyboardShown ); + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph + ); + await editorPage.typeTextToTextBlock( + paragraphBlockElement, + testData.listItem1 + ); + const typedText = await paragraphBlockElement.getText(); + expect( typedText ).toMatch( testData.listItem1 ); + } ); + + it( 'should be able to split and merge paragraph blocks', async () => { + await editorPage.initializeEditor(); + + // Add the first Paragraph block using the default block appender + const defaultBlockAppenderElement = + await editorPage.getDefaultBlockAppenderElement(); + await defaultBlockAppenderElement.click(); + + // Type text into the first Paragraph block + const firstParagraphBlockElement = + await editorPage.getTextBlockAtPosition( blockNames.paragraph ); + await editorPage.typeTextToTextBlock( + firstParagraphBlockElement, + testData.shortText + ); + + // Add a second Paragraph block and type some text + await editorPage.addParagraphBlockByTappingEmptyAreaBelowLastBlock(); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + const secondParagraphBlockElement = + await editorPage.getTextBlockAtPosition( blockNames.paragraph, 2 ); + await editorPage.typeTextToTextBlock( + secondParagraphBlockElement, + testData.mediumText + ); + + // Merge Paragraph blocks + await clickBeginningOfElement( + editorPage.driver, + secondParagraphBlockElement + ); + await editorPage.typeTextToTextBlock( + secondParagraphBlockElement, + backspace + ); + + // Wait for blocks to be merged + await editorPage.driver.waitUntil( async function () { + return ( await editorPage.getNumberOfParagraphBlocks() ) === 1; + } ); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + + // Split the current Paragraph block right where the caret is positioned + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph, + 1, + true + ); + await editorPage.typeTextToTextBlock( paragraphBlockElement, '\n' ); + + // Wait for blocks to be split + await editorPage.driver.waitUntil( async function () { + return ( await editorPage.getNumberOfParagraphBlocks() ) === 2; + } ); + expect( await editorPage.driver.isKeyboardShown() ).toBe( true ); + + const firstParagraphText = + await editorPage.getTextForParagraphBlockAtPosition( 1 ); + const secondParagraphText = + await editorPage.getTextForParagraphBlockAtPosition( 2 ); + + expect( firstParagraphText ).toEqual( testData.shortText ); + expect( secondParagraphText ).toEqual( testData.mediumText ); + } ); + + it( 'should be able to create a post with multiple paragraph blocks', async () => { + await editorPage.initializeEditor(); + const defaultBlockAppenderElement = + await editorPage.getDefaultBlockAppenderElement(); + await defaultBlockAppenderElement.click(); + + await editorPage.sendTextToParagraphBlock( 1, testData.longText ); + expect( await editorPage.getNumberOfParagraphBlocks() ).toEqual( 3 ); + } ); + + it( 'should be able to merge blocks with unknown html elements', async () => { + await editorPage.initializeEditor( { + initialData: [ + testData.unknownElementParagraphBlock, + testData.lettersInParagraphBlock, + ].join( '\n\n' ), + } ); + + // Merge paragraphs. + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph, + 2 + ); + + const text0 = await editorPage.getTextForParagraphBlockAtPosition( 1 ); + const text1 = await editorPage.getTextForParagraphBlockAtPosition( 2 ); + + await clickBeginningOfElement( + editorPage.driver, + paragraphBlockElement + ); + await editorPage.typeTextToTextBlock( + paragraphBlockElement, + backspace + ); + + // Verify the editor has not crashed. + const mergedBlockText = + await editorPage.getTextForParagraphBlockAtPosition( 1 ); + expect( text0 + text1 ).toMatch( mergedBlockText ); + } ); + + it( 'should be able to create a post with heading and paragraph blocks', async () => { + await editorPage.initializeEditor(); + await editorPage.addNewBlock( blockNames.heading ); + const headingBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.heading + ); + + await editorPage.typeTextToTextBlock( + headingBlockElement, + testData.heading + ); + + await editorPage.addParagraphBlockByTappingEmptyAreaBelowLastBlock(); + let paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph, + 2 + ); + await editorPage.typeTextToTextBlock( + paragraphBlockElement, + testData.mediumText + ); + + await editorPage.addParagraphBlockByTappingEmptyAreaBelowLastBlock(); + paragraphBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.paragraph, + 3 + ); + await editorPage.typeTextToTextBlock( + paragraphBlockElement, + testData.mediumText + ); + + // Assert that even though there are 3 blocks, there should only be 2 paragraph blocks + expect( await editorPage.getNumberOfParagraphBlocks() ).toEqual( 2 ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/pages/editor-page.js b/packages/react-native-editor/__device-tests__/pages/editor-page.js index a19aaf5445d79f..b00be20458e802 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -45,8 +45,18 @@ class EditorPage { } } - async initializeEditor( { initialData, rawStyles, rawFeatures } = {} ) { - await launchApp( this.driver, { initialData, rawStyles, rawFeatures } ); + async initializeEditor( { + initialTitle, + initialData, + rawStyles, + rawFeatures, + } = {} ) { + await launchApp( this.driver, { + initialTitle, + initialData, + rawStyles, + rawFeatures, + } ); // Stores initial values from the editor for different helpers. const addButton = await this.driver.$$( `~${ ADD_BLOCK_ID }` ); @@ -72,9 +82,13 @@ class EditorPage { // Text blocks functions // E.g. Paragraph, Heading blocks // =============================== - async getTextBlockAtPosition( blockName, position = 1 ) { + async getTextBlockAtPosition( + blockName, + position = 1, + skipWrapperClick = false + ) { // iOS needs a click to get the text element - if ( ! isAndroid() ) { + if ( ! isAndroid() && ! skipWrapperClick ) { const textBlockLocator = `(//XCUIElementTypeButton[contains(@name, "${ blockName } Block. Row ${ position }")])`; await clickIfClickable( this.driver, textBlockLocator ); @@ -171,14 +185,25 @@ class EditorPage { } async addParagraphBlockByTappingEmptyAreaBelowLastBlock() { - const emptyAreaBelowLastBlock = - await this.driver.elementByAccessibilityId( 'Add paragraph block' ); + const element = isAndroid() + ? '~Add paragraph block' + : '(//XCUIElementTypeOther[@name="Add paragraph block"])'; + const emptyAreaBelowLastBlock = await this.driver.$( element ); await emptyAreaBelowLastBlock.click(); } - async getTitleElement( options = { autoscroll: false } ) { + async getDefaultBlockAppenderElement() { + const appenderElement = isAndroid() + ? `//android.widget.EditText[@text='Start writing…']` + : '(//XCUIElementTypeOther[contains(@name, "Start writing…")])[2]'; + return this.driver.$( appenderElement ); + } + + async getTitleElement( options = { autoscroll: false, isEmpty: false } ) { const titleElement = isAndroid() - ? 'Post title. Welcome to Gutenberg!' + ? `Post title. ${ + options.isEmpty ? 'Empty' : 'Welcome to Gutenberg!' + }` : 'post-title'; if ( options.autoscroll ) { @@ -200,6 +225,18 @@ class EditorPage { return elements[ 0 ]; } + async getEmptyTitleTextInputElement() { + const titleWrapperElement = await this.getTitleElement( { + isEmpty: true, + } ); + await titleWrapperElement.click(); + + const titleElement = isAndroid() + ? '//android.widget.EditText[@content-desc="Post title. Empty"]' + : '~Add title'; + return this.driver.$( titleElement ); + } + // iOS loads the block list more eagerly compared to Android. // This makes this function return elements without scrolling on iOS. // So we are keeping this Android only. @@ -370,10 +407,14 @@ class EditorPage { await settingsButton.click(); } - async removeBlock() { - const blockActionsButtonElement = isAndroid() + getBlockActionsMenuElement() { + return isAndroid() ? '//android.widget.Button[contains(@content-desc, "Open Block Actions Menu")]' : '//XCUIElementTypeButton[@name="Open Block Actions Menu"]'; + } + + async removeBlock() { + const blockActionsButtonElement = this.getBlockActionsMenuElement(); const blockActionsMenu = await this.swipeToolbarToElement( blockActionsButtonElement ); @@ -391,6 +432,12 @@ class EditorPage { return await swipeDown( this.driver ); } + async isBlockActionsMenuButtonDisplayed() { + const menuButtonElement = this.getBlockActionsMenuElement(); + const elementsFound = await this.driver.$$( menuButtonElement ); + return elementsFound.length !== 0; + } + // ========================= // Block toolbar functions // ========================= @@ -406,8 +453,6 @@ class EditorPage { swipeRight: true, } ); await addButton[ 0 ].click(); - // Wait for Bottom sheet animation to finish - await this.driver.pause( 3000 ); } // Click on block of choice. @@ -425,10 +470,9 @@ class EditorPage { const inserterElement = isAndroid() ? 'Blocks menu' : 'InserterUI-Blocks'; - return await this.waitForElementToBeDisplayedById( - inserterElement, - 4000 - ); + await this.driver + .$( `~${ inserterElement }` ) + .waitForDisplayed( { timeout: 4000 } ); } static async isElementOutOfBounds( element, { width, height } = {} ) { @@ -787,13 +831,25 @@ class EditorPage { await clickIfClickable( this.driver, mediaLibraryLocator ); } + async getImageBlockCaptionButton() { + const captionElement = isAndroid() + ? '//android.widget.Button[starts-with(@content-desc, "Image caption")]' + : '//XCUIElementTypeButton[starts-with(@name, "Image caption.")]'; + return this.driver.$( captionElement ); + } + + async getImageBlockCaptionInput( imageBlockCaptionButton ) { + const captionInputElement = isAndroid() + ? '//android.widget.EditText' + : '//XCUIElementTypeTextView'; + return imageBlockCaptionButton.$( captionInputElement ); + } + async enterCaptionToSelectedImageBlock( caption, clear = true ) { - const imageBlockCaptionButton = await this.driver.$( - '//XCUIElementTypeButton[starts-with(@name, "Image caption.")]' - ); + const imageBlockCaptionButton = await this.getImageBlockCaptionButton(); await imageBlockCaptionButton.click(); - const imageBlockCaptionField = await imageBlockCaptionButton.$( - '//XCUIElementTypeTextView' + const imageBlockCaptionField = await this.getImageBlockCaptionInput( + imageBlockCaptionButton ); await typeString( this.driver, imageBlockCaptionField, caption, clear ); } @@ -814,6 +870,16 @@ class EditorPage { .perform(); } + async isImageBlockSelected() { + // Since there isn't an easy way to see if a block is selected, + // it will check if the edit image button is visible + const editImageElement = isAndroid() + ? '(//android.widget.Button[@content-desc="Edit image"])' + : '(//XCUIElementTypeButton[@name="Edit image"])'; + + return await this.driver.$( editImageElement ).isDisplayed(); + } + // ============================= // Search Block functions // ============================= diff --git a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainActivity.java b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainActivity.java index a938df715ec008..3ea19fa97b3831 100644 --- a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainActivity.java +++ b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainActivity.java @@ -168,6 +168,7 @@ private Bundle getAppOptions() { Bundle bundle = new Bundle(); // Parse initial props from launch arguments + String initialTitle = null; String initialData = null; String rawStyles = null; String rawFeatures = null; @@ -177,6 +178,9 @@ private Bundle getAppOptions() { String initialProps = extrasBundle.getString(EXTRAS_INITIAL_PROPS, "{}"); try { JSONObject jsonObject = new JSONObject(initialProps); + if (jsonObject.has(GutenbergProps.PROP_INITIAL_TITLE)) { + initialTitle = jsonObject.getString(GutenbergProps.PROP_INITIAL_TITLE); + } if (jsonObject.has(GutenbergProps.PROP_INITIAL_DATA)) { initialData = jsonObject.getString(GutenbergProps.PROP_INITIAL_DATA); } @@ -211,6 +215,9 @@ private Bundle getAppOptions() { capabilities.putBoolean(GutenbergProps.PROP_CAPABILITIES_SMARTFRAME_EMBED_BLOCK, true); bundle.putBundle(GutenbergProps.PROP_CAPABILITIES, capabilities); + if(initialTitle != null) { + bundle.putString(GutenbergProps.PROP_INITIAL_TITLE, initialTitle); + } if(initialData != null) { bundle.putString(GutenbergProps.PROP_INITIAL_DATA, initialData); } diff --git a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift index ef95c7e65862f6..0c04308125df71 100644 --- a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift +++ b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift @@ -399,7 +399,10 @@ extension GutenbergViewController: GutenbergBridgeDataSource { } func gutenbergInitialTitle() -> String? { - return nil + guard isUITesting(), let initialProps = getInitialPropsFromArgs() else { + return nil + } + return initialProps["initialTitle"] } func gutenbergHostAppNamespace() -> String {