From 6ea1d57c34309c8b1c82235a7fcb95973c37a927 Mon Sep 17 00:00:00 2001 From: jhnstn Date: Wed, 11 May 2022 14:30:19 -0400 Subject: [PATCH 01/49] Release script: Update react-native-editor version to 1.76.0 --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index 702f33d04d7204..1cb0d6af9cef32 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.75.0", + "version": "1.76.0", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 4d6a09542ceefc..fa60ec4b09d7ee 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.75.0", + "version": "1.76.0", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 71074238fe315e..417f3ed91782c3 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.75.0", + "version": "1.76.0", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From bcddcbcbdb3f70fc42a58802d9634991f96023ad Mon Sep 17 00:00:00 2001 From: jhnstn Date: Wed, 11 May 2022 14:30:33 -0400 Subject: [PATCH 02/49] Release script: Update with changes from 'npm run core preios' --- .../ios/GutenbergDemo.xcodeproj/project.pbxproj | 4 ++-- packages/react-native-editor/ios/Podfile.lock | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj b/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj index 65506b8af1e287..a08280d71e173f 100644 --- a/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj +++ b/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj @@ -764,7 +764,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -808,7 +808,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 90642b3975f923..facae9f7cbf87b 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - ReactCommon/turbomodule/core (= 0.66.2) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.75.0): + - Gutenberg (1.76.0): - React-Core (= 0.66.2) - React-CoreModules (= 0.66.2) - React-RCTImage (= 0.66.2) @@ -337,7 +337,7 @@ PODS: - React-Core - RNSVG (9.13.6): - React-Core - - RNTAztecView (1.75.0): + - RNTAztecView (1.76.0): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - WordPress-Aztec-iOS (1.19.8) @@ -503,7 +503,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 18438b1c04ce502ed681cd19db3f4508964c082a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 5337263514dd6f09803962437687240c5dc39aa4 - Gutenberg: a801215930ac72f0872814bfcce347532b6c20b6 + Gutenberg: 2398cf3b4c356206c72f83c216d3d01b624cc689 RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 RCTRequired: 5e9e85f48da8dd447f5834ce14c6799ea8c7f41a RCTTypeSafety: aba333d04d88d1f954e93666a08d7ae57a87ab30 @@ -542,10 +542,10 @@ SPEC CHECKSUMS: RNReanimated: d87c75f1076bab3402d6cd0b7322be51d333d10e RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 - RNTAztecView: acf0844256727ca793923232f9eb93fc5fbb4343 + RNTAztecView: 0783a43eb5241e38587a52cb4eadc0ef2852607a WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 Yoga: 9a08effa851c1d8cc1647691895540bc168ea65f -PODFILE CHECKSUM: 563423e4045de5607d9ba4dd3a53595bb1a8c8cc +PODFILE CHECKSUM: d0a2d4714ee19a1821eb2a30029333be8d6a729f COCOAPODS: 1.10.1 From 678bd2f76732db68ed754b913c39015d44105fd2 Mon Sep 17 00:00:00 2001 From: jhnstn Date: Wed, 11 May 2022 15:13:42 -0400 Subject: [PATCH 03/49] Update Changelog --- packages/react-native-editor/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index ed8a8556a7d5eb..34b2157e09f909 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,9 @@ For each user feature we should also add a importance categorization label to i ## Unreleased + +## 1.76.0 + - [**] [Buttons block] Fix Android-only issue related to displaying formatting buttons after closing the block settings [#40725] - [**] [Cover block] Improve color contrast between background and text [#40691] - [*] [Gallery block] Fix broken "Link To" settings and add "Image Size" settings [#40947] From 21b838c24f8ed4df6f6d6e250a40180b1af9993f Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 20 May 2022 12:48:33 +0200 Subject: [PATCH 04/49] Release script: Update react-native-editor version to 1.76.1 --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index 1cb0d6af9cef32..ee0f60f4566e58 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.76.0", + "version": "1.76.1", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index fa60ec4b09d7ee..655b7f42de9574 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.76.0", + "version": "1.76.1", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 417f3ed91782c3..706c368919e23a 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.76.0", + "version": "1.76.1", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 7f79f5251cb94944f22c067d6098575022c13003 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 20 May 2022 12:48:46 +0200 Subject: [PATCH 05/49] Release script: Update with changes from 'npm run core preios' --- packages/react-native-editor/ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index facae9f7cbf87b..b2fd1726c26875 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - ReactCommon/turbomodule/core (= 0.66.2) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.76.0): + - Gutenberg (1.76.1): - React-Core (= 0.66.2) - React-CoreModules (= 0.66.2) - React-RCTImage (= 0.66.2) @@ -337,7 +337,7 @@ PODS: - React-Core - RNSVG (9.13.6): - React-Core - - RNTAztecView (1.76.0): + - RNTAztecView (1.76.1): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - WordPress-Aztec-iOS (1.19.8) @@ -503,7 +503,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 18438b1c04ce502ed681cd19db3f4508964c082a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 5337263514dd6f09803962437687240c5dc39aa4 - Gutenberg: 2398cf3b4c356206c72f83c216d3d01b624cc689 + Gutenberg: 9d1e70315e26fc3e9e8445b3f17c8ecb29f77e1b RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 RCTRequired: 5e9e85f48da8dd447f5834ce14c6799ea8c7f41a RCTTypeSafety: aba333d04d88d1f954e93666a08d7ae57a87ab30 @@ -542,7 +542,7 @@ SPEC CHECKSUMS: RNReanimated: d87c75f1076bab3402d6cd0b7322be51d333d10e RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 - RNTAztecView: 0783a43eb5241e38587a52cb4eadc0ef2852607a + RNTAztecView: def74944705c4bef636fc653d11eaa1f49b64c22 WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 Yoga: 9a08effa851c1d8cc1647691895540bc168ea65f From 981471e673ba031f057e15d0b1a93689bef88272 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Wed, 18 May 2022 10:52:53 +0200 Subject: [PATCH 06/49] [Mobile] - BlockList - Add internal onLayout from CellRendererComponent to BlockListItemCell (#41105) * Mobile - BlockList - Pass FlatList internal onLayout through CellRendererComponent * Mobile - Update onLayout naming for BlockListItemCell --- .../block-list/block-list-item-cell.native.js | 17 ++++++++++++----- .../src/components/block-list/index.native.js | 3 ++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block-list-item-cell.native.js b/packages/block-editor/src/components/block-list/block-list-item-cell.native.js index c399643a633996..e32d793af46d67 100644 --- a/packages/block-editor/src/components/block-list/block-list-item-cell.native.js +++ b/packages/block-editor/src/components/block-list/block-list-item-cell.native.js @@ -13,7 +13,7 @@ import { useEffect, useCallback } from '@wordpress/element'; */ import { useBlockListContext } from './block-list-context'; -function BlockListItemCell( { children, clientId, rootClientId } ) { +function BlockListItemCell( { children, clientId, rootClientId, onLayout } ) { const { blocksLayouts, updateBlocksLayouts } = useBlockListContext(); useEffect( () => { @@ -25,18 +25,25 @@ function BlockListItemCell( { children, clientId, rootClientId } ) { }; }, [] ); - const onLayout = useCallback( - ( { nativeEvent: { layout } } ) => { + const onCellLayout = useCallback( + ( event ) => { + const { + nativeEvent: { layout }, + } = event; updateBlocksLayouts( blocksLayouts, { clientId, rootClientId, ...layout, } ); + + if ( onLayout ) { + onLayout( event ); + } }, - [ clientId, rootClientId, updateBlocksLayouts ] + [ clientId, rootClientId, updateBlocksLayouts, onLayout ] ); - return { children }; + return { children }; } export default BlockListItemCell; diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 3ec3fefc8207b3..a4248327589ed1 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -162,12 +162,13 @@ export class BlockList extends Component { return this.extraData; } - getCellRendererComponent( { children, item } ) { + getCellRendererComponent( { children, item, onLayout } ) { const { rootClientId } = this.props; return ( ); From 0087e3d1592e4c1c991162f6a434945f09cf53af Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Wed, 18 May 2022 11:05:19 +0200 Subject: [PATCH 07/49] [Mobile] - Fix Drag & Drop Chip positioning issue with RTL languages (#41053) * Mobile - Fix - Drag & drop chip issue with RTL languages * Mobile - BlockDraggable - Rename maxWidth variable to contentWidth * Mobile - BlockDraggable - Add custom exiting animation that uses the same functionality as ZoomOutEasyDown but customizing the translateX value taking into account RTL languages. --- .../block-draggable/index.native.js | 51 +++++++++++++++++-- .../src/components/block-list/index.native.js | 7 ++- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-draggable/index.native.js b/packages/block-editor/src/components/block-draggable/index.native.js index a52396db27fc43..ec76781d449fcc 100644 --- a/packages/block-editor/src/components/block-draggable/index.native.js +++ b/packages/block-editor/src/components/block-draggable/index.native.js @@ -2,6 +2,10 @@ * External dependencies */ import { AccessibilityInfo } from 'react-native'; +import { + useSafeAreaInsets, + useSafeAreaFrame, +} from 'react-native-safe-area-context'; import Animated, { runOnJS, runOnUI, @@ -11,7 +15,6 @@ import Animated, { withDelay, withTiming, ZoomInEasyDown, - ZoomOutEasyDown, } from 'react-native-reanimated'; /** @@ -61,10 +64,11 @@ const DEFAULT_IOS_LONG_PRESS_MIN_DURATION = * * @param {Object} props Component props. * @param {JSX.Element} props.children Children to be rendered. + * @param {boolean} props.isRTL Check if current locale is RTL. * * @return {Function} Render function that passes `onScroll` event handler. */ -const BlockDraggableWrapper = ( { children } ) => { +const BlockDraggableWrapper = ( { children, isRTL } ) => { const [ draggedBlockIcon, setDraggedBlockIcon ] = useState(); const { @@ -75,6 +79,10 @@ const BlockDraggableWrapper = ( { children } ) => { const { scrollRef } = useBlockListContext(); const animatedScrollRef = useAnimatedRef(); + const { left, right } = useSafeAreaInsets(); + const { width } = useSafeAreaFrame(); + const safeAreaOffset = left + right; + const contentWidth = width - safeAreaOffset; animatedScrollRef( scrollRef ); const scroll = { @@ -198,9 +206,16 @@ const BlockDraggableWrapper = ( { children } ) => { }; const chipDynamicStyles = useAnimatedStyle( () => { + const chipOffset = chip.width.value / 2; + const translateX = ! isRTL + ? chip.x.value - chipOffset + : -( contentWidth - ( chip.x.value + chipOffset ) ); + return { transform: [ - { translateX: chip.x.value - chip.width.value / 2 }, + { + translateX, + }, { translateY: chip.y.value - @@ -215,6 +230,34 @@ const BlockDraggableWrapper = ( { children } ) => { styles[ 'draggable-chip__wrapper' ], ]; + const exitingAnimation = ( { currentHeight, currentWidth } ) => { + 'worklet'; + const translateX = ! isRTL ? 0 : currentWidth * -1; + const duration = 150; + const animations = { + transform: [ + { + translateY: withTiming( currentHeight, { + duration, + } ), + }, + { + translateX: withTiming( translateX, { + duration, + } ), + }, + { scale: withTiming( 0, { duration } ) }, + ], + }; + const initialValues = { + transform: [ { translateY: 0 }, { translateX }, { scale: 1 } ], + }; + return { + initialValues, + animations, + }; + }; + return ( <> { { draggedBlockIcon && ( diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index a4248327589ed1..788ec01b59bb61 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -190,7 +190,7 @@ export class BlockList extends Component { } render() { - const { isRootList } = this.props; + const { isRootList, isRTL } = this.props; // Use of Context to propagate the main scroll ref to its children e.g InnerBlocks. const blockList = isRootList ? ( - + { ( { onScroll } ) => this.renderList( { onScroll } ) } @@ -439,6 +439,8 @@ export default compose( [ const isFloatingToolbarVisible = !! selectedBlockClientId && hasRootInnerBlocks; + const isRTL = getSettings().isRTL; + return { blockClientIds, blockCount, @@ -449,6 +451,7 @@ export default compose( [ isFloatingToolbarVisible, isStackedHorizontally, maxWidth, + isRTL, }; } ), From 5c6fe0d99b3f2d451e9020b9bebcb951cab5507c Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 19 May 2022 18:03:00 +0200 Subject: [PATCH 08/49] [RNMobile] Add drag & drop help guide in Help & Support screen (#40961) * Add drag & drop help guide * Update content of help screen related to moving blocks * Update drand-and-drop images * Update styles of heading component of help screen * Add HelpDetailBadge component This component will be rendered in `HelpDetailSectionHeadingText` component via the `badge` prop. * Add NEW badge to move blocks help section * Optimize drag-and-drop images * Add move-blocks icon to Help & Support screen * Update react-native-editor changelog * Add HelpSectionTitle component * Prevent rendering separator on last help topic item --- .../editor-help/help-section-title.native.js | 29 ++++++++++++ .../editor-help/help-topic-row.native.js | 4 +- .../editor-help/icon-move-blocks.native.js | 10 +++++ .../editor-help/images/drag-and-drop-dark.png | Bin 0 -> 2288 bytes .../images/drag-and-drop-dark@2x.png | Bin 0 -> 6381 bytes .../images/drag-and-drop-dark@3x.png | Bin 0 -> 12629 bytes .../images/drag-and-drop-light.png | Bin 0 -> 2990 bytes .../images/drag-and-drop-light@2x.png | Bin 0 -> 8441 bytes .../images/drag-and-drop-light@3x.png | Bin 0 -> 16898 bytes .../components/editor-help/index.native.js | 42 +++++++++--------- .../editor-help/move-blocks.native.js | 24 +++++++++- .../src/components/editor-help/style.scss | 40 +++++++++++++++-- .../editor-help/view-sections.native.js | 31 +++++++++---- 13 files changed, 142 insertions(+), 38 deletions(-) create mode 100644 packages/editor/src/components/editor-help/help-section-title.native.js create mode 100644 packages/editor/src/components/editor-help/icon-move-blocks.native.js create mode 100644 packages/editor/src/components/editor-help/images/drag-and-drop-dark.png create mode 100644 packages/editor/src/components/editor-help/images/drag-and-drop-dark@2x.png create mode 100644 packages/editor/src/components/editor-help/images/drag-and-drop-dark@3x.png create mode 100644 packages/editor/src/components/editor-help/images/drag-and-drop-light.png create mode 100644 packages/editor/src/components/editor-help/images/drag-and-drop-light@2x.png create mode 100644 packages/editor/src/components/editor-help/images/drag-and-drop-light@3x.png diff --git a/packages/editor/src/components/editor-help/help-section-title.native.js b/packages/editor/src/components/editor-help/help-section-title.native.js new file mode 100644 index 00000000000000..573d48d24fd6ce --- /dev/null +++ b/packages/editor/src/components/editor-help/help-section-title.native.js @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { Text, View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +const HelpSectionTitle = ( { children } ) => { + const helpSectionTitle = usePreferredColorSchemeStyle( + styles.helpSectionTitle, + styles.helpSectionTitleDark + ); + + return ( + + { children } + + ); +}; + +export default HelpSectionTitle; diff --git a/packages/editor/src/components/editor-help/help-topic-row.native.js b/packages/editor/src/components/editor-help/help-topic-row.native.js index e04ffae4a00b59..89495fc9a601d3 100644 --- a/packages/editor/src/components/editor-help/help-topic-row.native.js +++ b/packages/editor/src/components/editor-help/help-topic-row.native.js @@ -9,7 +9,7 @@ import { useNavigation } from '@react-navigation/native'; import { TextControl, Icon } from '@wordpress/components'; import { chevronRight } from '@wordpress/icons'; -const HelpTopicRow = ( { label, icon, screenName } ) => { +const HelpTopicRow = ( { label, icon, screenName, isLastItem } ) => { const navigation = useNavigation(); const openSubSheet = () => { @@ -18,7 +18,7 @@ const HelpTopicRow = ( { label, icon, screenName } ) => { return ( + + +); diff --git a/packages/editor/src/components/editor-help/images/drag-and-drop-dark.png b/packages/editor/src/components/editor-help/images/drag-and-drop-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..47188faf0a1a446bf45483fb40546762af380a94 GIT binary patch literal 2288 zcmV4E+Zo&ARr(mB_%U6GdemtIXO8nFfb=4Cr3v|R8&+zKtL!c zC^R%QP*6}&QBh4zO-@cuH#avQA0K;rd$hE)ySuxtuCA!4sE3D#r>Cd2wY8O%m6Vi} z7<|MQiQ5;6+ZKr07l_*zh}sv3*%pV{7KhjthSwH`))$7>7KPOoh13><(G`Kt6@SZt zfq@o)%oc*q7KGCmhu9a0*cOP{7Kz#ye8d-t+7^b`6@}Foh13;-(H4Quh=_<4fXfzw z&lZEy7KhgthuIg3+!uVqVPRp8j*g+Bp_`kVTU%QzD=SG!NlHpeJv}{7Pfs;9HBwSi zFE1}eMMXF`I6pr>K0ZDr^qaT<00)#wL_t(|0qokb4FfO;ML}a=|CLiX3#2fzl)DjL z34iei0000000000004m4Sw+X~&N3f)xpddTURTu@DfAGbg?^XNLL;=$&kj15*h2R=%R98>kI$@0APDZ$IO|OjG65nd*)|I3Oz(lgA2BS(MUELdlzLaPTspf=<<}yz%w+PI6Lqr1XGY%&}eRG z6QdQ;tj!V6)(cyMWCpYNDUySFtd5X5@Vj?s{#!X%)1fA$fZS3jmnn6{!vx6gLE`I~ z-&~|Daf+603aD+DWY*7+D=Hzjo7_J}-_QMxwF)me!lBtBlUoZNwadx+`7-TVa1`tF zeq*iGaF;Vmqe!qxUJ~rsY%9WlUI_aa#c@s1#O831$ z--iL5kwM3|l_b6P0{t52>BP98P1K$~AG*mfb13Ss=LH=xNc}*6D(Tn{4ZocH8*5}7 zs3&MC@@3dJl~@B;Z_r1J@BhT#$|>~*T~ISW122*~gO*4Ns3&Ko)EBg4{=vRk=r$9? zKsYOmIO2f!!Uti-|1YK(eE+9uU3SkW-KODq+Ax0_&8{7X3lN#S+pwdNGQaTvg~3giy*%^nG`I`RH_@BX+X|MngdNNEzBwCuo(^33{Sh= zk8lYrSfaw2PPZTvgMIXJ6=EO#jaj-KKeWwWhdOUlD~12xj*e`iV4)c;Z6*KDG7)uH z&?zfTWa$7$M}_mR5X zH4p<~DKj^JPCT4}wgvkB&jHEa$X+FYpez2Ex!lIi5mxuboj9qQ-p}|Kl?i}|b)dHZ z03Om%*Ju1o5)(BTu`L7*5QOIvrLNET7bTiBNMI@G08r+l8b9M-Qb~YqEoi3H_8I@K zGM5@e1Y)Um6w(sa_i4Mz5G25e#TBjesAK2}`gDTA%8HI4D0r&Q6SS#zx_eMK=Nv@%rW6C&Jf=$p6&l(H8Xl z?eX9o+xFq>w@Ghr$M7~XulASIeYEa~hyVd8C+ce*CVjqFy2WN~x3aq3Ms)LQANF)o z#1;UNUg68a&K^xkB?AEaIrl!M`dscL^r~+c(Mwoe# zKd9R?K>+g=;iIzE*g9kawW_nOfe0EOjNJ(;cnbhxVoW{e>3zYX%bp83-lKNMfIxtR zOBM(?L4ZMTCrdrcOZPrnX8_1GESfq7MF#*FV-4?+$I8;K);E>1!JTc|xL_AaGZ}%` zf!snC-dDF&)E9I%9byqon79x}X^Lz;dUqYheU`z(0X@vr$LOT6&erY_`+{~m zaESqar&HIeg;Rj=)(g{cQ8)ipR&v%E^My(o+hu;=sLM&?K3esv;;(dxT0W3j*X>V+ zl5a#TlX8@gq8eKVlfchF>rd%YnTtwyK8D6O{+X0T&9tOn?wOk7;zm>3!M>o)!cb+v za(rFp1E9kqHyK_YqLa3u>un`XXUMu#sn+XU(AA7(v@RwX~B$0W8-|UZS)EG3) zOXiT+S@;^eG23+w_>ns+J4rc1W6)^?DvXD!X&aSP%RA#jRHsjlv^(hf^PVdeT9WEN zE#;lNSDQ}Yb#IZaL0bi(-!(JmNtx<0pY<<5e{Y8l1ONa400jA453oWZglN&CMT-`V zXwjlYix!P&(V|6*7L91pqD6}qjcC!LMT-`VXwjlYix!P&(V{1IK$*qpr=k`B0000< KMNUMnLSTa97dR&X literal 0 HcmV?d00001 diff --git a/packages/editor/src/components/editor-help/images/drag-and-drop-dark@2x.png b/packages/editor/src/components/editor-help/images/drag-and-drop-dark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2e928d2fcd98a92404430c107776e308b783f14e GIT binary patch literal 6381 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 n=2MRUeMPG%XvkN~nuq=u#n=!u literal 0 HcmV?d00001 diff --git a/packages/editor/src/components/editor-help/images/drag-and-drop-dark@3x.png b/packages/editor/src/components/editor-help/images/drag-and-drop-dark@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c49248c449ad26a21984ab2cd4a37e8719cb6ea8 GIT binary patch literal 12629 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 literal 0 HcmV?d00001 diff --git a/packages/editor/src/components/editor-help/images/drag-and-drop-light.png b/packages/editor/src/components/editor-help/images/drag-and-drop-light.png new file mode 100644 index 0000000000000000000000000000000000000000..844373d735f00e571d6a8863da23940e158d1bc7 GIT binary patch literal 2990 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 literal 0 HcmV?d00001 diff --git a/packages/editor/src/components/editor-help/images/drag-and-drop-light@3x.png b/packages/editor/src/components/editor-help/images/drag-and-drop-light@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f906e8f6f832261423fc91ae53e983fac3fea681 GIT binary patch literal 16898 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 literal 0 HcmV?d00001 diff --git a/packages/editor/src/components/editor-help/index.native.js b/packages/editor/src/components/editor-help/index.native.js index a4ce122aa95620..c9161b47863b4e 100644 --- a/packages/editor/src/components/editor-help/index.native.js +++ b/packages/editor/src/components/editor-help/index.native.js @@ -2,7 +2,7 @@ * External dependencies */ import { kebabCase } from 'lodash'; -import { Text, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native'; +import { SafeAreaView, ScrollView, StyleSheet, View } from 'react-native'; import { TransitionPresets } from '@react-navigation/stack'; /** @@ -14,16 +14,9 @@ import { PanelBody, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { - helpFilled, - plusCircleFilled, - alignJustify, - trash, - cog, -} from '@wordpress/icons'; +import { helpFilled, plusCircleFilled, trash, cog } from '@wordpress/icons'; import { useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; -import { usePreferredColorSchemeStyle } from '@wordpress/compose'; import { requestContactCustomerSupport, requestGotoCustomerSupportOptions, @@ -41,6 +34,8 @@ import AddBlocks from './add-blocks'; import MoveBlocks from './move-blocks'; import RemoveBlocks from './remove-blocks'; import CustomizeBlocks from './customize-blocks'; +import moveBlocksIcon from './icon-move-blocks'; +import HelpSectionTitle from './help-section-title'; const HELP_TOPICS = [ { @@ -53,7 +48,7 @@ const HELP_TOPICS = [ icon: plusCircleFilled, view: , }, - { label: __( 'Move blocks' ), icon: alignJustify, view: }, + { label: __( 'Move blocks' ), icon: moveBlocksIcon, view: }, { label: __( 'Remove blocks' ), icon: trash, view: }, { label: __( 'Customize blocks' ), @@ -67,11 +62,6 @@ function EditorHelpTopics( { close, isVisible, onClose } ) { postType: select( editorStore ).getEditedPostAttribute( 'type' ), } ) ); - const sectionTitle = usePreferredColorSchemeStyle( - styles.helpDetailSectionHeading, - styles.helpDetailSectionHeadingDark - ); - const title = postType === 'page' ? __( 'How to edit your page' ) @@ -130,15 +120,22 @@ function EditorHelpTopics( { close, isVisible, onClose } ) { } } > - + { __( 'The basics' ) } - + { /* Print out help topics. */ } { HELP_TOPICS.map( - ( { label, icon } ) => { + ( + { label, icon }, + index + ) => { const labelSlug = kebabCase( label ); + const isLastItem = + index === + HELP_TOPICS.length - + 1; return ( ); } ) } { - + { __( 'Get support' ) } - + } { { return ( <> + + + + + + diff --git a/packages/editor/src/components/editor-help/style.scss b/packages/editor/src/components/editor-help/style.scss index 4bac8875811b5d..8058f00381da95 100644 --- a/packages/editor/src/components/editor-help/style.scss +++ b/packages/editor/src/components/editor-help/style.scss @@ -31,6 +31,21 @@ padding: 0; } +.helpSectionTitleContainer { + margin-top: 24px; + margin-bottom: 16px; +} + +.helpSectionTitle { + color: $light-primary; + font-weight: 600; + font-size: 16px; +} + +.helpSectionTitleDark { + color: $dark-secondary; +} + .helpDetailContainer { padding: 0 16px; } @@ -62,14 +77,18 @@ } .helpDetailSectionHeading { + flex-direction: row; + align-items: center; +} + +.helpDetailSectionHeadingText { color: $light-primary; - font-weight: 600; + font-weight: 700; font-size: 16px; - margin-top: 24px; } -.helpDetailSectionHeadingDark { - color: $dark-secondary; +.helpDetailSectionHeadingTextDark { + color: $dark-primary; } .helpDetailBody { @@ -81,3 +100,16 @@ .helpDetailBodyDark { color: $dark-secondary; } + +.helpDetailBadgeContainer { + padding: 2px 6px; + margin-right: 8px; + border-radius: 6px; + background-color: #c9356e; +} + +.helpDetailBadgeText { + color: $white; + font-weight: 500; + font-size: 12px; +} diff --git a/packages/editor/src/components/editor-help/view-sections.native.js b/packages/editor/src/components/editor-help/view-sections.native.js index a1ed66e0525489..fefca22fa7e097 100644 --- a/packages/editor/src/components/editor-help/view-sections.native.js +++ b/packages/editor/src/components/editor-help/view-sections.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Text, Image } from 'react-native'; +import { Text, Image, View } from 'react-native'; /** * WordPress dependencies @@ -28,15 +28,22 @@ export const HelpDetailBodyText = ( { text } ) => { ); }; -export const HelpDetailSectionHeadingText = ( { text } ) => { - const headingStyle = usePreferredColorSchemeStyle( - styles.helpDetailSectionHeading, - styles.helpDetailSectionHeadingDark +export const HelpDetailSectionHeadingText = ( { text, badge } ) => { + const headingTextStyle = usePreferredColorSchemeStyle( + styles.helpDetailSectionHeadingText, + styles.helpDetailSectionHeadingTextDark ); return ( - - { text } - + + { badge && } + + { text } + + ); }; @@ -62,3 +69,11 @@ export const HelpDetailImage = ( { /> ); }; + +const HelpDetailBadge = ( { text } ) => { + return ( + + { text } + + ); +}; From 09d4368f2a81c604baaecca06a7b31e0cd777ba1 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Fri, 20 May 2022 12:28:28 +0200 Subject: [PATCH 09/49] [RNMobile] Fix drag mode not being enabled when long-pressing over Shortcode block (#41155) * Add prop for disabling suggestions button * Use allowed formats in format types calculation * Add RichText version to PlainText component * Use experimental version of PlainText in Shortcode block * Add disableAutocorrection prop to RichText * Disable autocorrection in Shortcode block * Update PlainText props in Shortcode block * Use pre as tagName in PlainText * Rename replaceLineBreaks function * Update shortcode block unit tests * Prevent text input focus when selecting Shortcode block * Force text color in Shortcode block * Remove tagName prop from PlainText component --- .../src/components/plain-text/index.native.js | 72 ++++++++++++-- .../src/components/rich-text/index.js | 2 + .../src/components/rich-text/index.native.js | 4 + .../src/shortcode/edit.native.js | 44 ++++++--- .../src/shortcode/style.native.scss | 15 ++- .../test/__snapshots__/edit.native.js.snap | 9 ++ .../src/shortcode/test/edit.native.js | 98 +++++++++++-------- .../ReactNativeAztec/ReactAztecManager.java | 11 +++ .../ios/RNTAztecView/RCTAztecView.swift | 6 ++ .../ios/RNTAztecView/RCTAztecViewManager.m | 1 + .../rich-text/src/component/index.native.js | 27 +++-- test/native/__mocks__/styleMock.js | 3 + 12 files changed, 217 insertions(+), 75 deletions(-) create mode 100644 packages/block-library/src/shortcode/test/__snapshots__/edit.native.js.snap diff --git a/packages/block-editor/src/components/plain-text/index.native.js b/packages/block-editor/src/components/plain-text/index.native.js index 13d2bbf851837b..6c8259ceae1600 100644 --- a/packages/block-editor/src/components/plain-text/index.native.js +++ b/packages/block-editor/src/components/plain-text/index.native.js @@ -7,7 +7,7 @@ import { TextInput, Platform, Dimensions } from 'react-native'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { getPxFromCssUnit } from '@wordpress/block-editor'; +import { RichText, getPxFromCssUnit } from '@wordpress/block-editor'; /** * Internal dependencies @@ -18,6 +18,9 @@ export default class PlainText extends Component { constructor() { super( ...arguments ); this.isAndroid = Platform.OS === 'android'; + + this.onChangeTextInput = this.onChangeTextInput.bind( this ); + this.onChangeRichText = this.onChangeRichText.bind( this ); } componentDidMount() { @@ -44,7 +47,7 @@ export default class PlainText extends Component { componentDidUpdate( prevProps ) { if ( ! this.props.isSelected && prevProps.isSelected ) { - this._input.blur(); + this._input?.blur(); } } @@ -55,11 +58,11 @@ export default class PlainText extends Component { } focus() { - this._input.focus(); + this._input?.focus(); } blur() { - this._input.blur(); + this._input?.blur(); } getFontSize() { @@ -79,20 +82,73 @@ export default class PlainText extends Component { }; } + replaceLineBreakTags( value ) { + return value?.replace( RegExp( '
', 'gim' ), '\n' ); + } + + onChangeTextInput( event ) { + const { onChange } = this.props; + onChange( event.nativeEvent.text ); + } + + onChangeRichText( value ) { + const { onChange } = this.props; + // The
tags have to be replaced with new line characters + // as the content of plain text shouldn't contain HTML tags. + onChange( this.replaceLineBreakTags( value ) ); + } + render() { - const { style } = this.props; + const { + style, + __experimentalVersion, + onFocus, + ...otherProps + } = this.props; const textStyles = [ style || styles[ 'block-editor-plain-text' ], this.getFontSize(), ]; + if ( __experimentalVersion === 2 ) { + const disableFormattingProps = { + withoutInteractiveFormatting: true, + disableEditingMenu: true, + __unstableDisableFormats: true, + disableSuggestions: true, + }; + + const forcePlainTextProps = { + preserveWhiteSpace: true, + __unstablePastePlainText: true, + multiline: false, + }; + + const fontProps = { + fontFamily: style?.fontFamily, + fontSize: style?.fontSize, + fontWeight: style?.fontWeight, + }; + + return ( + + ); + } + return ( ( this._input = x ) } - onChange={ ( event ) => { - this.props.onChange( event.nativeEvent.text ); - } } + onChange={ this.onChangeTextInput } onFocus={ this.props.onFocus } // Always assign onFocus as a props. onBlur={ this.props.onBlur } // Always assign onBlur as a props. fontFamily={ diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index d30fa8eae5e92d..eacd225eeb091e 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -76,6 +76,8 @@ function removeNativeProps( props ) { 'minWidth', 'maxWidth', 'setRef', + 'disableSuggestions', + 'disableAutocorrection', ] ); } diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 2c24022c8e9749..0232b68a2a671c 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -111,6 +111,8 @@ function RichTextWrapper( maxWidth, onBlur, setRef, + disableSuggestions, + disableAutocorrection, ...props }, forwardedRef @@ -635,6 +637,8 @@ function RichTextWrapper( maxWidth={ maxWidth } onBlur={ onBlur } setRef={ setRef } + disableSuggestions={ disableSuggestions } + disableAutocorrection={ disableAutocorrection } // Props to be set on the editable container are destructured on the // element itself for web (see below), but passed through rich text // for native. diff --git a/packages/block-library/src/shortcode/edit.native.js b/packages/block-library/src/shortcode/edit.native.js index 778254784682f1..044ad1d23877f8 100644 --- a/packages/block-library/src/shortcode/edit.native.js +++ b/packages/block-library/src/shortcode/edit.native.js @@ -9,6 +9,7 @@ import { View, Text } from 'react-native'; import { __ } from '@wordpress/i18n'; import { PlainText } from '@wordpress/block-editor'; import { withPreferredColorScheme } from '@wordpress/compose'; +import { useCallback } from '@wordpress/element'; /** * Internal dependencies @@ -23,11 +24,16 @@ export function ShortcodeEdit( props ) { onFocus, onBlur, getStylesFromColorScheme, + blockWidth, } = props; const titleStyle = getStylesFromColorScheme( styles.blockTitle, styles.blockTitleDark ); + const shortcodeContainerStyle = getStylesFromColorScheme( + styles.blockShortcodeContainer, + styles.blockShortcodeContainerDark + ); const shortcodeStyle = getStylesFromColorScheme( styles.blockShortcode, styles.blockShortcodeDark @@ -37,24 +43,32 @@ export function ShortcodeEdit( props ) { styles.placeholderDark ); + const maxWidth = + blockWidth - + shortcodeContainerStyle.paddingLeft + + shortcodeContainerStyle.paddingRight; + + const onChange = useCallback( ( text ) => setAttributes( { text } ), [ + setAttributes, + ] ); + return ( { __( 'Shortcode' ) } - setAttributes( { text } ) } - placeholder={ __( 'Add a shortcode…' ) } - aria-label={ __( 'Shortcode' ) } - isSelected={ props.isSelected } - onFocus={ onFocus } - onBlur={ onBlur } - autoCorrect={ false } - autoComplete="off" - placeholderTextColor={ placeholderStyle.color } - /> + <View style={ shortcodeContainerStyle }> + <PlainText + __experimentalVersion={ 2 } + value={ attributes.text } + style={ shortcodeStyle } + onChange={ onChange } + placeholder={ __( 'Add a shortcode…' ) } + onFocus={ onFocus } + onBlur={ onBlur } + placeholderTextColor={ placeholderStyle.color } + maxWidth={ maxWidth } + disableAutocorrection + /> + </View> </View> ); } diff --git a/packages/block-library/src/shortcode/style.native.scss b/packages/block-library/src/shortcode/style.native.scss index ed7d235960abdd..b828e7ff28f7a9 100644 --- a/packages/block-library/src/shortcode/style.native.scss +++ b/packages/block-library/src/shortcode/style.native.scss @@ -11,18 +11,25 @@ color: $gray-50; } +.blockShortcodeContainer { + padding: 12px; + border-radius: 4px; + background-color: $gray-light; +} + +.blockShortcodeContainerDark { + background-color: $gray-100; +} + .blockShortcode { font-family: $default-monospace-font; font-weight: 400; font-size: 14px; - padding: 12px; - border-radius: 4px; - background-color: $gray-light; + color: $gray-900; } .blockShortcodeDark { color: $white; - background-color: $gray-100; } .placeholder { diff --git a/packages/block-library/src/shortcode/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/shortcode/test/__snapshots__/edit.native.js.snap new file mode 100644 index 00000000000000..12d5722a68c39d --- /dev/null +++ b/packages/block-library/src/shortcode/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Shortcode block edits content 1`] = ` +"<!-- wp:shortcode --> +[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg] +<!-- /wp:shortcode -->" +`; + +exports[`Shortcode block inserts block 1`] = `"<!-- wp:shortcode /-->"`; diff --git a/packages/block-library/src/shortcode/test/edit.native.js b/packages/block-library/src/shortcode/test/edit.native.js index c6aa9261b6d9f5..f6c9ce705f5ad3 100644 --- a/packages/block-library/src/shortcode/test/edit.native.js +++ b/packages/block-library/src/shortcode/test/edit.native.js @@ -1,58 +1,76 @@ /** * External dependencies */ -import renderer from 'react-test-renderer'; -import { TextInput } from 'react-native'; +import { + getEditorHtml, + initializeEditor, + fireEvent, + waitFor, +} from 'test/helpers'; /** * WordPress dependencies */ -import { BlockEdit } from '@wordpress/block-editor'; -import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; -/** - * Internal dependencies - */ -import { metadata, settings, name } from '../index'; +beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); +} ); + +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); + +describe( 'Shortcode block', () => { + it( 'inserts block', async () => { + const { + getByA11yLabel, + getByTestId, + getByText, + } = await initializeEditor(); -const Shortcode = ( { clientId, ...props } ) => ( - <BlockEdit name={ name } clientId={ clientId || 0 } { ...props } /> -); + fireEvent.press( getByA11yLabel( 'Add block' ) ); -describe( 'Shortcode', () => { - beforeAll( () => { - registerBlockType( name, { - ...metadata, - ...settings, + const blockList = getByTestId( 'InserterUI-Blocks' ); + // onScroll event used to force the FlatList to render all items + fireEvent.scroll( blockList, { + nativeEvent: { + contentOffset: { y: 0, x: 0 }, + contentSize: { width: 100, height: 100 }, + layoutMeasurement: { width: 100, height: 100 }, + }, } ); - } ); - afterAll( () => { - unregisterBlockType( name ); - } ); + fireEvent.press( await waitFor( () => getByText( 'Shortcode' ) ) ); - it( 'renders without crashing', () => { - const component = renderer.create( - <Shortcode attributes={ { text: '' } } /> - ); - const rendered = component.toJSON(); - expect( rendered ).toBeTruthy(); + expect( getByA11yLabel( /Shortcode Block\. Row 1/ ) ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); } ); - it( 'renders given text without crashing', () => { - const component = renderer.create( - <Shortcode - attributes={ { - text: - '[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg]', - } } - /> - ); - const testInstance = component.root; - const textInput = testInstance.findByType( TextInput ); - expect( textInput ).toBeTruthy(); - expect( textInput.props.value ).toBe( - '[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg]' + it( 'edits content', async () => { + const { getByA11yLabel, getByPlaceholderText } = await initializeEditor( + { + initialHtml: '<!-- wp:shortcode /-->', + } ); + const shortcodeBlock = getByA11yLabel( /Shortcode Block\. Row 1/ ); + fireEvent.press( shortcodeBlock ); + + const textField = getByPlaceholderText( 'Add a shortcode…' ); + fireEvent( textField, 'focus' ); + fireEvent( textField, 'onChange', { + nativeEvent: { + eventCount: 1, + target: undefined, + text: '[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg]', + }, + } ); + + expect( getEditorHtml() ).toMatchSnapshot(); } ); } ); diff --git a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java index 52dac695bc9973..1e3e90c3a48442 100644 --- a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java +++ b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java @@ -13,6 +13,7 @@ import android.os.Handler; import android.os.Looper; import android.text.Editable; +import android.text.InputType; import android.text.Layout; import android.text.TextUtils; import android.text.TextWatcher; @@ -617,6 +618,16 @@ public void setShouldDeleteEnter(final ReactAztecText view, boolean shouldDelete view.shouldDeleteEnter = shouldDeleteEnter; } + @ReactProp(name = "disableAutocorrection", defaultBoolean = false) + public void disableAutocorrection(final ReactAztecText view, boolean disable) { + if (disable) { + view.setInputType((view.getInputType() & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + } + else { + view.setInputType((view.getInputType() & ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); + } + } + @Override public Map<String, Integer> getCommandsMap() { return MapBuilder.<String, Integer>builder() diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift index 5413041b6ac6ff..cc87ef29e9704b 100644 --- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift +++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift @@ -31,6 +31,12 @@ class RCTAztecView: Aztec.TextView { } } + @objc var disableAutocorrection: Bool = false { + didSet { + autocorrectionType = disableAutocorrection ? .no : .default + } + } + override var textAlignment: NSTextAlignment { set { super.textAlignment = newValue diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m index bad6dd8abf7680..e8038ab17a7044 100644 --- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m +++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m @@ -31,6 +31,7 @@ @interface RCT_EXTERN_MODULE(RCTAztecViewManager, NSObject) RCT_EXPORT_VIEW_PROPERTY(lineHeight, CGFloat) RCT_EXPORT_VIEW_PROPERTY(disableEditingMenu, BOOL) +RCT_EXPORT_VIEW_PROPERTY(disableAutocorrection, BOOL) RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment, NSTextAlignment) RCT_REMAP_VIEW_PROPERTY(selectionColor, tintColor, UIColor) diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index f460019df8b3ce..4fadc90b8d4baa 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -1050,6 +1050,7 @@ export class RichText extends Component { baseGlobalStyles, selectionStart, selectionEnd, + disableSuggestions, } = this.props; const { currentFontSize } = this.state; @@ -1220,6 +1221,7 @@ export class RichText extends Component { minWidth={ minWidth } id={ this.props.id } selectionColor={ this.props.selectionColor } + disableAutocorrection={ this.props.disableAutocorrection } /> { isSelected && ( <> @@ -1230,11 +1232,13 @@ export class RichText extends Component { onChange={ this.onFormatChange } onFocus={ () => {} } /> - <BlockFormatControls> - <ToolbarButtonWithOptions - options={ this.suggestionOptions() } - /> - </BlockFormatControls> + { ! disableSuggestions && ( + <BlockFormatControls> + <ToolbarButtonWithOptions + options={ this.suggestionOptions() } + /> + </BlockFormatControls> + ) } </> ) } </View> @@ -1249,10 +1253,17 @@ RichText.defaultProps = { }; const withFormatTypes = ( WrappedComponent ) => ( props ) => { + const { + clientId, + identifier, + withoutInteractiveFormatting, + allowedFormats, + } = props; const { formatTypes } = useFormatTypes( { - clientId: props.clientId, - identifier: props.identifier, - withoutInteractiveFormatting: props.withoutInteractiveFormatting, + clientId, + identifier, + withoutInteractiveFormatting, + allowedFormats, } ); return <WrappedComponent { ...props } formatTypes={ formatTypes } />; diff --git a/test/native/__mocks__/styleMock.js b/test/native/__mocks__/styleMock.js index 4723663203954f..11f58d79b1afcd 100644 --- a/test/native/__mocks__/styleMock.js +++ b/test/native/__mocks__/styleMock.js @@ -162,4 +162,7 @@ module.exports = { 'dropping-insertion-point': { height: 3, }, + blockShortcodeContainer: { + padding: 12, + }, }; From c951e5965ed5be70e011314f7578e0ce5558f8b0 Mon Sep 17 00:00:00 2001 From: Gerardo <gerardo.pacheco@automattic.com> Date: Fri, 20 May 2022 13:22:11 +0200 Subject: [PATCH 10/49] Mobile - Update changelog --- packages/react-native-editor/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 34b2157e09f909..ac9774f6013363 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,12 @@ For each user feature we should also add a importance categorization label to i ## Unreleased +## 1.76.1 + +- [*] BlockList - Add internal onLayout from CellRendererComponent to BlockListItemCell [#41105] +- [*] Fix Drag & Drop Chip positioning issue with RTL languages [#41053] +- [*] Add drag & drop help guide in Help & Support screen [#40961] +- [**] Fix drag mode not being enabled when long-pressing over Shortcode block [#41155] ## 1.76.0 From d0b2cea01f9d12ae384ea8f1c9d32c0664cad5b8 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Fri, 20 May 2022 16:12:43 +0200 Subject: [PATCH 11/49] Translate NEW badge in Move blocks help screen --- .../editor/src/components/editor-help/move-blocks.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/editor-help/move-blocks.native.js b/packages/editor/src/components/editor-help/move-blocks.native.js index 0bfdd64eb2253e..44e9f35f6ec375 100644 --- a/packages/editor/src/components/editor-help/move-blocks.native.js +++ b/packages/editor/src/components/editor-help/move-blocks.native.js @@ -28,7 +28,7 @@ const MoveBlocks = () => { <View style={ styles.helpDetailContainer }> <HelpDetailSectionHeadingText text={ __( 'Drag & drop' ) } - badge="NEW" + badge={ __( 'NEW' ) } /> <HelpDetailBodyText text={ __( From 132bc3abcbc839eed5c15a62fe528efeb78c653f Mon Sep 17 00:00:00 2001 From: Siobhan <siobhan@automattic.com> Date: Tue, 24 May 2022 10:47:23 +0100 Subject: [PATCH 12/49] Release script: Update react-native-editor version to 1.77.0 --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index ee0f60f4566e58..1d6c079cfbf0c2 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.76.1", + "version": "1.77.0", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 655b7f42de9574..525cbcfbc2465f 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.76.1", + "version": "1.77.0", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 706c368919e23a..c02f10c28a5ef3 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.76.1", + "version": "1.77.0", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From cb4105f5f6e0342430a2e987f8c0c12bbf7ff034 Mon Sep 17 00:00:00 2001 From: Siobhan <siobhan@automattic.com> Date: Tue, 24 May 2022 10:48:01 +0100 Subject: [PATCH 13/49] Release script: Update with changes from 'npm run core preios' --- packages/react-native-editor/ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index b2fd1726c26875..d12d4b360b34c6 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - ReactCommon/turbomodule/core (= 0.66.2) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.76.1): + - Gutenberg (1.77.0): - React-Core (= 0.66.2) - React-CoreModules (= 0.66.2) - React-RCTImage (= 0.66.2) @@ -337,7 +337,7 @@ PODS: - React-Core - RNSVG (9.13.6): - React-Core - - RNTAztecView (1.76.1): + - RNTAztecView (1.77.0): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - WordPress-Aztec-iOS (1.19.8) @@ -503,7 +503,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 18438b1c04ce502ed681cd19db3f4508964c082a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 5337263514dd6f09803962437687240c5dc39aa4 - Gutenberg: 9d1e70315e26fc3e9e8445b3f17c8ecb29f77e1b + Gutenberg: 7f0a288f26e36807c8e27f97b3a6ff929310eea3 RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 RCTRequired: 5e9e85f48da8dd447f5834ce14c6799ea8c7f41a RCTTypeSafety: aba333d04d88d1f954e93666a08d7ae57a87ab30 @@ -542,7 +542,7 @@ SPEC CHECKSUMS: RNReanimated: d87c75f1076bab3402d6cd0b7322be51d333d10e RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 - RNTAztecView: def74944705c4bef636fc653d11eaa1f49b64c22 + RNTAztecView: 2524811d1168d1f5148220ec8ea3c012ec8f059c WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 Yoga: 9a08effa851c1d8cc1647691895540bc168ea65f From 639598f6f5ab6c884ee415200bea92915ab1f305 Mon Sep 17 00:00:00 2001 From: Siobhan Bamber <siobhan@automattic.com> Date: Fri, 13 May 2022 17:33:45 +0100 Subject: [PATCH 14/49] [RNMobile] Improve text read by screen readers for BottomSheetSelectControl (#41036) Improves the text that's read by screen readers by the BottomSheetSelectControl component, adding extra context and making its purpose clearer. --- .../mobile/bottom-sheet-select-control/index.native.js | 9 +++++++-- packages/react-native-editor/CHANGELOG.md | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/components/src/mobile/bottom-sheet-select-control/index.native.js b/packages/components/src/mobile/bottom-sheet-select-control/index.native.js index 6d88c349b78340..cc0a340104f86c 100644 --- a/packages/components/src/mobile/bottom-sheet-select-control/index.native.js +++ b/packages/components/src/mobile/bottom-sheet-select-control/index.native.js @@ -57,11 +57,16 @@ const BottomSheetSelectControl = ( { value={ selectedOption.label } onPress={ openSubSheet } accessibilityRole={ 'button' } - accessibilityLabel={ selectedOption.label } + accessibilityLabel={ sprintf( + // translators: %1$s: Select control button label e.g. "Button width". %2$s: Select control option value e.g: "Auto, 25%". + __( '%1$s. Currently selected: %2$s' ), + label, + selectedOption.label + ) } accessibilityHint={ sprintf( // translators: %s: Select control button label e.g. "Button width" __( 'Navigates to select %s' ), - selectedOption.label + label ) } > <Icon icon={ chevronRight }></Icon> diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index ac9774f6013363..9b3a6cf719b568 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,10 @@ For each user feature we should also add a importance categorization label to i ## Unreleased +## 1.77.0 + +- [*] [a11y] Improve text read by screen readers for BottomSheetSelectControl [#41036] + ## 1.76.1 - [*] BlockList - Add internal onLayout from CellRendererComponent to BlockListItemCell [#41105] From 3fe5683d95e804b11892f5c270c8527297231b58 Mon Sep 17 00:00:00 2001 From: Derek Blank <derekpblank@gmail.com> Date: Tue, 24 May 2022 00:24:30 +1000 Subject: [PATCH 15/49] [RNMobile] Add 'Insert from URL' option to Image block (#40334) * Add 'Insert from URL' option to Video and Image blocks * Update code style from linting * Improve test cases for Media Upload capture options * Fix whitespace issue * Update Media Upload option tests to be asynchronous * Update native image block to use correct image URL * Add error handling for invalid URLs to native Image block * Clear invalid URL error on Image URL success * Fix synchronicity of Media Upload option tests * Add check for URL handler to native Image block picker options * Update code style * Remove Video block from urlSource options Why: to be introduced in a later PR * Remove URL option from Video block for Media Upload test * Use Notice snackbar for native Image block error handling * Update Image/Media Upload code style and helpers * Use getImage to determine if URL is a valid image within Image block * Add loading indicator and isURL check to native Image block URL behavior * Add loading indicator to native Image block media placeholder * Fix whitespace issue in native Image block code style * Reuse native Image block loading indicator * Use undefined dimension attributes for the native Image block URL behavior Co-authored-by: Derek Blank <derekblank@Dereks-MacBook-Pro.local> --- .../components/media-upload/index.native.js | 8 ++- .../media-upload/test/index.native.js | 37 ++++++++-- .../block-library/src/image/edit.native.js | 68 ++++++++++++++++++- .../src/image/styles.native.scss | 11 +++ 4 files changed, 113 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js index 05aabd9a9a3cd1..8f793e4c0fa3f6 100644 --- a/packages/block-editor/src/components/media-upload/index.native.js +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -39,6 +39,7 @@ export const OPTION_TAKE_VIDEO = __( 'Take a Video' ); export const OPTION_TAKE_PHOTO = __( 'Take a Photo' ); export const OPTION_TAKE_PHOTO_OR_VIDEO = __( 'Take a Photo or Video' ); export const OPTION_INSERT_FROM_URL = __( 'Insert from URL' ); +export const OPTION_WORDPRESS_MEDIA_LIBRARY = __( 'WordPress Media Library' ); const URL_MEDIA_SOURCE = 'URL'; @@ -78,6 +79,8 @@ export class MediaUpload extends Component { } getAllSources() { + const { onSelectURL } = this.props; + const cameraImageSource = { id: mediaSources.deviceCamera, // ID is the value sent to native. value: mediaSources.deviceCamera + '-IMAGE', // This is needed to diferenciate image-camera from video-camera sources. @@ -124,16 +127,17 @@ export class MediaUpload extends Component { id: URL_MEDIA_SOURCE, value: URL_MEDIA_SOURCE, label: __( 'Insert from URL' ), - types: [ MEDIA_TYPE_AUDIO ], + types: [ MEDIA_TYPE_AUDIO, MEDIA_TYPE_IMAGE ], icon: globe, }; + // Only include `urlSource` option if `onSelectURL` prop is present, in order to match the web behavior. const internalSources = [ deviceLibrarySource, cameraImageSource, cameraVideoSource, siteLibrarySource, - urlSource, + ...( onSelectURL ? [ urlSource ] : [] ), ]; return internalSources.concat( this.state.otherMediaOptions ); diff --git a/packages/block-editor/src/components/media-upload/test/index.native.js b/packages/block-editor/src/components/media-upload/test/index.native.js index e54a9f5219321d..73cbf41b7dfc14 100644 --- a/packages/block-editor/src/components/media-upload/test/index.native.js +++ b/packages/block-editor/src/components/media-upload/test/index.native.js @@ -20,6 +20,7 @@ import { OPTION_TAKE_VIDEO, OPTION_TAKE_PHOTO, OPTION_INSERT_FROM_URL, + OPTION_WORDPRESS_MEDIA_LIBRARY, } from '../index'; const MEDIA_URL = 'http://host.media.type'; @@ -33,8 +34,11 @@ describe( 'MediaUpload component', () => { expect( wrapper ).toBeTruthy(); } ); - it( 'shows right media capture option for media type', () => { - const expectOptionForMediaType = ( mediaType, expectedOption ) => { + describe( 'Media capture options for different media block types', () => { + const expectOptionForMediaType = async ( + mediaType, + expectedOptions + ) => { const wrapper = render( <MediaUpload allowedTypes={ [ mediaType ] } @@ -52,11 +56,32 @@ describe( 'MediaUpload component', () => { ); fireEvent.press( wrapper.getByText( 'Open Picker' ) ); - wrapper.getByText( expectedOption ); + expectedOptions.forEach( ( item ) => { + const option = wrapper.getByText( item ); + expect( option ).toBeVisible(); + } ); }; - expectOptionForMediaType( MEDIA_TYPE_IMAGE, OPTION_TAKE_PHOTO ); - expectOptionForMediaType( MEDIA_TYPE_VIDEO, OPTION_TAKE_VIDEO ); - expectOptionForMediaType( MEDIA_TYPE_AUDIO, OPTION_INSERT_FROM_URL ); + + it( 'shows the correct media capture options for the Image block', () => { + expectOptionForMediaType( MEDIA_TYPE_IMAGE, [ + OPTION_TAKE_PHOTO, + OPTION_WORDPRESS_MEDIA_LIBRARY, + OPTION_INSERT_FROM_URL, + ] ); + } ); + + it( 'shows the correct media capture options for the Video block', () => { + expectOptionForMediaType( MEDIA_TYPE_VIDEO, [ + OPTION_TAKE_VIDEO, + OPTION_WORDPRESS_MEDIA_LIBRARY, + ] ); + } ); + + it( 'shows the correct media capture options for the Audio block', () => { + expectOptionForMediaType( MEDIA_TYPE_AUDIO, [ + OPTION_INSERT_FROM_URL, + ] ); + } ); } ); const expectMediaPickerForOption = ( diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index f9025bf2333f9c..24df9fce2866a4 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -1,7 +1,12 @@ /** * External dependencies */ -import { View, TouchableWithoutFeedback } from 'react-native'; +import { + ActivityIndicator, + Image as RNImage, + TouchableWithoutFeedback, + View, +} from 'react-native'; import { useRoute } from '@react-navigation/native'; /** @@ -45,7 +50,7 @@ import { blockSettingsScreens, } from '@wordpress/block-editor'; import { __, _x, sprintf } from '@wordpress/i18n'; -import { getProtocol, hasQueryArg } from '@wordpress/url'; +import { getProtocol, hasQueryArg, isURL } from '@wordpress/url'; import { doAction, hasAction } from '@wordpress/hooks'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -57,6 +62,7 @@ import { } from '@wordpress/icons'; import { store as coreStore } from '@wordpress/core-data'; import { store as editPostStore } from '@wordpress/edit-post'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -207,6 +213,7 @@ export class ImageEdit extends Component { this.onImagePressed = this.onImagePressed.bind( this ); this.onSetFeatured = this.onSetFeatured.bind( this ); this.onFocusCaption = this.onFocusCaption.bind( this ); + this.onSelectURL = this.onSelectURL.bind( this ); this.updateAlignment = this.updateAlignment.bind( this ); this.accessibilityLabelCreator = this.accessibilityLabelCreator.bind( this @@ -461,6 +468,45 @@ export class ImageEdit extends Component { } ); } + onSelectURL( newURL ) { + const { + createErrorNotice, + imageDefaultSize, + setAttributes, + } = this.props; + + if ( isURL( newURL ) ) { + this.setState( { + isFetchingImage: true, + } ); + + // Use RN's Image.getSize to determine if URL is a valid image + RNImage.getSize( + newURL, + () => { + setAttributes( { + url: newURL, + id: undefined, + width: undefined, + height: undefined, + sizeSlug: imageDefaultSize, + } ); + this.setState( { + isFetchingImage: false, + } ); + }, + () => { + createErrorNotice( __( 'Image file not found.' ) ); + this.setState( { + isFetchingImage: false, + } ); + } + ); + } else { + createErrorNotice( __( 'Invalid URL.' ) ); + } + } + onFocusCaption() { if ( this.props.onFocus ) { this.props.onFocus(); @@ -484,6 +530,14 @@ export class ImageEdit extends Component { ); } + showLoadingIndicator() { + return ( + <View style={ styles.image__loading }> + <ActivityIndicator animating /> + </View> + ); + } + getWidth() { const { attributes } = this.props; const { align, width } = attributes; @@ -611,7 +665,7 @@ export class ImageEdit extends Component { } render() { - const { isCaptionSelected } = this.state; + const { isCaptionSelected, isFetchingImage } = this.state; const { attributes, isSelected, @@ -713,9 +767,11 @@ export class ImageEdit extends Component { if ( ! url ) { return ( <View style={ styles.content }> + { isFetchingImage && this.showLoadingIndicator() } <MediaPlaceholder allowedTypes={ [ MEDIA_TYPE_IMAGE ] } onSelect={ this.onSelectMediaUploadOption } + onSelectURL={ this.onSelectURL } icon={ this.getPlaceholderIcon() } onFocus={ this.props.onFocus } autoOpenMediaUpload={ @@ -784,6 +840,8 @@ export class ImageEdit extends Component { } ) => { return ( <View style={ imageContainerStyles }> + { isFetchingImage && + this.showLoadingIndicator() } <Image align={ align && alignToFlex[ align ] @@ -836,6 +894,7 @@ export class ImageEdit extends Component { allowedTypes={ [ MEDIA_TYPE_IMAGE ] } isReplacingMedia={ true } onSelect={ this.onSelectMediaUploadOption } + onSelectURL={ this.onSelectURL } render={ ( { open, getMediaOptions } ) => { return getImageComponent( open, getMediaOptions ); } } @@ -881,7 +940,10 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { + const { createErrorNotice } = dispatch( noticesStore ); + return { + createErrorNotice, closeSettingsBottomSheet() { dispatch( editPostStore ).closeGeneralSidebar(); }, diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss index 476bb8b2be4716..0bcec21b571893 100644 --- a/packages/block-library/src/image/styles.native.scss +++ b/packages/block-library/src/image/styles.native.scss @@ -57,3 +57,14 @@ .removeFeaturedButton { color: $alert-red; } + +.image__loading { + align-items: center; + background-color: rgba(10, 10, 10, 0.5); + flex: 1; + height: 100%; + justify-content: center; + position: absolute; + width: 100%; + z-index: 1; +} From a6dd4946db9283cdb2f4de2be2540cf4769ec32c Mon Sep 17 00:00:00 2001 From: Jos <jostnes@users.noreply.github.com> Date: Mon, 16 May 2022 10:33:48 +0800 Subject: [PATCH 16/49] [RNMobile] - E2E Simplify heading and lists blocks functions (#40670) * update tests using paragraph, heading and list blocks * fix slash inserter tests to work in ci * lint fixes * wait for ordered list to appear * lint fixes * extra click only on local env * wait to get backspace click reflected * re-add extra click only for local env * add wait to wait for backspace key to be reflected * lint fixes * break function, set position to get list block * lint fixes * use correct params, update function name * lint fixes * make maxIteration a parameter for isElementVisible * update xpath for list block * utilize waitForVisible for isElementVisible * lint fixes * add wait to getNumberOfParagraphBlocks and update xpath for android list block * update edit text xpath to be read from any level Co-authored-by: jos <17252150+jostnes@users.noreply.github.com> --- .../gutenberg-editor-heading-@canary.test.js | 29 ++-- .../gutenberg-editor-lists-@canary.test.js | 34 ++-- .../gutenberg-editor-lists-end.test.js | 21 +-- .../gutenberg-editor-lists.test.js | 31 ++-- .../gutenberg-editor-paragraph.test.js | 20 +-- .../gutenberg-editor-paste.test.js | 14 +- .../gutenberg-editor-rotation.test.js | 19 +-- ...berg-editor-slash-inserter-@canary.test.js | 78 +++------ .../__device-tests__/helpers/utils.js | 94 ++++++++--- .../__device-tests__/pages/editor-page.js | 157 +++++++++--------- 10 files changed, 233 insertions(+), 264 deletions(-) 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 index 17f58bfec5f71e..f806b43d2f8e42 100644 --- 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 @@ -2,26 +2,18 @@ * Internal dependencies */ import { blockNames } from './pages/editor-page'; -import { isAndroid } from './helpers/utils'; 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.addNewBlock( blockNames.heading ); - let headingBlockElement = await editorPage.getBlockAtPosition( - blockNames.heading, - 1, - { - useWaitForVisible: true, - } + let headingBlockElement = await editorPage.getTextBlockAtPosition( + blockNames.heading ); - if ( isAndroid() ) { - await headingBlockElement.click(); - } - await editorPage.sendTextToHeadingBlock( + + await editorPage.typeTextToTextBlock( headingBlockElement, - testData.heading, - false + testData.heading ); await editorPage.addNewBlock( blockNames.paragraph ); @@ -29,7 +21,7 @@ describe( 'Gutenberg Editor tests', () => { blockNames.paragraph, 2 ); - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, testData.mediumText ); @@ -39,7 +31,7 @@ describe( 'Gutenberg Editor tests', () => { blockNames.paragraph, 3 ); - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, testData.mediumText ); @@ -49,7 +41,7 @@ describe( 'Gutenberg Editor tests', () => { blockNames.heading, 4 ); - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( headingBlockElement, testData.heading ); @@ -59,9 +51,12 @@ describe( 'Gutenberg Editor tests', () => { blockNames.paragraph, 5 ); - await editorPage.typeTextToParagraphBlock( + 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-lists-@canary.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-@canary.test.js index 80939bd9e58018..970fd669675283 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-@canary.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-@canary.test.js @@ -8,27 +8,26 @@ import testData from './helpers/test-data'; describe( 'Gutenberg Editor tests for List block', () => { it( 'should be able to add a new List block', async () => { await editorPage.addNewBlock( blockNames.list ); - const listBlockElement = await editorPage.getBlockAtPosition( - blockNames.list - ); - // Click List block on Android to force EditText focus - if ( isAndroid() ) { - await listBlockElement.click(); - } + let listBlockElement = await editorPage.getListBlockAtPosition( 1, { + isEmptyBlock: true, + } ); - // Send the first list item text. - await editorPage.sendTextToListBlock( + await editorPage.typeTextToTextBlock( listBlockElement, - testData.listItem1 + testData.listItem1, + false ); + listBlockElement = await editorPage.getListBlockAtPosition(); + // Send an Enter. - await editorPage.sendTextToListBlock( listBlockElement, '\n' ); + await editorPage.typeTextToTextBlock( listBlockElement, '\n', false ); // Send the second list item text. - await editorPage.sendTextToListBlock( + await editorPage.typeTextToTextBlock( listBlockElement, - testData.listItem2 + testData.listItem2, + false ); // Switch to html and verify html. @@ -38,12 +37,11 @@ describe( 'Gutenberg Editor tests for List block', () => { // This test depends on being run immediately after 'should be able to add a new List block' it( 'should update format to ordered list, using toolbar button', async () => { - let listBlockElement = await editorPage.getBlockAtPosition( - blockNames.list - ); + let listBlockElement = await editorPage.getListBlockAtPosition(); - // Click List block to force EditText focus. - await listBlockElement.click(); + if ( isAndroid() ) { + await listBlockElement.click(); + } // Send a click on the order list format button. await editorPage.clickOrderedListToolBarButton(); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js index d113be5371a387..e242f2f6f99e08 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js @@ -2,32 +2,21 @@ * Internal dependencies */ import { blockNames } from './pages/editor-page'; -import { isAndroid } from './helpers/utils'; import testData from './helpers/test-data'; describe( 'Gutenberg Editor tests for List block (end)', () => { it( 'should be able to end a List block', async () => { await editorPage.addNewBlock( blockNames.list ); - const listBlockElement = await editorPage.getBlockAtPosition( - blockNames.list - ); - - // Click List block on Android to force EditText focus - if ( isAndroid() ) { - await listBlockElement.click(); - } + const listBlockElement = await editorPage.getListBlockAtPosition(); - // Send the first list item text. - await editorPage.sendTextToListBlock( + await editorPage.typeTextToTextBlock( listBlockElement, - testData.listItem1 + testData.listItem1, + false ); // Send an Enter. - await editorPage.sendTextToListBlock( listBlockElement, '\n' ); - - // Send an Enter. - await editorPage.sendTextToListBlock( listBlockElement, '\n' ); + await editorPage.typeTextToTextBlock( listBlockElement, '\n\n', false ); const html = await editorPage.getHtmlContent(); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js index 12537bfcae5350..6b2cb1c04509ff 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js @@ -2,28 +2,33 @@ * Internal dependencies */ import { blockNames } from './pages/editor-page'; -import { backspace, isAndroid } from './helpers/utils'; +import { waitIfAndroid, backspace } from './helpers/utils'; describe( 'Gutenberg Editor tests for List block', () => { // Prevent regression of https://github.com/wordpress-mobile/gutenberg-mobile/issues/871 it( 'should handle spaces in a list', async () => { await editorPage.addNewBlock( blockNames.list ); - let listBlockElement = await editorPage.getBlockAtPosition( - blockNames.list - ); - // Click List block on Android to force EditText focus - if ( isAndroid() ) { - await listBlockElement.click(); - } + let listBlockElement = await editorPage.getListBlockAtPosition(); // Send the list item text. - await editorPage.sendTextToListBlock( listBlockElement, ' a' ); + await editorPage.typeTextToTextBlock( listBlockElement, ' a', false ); // Send an Enter. - await editorPage.sendTextToListBlock( listBlockElement, '\n' ); + await editorPage.typeTextToTextBlock( listBlockElement, '\n', false ); + + // Instead of introducing separate conditions for local and CI environment, add this wait for Android to accomodate both environments + await waitIfAndroid(); // Send a backspace. - await editorPage.sendTextToListBlock( listBlockElement, backspace ); + await editorPage.typeTextToTextBlock( + listBlockElement, + backspace, + false + ); + + // There is a delay in Sauce Labs when a key is sent + // There isn't an element to check as it's being typed into an element that already exists, workaround is to add this wait until there's a better solution + await waitIfAndroid(); // Switch to html and verify html. const html = await editorPage.getHtmlContent(); @@ -35,9 +40,7 @@ describe( 'Gutenberg Editor tests for List block', () => { ); // Remove list block to reset editor to clean state. - listBlockElement = await editorPage.getBlockAtPosition( - blockNames.list - ); + listBlockElement = await editorPage.getListBlockAtPosition(); await listBlockElement.click(); await editorPage.removeBlockAtPosition( blockNames.list ); } ); 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 index 0ac9bae0743ef3..20d7d690b96bfc 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js @@ -16,12 +16,12 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { const paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, testData.shortText ); await clickMiddleOfElement( editorPage.driver, paragraphBlockElement ); - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, '\n', false @@ -44,19 +44,13 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { let paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); - if ( isAndroid() ) { - await paragraphBlockElement.click(); - } - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, testData.shortText ); await clickMiddleOfElement( editorPage.driver, paragraphBlockElement ); - await editorPage.typeTextToParagraphBlock( - paragraphBlockElement, - '\n' - ); + await editorPage.typeTextToTextBlock( paragraphBlockElement, '\n' ); const text0 = await editorPage.getTextForParagraphBlockAtPosition( 1 ); const text1 = await editorPage.getTextForParagraphBlockAtPosition( 2 ); @@ -71,7 +65,7 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { paragraphBlockElement ); - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, backspace ); @@ -112,7 +106,7 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { editorPage.driver, paragraphBlockElement ); - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, backspace ); @@ -140,7 +134,7 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { blockNames.paragraph, 2 ); - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, backspace ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js index 16c291584b8ed7..7404b0969d6adb 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js @@ -29,11 +29,8 @@ describe( 'Gutenberg Editor paste tests', () => { const paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); - if ( isAndroid() ) { - await paragraphBlockElement.click(); - } - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, testData.pastePlainText ); @@ -59,9 +56,6 @@ describe( 'Gutenberg Editor paste tests', () => { blockNames.paragraph, 2 ); - if ( isAndroid() ) { - await paragraphBlockElement2.click(); - } // Paste into second paragraph block. await longPressMiddleOfElement( @@ -83,9 +77,6 @@ describe( 'Gutenberg Editor paste tests', () => { const paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); - if ( isAndroid() ) { - await paragraphBlockElement.click(); - } // Copy content to clipboard. await longPressMiddleOfElement( @@ -108,9 +99,6 @@ describe( 'Gutenberg Editor paste tests', () => { blockNames.paragraph, 2 ); - if ( isAndroid() ) { - await paragraphBlockElement2.click(); - } // Paste into second paragraph block. await longPressMiddleOfElement( diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js index 9056854fa42c7f..215b81bf98b4d4 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js @@ -11,11 +11,8 @@ describe( 'Gutenberg Editor tests', () => { let paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); - if ( isAndroid() ) { - await paragraphBlockElement.click(); - } - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, testData.mediumText ); @@ -23,27 +20,21 @@ describe( 'Gutenberg Editor tests', () => { await toggleOrientation( editorPage.driver ); // On Android the keyboard hides the add block button, let's hide it after rotation if ( isAndroid() ) { - await editorPage.driver.hideDeviceKeyboard(); + await editorPage.dismissKeyboard(); } await editorPage.addNewBlock( blockNames.paragraph ); if ( isAndroid() ) { - await editorPage.driver.hideDeviceKeyboard(); + await editorPage.dismissKeyboard(); } paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph, 2 ); - while ( ! paragraphBlockElement ) { - await editorPage.driver.hideDeviceKeyboard(); - paragraphBlockElement = await editorPage.getBlockAtPosition( - blockNames.paragraph, - 2 - ); - } - await editorPage.typeTextToParagraphBlock( + + await editorPage.typeTextToTextBlock( paragraphBlockElement, testData.mediumText ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-slash-inserter-@canary.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-slash-inserter-@canary.test.js index 563445768709a7..d16e37bc7090f5 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-slash-inserter-@canary.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-slash-inserter-@canary.test.js @@ -5,105 +5,68 @@ import { blockNames } from './pages/editor-page'; import { isAndroid } from './helpers/utils'; import { slashInserter, shortText } from './helpers/test-data'; -const ANIMATION_TIME = 200; - -// Helper function for asserting slash inserter presence. -async function assertSlashInserterPresent( checkIsVisible ) { - let areResultsDisplayed; - try { - const foundElements = await editorPage.driver.elementsByAccessibilityId( - 'Slash inserter results' - ); - areResultsDisplayed = !! foundElements.length; - } catch ( e ) { - areResultsDisplayed = false; - } - if ( checkIsVisible ) { - expect( areResultsDisplayed ).toBeTruthy(); - } else { - expect( areResultsDisplayed ).toBeFalsy(); - } -} - -// Due to flakiness, disabling until its more stable -// https://github.com/wordpress-mobile/gutenberg-mobile/issues/3699 -// eslint-disable-next-line jest/no-disabled-tests -describe.skip( 'Gutenberg Editor Slash Inserter tests', () => { +describe( 'Gutenberg Editor Slash Inserter tests', () => { it( 'should show the menu after typing /', async () => { await editorPage.addNewBlock( blockNames.paragraph ); - const paragraphBlockElement = await editorPage.getBlockAtPosition( + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); - if ( isAndroid() ) { - await paragraphBlockElement.click(); - } - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, slashInserter ); - await editorPage.driver.sleep( ANIMATION_TIME ); - - assertSlashInserterPresent( true ); + expect( await editorPage.assertSlashInserterPresent() ).toBe( true ); await editorPage.removeBlockAtPosition( blockNames.paragraph ); } ); it( 'should hide the menu after deleting the / character', async () => { await editorPage.addNewBlock( blockNames.paragraph ); - const paragraphBlockElement = await editorPage.getBlockAtPosition( + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); - if ( isAndroid() ) { - await paragraphBlockElement.click(); - } - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, slashInserter ); - await editorPage.driver.sleep( ANIMATION_TIME ); - assertSlashInserterPresent( true ); + expect( await editorPage.assertSlashInserterPresent() ).toBe( true ); // Remove / character. if ( isAndroid() ) { - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, `${ shortText }`, true ); } else { - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, `\b ${ shortText }`, false ); } - await editorPage.driver.sleep( ANIMATION_TIME ); // Check if the slash inserter UI no longer exists. - assertSlashInserterPresent( false ); + expect( await editorPage.assertSlashInserterPresent() ).toBe( false ); await editorPage.removeBlockAtPosition( blockNames.paragraph ); } ); it( 'should add an Image block after tying /image and tapping on the Image block button', async () => { await editorPage.addNewBlock( blockNames.paragraph ); - const paragraphBlockElement = await editorPage.getBlockAtPosition( + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); - if ( isAndroid() ) { - await paragraphBlockElement.click(); - } - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, `${ slashInserter }image` ); - await editorPage.driver.sleep( ANIMATION_TIME ); - assertSlashInserterPresent( true ); + expect( await editorPage.assertSlashInserterPresent() ).toBe( true ); // Find Image block button. const imageButtonElement = await editorPage.driver.elementByAccessibilityId( @@ -120,30 +83,27 @@ describe.skip( 'Gutenberg Editor Slash Inserter tests', () => { ).toBe( true ); // Slash inserter UI should not be present after adding a block. - assertSlashInserterPresent( false ); + expect( await editorPage.assertSlashInserterPresent() ).toBe( false ); // Remove image block. await editorPage.removeBlockAtPosition( blockNames.image ); } ); - it( 'should insert an image block with "/img" + enter', async () => { + it( 'should insert an embed image block with "/img" + enter', async () => { await editorPage.addNewBlock( blockNames.paragraph ); - const paragraphBlockElement = await editorPage.getBlockAtPosition( + const paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); - if ( isAndroid() ) { - await paragraphBlockElement.click(); - } - await editorPage.typeTextToParagraphBlock( + await editorPage.typeTextToTextBlock( paragraphBlockElement, '/img\n', false ); expect( - await editorPage.hasBlockAtPosition( 1, blockNames.image ) + await editorPage.hasBlockAtPosition( 1, blockNames.embed ) ).toBe( true ); - await editorPage.removeBlockAtPosition( blockNames.image ); + await editorPage.removeBlockAtPosition( blockNames.embed ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/helpers/utils.js b/packages/react-native-editor/__device-tests__/helpers/utils.js index 03f3002c59b142..1658d553c9c7a2 100644 --- a/packages/react-native-editor/__device-tests__/helpers/utils.js +++ b/packages/react-native-editor/__device-tests__/helpers/utils.js @@ -464,48 +464,102 @@ const waitForMediaLibrary = async ( driver ) => { await waitForVisible( driver, locator ); }; -const waitForVisible = async ( driver, elementLocator, iteration = 0 ) => { - const maxIteration = 25; +/** + * @param {string} driver + * @param {string} elementLocator + * @param {number} maxIteration - Default value is 25 + * @param {number} iteration - Default value is 0 + * @return {string} - Returns the first element found, empty string if not found + */ +const waitForVisible = async ( + driver, + elementLocator, + maxIteration = 25, + iteration = 0 +) => { const timeout = 1000; if ( iteration >= maxIteration ) { - throw new Error( + // if element not found, print error and return empty string + // eslint-disable-next-line no-console + console.error( `"${ elementLocator }" is still not visible after ${ iteration } retries!` ); + return ''; } else if ( iteration !== 0 ) { // wait before trying to locate element again await driver.sleep( timeout ); } - const locator = await driver.elementsByXPath( elementLocator ); - if ( locator.length !== 1 ) { + const element = await driver.elementsByXPath( elementLocator ); + if ( element.length !== 1 ) { // if locator is not visible, try again - return waitForVisible( driver, elementLocator, iteration + 1 ); + return waitForVisible( + driver, + elementLocator, + maxIteration, + iteration + 1 + ); + } + + return element[ 0 ]; +}; + +/** + * @param {string} driver + * @param {string} elementLocator + * @param {number} maxIteration - Default value is 25, can be adjusted to be less to wait for element to not be visible + * @return {boolean} - Returns true if element is found, false otherwise + */ +const isElementVisible = async ( + driver, + elementLocator, + maxIteration = 25 +) => { + const element = await waitForVisible( + driver, + elementLocator, + maxIteration + ); + + // if there is no element, return false + if ( ! element ) { + return false; + } + + return true; +}; + +// Only for Android +const waitIfAndroid = async () => { + if ( isAndroid() ) { + await editorPage.driver.sleep( 1000 ); } - return locator[ 0 ]; }; module.exports = { backspace, - timer, - setupDriver, - isLocalEnvironment, - isAndroid, - typeString, - clickMiddleOfElement, clickBeginningOfElement, + clickMiddleOfElement, + doubleTap, + isAndroid, + isEditorVisible, + isElementVisible, + isLocalEnvironment, longPressMiddleOfElement, - tapSelectAllAboveElement, - tapCopyAboveElement, - tapPasteAboveElement, + setupDriver, + stopDriver, swipeDown, - swipeUp, swipeFromTo, - stopDriver, + swipeUp, + tapCopyAboveElement, + tapPasteAboveElement, + tapSelectAllAboveElement, + timer, toggleHtmlMode, toggleOrientation, - doubleTap, - isEditorVisible, + typeString, waitForMediaLibrary, waitForVisible, + waitIfAndroid, }; 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 f408d8baf0fc8f..440e256566ef91 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -2,17 +2,18 @@ * Internal dependencies */ const { + doubleTap, + isAndroid, + isEditorVisible, + isElementVisible, + longPressMiddleOfElement, setupDriver, stopDriver, - isAndroid, - swipeUp, swipeDown, - typeString, - toggleHtmlMode, swipeFromTo, - longPressMiddleOfElement, - doubleTap, - isEditorVisible, + swipeUp, + toggleHtmlMode, + typeString, waitForVisible, } = require( '../helpers/utils' ); @@ -45,11 +46,15 @@ class EditorPage { return await this.driver.hasElementByAccessibilityId( 'block-list' ); } - // For text blocks, e.g. Paragraph, Heading + // =============================== + // Text blocks functions + // E.g. Paragraph, Heading blocks + // =============================== async getTextBlockAtPosition( blockName, position = 1 ) { - // iOS needs a click before + // iOS needs a click to get the text element if ( ! isAndroid() ) { const textBlockLocator = `(//XCUIElementTypeButton[contains(@name, "${ blockName } Block. Row ${ position }")])`; + const textBlock = await waitForVisible( this.driver, textBlockLocator @@ -64,6 +69,10 @@ class EditorPage { return await waitForVisible( this.driver, blockLocator ); } + async typeTextToTextBlock( block, text, clear ) { + await typeString( this.driver, block, text, clear ); + } + // Finds the wd element for new block that was added and sets the element attribute // and accessibilityId attributes on this object and selects the block // position uses one based numbering. @@ -506,10 +515,6 @@ class EditorPage { // Paragraph Block functions // ========================= - async typeTextToParagraphBlock( block, text, clear ) { - await typeString( this.driver, block, text, clear ); - } - async sendTextToParagraphBlock( position, text, clear ) { const paragraphs = text.split( '\n' ); for ( let i = 0; i < paragraphs.length; i++ ) { @@ -522,13 +527,9 @@ class EditorPage { await block.click(); } - await this.typeTextToParagraphBlock( - block, - paragraphs[ i ], - clear - ); + await this.typeTextToTextBlock( block, paragraphs[ i ], clear ); if ( i !== paragraphs.length - 1 ) { - await this.typeTextToParagraphBlock( block, '\n', false ); + await this.typeTextToTextBlock( block, '\n', false ); } } } @@ -542,35 +543,68 @@ class EditorPage { return await blockLocator.text(); } + async getNumberOfParagraphBlocks() { + const paragraphBlockLocator = isAndroid() + ? `//android.view.ViewGroup[contains(@content-desc, "Paragraph Block. Row")]//android.widget.EditText` + : `(//XCUIElementTypeButton[contains(@name, "Paragraph Block. Row")])`; + + const locator = await this.driver.elementsByXPath( + paragraphBlockLocator + ); + return locator.length; + } + + async assertSlashInserterPresent() { + const slashInserterLocator = isAndroid() + ? '//android.widget.HorizontalScrollView[@content-desc="Slash inserter results"]/android.view.ViewGroup' + : '(//XCUIElementTypeOther[@name="Slash inserter results"])[1]'; + + return await isElementVisible( this.driver, slashInserterLocator, 5 ); + } + // ========================= // List Block functions // ========================= - async getTextViewForListBlock( block ) { - let textViewElementName = 'XCUIElementTypeTextView'; - if ( isAndroid() ) { - textViewElementName = 'android.widget.EditText'; - } + async getListBlockAtPosition( + position = 1, + options = { isEmptyBlock: false } + ) { + // iOS needs a few extra steps to get the text element + if ( ! isAndroid() ) { + // Wait for and click the list in the correct position + let listBlock = await waitForVisible( + this.driver, + `(//XCUIElementTypeOther[contains(@name, "List Block. Row ${ position }")])[1]` + ); + await listBlock.click(); - const accessibilityId = await block.getAttribute( - this.accessibilityIdKey - ); - const blockLocator = `//*[@${ - this.accessibilityIdXPathAttrib - }=${ JSON.stringify( accessibilityId ) }]//${ textViewElementName }`; - return await this.driver.elementByXPath( blockLocator ); - } + const listBlockLocator = options.isEmptyBlock + ? `(//XCUIElementTypeStaticText[contains(@name, "List")])` + : `//XCUIElementTypeButton[contains(@name, "List")]`; - async sendTextToListBlock( block, text ) { - const textViewElement = await this.getTextViewForListBlock( block ); + // Wait for and click the list to get the text element + listBlock = await waitForVisible( this.driver, listBlockLocator ); + await listBlock.click(); + } - // Cannot clear list blocks because it messes up the list bullet. - const clear = false; + const listBlockTextLocatorIOS = options.isEmptyBlock + ? `(//XCUIElementTypeStaticText[contains(@name, "List")])` + : `//XCUIElementTypeButton[contains(@name, "List")]//XCUIElementTypeTextView`; - return await typeString( this.driver, textViewElement, text, clear ); + const listBlockTextLocator = isAndroid() + ? `//android.view.ViewGroup[contains(@content-desc, "List Block. Row ${ position }")]//android.widget.EditText` + : listBlockTextLocatorIOS; + + return await waitForVisible( this.driver, listBlockTextLocator ); } async clickOrderedListToolBarButton() { + const toolBarLocator = isAndroid() + ? `//android.widget.Button[@content-desc="${ this.orderedListButtonName }"]` + : `//XCUIElementTypeButton[@name="${ this.orderedListButtonName }"]`; + + await waitForVisible( this.driver, toolBarLocator ); await this.clickToolBarButton( this.orderedListButtonName ); } @@ -609,34 +643,6 @@ class EditorPage { await typeString( this.driver, imageBlockCaptionField, caption, clear ); } - // ========================= - // Heading Block functions - // ========================= - - // Inner element changes on iOS if Heading Block is empty - async getTextViewForHeadingBlock( block, empty ) { - let textViewElementName = empty - ? 'XCUIElementTypeStaticText' - : 'XCUIElementTypeTextView'; - if ( isAndroid() ) { - textViewElementName = 'android.widget.EditText'; - } - - const accessibilityId = await block.getAttribute( - this.accessibilityIdKey - ); - const blockLocator = `//*[@${ this.accessibilityIdXPathAttrib }="${ accessibilityId }"]//${ textViewElementName }`; - return await this.driver.elementByXPath( blockLocator ); - } - - async sendTextToHeadingBlock( block, text, clear = true ) { - const textViewElement = await this.getTextViewForHeadingBlock( - block, - true - ); - return await typeString( this.driver, textViewElement, text, clear ); - } - async closePicker() { if ( isAndroid() ) { // Wait for media block picker to load before closing @@ -760,34 +766,25 @@ class EditorPage { async sauceJobStatus( allPassed ) { await this.driver.sauceJobStatus( allPassed ); } - - async getNumberOfParagraphBlocks() { - const paragraphBlockLocator = isAndroid() - ? `//android.view.ViewGroup[contains(@content-desc, "Paragraph Block. Row")]//android.widget.EditText` - : `(//XCUIElementTypeButton[contains(@name, "Paragraph Block. Row")])`; - const locator = await this.driver.elementsByXPath( - paragraphBlockLocator - ); - return locator.length; - } } const blockNames = { - paragraph: 'Paragraph', - gallery: 'Gallery', + audio: 'Audio', columns: 'Columns', cover: 'Cover', + embed: 'Embed', + file: 'File', + gallery: 'Gallery', heading: 'Heading', image: 'Image', latestPosts: 'Latest Posts', list: 'List', more: 'More', + paragraph: 'Paragraph', + search: 'Search', separator: 'Separator', spacer: 'Spacer', verse: 'Verse', - file: 'File', - audio: 'Audio', - search: 'Search', }; module.exports = { initializeEditorPage, blockNames }; From 1b3391e790cd76310c9deb43e804201cf9f83542 Mon Sep 17 00:00:00 2001 From: Jason Johnston <jhnstn@users.noreply.github.com> Date: Thu, 12 May 2022 05:58:43 -0400 Subject: [PATCH 17/49] Add ruby version file (#41013) Use same version of ruby that is used on WP iOS --- packages/react-native-editor/ios/.ruby-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/react-native-editor/ios/.ruby-version diff --git a/packages/react-native-editor/ios/.ruby-version b/packages/react-native-editor/ios/.ruby-version new file mode 100644 index 00000000000000..a4dd9dba4fbfc5 --- /dev/null +++ b/packages/react-native-editor/ios/.ruby-version @@ -0,0 +1 @@ +2.7.4 From 148f835cc157124afe655cb52b9232eacbdc403a Mon Sep 17 00:00:00 2001 From: Siobhan Bamber <siobhan@automattic.com> Date: Mon, 23 May 2022 13:15:00 +0100 Subject: [PATCH 18/49] [RNMobile] Improvements to Getting Started Guides (#40964) * Update with copy from Gutenberg Mobile Gutenberg Mobile's readme had some copy in the troubleshooting section that wasn't included in Gutenberg's how-to: https://github.com/wordpress-mobile/gutenberg-mobile/edit/trunk/README.md This commit fixes that issue by updating Gutenberg's how-to with the latest from Gutenberg Mobile. * Tweak wording, update inconsistent capitalisation * Tweaks to copy and formatting With this commit, I've tidied up some of the copy, attempting to make the instructions as concise and helpful as possible. I've also set the images to the same width to tidy up the overall look of the document (the images will appear small on mobile devices, but can be tapped on to get the full image). * Set all images to fixed width, for consistency * Rearrange sections to improve overall flow - The "Unit Tests" is moved towards the bottom of the document, so that's it's closer to the instructions for integration tests. the document flows as follows: - The steps for installing Xcode are moved under the "iOS" section. - The overall flow of the document has been updated to the following: Clone Gutenberg > iOS > Android > Tests * Add details about recommended JDK * Add details about Cocoapods * Refine instructions for installing Cocoapods * Note `ffi` may already be installed Some versions of Ruby may not require the `ffi` to be manually installed, which is noted with this commit. --- .../getting-started-react-native.md | 2 + .../code/react-native/osx-setup-guide.md | 166 ++++++++++-------- 2 files changed, 90 insertions(+), 78 deletions(-) diff --git a/docs/contributors/code/react-native/getting-started-react-native.md b/docs/contributors/code/react-native/getting-started-react-native.md index 95a14e7eb50f5c..72cad307673861 100644 --- a/docs/contributors/code/react-native/getting-started-react-native.md +++ b/docs/contributors/code/react-native/getting-started-react-native.md @@ -72,6 +72,8 @@ To see a list of all of your available iOS devices, use `xcrun simctl list devic ### Troubleshooting +If the Android emulator doesn't start correctly, or compiling fails with `Could not initialize class org.codehaus.groovy.runtime.InvokerHelper` or similar, it may help to double check the set up of your development environment against the latest requirements in [React Native's documentation](https://reactnative.dev/docs/environment-setup). With Android Studio, for example, you will need to configure the `ANDROID_HOME` environment variable and ensure that your version of JDK matches the latest requirements. + Some times, and especially when tweaking anything in the `package.json`, Babel configuration (`.babelrc`) or the Jest configuration (`jest.config.js`), your changes might seem to not take effect as expected. On those times, you might need to stop the metro bunder process and restart it with `npm run native start:reset`. Other times, you might want to reinstall the NPM packages from scratch and the `npm run native clean:install` script can be handy. ## Developing with Visual Studio Code diff --git a/docs/contributors/code/react-native/osx-setup-guide.md b/docs/contributors/code/react-native/osx-setup-guide.md index 3d68364295f5dd..4f1d160a4b2b61 100644 --- a/docs/contributors/code/react-native/osx-setup-guide.md +++ b/docs/contributors/code/react-native/osx-setup-guide.md @@ -1,47 +1,22 @@ # Setup guide for React Native development (macOS) -Are you interested in contributing to the native mobile editor? This -guide is a detailed walk through designed to get you up and running! +Are you interested in contributing to the native mobile editor? This guide is a detailed walk through designed to get you up and running! -Note that the following instructions here are primarily focused on the -macOS environment. For other environments, [the React Native quickstart documentation](https://reactnative.dev/docs/environment-setup) -has helpful pointers and steps for getting set up. - -## Install Xcode - -Install [Xcode](https://developer.apple.com/xcode/) via the app store. We'll be using -the XCode to both compile the iOS app and use the phone simulator app. - -Once it has been installed from the App Store, open it by visiting `Applications > Xcode` - -After opening the application: - -- Accept the license agreement. -- Verify that `Xcode > Preferences > Locations > Command Line Tools` points at the current Xcode version. - -<img src="https://developer.wordpress.org/files/2021/10/xcode-command-line-tools.png" width="500" alt="Screenshot of XCode command line tools settings."> +Note that these instructions are primarily focused on the macOS environment. For other environments, [the React Native quickstart documentation](https://reactnative.dev/docs/environment-setup) has helpful pointers and steps for getting set up. ## Clone Gutenberg -If Xcode was installed successfully, we'll also have a version of git available. (It's possible to -update this later if we want to use a more recent version). - -In a terminal run: - ```sh git clone git@github.com:WordPress/gutenberg.git ``` ### Install node and npm -If you’re working in multiple JS projects, a node version manager may make sense. A manager will let you -switch between different node and npm versions of your choosing. - -Some good options are [nvm](https://github.com/nvm-sh/nvm) or [volta](https://volta.sh/). +If you’re working in multiple JS projects, a node version manager may make sense. A manager will let you switch between different node and npm versions of your choosing. -Pick one and follow the install instructions noted on their website. +We recommend [nvm](https://github.com/nvm-sh/nvm). -Then run: +After installing nvm, run the following from the top-level directory of the cloned project: ```sh nvm install 'lts/*' @@ -49,19 +24,13 @@ nvm alias default 'lts/*' # sets this as the default when opening a new terminal nvm use # switches to the project settings ``` -Or - -```sh -volta install node #defaults to installing lts -``` - -Then install dependencies from your Gutenberg checkout folder: +Then install dependencies: ``` npm ci ``` -#### Do you have an older existing Gutenberg checkout? +### Do you have an older existing Gutenberg checkout? If you have an existing Gutenberg checkout be sure to fully clean `node_modules` and re-install dependencies. This may help avoid errors in the future. @@ -71,28 +40,56 @@ npm run distclean npm ci ``` -## Unit Tests +## iOS -Unit tests should work at this point. +### CocoaPods -```sh -npm run native test +[CocoaPods](https://guides.cocoapods.org/using/getting-started.html) is required to fetch React and third-party dependencies. The steps to install it vary depending on how Ruby is managed on your machine. + +#### System Ruby + +If you're using the default Ruby available with MacOS, you'll need to use the `sudo` command to install gems like Cocoapods: + +``` +sudo gem install cocoapods ``` -## iOS +Note, Mac M1 is not directly compatible with Cocoapods. If you encounter issues, try running these commands to install the ffi package, which will enable pods to be installed with the proper architecture: + +``` +sudo arch -x86_64 gem install ffi +arch -x86_64 pod install +``` + +#### Ruby Manager + +It may not be necessary to manually install Cocoapods or the `ffi` package if you're using a Ruby Version manager. Please refer to your chosen manager's documentation for guidance. + +[`rbenv`](https://github.com/rbenv/rbenv) is the recommended manager if you're running Gutenberg from within [the WordPress iOS app](https://github.com/wordpress-mobile/WordPress-iOS) (vs. only the demo app). -The easiest way to figure out what needs to be installed is using the -[react native doctor](https://reactnative.dev/blog/2019/11/18/react-native-doctor). From your checkout, or -relative to `/packages/react-native-editor folder`, run: +### Set up Xcode + +Install [Xcode](https://developer.apple.com/xcode/) via the app store and then open it up: + +- Accept the license agreement. +- Verify that `Xcode > Preferences > Locations > Command Line Tools` points to the current Xcode version. + +<img src="https://developer.wordpress.org/files/2021/10/xcode-command-line-tools.png" width="700px" alt="Screenshot of XCode command line tools settings."> + +### react-native doctor + +[react-native doctor](https://reactnative.dev/blog/2019/11/18/react-native-doctor) can be used to identify anything that's missing from your development environment. From your Gutenberg checkout, or relative to `/packages/react-native-editor folder`, run: ```sh npx @react-native-community/cli doctor ``` -<img src="https://developer.wordpress.org/files/2021/10/react-native-doctor.png" width="500px" alt="Screenshot of react-native-community/cli doctor tool running in the terminal."> +<img src="https://developer.wordpress.org/files/2021/10/react-native-doctor.png" width="700px" alt="Screenshot of react-native-community/cli doctor tool running in the terminal."> See if `doctor` can fix both "common" and "iOS" issues. (Don't worry if "Android" still has ❌s at this stage, we'll get to those later!) +### Run the demo app + Once all common and iOS issues are resolved, try: ``` @@ -105,37 +102,46 @@ In another terminal type: npm run native ios ``` -After waiting for everything to build we should see: +After waiting for everything to build, the demo app should be running from the iOS simulator: -<img src="https://developer.wordpress.org/files/2021/10/iOS-Simulator.png" alt="Screenshot of the block editor in iOS simulator." /> +<img src="https://developer.wordpress.org/files/2021/10/iOS-Simulator.png" width="700px" alt="Screenshot of the block editor in iOS simulator." /> ## Android -To keep things simple, let's use Android Studio for all JDK and SDK package management. -The first step is [downloading Android Studio](https://developer.android.com/studio). +### Java Development Kit (JDK) -Next, open an existing project and select the gutenberg folder you cloned: +The JDK recommended in [the React Native documentation](https://reactnative.dev/docs/environment-setup) is called Azul Zulu. It can be installed using [Homebrew](https://brew.sh/). To install it, run the following commands in a terminal after installing Homebrew: -Click on the cube with the down arrow: +``` +brew tap homebrew/cask-versions +brew install --cask zulu11 +``` -<img src="https://developer.wordpress.org/files/2021/10/react-native-package-manager.png" alt="Screenshot highlighting where the package manager button is located in Android Studio."> +If you already have a JDK installed on your system, it should be JDK 11 or newer. -We can download SDK platforms, packages and other tools on this screen. Specific versions are -hidden behind the "Show package details" checkbox, check it, since our build requires specific versions for E2E and -development: +### Set up Android Studio -<img src="https://developer.wordpress.org/files/2021/10/react-native-show-package-details.png" alt="Screenshot of the package manager in Android Studio, highlighting the Show Package Details checkbox."> +To compile the Android app, [download Android Studio](https://developer.android.com/studio). -Check all related packages from [build.gradle](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/android/build.gradle). -Then click on "Apply" to download items. There may be other related dependencies from build.gradle files in node_modules. -If you don’t want to dig through files, stack traces will complain of missing packages, but it does take quite a number -of tries if you go through this route. +Next, open an existing project and select the Gutenberg folder you cloned. -<img src="https://developer.wordpress.org/files/2021/10/react-native-editor-build-gradle.png" alt="Screenshot of the build.gradle configuration file."> +From here, click on the cube icon that's highlighted in the following screenshot to access the SDK Manager. Another way to the SDK Manager is to navigate to `Tools > SDK Manager`: -<img src="https://developer.wordpress.org/files/2021/10/react-native-sdk.png" width="500" alt="Screenshot of the package manager displaying SDK Platforms."> +<img src="https://developer.wordpress.org/files/2021/10/react-native-package-manager.png" width="700px" alt="Screenshot highlighting where the package manager button is located in Android Studio."> -<img src="https://developer.wordpress.org/files/2021/10/react-native-sdk-tools.png" width="500" alt="Screenshot of the package manager displaying SDK Tools."> +We can download SDK platforms, packages and other tools on this screen. Specific versions are hidden behind the "Show package details" checkbox, check it, since our build requires specific versions for E2E and development: + +<img src="https://developer.wordpress.org/files/2021/10/react-native-show-package-details.png" width="700px" alt="Screenshot of the package manager in Android Studio, highlighting the Show Package Details checkbox."> + +Check all related packages from [build.gradle](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/android/build.gradle). Then click on "Apply" to download items. There may be other related dependencies from build.gradle files in node_modules. + +If you don’t want to dig through files, stack traces will complain of missing packages, but it does take quite a number of tries if you go through this route. + +<img src="https://developer.wordpress.org/files/2021/10/react-native-editor-build-gradle.png" width="700px" alt="Screenshot of the build.gradle configuration file."> + +<img src="https://developer.wordpress.org/files/2021/10/react-native-sdk.png" width="700px" alt="Screenshot of the package manager displaying SDK Platforms."> + +<img src="https://developer.wordpress.org/files/2021/10/react-native-sdk-tools.png" width="700px" alt="Screenshot of the package manager displaying SDK Tools."> ### Update Paths @@ -167,26 +173,25 @@ source ~/.bash_profile If the SDK path can't be found, you can verify its location by visiting Android Studio > Preferences > System Settings > Android SDK -<img src="https://developer.wordpress.org/files/2021/10/sdk-path.png" alt="Screenshot of where the SDK Path may be found in Android Studio."> +<img src="https://developer.wordpress.org/files/2021/10/sdk-path.png" width="700px" alt="Screenshot of where the SDK Path may be found in Android Studio."> ### Create a new device image Next, let’s create a virtual device image. Click on the phone icon with an android to the bottom-right. -<img src="https://developer.wordpress.org/files/2021/10/react-native-android-device-manager-button.png" alt="Screenshot of where to find the android device manager button."> +<img src="https://developer.wordpress.org/files/2021/10/react-native-android-device-manager-button.png" width="700px" alt="Screenshot of where to find the android device manager button."> This brings up the “Android Virtual Device Manager” or (AVD). Click on “Create Virtual Device”. Pick a phone type of your choice: -<img src="https://developer.wordpress.org/files/2021/10/react-native-android-select-hardware.png" alt="Screenshot of the Virtual Device Configuration setup."> +<img src="https://developer.wordpress.org/files/2021/10/react-native-android-select-hardware.png" width="700px" alt="Screenshot of the Virtual Device Configuration setup."> -Pick the target SDK version. This is the targetSdkVersion set in the -[build.gradle](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/android/build.gradle) file. +Pick the target SDK version. This is the targetSdkVersion set in the [build.gradle](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/android/build.gradle) file. -<img src="https://developer.wordpress.org/files/2021/10/react-native-adv-system-image.png" alt="Screenshot of picking a system image in the Android Device Manager workflow."> +<img src="https://developer.wordpress.org/files/2021/10/react-native-adv-system-image.png" width="700px" alt="Screenshot of picking a system image in the Android Device Manager workflow."> There are some advanced settings we can toggle, but these are optional. Click finish. -### Putting it all together +### Run the demo app Start metro: @@ -202,7 +207,13 @@ npm run native android After a bit of a wait, we’ll see something like this: -<img src="https://developer.wordpress.org/files/2021/10/android-simulator.png" alt="Screenshot of a the block editor in Android Simulator."> +<img src="https://developer.wordpress.org/files/2021/10/android-simulator.png" width="700px" alt="Screenshot of a the block editor in Android Simulator."> + +## Unit Tests + +```sh +npm run native test +``` ## Integration Tests @@ -212,14 +223,13 @@ After a bit of a wait, we’ll see something like this: npx appium-doctor ``` -<img src="https://developer.wordpress.org/files/2021/10/CleanShot-2021-10-27-at-15.20.16.png" width="500px" alt="Screenshot of the appium-doctor tool running in the terminal."> +<img src="https://developer.wordpress.org/files/2021/10/CleanShot-2021-10-27-at-15.20.16.png" width="700px" alt="Screenshot of the appium-doctor tool running in the terminal."> Resolve any required dependencies. ### iOS Integration Tests -If we know we can run the iOS local environment without issue, E2Es for iOS are straightforward. Stop any running metro processes. -This was launched previously with `npm run native start:reset`. +If we know we can run the iOS local environment without issue, E2Es for iOS are straightforward. Stop any running metro processes. This was launched previously with `npm run native start:reset`. Then in terminal type: @@ -243,7 +253,7 @@ If all things go well, it should look like: Start the virtual device first. Go back to the AVD by clicking on the phone icon, then click the green play button. -<img src="https://developer.wordpress.org/files/2021/10/adv-integration.png" alt="A screenshot of how to start the Android Simulator."> +<img src="https://developer.wordpress.org/files/2021/10/adv-integration.png" width="700px" alt="A screenshot of how to start the Android Simulator."> Make sure no metro processes are running. This was launched previously with `npm run native start:reset`. @@ -261,4 +271,4 @@ npm run native test:e2e:android:local gutenberg-editor-paragraph.test.js After a bit of a wait we should see: -<img src="https://developer.wordpress.org/files/2021/10/CleanShot-2021-10-27-at-15.28.22.png" alt="A screenshot of block editor integration tests in Android Simulator."> +<img src="https://developer.wordpress.org/files/2021/10/CleanShot-2021-10-27-at-15.28.22.png" width="700px" alt="A screenshot of block editor integration tests in Android Simulator."> From a046a618ee1e38cbc472799afbeb01e6604b5913 Mon Sep 17 00:00:00 2001 From: Jos <jostnes@users.noreply.github.com> Date: Wed, 25 May 2022 17:25:00 +0800 Subject: [PATCH 19/49] update expected html for file block (#41300) Co-authored-by: jos <17252150+jostnes@users.noreply.github.com> --- .../react-native-editor/__device-tests__/helpers/test-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-editor/__device-tests__/helpers/test-data.js b/packages/react-native-editor/__device-tests__/helpers/test-data.js index b7dcf9267e40d6..99fea16d643fce 100644 --- a/packages/react-native-editor/__device-tests__/helpers/test-data.js +++ b/packages/react-native-editor/__device-tests__/helpers/test-data.js @@ -135,7 +135,7 @@ exports.coverHeightWithRemUnit = `<!-- wp:cover {"customOverlayColor":"#ffffff", <!-- /wp:cover -->`; exports.fileBlockPlaceholder = `<!-- wp:file {"id":3,"href":"https://wordpress.org/latest.zip"} --> -<div class="wp-block-file"><a href="https://wordpress.org/latest.zip">WordPress.zip</a><a href="https://wordpress.org/latest.zip" class="wp-block-file__button" download>Download</a></div> +<div class="wp-block-file"><a href="https://wordpress.org/latest.zip">WordPress.zip</a><a href="https://wordpress.org/latest.zip" class="wp-block-file__button wp-element-button" download>Download</a></div> <!-- /wp:file -->`; exports.audioBlockPlaceholder = `<!-- wp:audio {"id":5} --> From ede6624935f67df415872e5433ab9da3170325d3 Mon Sep 17 00:00:00 2001 From: Jos <jostnes@users.noreply.github.com> Date: Wed, 25 May 2022 13:47:27 +0800 Subject: [PATCH 20/49] Add waitForVisible() to all blocks (#41126) * add waits to all block - 1st try * fix failing tests * correct if else condition * fix failing cover block test * update spaces * update to use new click helper * wait for blocks to be visible first in getFirstBlock and getLastBlock * remove length as a parameter * update timing for long press since it's failing intermittently in ci * remove deleted param, revert space changes * remove redundant code * exit function once condition is met * increse wait time for long press * remove unneccesary condition, update message, return click value Co-authored-by: jos <17252150+jostnes@users.noreply.github.com> --- .../gutenberg-editor-block-insertion.test.js | 7 - .../gutenberg-editor-cover.test.js | 90 +++------- .../gutenberg-editor-image-@canary.test.js | 6 +- .../gutenberg-editor-search.test.js | 2 + .../gutenberg-editor-spacer.test.js | 4 +- ...utenberg-editor-unsupported-blocks.test.js | 5 +- .../__device-tests__/helpers/utils.js | 75 ++++++-- .../__device-tests__/pages/editor-page.js | 167 ++++++++++-------- 8 files changed, 174 insertions(+), 182 deletions(-) diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js index ead25e7bee9a2e..4dfbf56f52a5a5 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js @@ -39,9 +39,6 @@ describe( 'Gutenberg Editor tests for Block insertion', () => { testData.blockInsertionHtml.toLowerCase() ); - // wait for the block editor to load and for accessibility ids to update - await editorPage.driver.sleep( 3000 ); - // Workaround for now since deleting the first element causes a crash on CI for Android if ( isAndroid() ) { paragraphBlockElement = await editorPage.getTextBlockAtPosition( @@ -55,8 +52,6 @@ describe( 'Gutenberg Editor tests for Block insertion', () => { await paragraphBlockElement.click(); await editorPage.removeBlockAtPosition( blockNames.paragraph, 3 ); for ( let i = 3; i > 0; i-- ) { - // wait for accessibility ids to update - await editorPage.driver.sleep( 1000 ); paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph, i, @@ -72,8 +67,6 @@ describe( 'Gutenberg Editor tests for Block insertion', () => { } } else { for ( let i = 4; i > 0; i-- ) { - // wait for accessibility ids to update - await editorPage.driver.sleep( 1000 ); paragraphBlockElement = await editorPage.getTextBlockAtPosition( blockNames.paragraph ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-cover.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-cover.test.js index 8d6eaa429a9e80..4905936f7f8f3c 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-cover.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-cover.test.js @@ -2,95 +2,49 @@ * Internal dependencies */ import { blockNames } from './pages/editor-page'; -import { isAndroid, waitForVisible } from './helpers/utils'; +import { isAndroid } from './helpers/utils'; import testData from './helpers/test-data'; describe( 'Gutenberg Editor Cover Block test', () => { it( 'should displayed properly and have properly converted height (ios only)', async () => { - await editorPage.setHtmlContent( testData.coverHeightWithRemUnit ); - - const coverBlock = await editorPage.getBlockAtPosition( - blockNames.cover, - 1, - { useWaitForVisible: true } - ); - // Temporarily this test is skipped on Android,due to the inconsistency of the results, // which are related to getting values in raw pixels instead of density pixels on Android. /* eslint-disable jest/no-conditional-expect */ if ( ! isAndroid() ) { + await editorPage.setHtmlContent( testData.coverHeightWithRemUnit ); + + const coverBlock = await editorPage.getBlockAtPosition( + blockNames.cover + ); + const { height } = await coverBlock.getSize(); // Height is set to 20rem, where 1rem is 16. // There is also block's vertical padding equal 32. // Finally, the total height should be 20 * 16 + 32 = 352. expect( height ).toBe( 352 ); - } - /* eslint-enable jest/no-conditional-expect */ + /* eslint-enable jest/no-conditional-expect */ - await coverBlock.click(); - expect( coverBlock ).toBeTruthy(); - await editorPage.removeBlockAtPosition( blockNames.cover ); + await coverBlock.click(); + expect( coverBlock ).toBeTruthy(); + await editorPage.removeBlockAtPosition( blockNames.cover ); + } } ); // Testing this for iOS on a device is valuable to ensure that it properly // handles opening multiple modals, as only one can be open at a time. it( 'allows modifying media from within block settings', async () => { - await editorPage.setHtmlContent( testData.coverHeightWithRemUnit ); - - const coverBlock = await editorPage.getBlockAtPosition( - blockNames.cover, - 1, - { useWaitForVisible: true } - ); - await coverBlock.click(); - // Can only add image from media library on iOS if ( ! isAndroid() ) { - // Open block settings. - const settingsButton = await editorPage.driver.elementByAccessibilityId( - 'Open Settings' - ); - await settingsButton.click(); + await editorPage.setHtmlContent( testData.coverHeightWithRemUnit ); - // Add initial media via button within bottom sheet. - const mediaSection = await editorPage.driver.elementByAccessibilityId( - 'Media Add image or video' - ); - const addMediaButton = await mediaSection.elementByAccessibilityId( - 'Add image or video' + const coverBlock = await editorPage.getBlockAtPosition( + blockNames.cover ); - await addMediaButton.click(); - await editorPage.chooseMediaLibrary(); - await editorPage.driver.sleep( 2000 ); // Await media load. - // Get Edit image button of block - const editImageButtonLocator = - '//XCUIElementTypeButton[@name="Edit image"][@enabled="true"]'; - const blockEditImageButton = await waitForVisible( - editorPage.driver, - editImageButtonLocator - ); - - // Edit media within block settings. - await settingsButton.click(); - await editorPage.driver.sleep( 2000 ); // Await media load. - - // Get Edit image button of block settings. - // NOTE: Since we have multiple Edit image buttons at this - // point, we have to filter them to obtain the correct one. - const settingsEditImageButtons = await editorPage.driver.elementsByXPath( - editImageButtonLocator - ); - const settingsEditImageButton = settingsEditImageButtons.find( - ( element ) => element.value !== blockEditImageButton.value - ); - await settingsEditImageButton.click(); - - // Replace image. - const replaceButton = await editorPage.driver.elementByAccessibilityId( - 'Replace' - ); - await replaceButton.click(); + await editorPage.openBlockSettings( coverBlock ); + await editorPage.clickAddMediaFromCoverBlock(); + await editorPage.chooseMediaLibrary(); + await editorPage.replaceMediaImage(); // First modal should no longer be presented. const replaceButtons = await editorPage.driver.elementsByAccessibilityId( @@ -101,9 +55,9 @@ describe( 'Gutenberg Editor Cover Block test', () => { // Select different media. await editorPage.chooseMediaLibrary(); - } - expect( coverBlock ).toBeTruthy(); - await editorPage.removeBlockAtPosition( blockNames.cover ); + expect( coverBlock ).toBeTruthy(); + await editorPage.removeBlockAtPosition( blockNames.cover ); + } } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-image-@canary.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-image-@canary.test.js index 03a929d5c12094..d07e8a3ea2cb26 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-image-@canary.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-image-@canary.test.js @@ -11,11 +11,7 @@ describe( 'Gutenberg Editor Image Block tests', () => { await editorPage.closePicker(); const imageBlock = await editorPage.getBlockAtPosition( - blockNames.image, - 1, - { - useWaitForVisible: true, - } + blockNames.image ); // Can only add image from media library on iOS diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-search.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-search.test.js index 7387e4b7125bc1..2233b56f6c37da 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-search.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-search.test.js @@ -124,6 +124,7 @@ describe( 'Gutenberg Editor Search Block tests.', () => { searchBlock, 'Button inside' ); + await editorPage.isSearchSettingsVisible(); await editorPage.dismissBottomSheet(); // Switch to html and verify. @@ -141,6 +142,7 @@ describe( 'Gutenberg Editor Search Block tests.', () => { searchBlock, 'No button' ); + await editorPage.isSearchSettingsVisible(); await editorPage.dismissBottomSheet(); // Switch to html and verify. diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-spacer.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-spacer.test.js index d234e9ae1dcae5..721ae86ea7eee4 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-spacer.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-spacer.test.js @@ -7,9 +7,7 @@ describe( 'Gutenberg Editor Spacer Block test', () => { it( 'should be able to add a spacer block', async () => { await editorPage.addNewBlock( blockNames.spacer ); const spacerBlock = await editorPage.getBlockAtPosition( - blockNames.spacer, - 1, - { useWaitForVisible: true } + blockNames.spacer ); expect( spacerBlock ).toBeTruthy(); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-unsupported-blocks.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-unsupported-blocks.test.js index 6e1be2a13dddb6..e2ae927d4f9449 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-unsupported-blocks.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-unsupported-blocks.test.js @@ -16,8 +16,7 @@ describe( 'Gutenberg Editor Unsupported Block Editor Tests', () => { const editButton = await editorPage.getUnsupportedBlockBottomSheetEditButton(); await editButton.click(); - await expect( - editorPage.getUnsupportedBlockWebView() - ).resolves.toBeTruthy(); + const webView = await editorPage.getUnsupportedBlockWebView(); + await expect( webView ).toBeTruthy(); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/helpers/utils.js b/packages/react-native-editor/__device-tests__/helpers/utils.js index 1658d553c9c7a2..b3225a03cfbae0 100644 --- a/packages/react-native-editor/__device-tests__/helpers/utils.js +++ b/packages/react-native-editor/__device-tests__/helpers/utils.js @@ -310,7 +310,8 @@ const longPressMiddleOfElement = async ( driver, element ) => { const x = location.x + size.width / 2; const y = location.y + size.height / 2; action.press( { x, y } ); - action.wait( 2000 ); + // Setting to wait a bit longer because this is failing more frequently on the CI + action.wait( 5000 ); action.release(); await action.perform(); }; @@ -419,24 +420,28 @@ const toggleHtmlMode = async ( driver, toggleOn ) => { const showHtmlButtonXpath = '/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.ListView/android.widget.TextView[9]'; - const showHtmlButton = await driver.elementByXPath( - showHtmlButtonXpath + + await clickIfClickable( driver, showHtmlButtonXpath ); + } else if ( toggleOn ) { + await clickIfClickable( + driver, + '//XCUIElementTypeButton[@name="..."]' + ); + await clickIfClickable( + driver, + '//XCUIElementTypeButton[@name="Switch to HTML"]' ); - await showHtmlButton.click(); } else { - const menuButton = await driver.elementByAccessibilityId( '...' ); - await menuButton.click(); - let toggleHtmlButton; - if ( toggleOn ) { - toggleHtmlButton = await driver.elementByAccessibilityId( - 'Switch to HTML' - ); - } else { - toggleHtmlButton = await driver.elementByAccessibilityId( - 'Switch To Visual' - ); - } - await toggleHtmlButton.click(); + // This is to wait for the clipboard paste notification to disappear, currently it overlaps with the menu button + await driver.sleep( 3000 ); + await clickIfClickable( + driver, + '//XCUIElementTypeButton[@name="..."]' + ); + await clickIfClickable( + driver, + '//XCUIElementTypeButton[@name="Switch To Visual"]' + ); } }; @@ -492,7 +497,7 @@ const waitForVisible = async ( } const element = await driver.elementsByXPath( elementLocator ); - if ( element.length !== 1 ) { + if ( element.length === 0 ) { // if locator is not visible, try again return waitForVisible( driver, @@ -530,6 +535,39 @@ const isElementVisible = async ( return true; }; +const clickIfClickable = async ( + driver, + elementLocator, + maxIteration = 25, + iteration = 0 +) => { + const element = await waitForVisible( + driver, + elementLocator, + maxIteration, + iteration + ); + + try { + return await element.click(); + } catch ( error ) { + if ( iteration >= maxIteration ) { + // eslint-disable-next-line no-console + console.error( + `"${ elementLocator }" still not clickable after "${ iteration }" retries` + ); + return ''; + } + + return clickIfClickable( + driver, + elementLocator, + maxIteration, + iteration + 1 + ); + } +}; + // Only for Android const waitIfAndroid = async () => { if ( isAndroid() ) { @@ -540,6 +578,7 @@ const waitIfAndroid = async () => { module.exports = { backspace, clickBeginningOfElement, + clickIfClickable, clickMiddleOfElement, doubleTap, isAndroid, 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 440e256566ef91..f7e69057ece2a2 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -15,6 +15,7 @@ const { toggleHtmlMode, typeString, waitForVisible, + clickIfClickable, } = require( '../helpers/utils' ); const initializeEditorPage = async () => { @@ -79,31 +80,24 @@ class EditorPage { async getBlockAtPosition( blockName, position = 1, - options = { autoscroll: false, useWaitForVisible: false } + options = { autoscroll: false } ) { - let blockLocator; - - // Make it optional to use waitForVisible() so we can handle this test by test. - // This condition can be removed once we have gone through all test cases. - if ( options.useWaitForVisible ) { - let elementType; - switch ( blockName ) { - case blockNames.cover: - elementType = 'XCUIElementTypeButton'; - break; - default: - elementType = 'XCUIElementTypeOther'; - break; - } + let elementType; + switch ( blockName ) { + case blockNames.cover: + elementType = 'XCUIElementTypeButton'; + break; + default: + elementType = 'XCUIElementTypeOther'; + break; + } - blockLocator = isAndroid() - ? `//android.view.ViewGroup[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }")]` - : `(//${ elementType }[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }")])[1]`; + const blockLocator = isAndroid() + ? `//android.view.ViewGroup[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }")]` + : `(//${ elementType }[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }")])[1]`; + + await waitForVisible( this.driver, blockLocator ); - await waitForVisible( this.driver, blockLocator ); - } else { - blockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }")]`; - } const elements = await this.driver.elementsByXPath( blockLocator ); const lastElementFound = elements[ elements.length - 1 ]; if ( elements.length === 0 && options.autoscroll ) { @@ -151,12 +145,12 @@ class EditorPage { async getFirstBlockVisible() { const firstBlockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, " Block. Row ")]`; - const elements = await this.driver.elementsByXPath( firstBlockLocator ); - return elements[ 0 ]; + return await waitForVisible( this.driver, firstBlockLocator ); } async getLastBlockVisible() { const firstBlockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, " Block. Row ")]`; + await waitForVisible( this.driver, firstBlockLocator ); const elements = await this.driver.elementsByXPath( firstBlockLocator ); return elements[ elements.length - 1 ]; } @@ -164,9 +158,7 @@ class EditorPage { async hasBlockAtPosition( position = 1, blockName = '' ) { return ( undefined !== - ( await this.getBlockAtPosition( blockName, position, { - useWaitForVisible: true, - } ) ) + ( await this.getBlockAtPosition( blockName, position ) ) ); } @@ -252,19 +244,18 @@ class EditorPage { // Sometimes double tap is not enough for paste menu to appear, so we also long press. await longPressMiddleOfElement( this.driver, htmlContentView ); - const pasteButton = this.driver.elementByXPath( + const pasteButton = await waitForVisible( + this.driver, '//XCUIElementTypeMenuItem[@name="Paste"]' ); await pasteButton.click(); - await this.driver.sleep( 3000 ); // Wait for paste notification to disappear. } await toggleHtmlMode( this.driver, false ); } async dismissKeyboard() { - await this.driver.sleep( 1000 ); // Wait for any keyboard animations. const keyboardShown = await this.driver.isKeyboardShown(); if ( ! keyboardShown ) { return; @@ -325,11 +316,7 @@ class EditorPage { ? '//android.widget.Button[@content-desc="Add Block Before"]' : '//XCUIElementTypeButton[@name="Add Block Before"]'; - const addBlockBeforeButton = await waitForVisible( - this.driver, - addBlockBeforeButtonLocator - ); - await addBlockBeforeButton.click(); + await clickIfClickable( this.driver, addBlockBeforeButtonLocator ); } else { await addButton.click(); } @@ -486,9 +473,7 @@ class EditorPage { blockActionsMenuButtonLocator ); if ( isAndroid() ) { - const block = await this.getBlockAtPosition( blockName, position, { - useWaitForVisible: true, - } ); + const block = await this.getBlockAtPosition( blockName, position ); let checkList = await this.driver.elementsByXPath( blockActionsMenuButtonLocator ); @@ -572,20 +557,17 @@ class EditorPage { ) { // iOS needs a few extra steps to get the text element if ( ! isAndroid() ) { - // Wait for and click the list in the correct position - let listBlock = await waitForVisible( + // Click the list in the correct position + await clickIfClickable( this.driver, `(//XCUIElementTypeOther[contains(@name, "List Block. Row ${ position }")])[1]` ); - await listBlock.click(); const listBlockLocator = options.isEmptyBlock ? `(//XCUIElementTypeStaticText[contains(@name, "List")])` : `//XCUIElementTypeButton[contains(@name, "List")]`; - // Wait for and click the list to get the text element - listBlock = await waitForVisible( this.driver, listBlockLocator ); - await listBlock.click(); + await clickIfClickable( this.driver, listBlockLocator ); } const listBlockTextLocatorIOS = options.isEmptyBlock @@ -608,6 +590,33 @@ class EditorPage { await this.clickToolBarButton( this.orderedListButtonName ); } + // ========================= + // Cover Block functions + // For iOS only + // ========================= + + async clickAddMediaFromCoverBlock() { + const mediaSection = await waitForVisible( + this.driver, + '//XCUIElementTypeOther[@name="Media Add image or video"]' + ); + const addMediaButton = await mediaSection.elementByAccessibilityId( + 'Add image or video' + ); + await addMediaButton.click(); + } + + async replaceMediaImage() { + await clickIfClickable( + this.driver, + '(//XCUIElementTypeButton[@name="Edit image"])[1]' + ); + await clickIfClickable( + this.driver, + '//XCUIElementTypeButton[@name="Replace"]' + ); + } + // ========================= // Image Block functions // ========================= @@ -687,10 +696,12 @@ class EditorPage { const elementName = isAndroid() ? '//*' : '//XCUIElementTypeOther'; const locator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Hide search heading")]`; - return await this.driver - .elementByXPath( locator ) - .click() - .sleep( isAndroid() ? 200 : 0 ); + const hideSearchHeadingToggle = await waitForVisible( + this.driver, + locator + ); + + return await hideSearchHeadingToggle.click(); } async changeSearchButtonPositionSetting( block, buttonPosition ) { @@ -699,13 +710,16 @@ class EditorPage { const elementName = isAndroid() ? '//*' : '//XCUIElementTypeButton'; const locator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Button position")]`; - await this.driver.elementByXPath( locator ).click(); + let optionMenuButton = await waitForVisible( this.driver, locator ); + await optionMenuButton.click(); const optionMenuButtonLocator = `${ elementName }[contains(@${ this.accessibilityIdXPathAttrib }, "${ buttonPosition }")]`; - return await this.driver - .elementByXPath( optionMenuButtonLocator ) - .click() - .sleep( isAndroid() ? 600 : 200 ); // sleep a little longer due to multiple menus. + optionMenuButton = await waitForVisible( + this.driver, + optionMenuButtonLocator + ); + + return await optionMenuButton.click(); } async toggleSearchIconOnlySetting( block ) { @@ -714,10 +728,16 @@ class EditorPage { const elementName = isAndroid() ? '//*' : '//XCUIElementTypeOther'; const locator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Use icon button")]`; - return await this.driver - .elementByXPath( locator ) - .click() - .sleep( isAndroid() ? 200 : 0 ); + const useIconButton = await waitForVisible( this.driver, locator ); + + return await useIconButton.click(); + } + + async isSearchSettingsVisible() { + const elementName = isAndroid() ? '//*' : '//XCUIElementTypeButton'; + const buttonPositionLocator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Button position")]`; + + return await waitForVisible( this.driver, buttonPositionLocator ); } // ============================= @@ -726,37 +746,28 @@ class EditorPage { async getUnsupportedBlockHelpButton() { const accessibilityId = 'Help button'; - let blockLocator = - '//android.widget.Button[@content-desc="Help button, Tap here to show help"]'; + const blockLocator = isAndroid() + ? `//android.widget.Button[starts-with(@content-desc, "${ accessibilityId }")]` + : `//XCUIElementTypeButton[@name="${ accessibilityId }"]`; - if ( ! isAndroid() ) { - blockLocator = `//XCUIElementTypeButton[@name="${ accessibilityId }"]`; - } - return await this.driver.elementByXPath( blockLocator ); + return await waitForVisible( this.driver, blockLocator ); } async getUnsupportedBlockBottomSheetEditButton() { const accessibilityId = 'Edit using web editor'; - let blockLocator = - '//android.widget.Button[@content-desc="Edit using web editor"]'; + const blockLocator = isAndroid() + ? `//android.widget.Button[@content-desc="${ accessibilityId }"]` + : `//XCUIElementTypeButton[@name="${ accessibilityId }"]`; - if ( ! isAndroid() ) { - blockLocator = `//XCUIElementTypeButton[@name="${ accessibilityId }"]`; - } - return await this.driver.elementByXPath( blockLocator ); + return await waitForVisible( this.driver, blockLocator ); } async getUnsupportedBlockWebView() { - let blockLocator = '//android.webkit.WebView'; - - if ( ! isAndroid() ) { - blockLocator = '//XCUIElementTypeWebView'; - } + const blockLocator = isAndroid() + ? '//android.webkit.WebView' + : '//XCUIElementTypeWebView'; - this.driver.setImplicitWaitTimeout( 20000 ); - const element = await this.driver.elementByXPath( blockLocator ); - this.driver.setImplicitWaitTimeout( 5000 ); - return element; + return await waitForVisible( this.driver, blockLocator ); } async stopDriver() { From 2daf775b8a435ed4862b126c1488e1da3e3f7ade Mon Sep 17 00:00:00 2001 From: Siobhan <siobhan@automattic.com> Date: Thu, 26 May 2022 09:52:36 +0100 Subject: [PATCH 21/49] Update CHANGELOG --- packages/react-native-editor/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 9b3a6cf719b568..6e1eecd42563c0 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -14,6 +14,7 @@ For each user feature we should also add a importance categorization label to i ## 1.77.0 - [*] [a11y] Improve text read by screen readers for BottomSheetSelectControl [#41036] +- [*] Add 'Insert from URL' option to Image block [#40334] ## 1.76.1 From d8fbfbdbb19e52adec4fde126b6b9b087694b186 Mon Sep 17 00:00:00 2001 From: Siobhan <siobhan@automattic.com> Date: Thu, 26 May 2022 09:57:29 +0100 Subject: [PATCH 22/49] Revert "update expected html for file block (#41300)" This reverts commit a046a618ee1e38cbc472799afbeb01e6604b5913. --- .../react-native-editor/__device-tests__/helpers/test-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-editor/__device-tests__/helpers/test-data.js b/packages/react-native-editor/__device-tests__/helpers/test-data.js index 99fea16d643fce..b7dcf9267e40d6 100644 --- a/packages/react-native-editor/__device-tests__/helpers/test-data.js +++ b/packages/react-native-editor/__device-tests__/helpers/test-data.js @@ -135,7 +135,7 @@ exports.coverHeightWithRemUnit = `<!-- wp:cover {"customOverlayColor":"#ffffff", <!-- /wp:cover -->`; exports.fileBlockPlaceholder = `<!-- wp:file {"id":3,"href":"https://wordpress.org/latest.zip"} --> -<div class="wp-block-file"><a href="https://wordpress.org/latest.zip">WordPress.zip</a><a href="https://wordpress.org/latest.zip" class="wp-block-file__button wp-element-button" download>Download</a></div> +<div class="wp-block-file"><a href="https://wordpress.org/latest.zip">WordPress.zip</a><a href="https://wordpress.org/latest.zip" class="wp-block-file__button" download>Download</a></div> <!-- /wp:file -->`; exports.audioBlockPlaceholder = `<!-- wp:audio {"id":5} --> From efc0f2c8510cc33f336c5d80389a686e216d5601 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Thu, 26 May 2022 15:43:58 +0200 Subject: [PATCH 23/49] [RNMobile] Ensure post title gets focused when is notified from native side (#41371) --- packages/edit-post/src/editor.native.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js index cf4eaa53389650..20e4585307ed31 100644 --- a/packages/edit-post/src/editor.native.js +++ b/packages/edit-post/src/editor.native.js @@ -92,6 +92,9 @@ class Editor extends Component { () => { if ( this.postTitleRef ) { this.postTitleRef.focus(); + } else { + // If the post title ref is not available, we postpone setting focus to when it's available. + this.focusTitleWhenAvailable = true; } } ); @@ -122,6 +125,11 @@ class Editor extends Component { } setTitleRef( titleRef ) { + if ( this.focusTitleWhenAvailable && ! this.postTitleRef ) { + this.focusTitleWhenAvailable = false; + titleRef.focus(); + } + this.postTitleRef = titleRef; } From 4c714af7f0cb928e97af26404abefb8c7db1a38e Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Thu, 2 Jun 2022 11:11:56 +0200 Subject: [PATCH 24/49] Release script: Update react-native-editor version to 1.77.1 --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index 1d6c079cfbf0c2..9eedd000b067b1 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.77.0", + "version": "1.77.1", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 525cbcfbc2465f..73dee5559892f4 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.77.0", + "version": "1.77.1", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index c02f10c28a5ef3..a4e5335c4c6c9b 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.77.0", + "version": "1.77.1", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 9365c4ec70c4ff329d51de623c6c0ed8eaa8b4d4 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Thu, 2 Jun 2022 11:12:34 +0200 Subject: [PATCH 25/49] Release script: Update with changes from 'npm run core preios' --- packages/react-native-editor/ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index d12d4b360b34c6..902c5377a6ca4a 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - ReactCommon/turbomodule/core (= 0.66.2) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.77.0): + - Gutenberg (1.77.1): - React-Core (= 0.66.2) - React-CoreModules (= 0.66.2) - React-RCTImage (= 0.66.2) @@ -337,7 +337,7 @@ PODS: - React-Core - RNSVG (9.13.6): - React-Core - - RNTAztecView (1.77.0): + - RNTAztecView (1.77.1): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - WordPress-Aztec-iOS (1.19.8) @@ -503,7 +503,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 18438b1c04ce502ed681cd19db3f4508964c082a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 5337263514dd6f09803962437687240c5dc39aa4 - Gutenberg: 7f0a288f26e36807c8e27f97b3a6ff929310eea3 + Gutenberg: 66dc1842f9cfad675624e28cfeedf6517d1c42a3 RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 RCTRequired: 5e9e85f48da8dd447f5834ce14c6799ea8c7f41a RCTTypeSafety: aba333d04d88d1f954e93666a08d7ae57a87ab30 @@ -542,7 +542,7 @@ SPEC CHECKSUMS: RNReanimated: d87c75f1076bab3402d6cd0b7322be51d333d10e RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 - RNTAztecView: 2524811d1168d1f5148220ec8ea3c012ec8f059c + RNTAztecView: 4d1509eb51c287d059942c311fe1ae7d445e3653 WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 Yoga: 9a08effa851c1d8cc1647691895540bc168ea65f From ad2c8e43a42b368e8e39fe5e0465a7c4d9102ce9 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Thu, 2 Jun 2022 11:44:51 +0200 Subject: [PATCH 26/49] [RNMobile] Bump `react-native-reanimated` version to `2.4.1-wp-3` (#41482) --- package-lock.json | 6 +++--- packages/react-native-editor/ios/Podfile.lock | 4 ++-- packages/react-native-editor/package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 37baac458da62a..d11ca8d655a9be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18231,7 +18231,7 @@ "react-native-linear-gradient": "https://raw.githubusercontent.com/wordpress-mobile/react-native-linear-gradient/v2.5.6-wp-2/react-native-linear-gradient-2.5.6-wp-2.tgz", "react-native-modal": "^11.10.0", "react-native-prompt-android": "https://raw.githubusercontent.com/wordpress-mobile/react-native-prompt-android/v1.0.0-wp-2/react-native-prompt-android-1.0.0-wp-2.tgz", - "react-native-reanimated": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.4.1-wp-2/react-native-reanimated-2.4.1-wp-2.tgz", + "react-native-reanimated": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.4.1-wp-3/react-native-reanimated-2.4.1-wp-3.tgz", "react-native-safe-area": "^0.5.0", "react-native-safe-area-context": "3.2.0", "react-native-sass-transformer": "^1.1.1", @@ -50931,8 +50931,8 @@ "integrity": "sha512-9whL4Kc5OU5Q89Dneq8oT8vpQTA/cEz24EIPXEQ2KGo1Dkf4qzer5+98YXJM2F8yitCP8UKHOL8WIiE7zukXBA==" }, "react-native-reanimated": { - "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.4.1-wp-2/react-native-reanimated-2.4.1-wp-2.tgz", - "integrity": "sha512-8Mu7150ezI5PGBYAatqhQlau0nkeXMVNZIODAU7l1e7qjfEALZiuxKMkvWhFw1xBCqx+qRv24yYns7I5GGiZGQ==", + "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.4.1-wp-3/react-native-reanimated-2.4.1-wp-3.tgz", + "integrity": "sha512-LnfbSRe9WZIj/LT9ZrtiDKCjEqCPp+wcugBIUCgfb6przB3dwrCOiFdxZBD+Py58h6wN7fVpIOhPaP5rGQ1TQQ==", "requires": { "@babel/plugin-transform-object-assign": "^7.10.4", "@types/invariant": "^2.2.35", diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 902c5377a6ca4a..0516c50d01d805 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -306,7 +306,7 @@ PODS: - React-Core - RNGestureHandler (2.2.0-wp-4): - React-Core - - RNReanimated (2.4.1-wp-2): + - RNReanimated (2.4.1-wp-3): - DoubleConversion - FBLazyVector - FBReactNativeSpec @@ -539,7 +539,7 @@ SPEC CHECKSUMS: RNCClipboard: 99fc8ad669a376b756fbc8098ae2fd05c0ed0668 RNCMaskedView: c298b644a10c0c142055b3ae24d83879ecb13ccd RNGestureHandler: 93b98c40b9419b1a82b008b513c182fe09288d1f - RNReanimated: d87c75f1076bab3402d6cd0b7322be51d333d10e + RNReanimated: b413cc7aa3e2a740d9804cda3a9396a68f9eea7f RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 RNTAztecView: 4d1509eb51c287d059942c311fe1ae7d445e3653 diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index a4e5335c4c6c9b..f1df15834b67dd 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -65,7 +65,7 @@ "react-native-linear-gradient": "https://raw.githubusercontent.com/wordpress-mobile/react-native-linear-gradient/v2.5.6-wp-2/react-native-linear-gradient-2.5.6-wp-2.tgz", "react-native-modal": "^11.10.0", "react-native-prompt-android": "https://raw.githubusercontent.com/wordpress-mobile/react-native-prompt-android/v1.0.0-wp-2/react-native-prompt-android-1.0.0-wp-2.tgz", - "react-native-reanimated": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.4.1-wp-2/react-native-reanimated-2.4.1-wp-2.tgz", + "react-native-reanimated": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.4.1-wp-3/react-native-reanimated-2.4.1-wp-3.tgz", "react-native-safe-area": "^0.5.0", "react-native-safe-area-context": "3.2.0", "react-native-sass-transformer": "^1.1.1", From f10a378f254793402fb9837b9414765235f36e19 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Thu, 2 Jun 2022 11:47:44 +0200 Subject: [PATCH 27/49] Update react-native-editor changelog --- packages/react-native-editor/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 6e1eecd42563c0..6dde09c5afd547 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,10 @@ For each user feature we should also add a importance categorization label to i ## Unreleased +## 1.77.1 + +- [***] Fixes a crash on iOS related to JSI and Reanimated [#41482] + ## 1.77.0 - [*] [a11y] Improve text read by screen readers for BottomSheetSelectControl [#41036] From 4cfbd825072365698f10e3c2acbaeff1b6eb177a Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Fri, 10 Jun 2022 10:20:53 +0200 Subject: [PATCH 28/49] Release script: Update react-native-editor version to 1.78.0 --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index 9eedd000b067b1..8a2d54acbf4e36 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.77.1", + "version": "1.78.0", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 73dee5559892f4..969cdd6a53e9a1 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.77.1", + "version": "1.78.0", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index f1df15834b67dd..d252432fe2d98d 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.77.1", + "version": "1.78.0", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 5b447b441735f8949b5cf432a39772190c104e07 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Fri, 10 Jun 2022 10:21:21 +0200 Subject: [PATCH 29/49] Release script: Update with changes from 'npm run core preios' --- packages/react-native-editor/ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 0516c50d01d805..7ad8acf1adcad6 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - ReactCommon/turbomodule/core (= 0.66.2) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.77.1): + - Gutenberg (1.78.0): - React-Core (= 0.66.2) - React-CoreModules (= 0.66.2) - React-RCTImage (= 0.66.2) @@ -337,7 +337,7 @@ PODS: - React-Core - RNSVG (9.13.6): - React-Core - - RNTAztecView (1.77.1): + - RNTAztecView (1.78.0): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - WordPress-Aztec-iOS (1.19.8) @@ -503,7 +503,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 18438b1c04ce502ed681cd19db3f4508964c082a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 5337263514dd6f09803962437687240c5dc39aa4 - Gutenberg: 66dc1842f9cfad675624e28cfeedf6517d1c42a3 + Gutenberg: 80e1d305a53a6b59409353860691826756d23da4 RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 RCTRequired: 5e9e85f48da8dd447f5834ce14c6799ea8c7f41a RCTTypeSafety: aba333d04d88d1f954e93666a08d7ae57a87ab30 @@ -542,7 +542,7 @@ SPEC CHECKSUMS: RNReanimated: b413cc7aa3e2a740d9804cda3a9396a68f9eea7f RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 - RNTAztecView: 4d1509eb51c287d059942c311fe1ae7d445e3653 + RNTAztecView: 3f363845da564392805047da58552665dc3be95a WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 Yoga: 9a08effa851c1d8cc1647691895540bc168ea65f From 8bd8d90c697784f136a4c66768e4b018e39ee4c0 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Fri, 27 May 2022 17:14:35 +0200 Subject: [PATCH 30/49] [RNMobile] Bump `react-native-gesture-handler` to version `2.3.2` (#41337) --- package-lock.json | 6 +++--- .../block-mover/test/__snapshots__/index.native.js.snap | 4 ++++ .../wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java | 2 +- .../app/src/main/java/com/gutenberg/MainApplication.java | 2 +- packages/react-native-editor/ios/Podfile.lock | 4 ++-- packages/react-native-editor/package.json | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index d11ca8d655a9be..00e90e22c63027 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18223,7 +18223,7 @@ "jsdom-jscore-rn": "git+https://github.com/iamcco/jsdom-jscore-rn.git#a562f3d57c27c13e5bfc8cf82d496e69a3ba2800", "node-fetch": "^2.6.0", "react-native": "0.66.2", - "react-native-gesture-handler": "https://raw.githubusercontent.com/wordpress-mobile/react-native-gesture-handler/2.2.0-wp-4/react-native-gesture-handler-2.2.0-wp-4.tgz", + "react-native-gesture-handler": "https://raw.githubusercontent.com/wordpress-mobile/react-native-gesture-handler/2.3.2-wp-1/react-native-gesture-handler-2.3.2-wp-1.tgz", "react-native-get-random-values": "1.4.0", "react-native-hr": "git+https://github.com/Riglerr/react-native-hr.git#2d01a5cf77212d100e8b99e0310cce5234f977b3", "react-native-hsv-color-picker": "https://raw.githubusercontent.com/wordpress-mobile/react-native-hsv-color-picker/v1.0.1-wp-2/react-native-hsv-color-picker-1.0.1-wp-2.tgz", @@ -50870,8 +50870,8 @@ } }, "react-native-gesture-handler": { - "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-gesture-handler/2.2.0-wp-4/react-native-gesture-handler-2.2.0-wp-4.tgz", - "integrity": "sha512-3pHMTuMWAko1nFJYRRrdqjnEGmPKxPe7C/wRa4PZ+FCelqdXoomwMLp1Rw7q722MXpQbjxQsQplfgPL4X5KxvQ==", + "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-gesture-handler/2.3.2-wp-1/react-native-gesture-handler-2.3.2-wp-1.tgz", + "integrity": "sha512-XKwFyoU2rsyQVVK9DhygNUsSipHBbqc5o/P/3davbRF81T7uPHwnTf/9X3Jze4eIwfiZmnRLz9rFgVxvK/1JyA==", "requires": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", diff --git a/packages/block-editor/src/components/block-mover/test/__snapshots__/index.native.js.snap b/packages/block-editor/src/components/block-mover/test/__snapshots__/index.native.js.snap index 79b79766caaa68..ec1b5b196d57bb 100644 --- a/packages/block-editor/src/components/block-mover/test/__snapshots__/index.native.js.snap +++ b/packages/block-editor/src/components/block-mover/test/__snapshots__/index.native.js.snap @@ -35,6 +35,8 @@ Array [ > <View collapsable={false} + handlerTag={3} + handlerType="LongPressGestureHandler" onGestureHandlerEvent={[Function]} onGestureHandlerStateChange={[Function]} style={ @@ -93,6 +95,8 @@ Array [ > <View collapsable={false} + handlerTag={4} + handlerType="LongPressGestureHandler" onGestureHandlerEvent={[Function]} onGestureHandlerStateChange={[Function]} style={ diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java index 86e5a6b63ce2fa..3e4d06bcd935c1 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java @@ -46,7 +46,7 @@ import org.linusu.RNGetRandomValuesPackage; import com.reactnativecommunity.webview.RNCWebViewPackage; import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; -import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; +import com.swmansion.gesturehandler.RNGestureHandlerPackage; import com.swmansion.reanimated.ReanimatedJSIModulePackage; import com.swmansion.reanimated.ReanimatedPackage; import com.swmansion.rnscreens.RNScreensPackage; diff --git a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java index d74315aba221a2..b94e581a736cbe 100644 --- a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java +++ b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java @@ -37,7 +37,7 @@ import com.facebook.react.shell.MainReactPackage; import com.facebook.soloader.SoLoader; import com.reactnativecommunity.webview.RNCWebViewPackage; -import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; +import com.swmansion.gesturehandler.RNGestureHandlerPackage; import com.swmansion.reanimated.ReanimatedJSIModulePackage; import com.swmansion.reanimated.ReanimatedPackage; import com.swmansion.rnscreens.RNScreensPackage; diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 7ad8acf1adcad6..1b37acba4098ab 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -304,7 +304,7 @@ PODS: - React-Core - RNCMaskedView (0.2.6): - React-Core - - RNGestureHandler (2.2.0-wp-4): + - RNGestureHandler (2.3.2-wp-1): - React-Core - RNReanimated (2.4.1-wp-3): - DoubleConversion @@ -538,7 +538,7 @@ SPEC CHECKSUMS: ReactCommon: c0263c1a41509aeb94be3214fa7bc3b71eae5ef6 RNCClipboard: 99fc8ad669a376b756fbc8098ae2fd05c0ed0668 RNCMaskedView: c298b644a10c0c142055b3ae24d83879ecb13ccd - RNGestureHandler: 93b98c40b9419b1a82b008b513c182fe09288d1f + RNGestureHandler: 3b13cc25407d1cdbee33b6ae65790a55c032d2a9 RNReanimated: b413cc7aa3e2a740d9804cda3a9396a68f9eea7f RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index d252432fe2d98d..8fa80cf8699391 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -57,7 +57,7 @@ "jsdom-jscore-rn": "git+https://github.com/iamcco/jsdom-jscore-rn.git#a562f3d57c27c13e5bfc8cf82d496e69a3ba2800", "node-fetch": "^2.6.0", "react-native": "0.66.2", - "react-native-gesture-handler": "https://raw.githubusercontent.com/wordpress-mobile/react-native-gesture-handler/2.2.0-wp-4/react-native-gesture-handler-2.2.0-wp-4.tgz", + "react-native-gesture-handler": "https://raw.githubusercontent.com/wordpress-mobile/react-native-gesture-handler/2.3.2-wp-1/react-native-gesture-handler-2.3.2-wp-1.tgz", "react-native-get-random-values": "1.4.0", "react-native-hr": "git+https://github.com/Riglerr/react-native-hr.git#2d01a5cf77212d100e8b99e0310cce5234f977b3", "react-native-hsv-color-picker": "https://raw.githubusercontent.com/wordpress-mobile/react-native-hsv-color-picker/v1.0.1-wp-2/react-native-hsv-color-picker-1.0.1-wp-2.tgz", From bdaaf6e1b1b02bf2ab933fbbbb47284ca681604d Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Fri, 10 Jun 2022 10:55:50 +0200 Subject: [PATCH 31/49] Update react-native-editor changelog --- packages/react-native-editor/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 6dde09c5afd547..0a7190341a464b 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,9 +11,13 @@ For each user feature we should also add a importance categorization label to i ## Unreleased +## 1.78.0 + +- [*] Bump react-native-gesture-handler to version 2.3.2 [#41337] + ## 1.77.1 -- [***] Fixes a crash on iOS related to JSI and Reanimated [#41482] +- [***] Fix crash on iOS related to JSI and Reanimated [#41482] ## 1.77.0 From 6e13c9ec0d8f871abdc1c60180767d7f222fa04c Mon Sep 17 00:00:00 2001 From: Siobhan <siobhan@automattic.com> Date: Thu, 16 Jun 2022 12:05:40 +0100 Subject: [PATCH 32/49] Release script: Update react-native-editor version to 1.78.1 --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index 8a2d54acbf4e36..cb149c5b26140f 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.78.0", + "version": "1.78.1", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 969cdd6a53e9a1..7e5fa852b0347b 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.78.0", + "version": "1.78.1", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 8fa80cf8699391..862626e5a6356b 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.78.0", + "version": "1.78.1", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 32ab484ab0b13f832808d003ecb8adb35c013717 Mon Sep 17 00:00:00 2001 From: Siobhan <siobhan@automattic.com> Date: Thu, 16 Jun 2022 12:06:34 +0100 Subject: [PATCH 33/49] Release script: Update with changes from 'npm run core preios' --- packages/react-native-editor/ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 1b37acba4098ab..04ed013a9c3759 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - ReactCommon/turbomodule/core (= 0.66.2) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.78.0): + - Gutenberg (1.78.1): - React-Core (= 0.66.2) - React-CoreModules (= 0.66.2) - React-RCTImage (= 0.66.2) @@ -337,7 +337,7 @@ PODS: - React-Core - RNSVG (9.13.6): - React-Core - - RNTAztecView (1.78.0): + - RNTAztecView (1.78.1): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - WordPress-Aztec-iOS (1.19.8) @@ -503,7 +503,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 18438b1c04ce502ed681cd19db3f4508964c082a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 5337263514dd6f09803962437687240c5dc39aa4 - Gutenberg: 80e1d305a53a6b59409353860691826756d23da4 + Gutenberg: 214a8fe3bb352b754d9909d2bef764cd8289d1be RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 RCTRequired: 5e9e85f48da8dd447f5834ce14c6799ea8c7f41a RCTTypeSafety: aba333d04d88d1f954e93666a08d7ae57a87ab30 @@ -542,7 +542,7 @@ SPEC CHECKSUMS: RNReanimated: b413cc7aa3e2a740d9804cda3a9396a68f9eea7f RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 - RNTAztecView: 3f363845da564392805047da58552665dc3be95a + RNTAztecView: 57ebf9512a9318d4dfd9099dc290b11ad4b5ece6 WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 Yoga: 9a08effa851c1d8cc1647691895540bc168ea65f From 4ae461c0cd42061c0a3c31829a3b7f5ed8c76cc6 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Tue, 14 Jun 2022 17:16:59 +0200 Subject: [PATCH 34/49] [RNMobile] Gallery block: Re-introduce `v1` (#41533) * Fix isGalleryV2Enabled calculation for the native version * Update comment in isGalleryV2Enabled function Co-authored-by: David Calhoun <438664+dcalhoun@users.noreply.github.com> Co-authored-by: David Calhoun <438664+dcalhoun@users.noreply.github.com> --- packages/block-library/src/gallery/shared.js | 31 ++++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/block-library/src/gallery/shared.js b/packages/block-library/src/gallery/shared.js index 6ebd8c8b37f3e7..08028953b4f5c9 100644 --- a/packages/block-library/src/gallery/shared.js +++ b/packages/block-library/src/gallery/shared.js @@ -3,6 +3,11 @@ */ import { get, pick } from 'lodash'; +/** + * WordPress dependencies + */ +import { Platform } from '@wordpress/element'; + export function defaultColumnsNumber( imageCount ) { return imageCount ? Math.min( 3, imageCount ) : 3; } @@ -22,6 +27,15 @@ export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { return imageProps; }; +function getGalleryBlockV2Enabled() { + // We want to fail early here, at least during beta testing phase, to ensure + // there aren't instances where undefined values cause false negatives. + if ( ! window.wp || typeof window.wp.galleryBlockV2Enabled !== 'boolean' ) { + throw 'window.wp.galleryBlockV2Enabled is not defined'; + } + return window.wp.galleryBlockV2Enabled; +} + /** * The new gallery block format is not compatible with the use_BalanceTags option * in WP versions <= 5.8 https://core.trac.wordpress.org/ticket/54130. The @@ -29,18 +43,17 @@ export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { * can be removed when minimum supported WP version >=5.9. */ export function isGalleryV2Enabled() { + // The logic for the native version is located in a different if statement + // due to a lint rule that prohibits a single conditional combining + // `process.env.IS_GUTENBERG_PLUGIN` with a native platform check. + if ( Platform.isNative ) { + return getGalleryBlockV2Enabled(); + } + // Only run the Gallery version compat check if the plugin is running, otherwise // assume we are in 5.9 core and enable by default. if ( process.env.IS_GUTENBERG_PLUGIN ) { - // We want to fail early here, at least during beta testing phase, to ensure - // there aren't instances where undefined values cause false negatives. - if ( - ! window.wp || - typeof window.wp.galleryBlockV2Enabled !== 'boolean' - ) { - throw 'window.wp.galleryBlockV2Enabled is not defined'; - } - return window.wp.galleryBlockV2Enabled; + return getGalleryBlockV2Enabled(); } return true; From 01adee18e27b4ed0a6e8711d396d8a8f61c1a536 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Tue, 14 Jun 2022 13:33:26 +0200 Subject: [PATCH 35/49] [RNMobile] Fix missing translations for locales that include region (only on Android) (#41685) --- packages/react-native-editor/src/setup-locale.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-native-editor/src/setup-locale.js b/packages/react-native-editor/src/setup-locale.js index c4b222b54f8518..bb50c5e76d3fb9 100644 --- a/packages/react-native-editor/src/setup-locale.js +++ b/packages/react-native-editor/src/setup-locale.js @@ -22,6 +22,8 @@ export default ( pluginTranslations = [] ) => { const setDomainLocaleData = ( { getTranslation, domain = 'default' } ) => { + // Lowercase the locale value as translation filenames are also lowercased. + locale = locale?.toLowerCase(); let translations = getTranslation( locale ); if ( locale && ! translations ) { // Try stripping out the regional From b1352b75774e9acb994fc7b885df1f342104cea6 Mon Sep 17 00:00:00 2001 From: Siobhan <siobhan@automattic.com> Date: Thu, 16 Jun 2022 12:37:38 +0100 Subject: [PATCH 36/49] Update CHANGELOG --- packages/react-native-editor/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 0a7190341a464b..1a79d579c4a7f3 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,11 @@ For each user feature we should also add a importance categorization label to i ## Unreleased +## 1.78.1 + +- [**] Re-introduce support for v1 of the Gallery block to the native version of the editor [#41533] +- [**] Fix missing translations for locales that include region (only on Android) [#41685] + ## 1.78.0 - [*] Bump react-native-gesture-handler to version 2.3.2 [#41337] From 77417b6af47798d6b6b8267b36bc6b937c642e64 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Thu, 7 Jul 2022 09:15:01 -0500 Subject: [PATCH 37/49] Release script: Update react-native-editor version to 1.79.0 --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index cb149c5b26140f..ee3fec37565296 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.78.1", + "version": "1.79.0", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 7e5fa852b0347b..8b83c2c6482759 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.78.1", + "version": "1.79.0", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 862626e5a6356b..10ed2e7507ea3b 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.78.1", + "version": "1.79.0", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 4e35a9d78e36d71f57cd8a7004705e76a5409fee Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Thu, 7 Jul 2022 09:17:23 -0500 Subject: [PATCH 38/49] Release script: Update with changes from 'npm run core preios' --- .../ios/GutenbergDemo.xcodeproj/project.pbxproj | 4 ++-- packages/react-native-editor/ios/Podfile.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj b/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj index a08280d71e173f..65506b8af1e287 100644 --- a/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj +++ b/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj @@ -764,7 +764,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -808,7 +808,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 04ed013a9c3759..20a013f3cff6e4 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - ReactCommon/turbomodule/core (= 0.66.2) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.78.1): + - Gutenberg (1.79.0): - React-Core (= 0.66.2) - React-CoreModules (= 0.66.2) - React-RCTImage (= 0.66.2) @@ -337,7 +337,7 @@ PODS: - React-Core - RNSVG (9.13.6): - React-Core - - RNTAztecView (1.78.1): + - RNTAztecView (1.79.0): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - WordPress-Aztec-iOS (1.19.8) @@ -503,7 +503,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 18438b1c04ce502ed681cd19db3f4508964c082a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 5337263514dd6f09803962437687240c5dc39aa4 - Gutenberg: 214a8fe3bb352b754d9909d2bef764cd8289d1be + Gutenberg: 03d969bd5acf0f2787b81fd83a9af36efecaaee9 RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 RCTRequired: 5e9e85f48da8dd447f5834ce14c6799ea8c7f41a RCTTypeSafety: aba333d04d88d1f954e93666a08d7ae57a87ab30 @@ -542,7 +542,7 @@ SPEC CHECKSUMS: RNReanimated: b413cc7aa3e2a740d9804cda3a9396a68f9eea7f RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 - RNTAztecView: 57ebf9512a9318d4dfd9099dc290b11ad4b5ece6 + RNTAztecView: 48124f42ccc72c4b00a5aa4982ee3a816860b7c3 WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 Yoga: 9a08effa851c1d8cc1647691895540bc168ea65f From 2f318361ff38153ab09e84b0cec03f61a88017d2 Mon Sep 17 00:00:00 2001 From: Carlos Garcia <fluiddot@gmail.com> Date: Tue, 31 May 2022 15:56:41 +0200 Subject: [PATCH 39/49] [RNMobile] Add integration tests to cover Drag & Drop functionality (#41364) * Add testID prop to Draggable components * Add unit tests for Draggable component * Set draggingId shared value before enable dragging This change is required for testing, otherwise the dragging id is not passed when the dragging gesture begins. * Mock generateHapticFeedback function * Add testID to draggable chip component * Add testID to BlockDraggable component * Add test helpers for BlockDraggable component Additionally, helpers related to fake timers have been added and updated in the global helpers file. * Add drag and drop integration tests * Update react-native-aztec mock to use AztecInputState --- .../block-draggable/draggable-chip.native.js | 2 +- .../block-draggable/index.native.js | 4 + .../test/__snapshots__/index.native.js.snap | 73 +++ .../block-draggable/test/helpers.native.js | 183 +++++++ .../block-draggable/test/index.native.js | 496 ++++++++++++++++++ .../src/components/block-list/block.native.js | 1 + .../block-mobile-toolbar/index.native.js | 1 + .../components/src/draggable/index.native.js | 17 +- .../src/draggable/test/index.native.js | 130 +++++ .../@wordpress/react-native-aztec/index.js | 39 +- test/native/helpers.js | 134 ++++- test/native/setup.js | 1 + 12 files changed, 1038 insertions(+), 43 deletions(-) create mode 100644 packages/block-editor/src/components/block-draggable/test/__snapshots__/index.native.js.snap create mode 100644 packages/block-editor/src/components/block-draggable/test/helpers.native.js create mode 100644 packages/block-editor/src/components/block-draggable/test/index.native.js create mode 100644 packages/components/src/draggable/test/index.native.js diff --git a/packages/block-editor/src/components/block-draggable/draggable-chip.native.js b/packages/block-editor/src/components/block-draggable/draggable-chip.native.js index 682c4f5b2cd49c..597279fc03d24e 100644 --- a/packages/block-editor/src/components/block-draggable/draggable-chip.native.js +++ b/packages/block-editor/src/components/block-draggable/draggable-chip.native.js @@ -41,7 +41,7 @@ export default function BlockDraggableChip( { icon } ) { ); return ( - <View style={ [ containerStyle, shadowStyle ] }> + <View style={ [ containerStyle, shadowStyle ] } testID="draggable-chip"> <BlockIcon icon={ dragHandle } /> { icon && <BlockIcon icon={ icon } /> } </View> diff --git a/packages/block-editor/src/components/block-draggable/index.native.js b/packages/block-editor/src/components/block-draggable/index.native.js index ec76781d449fcc..ecf4b6a8259eb3 100644 --- a/packages/block-editor/src/components/block-draggable/index.native.js +++ b/packages/block-editor/src/components/block-draggable/index.native.js @@ -270,6 +270,7 @@ const BlockDraggableWrapper = ( { children, isRTL } ) => { onDragStart={ startDragging } onDragOver={ updateDragging } onDragEnd={ stopDragging } + testID="block-draggable-wrapper" > { children( { onScroll: scrollHandler } ) } </Draggable> @@ -302,6 +303,7 @@ const BlockDraggableWrapper = ( { children, isRTL } ) => { * @param {string} props.clientId Client id of the block. * @param {string} [props.draggingClientId] Client id to use for dragging. If not defined, the value from `clientId` will be used. * @param {boolean} [props.enabled] Enables the draggable trigger. + * @param {string} [props.testID] Id used for querying the long-press gesture handler in tests. * * @return {Function} Render function which includes the parameter `isDraggable` to determine if the block can be dragged. */ @@ -310,6 +312,7 @@ const BlockDraggable = ( { children, draggingClientId, enabled = true, + testID, } ) => { const wasBeingDragged = useRef( false ); const [ isEditingText, setIsEditingText ] = useState( false ); @@ -446,6 +449,7 @@ const BlockDraggable = ( { android: DEFAULT_LONG_PRESS_MIN_DURATION, } ) } onLongPress={ onLongPressDraggable } + testID={ testID } > <Animated.View style={ wrapperStyles }> { children( { isDraggable: true } ) } diff --git a/packages/block-editor/src/components/block-draggable/test/__snapshots__/index.native.js.snap b/packages/block-editor/src/components/block-draggable/test/__snapshots__/index.native.js.snap new file mode 100644 index 00000000000000..286c6e11e2a305 --- /dev/null +++ b/packages/block-editor/src/components/block-draggable/test/__snapshots__/index.native.js.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BlockDraggable moves blocks: Initial order 1`] = ` +"<!-- wp:paragraph --> +<p>This is a paragraph.</p> +<!-- /wp:paragraph --> + +<!-- wp:image {\\"sizeSlug\\":\\"large\\"} --> +<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:spacer --> +<div style=\\"height:100px\\" aria-hidden=\\"true\\" class=\\"wp-block-spacer\\"></div> +<!-- /wp:spacer --> + +<!-- wp:gallery {\\"linkTo\\":\\"none\\"} --> +<figure class=\\"wp-block-gallery has-nested-images columns-default is-cropped\\"><!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} --> +<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} --> +<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure> +<!-- /wp:image --></figure> +<!-- /wp:gallery -->" +`; + +exports[`BlockDraggable moves blocks: Paragraph block moved from first to second position 1`] = ` +"<!-- wp:image {\\"sizeSlug\\":\\"large\\"} --> +<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>This is a paragraph.</p> +<!-- /wp:paragraph --> + +<!-- wp:spacer --> +<div style=\\"height:100px\\" aria-hidden=\\"true\\" class=\\"wp-block-spacer\\"></div> +<!-- /wp:spacer --> + +<!-- wp:gallery {\\"linkTo\\":\\"none\\"} --> +<figure class=\\"wp-block-gallery has-nested-images columns-default is-cropped\\"><!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} --> +<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} --> +<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure> +<!-- /wp:image --></figure> +<!-- /wp:gallery -->" +`; + +exports[`BlockDraggable moves blocks: Spacer block moved from third to first position 1`] = ` +"<!-- wp:spacer --> +<div style=\\"height:100px\\" aria-hidden=\\"true\\" class=\\"wp-block-spacer\\"></div> +<!-- /wp:spacer --> + +<!-- wp:image {\\"sizeSlug\\":\\"large\\"} --> +<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>This is a paragraph.</p> +<!-- /wp:paragraph --> + +<!-- wp:gallery {\\"linkTo\\":\\"none\\"} --> +<figure class=\\"wp-block-gallery has-nested-images columns-default is-cropped\\"><!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} --> +<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} --> +<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure> +<!-- /wp:image --></figure> +<!-- /wp:gallery -->" +`; diff --git a/packages/block-editor/src/components/block-draggable/test/helpers.native.js b/packages/block-editor/src/components/block-draggable/test/helpers.native.js new file mode 100644 index 00000000000000..8408b80f2e8270 --- /dev/null +++ b/packages/block-editor/src/components/block-draggable/test/helpers.native.js @@ -0,0 +1,183 @@ +/** + * External dependencies + */ +import { + act, + fireEvent, + initializeEditor, + waitForStoreResolvers, + within, + advanceAnimationByFrame, +} from 'test/helpers'; +import { fireGestureHandler } from 'react-native-gesture-handler/jest-utils'; +import { State } from 'react-native-gesture-handler'; + +// Touch event type constants have been extracted from original source code, as they are not exported in the package. +// Reference: https://github.com/software-mansion/react-native-gesture-handler/blob/90895e5f38616a6be256fceec6c6a391cd9ad7c7/src/TouchEventType.ts +export const TouchEventType = { + UNDETERMINED: 0, + TOUCHES_DOWN: 1, + TOUCHES_MOVE: 2, + TOUCHES_UP: 3, + TOUCHES_CANCELLED: 4, +}; + +const DEFAULT_TOUCH_EVENTS = [ + { + id: 1, + eventType: TouchEventType.TOUCHES_DOWN, + x: 0, + y: 0, + }, +]; + +/** + * @typedef {Object} WPBlockWithLayout + * @property {string} name Name of the block (e.g. Paragraph). + * @property {string} html HTML content. + * @property {Object} layout Layout data. + * @property {Object} layout.x X position. + * @property {Object} layout.y Y position. + * @property {Object} layout.width Width. + * @property {Object} layout.height Height. + */ + +/** + * Initialize the editor with an array of blocks that include their HTML and layout. + * + * @param {WPBlockWithLayout[]} blocks Initial blocks. + * + * @return {import('@testing-library/react-native').RenderAPI} The Testing Library screen. + */ +export const initializeWithBlocksLayouts = async ( blocks ) => { + const initialHtml = blocks.map( ( block ) => block.html ).join( '\n' ); + + const screen = await initializeEditor( { initialHtml } ); + const { getByA11yLabel } = screen; + + const waitPromises = []; + blocks.forEach( ( block, index ) => { + const a11yLabel = new RegExp( + `${ block.name } Block\\. Row ${ index + 1 }` + ); + const element = getByA11yLabel( a11yLabel ); + // "onLayout" event will populate the blocks layouts data. + fireEvent( element, 'layout', { + nativeEvent: { layout: block.layout }, + } ); + if ( block.nestedBlocks ) { + // Nested blocks are rendered via the FlatList of the inner block list. + // In order to render the items of a FlatList, it's required to trigger the + // "onLayout" event. Additionally, the call is wrapped over "waitForStoreResolvers" + // because the nested blocks might make API requests (e.g. the Gallery block). + waitPromises.push( + waitForStoreResolvers( () => + fireEvent( + within( element ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: block.layout.width, + height: block.layout.height, + }, + }, + } + ) + ) + ); + + block.nestedBlocks.forEach( ( nestedBlock, nestedIndex ) => { + const nestedA11yLabel = new RegExp( + `${ nestedBlock.name } Block\\. Row ${ nestedIndex + 1 }` + ); + fireEvent( + within( element ).getByA11yLabel( nestedA11yLabel ), + 'layout', + { + nativeEvent: { layout: nestedBlock.layout }, + } + ); + } ); + } + } ); + await Promise.all( waitPromises ); + + return screen; +}; + +/** + * Fires long-press gesture event on a block. + * + * @param {import('react-test-renderer').ReactTestInstance} block Block test instance. + * @param {string} testID Id for querying the draggable trigger element. + * @param {Object} [options] Configuration options for the gesture event. + * @param {boolean} [options.failed] Determines if the gesture should fail. + * @param {number} [options.triggerIndex] In case there are multiple draggable triggers, this specifies the index to use. + */ +export const fireLongPress = ( + block, + testID, + { failed = false, triggerIndex } = {} +) => { + const draggableTrigger = + typeof triggerIndex !== 'undefined' + ? within( block ).getAllByTestId( testID )[ triggerIndex ] + : within( block ).getByTestId( testID ); + if ( failed ) { + fireGestureHandler( draggableTrigger, [ { state: State.FAILED } ] ); + } else { + fireGestureHandler( draggableTrigger, [ + { oldState: State.BEGAN, state: State.ACTIVE }, + { state: State.ACTIVE }, + { state: State.END }, + ] ); + } + // Advance timers one frame to ensure that shared values + // are updated and trigger animation reactions. + act( () => advanceAnimationByFrame( 1 ) ); +}; + +/** + * Fires pan gesture event on a BlockDraggable component. + * + * @param {import('react-test-renderer').ReactTestInstance} blockDraggable BlockDraggable test instance. + * @param {Object} [touchEvents] Array of touch events to dispatch on the pan gesture. + */ +export const firePanGesture = ( + blockDraggable, + touchEvents = DEFAULT_TOUCH_EVENTS +) => { + const gestureTouchEvents = touchEvents.map( + ( { eventType, ...touchEvent } ) => ( { + allTouches: [ touchEvent ], + eventType, + } ) + ); + fireGestureHandler( blockDraggable, [ + // TOUCHES_DOWN event is only received on ACTIVE state, so we have to fire it manually. + { oldState: State.BEGAN, state: State.ACTIVE }, + ...gestureTouchEvents, + { state: State.END }, + ] ); + // Advance timers one frame to ensure that shared values + // are updated and trigger animation reactions. + act( () => advanceAnimationByFrame( 1 ) ); +}; + +/** + * Gets the draggable chip element. + * + * @param {import('@testing-library/react-native').RenderAPI} screen The Testing Library screen. + * + * @return {import('react-test-renderer').ReactTestInstance} Draggable chip test instance. + */ +export const getDraggableChip = ( { getByTestId } ) => { + let draggableChip; + try { + draggableChip = getByTestId( 'draggable-chip' ); + } catch ( e ) { + // NOOP. + } + return draggableChip; +}; diff --git a/packages/block-editor/src/components/block-draggable/test/index.native.js b/packages/block-editor/src/components/block-draggable/test/index.native.js new file mode 100644 index 00000000000000..30c9d7c8baae16 --- /dev/null +++ b/packages/block-editor/src/components/block-draggable/test/index.native.js @@ -0,0 +1,496 @@ +/** + * External dependencies + */ +import { + fireEvent, + getEditorHtml, + within, + waitForStoreResolvers, + withReanimatedTimer, +} from 'test/helpers'; +import { getByGestureTestId } from 'react-native-gesture-handler/jest-utils'; +import TextInputState from 'react-native/Libraries/Components/TextInput/TextInputState'; + +/** + * WordPress dependencies + */ +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; +import '@wordpress/jest-console'; + +/** + * Internal dependencies + */ +import { + initializeWithBlocksLayouts, + fireLongPress, + firePanGesture, + TouchEventType, + getDraggableChip, +} from './helpers'; + +// Mock throttle to allow updating the dragging position on every "onDragOver" event. +jest.mock( 'lodash', () => ( { + ...jest.requireActual( 'lodash' ), + throttle: ( fn ) => { + fn.cancel = jest.fn(); + return fn; + }, +} ) ); + +beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); +} ); + +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); + +const TOUCH_EVENT_ID = 1; +const BLOCKS = [ + { + name: 'Paragraph', + html: ` + <!-- wp:paragraph --> + <p>This is a paragraph.</p> + <!-- /wp:paragraph -->`, + layout: { x: 0, y: 0, width: 100, height: 100 }, + }, + { + name: 'Image', + html: ` + <!-- wp:image {"sizeSlug":"large"} --> + <figure class="wp-block-image size-large"><img src="https://cldup.com/cXyG__fTLN.jpg" alt=""/></figure> + <!-- /wp:image -->`, + layout: { x: 0, y: 100, width: 100, height: 100 }, + }, + { + name: 'Spacer', + html: ` + <!-- wp:spacer --> + <div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div> + <!-- /wp:spacer -->`, + layout: { x: 0, y: 200, width: 100, height: 100 }, + }, + { + name: 'Gallery', + html: ` + <!-- wp:gallery {"linkTo":"none"} --> + <figure class="wp-block-gallery has-nested-images columns-default is-cropped"><!-- wp:image {"sizeSlug":"large","linkDestination":"none"} --> + <figure class="wp-block-image size-large"><img src="https://cldup.com/cXyG__fTLN.jpg" alt=""/></figure> + <!-- /wp:image --> + + <!-- wp:image {"sizeSlug":"large","linkDestination":"none"} --> + <figure class="wp-block-image size-large"><img src="https://cldup.com/cXyG__fTLN.jpg" alt=""/></figure> + <!-- /wp:image --></figure> + <!-- /wp:gallery -->`, + layout: { x: 0, y: 300, width: 100, height: 100 }, + nestedBlocks: [ + { name: 'Image', layout: { x: 0, y: 300, width: 50, height: 50 } }, + { name: 'Image', layout: { x: 50, y: 300, width: 50, height: 50 } }, + ], + }, +]; + +describe( 'BlockDraggable', () => { + describe( 'drag mode', () => { + describe( 'Text block', () => { + it( 'enables drag mode when unselected', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getByA11yLabel } = screen; + + // Start dragging from block's content + fireLongPress( + getByA11yLabel( /Paragraph Block\. Row 1/ ), + 'draggable-trigger-content' + ); + expect( getDraggableChip( screen ) ).toBeVisible(); + + // "firePanGesture" finishes the dragging gesture + firePanGesture( + getByGestureTestId( 'block-draggable-wrapper' ) + ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + } ) ); + + it( 'enables drag mode when selected', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getByA11yLabel } = screen; + const blockDraggableWrapper = getByGestureTestId( + 'block-draggable-wrapper' + ); + + const paragraphBlock = getByA11yLabel( + /Paragraph Block\. Row 1/ + ); + fireEvent.press( paragraphBlock ); + + // Start dragging from block's content + fireLongPress( + paragraphBlock, + 'draggable-trigger-content' + ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + + // Start dragging from block's mobile toolbar + fireLongPress( + paragraphBlock, + 'draggable-trigger-mobile-toolbar' + ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + } ) ); + + it( 'does not enable drag mode when selected and editing text', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getByA11yLabel } = screen; + + const paragraphBlock = getByA11yLabel( + /Paragraph Block\. Row 1/ + ); + + // Select Paragraph block and start editing text + fireEvent.press( paragraphBlock ); + fireEvent( + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ), + 'focus' + ); + + // Start dragging from block's content + fireLongPress( + paragraphBlock, + 'draggable-trigger-content', + { failed: true } + ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + // Check that no text input has been unfocused to confirm + // that editing text is still enabled. + expect( + TextInputState.blurTextInput + ).not.toHaveBeenCalled(); + } ) ); + + it( 'finishes editing text and enables drag mode when long-pressing over a different block', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getByA11yLabel } = screen; + + const paragraphBlock = getByA11yLabel( + /Paragraph Block\. Row 1/ + ); + const spacerBlock = getByA11yLabel( + /Spacer Block\. Row 3/ + ); + + // Select Paragraph block and start editing text + fireEvent.press( paragraphBlock ); + fireEvent( + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ), + 'focus' + ); + + // Start dragging from a different block's content + fireLongPress( spacerBlock, 'draggable-trigger-content' ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // Check that any text input has been unfocused to confirm + // that editing text finished. + expect( TextInputState.blurTextInput ).toHaveBeenCalled(); + } ) ); + } ); + + describe( 'Media block', () => { + it( 'enables drag mode when unselected', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getAllByA11yLabel } = screen; + + // We select the first Image block as the Gallery block + // also contains Image blocks. + const imageBlock = getAllByA11yLabel( + /Image Block\. Row 2/ + )[ 0 ]; + // Start dragging from block's content + fireLongPress( imageBlock, 'draggable-trigger-content' ); + expect( getDraggableChip( screen ) ).toBeVisible(); + + // "firePanGesture" finishes the dragging gesture + firePanGesture( + getByGestureTestId( 'block-draggable-wrapper' ) + ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + } ) ); + + it( 'enables drag mode when selected', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getAllByA11yLabel } = screen; + const blockDraggableWrapper = getByGestureTestId( + 'block-draggable-wrapper' + ); + + // We select the first Image block as the Gallery block + // also contains Image blocks. + const imageBlock = getAllByA11yLabel( + /Image Block\. Row 2/ + )[ 0 ]; + fireEvent.press( imageBlock ); + + // Start dragging from block's content + fireLongPress( imageBlock, 'draggable-trigger-content' ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + + // Start dragging from block's mobile toolbar + fireLongPress( + imageBlock, + 'draggable-trigger-mobile-toolbar' + ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + } ) ); + } ); + + describe( 'Nested block', () => { + it( 'enables drag mode when unselected', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getByA11yLabel } = screen; + + // Start dragging from block's content, specifically the first + // trigger index, which corresponds to the Gallery block content. + fireLongPress( + getByA11yLabel( /Gallery Block\. Row 4/ ), + 'draggable-trigger-content', + { triggerIndex: 0 } + ); + expect( getDraggableChip( screen ) ).toBeVisible(); + + // "firePanGesture" finishes the dragging gesture + firePanGesture( + getByGestureTestId( 'block-draggable-wrapper' ) + ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + } ) ); + + it( 'enables drag mode when selected', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getByA11yLabel } = screen; + const blockDraggableWrapper = getByGestureTestId( + 'block-draggable-wrapper' + ); + + const galleryBlock = getByA11yLabel( + /Gallery Block\. Row 4/ + ); + await waitForStoreResolvers( () => + fireEvent.press( galleryBlock ) + ); + + // Start dragging from block's content, specifically the first + // trigger index, which corresponds to the Gallery block content. + fireLongPress( galleryBlock, 'draggable-trigger-content', { + triggerIndex: 0, + } ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + + // Start dragging from block's mobile toolbar + fireLongPress( + galleryBlock, + 'draggable-trigger-mobile-toolbar' + ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + } ) ); + + it( 'enables drag mode when nested block is selected', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getByA11yLabel } = screen; + const blockDraggableWrapper = getByGestureTestId( + 'block-draggable-wrapper' + ); + + const galleryBlock = getByA11yLabel( + /Gallery Block\. Row 4/ + ); + const galleryItem = within( galleryBlock ).getByA11yLabel( + /Image Block\. Row 2/ + ); + fireEvent.press( galleryBlock ); + fireEvent.press( galleryItem ); + + // Start dragging from nested block's content + fireLongPress( galleryItem, 'draggable-trigger-content' ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + + // After dropping the block, the gallery item gets automatically selected. + // Hence, we have to select the gallery item again. + fireEvent.press( galleryItem ); + + // Start dragging from nested block's mobile toolbar + fireLongPress( + galleryItem, + 'draggable-trigger-mobile-toolbar' + ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + } ) ); + } ); + + describe( 'Other block', () => { + it( 'enables drag mode when unselected', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getByA11yLabel } = screen; + + // Start dragging from block's content + fireLongPress( + getByA11yLabel( /Spacer Block\. Row 3/ ), + 'draggable-trigger-content' + ); + expect( getDraggableChip( screen ) ).toBeVisible(); + + // "firePanGesture" finishes the dragging gesture + firePanGesture( + getByGestureTestId( 'block-draggable-wrapper' ) + ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + } ) ); + + it( 'enables drag mode when selected', async () => + withReanimatedTimer( async () => { + const screen = await initializeWithBlocksLayouts( BLOCKS ); + const { getByA11yLabel } = screen; + const blockDraggableWrapper = getByGestureTestId( + 'block-draggable-wrapper' + ); + + const spacerBlock = getByA11yLabel( + /Spacer Block\. Row 3/ + ); + await waitForStoreResolvers( () => + fireEvent.press( spacerBlock ) + ); + + // Start dragging from block's content + fireLongPress( spacerBlock, 'draggable-trigger-content' ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + + // Start dragging from block's mobile toolbar + fireLongPress( + spacerBlock, + 'draggable-trigger-mobile-toolbar' + ); + expect( getDraggableChip( screen ) ).toBeVisible(); + // "firePanGesture" finishes the dragging gesture + firePanGesture( blockDraggableWrapper ); + expect( getDraggableChip( screen ) ).not.toBeDefined(); + } ) ); + } ); + } ); + + it( 'moves blocks', async () => + withReanimatedTimer( async () => { + const { getByA11yLabel } = await initializeWithBlocksLayouts( + BLOCKS + ); + const blockDraggableWrapper = getByGestureTestId( + 'block-draggable-wrapper' + ); + + expect( getEditorHtml() ).toMatchSnapshot( 'Initial order' ); + + // Move Paragraph block from first to second position + fireLongPress( + getByA11yLabel( /Paragraph Block\. Row 1/ ), + 'draggable-trigger-content' + ); + firePanGesture( blockDraggableWrapper, [ + { + id: TOUCH_EVENT_ID, + eventType: TouchEventType.TOUCHES_DOWN, + x: 0, + y: 0, + }, + { + id: TOUCH_EVENT_ID, + eventType: TouchEventType.TOUCHES_MOVE, + x: 0, + // Dropping position is in the second half of the second block's height. + y: 180, + }, + ] ); + // Draggable Pan gesture uses the Gesture state manager to manually + // activate the gesture. Since this not available in tests, the library + // displays a warning message. + expect( console ).toHaveWarnedWith( + '[react-native-gesture-handler] You have to use react-native-reanimated in order to control the state of the gesture.' + ); + expect( getEditorHtml() ).toMatchSnapshot( + 'Paragraph block moved from first to second position' + ); + + // Move Spacer block from third to first position + fireLongPress( + getByA11yLabel( /Spacer Block\. Row 3/ ), + 'draggable-trigger-content' + ); + firePanGesture( blockDraggableWrapper, [ + { + id: TOUCH_EVENT_ID, + eventType: TouchEventType.TOUCHES_DOWN, + x: 0, + y: 250, + }, + { + id: TOUCH_EVENT_ID, + eventType: TouchEventType.TOUCHES_MOVE, + x: 0, + y: 0, + }, + ] ); + // Draggable Pan gesture uses the Gesture state manager to manually + // activate the gesture. Since this not available in tests, the library + // displays a warning message. + expect( console ).toHaveWarnedWith( + '[react-native-gesture-handler] You have to use react-native-reanimated in order to control the state of the gesture.' + ); + expect( getEditorHtml() ).toMatchSnapshot( + 'Spacer block moved from third to first position' + ); + } ) ); +} ); diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 822194e749906d..4ea24537e2e87c 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -263,6 +263,7 @@ class BlockListBlock extends Component { clientId={ clientId } draggingClientId={ draggingClientId } enabled={ draggingEnabled } + testID="draggable-trigger-content" > { () => isValid ? ( diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index 7e0ba45b76d422..b1b9c4635a29b7 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -78,6 +78,7 @@ const BlockMobileToolbar = ( { <BlockDraggable clientId={ clientId } draggingClientId={ draggingClientId } + testID="draggable-trigger-mobile-toolbar" > { () => <View style={ styles.spacer } /> } </BlockDraggable> diff --git a/packages/components/src/draggable/index.native.js b/packages/components/src/draggable/index.native.js index 57f2849d430291..a9cc6b40070ed4 100644 --- a/packages/components/src/draggable/index.native.js +++ b/packages/components/src/draggable/index.native.js @@ -34,10 +34,17 @@ const { Provider } = Context; * @param {Function} [props.onDragEnd] Callback when dragging ends. * @param {Function} [props.onDragOver] Callback when dragging happens over an element. * @param {Function} [props.onDragStart] Callback when dragging starts. + * @param {string} [props.testID] Id used for querying the pan gesture in tests. * * @return {JSX.Element} The component to be rendered. */ -const Draggable = ( { children, onDragEnd, onDragOver, onDragStart } ) => { +const Draggable = ( { + children, + onDragEnd, + onDragOver, + onDragStart, + testID, +} ) => { const isDragging = useSharedValue( false ); const isPanActive = useSharedValue( false ); const draggingId = useSharedValue( '' ); @@ -130,7 +137,8 @@ const Draggable = ( { children, onDragEnd, onDragOver, onDragStart } ) => { isDragging.value = false; } ) .withRef( panGestureRef ) - .shouldCancelWhenOutside( false ); + .shouldCancelWhenOutside( false ) + .withTestId( testID ); const providerValue = useMemo( () => { return { panGestureRef, isDragging, isPanActive, draggingId }; @@ -158,6 +166,7 @@ const Draggable = ( { children, onDragEnd, onDragOver, onDragStart } ) => { * @param {number} [props.minDuration] Minimum time, that a finger must remain pressed on the corresponding view. * @param {Function} [props.onLongPress] Callback when long-press gesture is triggered over an element. * @param {Function} [props.onLongPressEnd] Callback when long-press gesture ends. + * @param {string} [props.testID] Id used for querying the long-press gesture handler in tests. * * @return {JSX.Element} The component to be rendered. */ @@ -169,6 +178,7 @@ const DraggableTrigger = ( { minDuration = 500, onLongPress, onLongPressEnd, + testID, } ) => { const { panGestureRef, isDragging, isPanActive, draggingId } = useContext( Context @@ -180,8 +190,8 @@ const DraggableTrigger = ( { return; } - isDragging.value = true; draggingId.value = id; + isDragging.value = true; if ( onLongPress ) { runOnJS( onLongPress )( id ); } @@ -205,6 +215,7 @@ const DraggableTrigger = ( { simultaneousHandlers={ panGestureRef } shouldCancelWhenOutside={ false } onGestureEvent={ gestureHandler } + testID={ testID } > { children } </LongPressGestureHandler> diff --git a/packages/components/src/draggable/test/index.native.js b/packages/components/src/draggable/test/index.native.js new file mode 100644 index 00000000000000..e2f3e1b658fc3f --- /dev/null +++ b/packages/components/src/draggable/test/index.native.js @@ -0,0 +1,130 @@ +/** + * External dependencies + */ +import { render } from 'test/helpers'; +import { + fireGestureHandler, + getByGestureTestId, +} from 'react-native-gesture-handler/jest-utils'; +import { State } from 'react-native-gesture-handler'; +import Animated from 'react-native-reanimated'; + +/** + * WordPress dependencies + */ +import { Draggable, DraggableTrigger } from '@wordpress/components'; + +// Touch event type constants have been extracted from original source code, as they are not exported in the package. +// Reference: https://github.com/software-mansion/react-native-gesture-handler/blob/90895e5f38616a6be256fceec6c6a391cd9ad7c7/src/TouchEventType.ts +const TouchEventType = { + UNDETERMINED: 0, + TOUCHES_DOWN: 1, + TOUCHES_MOVE: 2, + TOUCHES_UP: 3, + TOUCHES_CANCELLED: 4, +}; + +// Reanimated uses "requestAnimationFrame" for notifying shared value updates when using "useAnimatedReaction" hook. +// For testing, we mock the "requestAnimationFrame" so it calls the callback passed instantly. +let requestAnimationFrameCopy; +beforeEach( () => { + requestAnimationFrameCopy = global.requestAnimationFrame; + global.requestAnimationFrame = ( callback ) => callback(); +} ); +afterEach( () => { + global.requestAnimationFrame = requestAnimationFrameCopy; +} ); + +describe( 'Draggable', () => { + it( 'triggers onLongPress handler', () => { + const triggerId = 'trigger-id'; + const onLongPress = jest.fn(); + const { getByTestId } = render( + <Draggable> + <DraggableTrigger + id={ triggerId } + enabled={ true } + minDuration={ 500 } + onLongPress={ onLongPress } + testID="draggableTrigger" + > + <Animated.View /> + </DraggableTrigger> + </Draggable> + ); + + const draggableTrigger = getByTestId( 'draggableTrigger' ); + fireGestureHandler( draggableTrigger, [ + { oldState: State.BEGAN, state: State.ACTIVE }, + { state: State.ACTIVE }, + ] ); + + expect( onLongPress ).toBeCalledTimes( 1 ); + expect( onLongPress ).toHaveBeenCalledWith( triggerId ); + } ); + + it( 'triggers dragging handlers', () => { + const triggerId = 'trigger-id'; + const onDragStart = jest.fn(); + const onDragOver = jest.fn(); + const onDragEnd = jest.fn(); + const { getByTestId } = render( + <Draggable + onDragStart={ onDragStart } + onDragOver={ onDragOver } + onDragEnd={ onDragEnd } + testID="draggable" + > + <DraggableTrigger id={ triggerId } testID="draggableTrigger"> + <Animated.View /> + </DraggableTrigger> + </Draggable> + ); + + const draggableTrigger = getByTestId( 'draggableTrigger' ); + const draggable = getByGestureTestId( 'draggable' ); + const touchEventId = 1; + const touchEvents = [ + { id: touchEventId, x: 0, y: 0 }, + { id: touchEventId, x: 100, y: 100 }, + { id: touchEventId, x: 50, y: 50 }, + ]; + fireGestureHandler( draggableTrigger, [ + { oldState: State.BEGAN, state: State.ACTIVE }, + { state: State.ACTIVE }, + ] ); + fireGestureHandler( draggable, [ + // TOUCHES_DOWN event is only received on ACTIVE state, so we have to fire it manually. + { oldState: State.BEGAN, state: State.ACTIVE }, + { + allTouches: [ touchEvents[ 0 ] ], + eventType: TouchEventType.TOUCHES_DOWN, + }, + { + allTouches: [ touchEvents[ 1 ] ], + eventType: TouchEventType.TOUCHES_MOVE, + }, + { + allTouches: [ touchEvents[ 2 ] ], + eventType: TouchEventType.TOUCHES_MOVE, + }, + { state: State.END }, + ] ); + + expect( onDragStart ).toBeCalledTimes( 1 ); + expect( onDragStart ).toHaveBeenCalledWith( { + id: triggerId, + x: touchEvents[ 0 ].x, + y: touchEvents[ 0 ].y, + } ); + expect( onDragOver ).toBeCalledTimes( 2 ); + expect( onDragOver ).toHaveBeenNthCalledWith( 1, touchEvents[ 1 ] ); + expect( onDragOver ).toHaveBeenNthCalledWith( 2, touchEvents[ 2 ] ); + expect( onDragEnd ).toBeCalledTimes( 1 ); + expect( onDragEnd ).toHaveBeenCalledWith( { + id: triggerId, + x: touchEvents[ 2 ].x, + y: touchEvents[ 2 ].y, + } ); + } ); +} ); diff --git a/test/native/__mocks__/@wordpress/react-native-aztec/index.js b/test/native/__mocks__/@wordpress/react-native-aztec/index.js index dfd2f21c0504b9..f64e93af002952 100644 --- a/test/native/__mocks__/@wordpress/react-native-aztec/index.js +++ b/test/native/__mocks__/@wordpress/react-native-aztec/index.js @@ -7,31 +7,48 @@ import { omit } from 'lodash'; /** * WordPress dependencies */ -import { forwardRef } from '@wordpress/element'; +import { forwardRef, useImperativeHandle, useRef } from '@wordpress/element'; -const reactNativeAztecMock = jest.createMockFromModule( - '@wordpress/react-native-aztec' -); // Preserve the mock of AztecInputState to be exported with the AztecView mock. -const AztecInputState = reactNativeAztecMock.default.InputState; +const AztecInputState = jest.requireActual( '@wordpress/react-native-aztec' ) + .default.InputState; const UNSUPPORTED_PROPS = [ 'style' ]; -const AztecView = ( { accessibilityLabel, text, ...rest }, ref ) => { +const RCTAztecView = ( { accessibilityLabel, text, ...rest }, ref ) => { + const inputRef = useRef(); + + useImperativeHandle( ref, () => ( { + // We need to reference the props of TextInput because they are used in TextColorEdit to calculate the color indicator. + // Reference: https://github.com/WordPress/gutenberg/blob/4407ae6fa20bdd3c3aa62d50344e796467359246/packages/format-library/src/text-color/index.native.js#L83-L86 + props: { ...inputRef.current.props }, + blur: () => { + AztecInputState.blur( inputRef.current ); + inputRef.current.blur(); + }, + focus: () => { + AztecInputState.focus( inputRef.current ); + inputRef.current.focus(); + }, + isFocused: () => { + const focusedElement = AztecInputState.getCurrentFocusedElement(); + return focusedElement && focusedElement === inputRef.current; + }, + } ) ); + return ( <TextInput { ...omit( rest, UNSUPPORTED_PROPS ) } accessibilityLabel={ accessibilityLabel || `Text input. ${ text.text || 'Empty' }` } - ref={ ref } + ref={ inputRef } value={ text.text } /> ); }; -// Replace default mock of AztecView component with custom implementation. -reactNativeAztecMock.default = forwardRef( AztecView ); -reactNativeAztecMock.default.InputState = AztecInputState; +const AztecView = forwardRef( RCTAztecView ); +AztecView.InputState = AztecInputState; -module.exports = reactNativeAztecMock; +export default AztecView; diff --git a/test/native/helpers.js b/test/native/helpers.js index e40c95273db74b..7217cef19439bc 100644 --- a/test/native/helpers.js +++ b/test/native/helpers.js @@ -31,48 +31,123 @@ provideToNativeHtml.mockImplementation( ( html ) => { serializedHtml = html; } ); +const frameTime = 1000 / 60; + /** - * Executes a function that triggers store resolvers and waits for them to be finished. + * Set up fake timers for executing a function and restores them afterwards. * - * Asynchronous store resolvers leverage `setTimeout` to run at the end of - * the current JavaScript block execution. In order to prevent "act" warnings - * triggered by updates to the React tree, we manually tick fake timers and - * await the resolution of the current block execution before proceeding. + * @param {Function} fn Function to trigger. * - * @param {Function} fn Function that triggers store resolvers. * @return {*} The result of the function call. */ -export async function waitForStoreResolvers( fn ) { +export async function withFakeTimers( fn ) { + const usingFakeTimers = jest.isMockFunction( setTimeout ); + // Portions of the React Native Animation API rely upon these APIs. However, // Jest's 'legacy' fake timers mutate these globals, which breaks the Animated // API. We preserve the original implementations to restore them later. - const originalRAF = global.requestAnimationFrame; - const originalCAF = global.cancelAnimationFrame; + const requestAnimationFrameCopy = global.requestAnimationFrame; + const cancelAnimationFrameCopy = global.cancelAnimationFrame; + + if ( ! usingFakeTimers ) { + jest.useFakeTimers( 'legacy' ); + } - jest.useFakeTimers( 'legacy' ); + const result = await fn(); - const result = fn(); + if ( ! usingFakeTimers ) { + jest.useRealTimers(); + + global.requestAnimationFrame = requestAnimationFrameCopy; + global.cancelAnimationFrame = cancelAnimationFrameCopy; + } + return result; +} - // Advance all timers allowing store resolvers to resolve. - act( () => jest.runAllTimers() ); +/** + * Prepare timers for executing a function that uses the Reanimated APIs. + * + * NOTE: This code is based on a similar function provided by the Reanimated library. + * Reference: https://github.com/software-mansion/react-native-reanimated/blob/b4ee4ea9a1f246c461dd1819c6f3d48440a25756/src/reanimated2/jestUtils.ts#L170-L174 + * + * @param {Function} fn Function to trigger. + * + * @return {*} The result of the function call. + */ +export async function withReanimatedTimer( fn ) { + return withFakeTimers( async () => { + global.requestAnimationFrame = ( callback ) => + setTimeout( callback, frameTime ); - // The store resolvers perform several API fetches during editor - // initialization. The most straightforward approach to ensure all of them - // resolve before we consider the editor initialized is to flush micro tasks, - // similar to the approach found in `@testing-library/react-native`. - // https://github.com/callstack/react-native-testing-library/blob/a010ffdbca906615279ecc3abee423525e528101/src/flushMicroTasks.js#L15-L23. - await act( async () => {} ); + const result = await fn(); - // Restore the default timer APIs for remainder of test arrangement, act, and - // assertion. - jest.useRealTimers(); + // As part of the clean up, we run all pending timers that might have been derived from animations. + act( () => jest.runOnlyPendingTimers() ); - // Restore the global animation frame APIs to their original state for the - // React Native Animated API. - global.requestAnimationFrame = originalRAF; - global.cancelAnimationFrame = originalCAF; + return result; + } ); +} - return result; +/** + * Advance Reanimated animations by time. + * This helper should be called within a function invoked by "withReanimatedTimer". + * + * NOTE: This code is based on a similar function provided by the Reanimated library. + * Reference: https://github.com/software-mansion/react-native-reanimated/blob/b4ee4ea9a1f246c461dd1819c6f3d48440a25756/src/reanimated2/jestUtils.ts#L176-L181 + * + * @param {number} time Time to advance timers. + */ +export const advanceAnimationByTime = ( time = frameTime ) => { + for ( let i = 0; i <= Math.ceil( time / frameTime ); i++ ) { + jest.advanceTimersByTime( frameTime ); + } + jest.advanceTimersByTime( frameTime ); +}; + +/** + * Advance Reanimated animations by frames. + * This helper should be called within a function invoked by "withReanimatedTimer". + * + * NOTE: This code is based on a similar function provided by the Reanimated library. + * Reference: https://github.com/software-mansion/react-native-reanimated/blob/b4ee4ea9a1f246c461dd1819c6f3d48440a25756/src/reanimated2/jestUtils.ts#L183-L188 + * + * @param {number} count Number of frames to advance timers. + */ +export const advanceAnimationByFrame = ( count ) => { + for ( let i = 0; i <= count; i++ ) { + jest.advanceTimersByTime( frameTime ); + } + jest.advanceTimersByTime( frameTime ); +}; + +/** + * Executes a function that triggers store resolvers and waits for them to be finished. + * + * Asynchronous store resolvers leverage `setTimeout` to run at the end of + * the current JavaScript block execution. In order to prevent "act" warnings + * triggered by updates to the React tree, we manually tick fake timers and + * await the resolution of the current block execution before proceeding. + * + * @param {Function} fn Function that to trigger. + * + * @return {*} The result of the function call. + */ +export async function waitForStoreResolvers( fn ) { + return withFakeTimers( async () => { + const result = fn(); + + // Advance all timers allowing store resolvers to resolve. + act( () => jest.runAllTimers() ); + + // The store resolvers perform several API fetches during editor + // initialization. The most straightforward approach to ensure all of them + // resolve before we consider the editor initialized is to flush micro tasks, + // similar to the approach found in `@testing-library/react-native`. + // https://github.com/callstack/react-native-testing-library/blob/a010ffdbca906615279ecc3abee423525e528101/src/flushMicroTasks.js#L15-L23. + await act( async () => {} ); + + return result; + } ); } /** @@ -95,7 +170,10 @@ export async function initializeEditor( props, { component } = {} ) { : internalInitializeEditor( uniqueId, postType, postId ); const screen = render( - cloneElement( editorElement, { initialTitle: 'test', ...props } ) + cloneElement( editorElement, { + initialTitle: 'test', + ...props, + } ) ); // A layout event must be explicitly dispatched in BlockList component, diff --git a/test/native/setup.js b/test/native/setup.js index 6a4884c2417323..69a4b32d93674e 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -98,6 +98,7 @@ jest.mock( '@wordpress/react-native-bridge', () => { }, fetchRequest: jest.fn(), requestPreview: jest.fn(), + generateHapticFeedback: jest.fn(), }; } ); From fb5f7e437605b22e6bc0d0b132a642521e033c9e Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco <gerardo.pacheco@automattic.com> Date: Tue, 31 May 2022 17:33:19 +0200 Subject: [PATCH 40/49] [Mobile] - Add E2E tests for the Drag & Drop blocks feature (#41368) * 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 --- .../gutenberg-editor-drag-and-drop.test.js | 153 ++++++++++++++++++ .../gutenberg-editor-paste.test.js | 11 +- .../__device-tests__/helpers/test-data.js | 8 + .../__device-tests__/helpers/utils.js | 100 ++++++++++-- .../__device-tests__/pages/editor-page.js | 38 ++++- 5 files changed, 286 insertions(+), 24 deletions(-) create mode 100644 packages/react-native-editor/__device-tests__/gutenberg-editor-drag-and-drop.test.js diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-drag-and-drop.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-drag-and-drop.test.js new file mode 100644 index 00000000000000..5d8e62cc7b664f --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-drag-and-drop.test.js @@ -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 ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js index 7404b0969d6adb..63be583561e4e0 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js @@ -3,6 +3,7 @@ */ import { blockNames } from './pages/editor-page'; import { + clearClipboard, longPressMiddleOfElement, tapSelectAllAboveElement, tapCopyAboveElement, @@ -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 () => { @@ -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 ); @@ -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. diff --git a/packages/react-native-editor/__device-tests__/helpers/test-data.js b/packages/react-native-editor/__device-tests__/helpers/test-data.js index b7dcf9267e40d6..87c4ae8ab51074 100644 --- a/packages/react-native-editor/__device-tests__/helpers/test-data.js +++ b/packages/react-native-editor/__device-tests__/helpers/test-data.js @@ -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 -->`; @@ -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 -->`; diff --git a/packages/react-native-editor/__device-tests__/helpers/utils.js b/packages/react-native-editor/__device-tests__/helpers/utils.js index b3225a03cfbae0..cc1585892dfb96 100644 --- a/packages/react-native-editor/__device-tests__/helpers/utils.js +++ b/packages/react-native-editor/__device-tests__/helpers/utils.js @@ -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 = () => { @@ -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(); }; @@ -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) @@ -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. @@ -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, 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 f7e69057ece2a2..92829551843ec4 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -7,6 +7,7 @@ const { isEditorVisible, isElementVisible, longPressMiddleOfElement, + setClipboard, setupDriver, stopDriver, swipeDown, @@ -227,9 +228,7 @@ class EditorPage { async setHtmlContent( html ) { await toggleHtmlMode( this.driver, true ); - const base64String = Buffer.from( html ).toString( 'base64' ); - - await this.driver.setClipboard( base64String, 'plaintext' ); + await setClipboard( this.driver, html ); const htmlContentView = await this.getTextViewForHtmlViewContent(); @@ -500,6 +499,15 @@ class EditorPage { // Paragraph Block functions // ========================= + async getParagraphBlockWrapperAtPosition( position = 1 ) { + // iOS needs a click to get the text element + const blockLocator = isAndroid() + ? `//android.view.ViewGroup[contains(@content-desc, "Paragraph Block. Row ${ position }")]` + : `(//XCUIElementTypeButton[contains(@name, "Paragraph Block. Row ${ position }")])`; + + return await waitForVisible( this.driver, blockLocator ); + } + async sendTextToParagraphBlock( position, text, clear ) { const paragraphs = text.split( '\n' ); for ( let i = 0; i < paragraphs.length; i++ ) { @@ -777,6 +785,29 @@ class EditorPage { async sauceJobStatus( allPassed ) { await this.driver.sauceJobStatus( allPassed ); } + + // ========================= + // Shortcode Block functions + // ========================= + + async getShortBlockTextInputAtPosition( blockName, position = 1 ) { + // iOS needs a click to get the text element + if ( ! isAndroid() ) { + const textBlockLocator = `(//XCUIElementTypeButton[contains(@name, "Shortcode Block. Row ${ position }")])`; + + const textBlock = await waitForVisible( + this.driver, + textBlockLocator + ); + await textBlock.click(); + } + + const blockLocator = isAndroid() + ? `//android.view.ViewGroup[@content-desc="Shortcode Block. Row ${ position }"]/android.view.ViewGroup/android.view.ViewGroup/android.widget.EditText` + : `//XCUIElementTypeButton[contains(@name, "Shortcode Block. Row ${ position }")]//XCUIElementTypeTextView`; + + return await waitForVisible( this.driver, blockLocator ); + } } const blockNames = { @@ -796,6 +827,7 @@ const blockNames = { separator: 'Separator', spacer: 'Spacer', verse: 'Verse', + shortcode: 'Shortcode', }; module.exports = { initializeEditorPage, blockNames }; From aee2a06a51d0c2245f062cf96211f2873885836a Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco <gerardo.pacheco@automattic.com> Date: Fri, 3 Jun 2022 15:48:16 +0200 Subject: [PATCH 41/49] Mobile - Skip some of the Drag & Drop E2E on iOS (#41529) * Mobile - Skip some of the Drag & Drop E2E on iOS * Replace if condition to use onlyOnAndroid wrapper instead --- .../gutenberg-editor-drag-and-drop.test.js | 121 ++++++++++-------- 1 file changed, 68 insertions(+), 53 deletions(-) diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-drag-and-drop.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-drag-and-drop.test.js index 5d8e62cc7b664f..777655f5f0c523 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-drag-and-drop.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-drag-and-drop.test.js @@ -12,6 +12,9 @@ import { } from './helpers/utils'; import testData from './helpers/test-data'; +// Used to skip some tests on iOS +const onlyOnAndroid = isAndroid() ? it : it.skip; + describe( 'Gutenberg Editor Drag & Drop blocks tests', () => { beforeEach( async () => { await clearClipboard( editorPage.driver ); @@ -53,61 +56,73 @@ describe( 'Gutenberg Editor Drag & Drop blocks tests', () => { 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(); + onlyOnAndroid( + '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 ); } - - // 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(); + ); + + onlyOnAndroid( + '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 ); } - - // 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 From c7a740ca64c87ec1cf6f953b5e2c33c5c1e14f9d Mon Sep 17 00:00:00 2001 From: Jos <jostnes@users.noreply.github.com> Date: Thu, 2 Jun 2022 14:32:35 +0800 Subject: [PATCH 42/49] where it can change to use clickIfClickable(), add new param for waitForVisible() to control return value (#41367) Co-authored-by: jos <17252150+jostnes@users.noreply.github.com> --- ...gutenberg-editor-block-insertion-2.test.js | 6 +- .../gutenberg-editor-image-@canary.test.js | 31 +++------- .../__device-tests__/helpers/utils.js | 27 +++++++-- .../__device-tests__/pages/editor-page.js | 59 +++++++------------ 4 files changed, 54 insertions(+), 69 deletions(-) diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion-2.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion-2.test.js index 2c6c9bf3481188..2ab5bdda7f6ca7 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion-2.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion-2.test.js @@ -24,11 +24,9 @@ describe( 'Gutenberg Editor tests for Block insertion 2', () => { } ); it( 'inserts between 2 existing blocks', async () => { - const headingBlockElement = await editorPage.getBlockAtPosition( - blockNames.heading - ); + const firstBlock = await editorPage.getFirstBlockVisible(); + await firstBlock.click(); - await headingBlockElement.click(); await editorPage.addNewBlock( blockNames.separator ); const expectedHtml = [ diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-image-@canary.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-image-@canary.test.js index d07e8a3ea2cb26..1d8d6fe3c9ea20 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-image-@canary.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-image-@canary.test.js @@ -7,15 +7,15 @@ import testData from './helpers/test-data'; describe( 'Gutenberg Editor Image Block tests', () => { it( 'should be able to add an image block', async () => { - await editorPage.addNewBlock( blockNames.image ); - await editorPage.closePicker(); + // iOS only test - Can only add image from media library on iOS + if ( ! isAndroid() ) { + await editorPage.addNewBlock( blockNames.image ); + await editorPage.closePicker(); - const imageBlock = await editorPage.getBlockAtPosition( - blockNames.image - ); + const imageBlock = await editorPage.getBlockAtPosition( + blockNames.image + ); - // Can only add image from media library on iOS - if ( ! isAndroid() ) { await editorPage.selectEmptyImageBlock( imageBlock ); await editorPage.chooseMediaLibrary(); @@ -31,27 +31,14 @@ describe( 'Gutenberg Editor Image Block tests', () => { true ); await editorPage.dismissKeyboard(); - } - await editorPage.addNewBlock( blockNames.paragraph ); - const paragraphBlockElement = await editorPage.getTextBlockAtPosition( - blockNames.paragraph, - 2 - ); - if ( isAndroid() ) { - await paragraphBlockElement.click(); - } - - await editorPage.sendTextToParagraphBlock( 2, testData.shortText ); - // skip HTML check for Android since we couldn't add image from media library - /* eslint-disable jest/no-conditional-expect */ - if ( ! isAndroid() ) { + await editorPage.addNewBlock( blockNames.paragraph ); + await editorPage.sendTextToParagraphBlock( 2, testData.shortText ); const html = await editorPage.getHtmlContent(); expect( html.toLowerCase() ).toBe( testData.imageShorteHtml.toLowerCase() ); } - /* eslint-enable jest/no-conditional-expect */ } ); } ); diff --git a/packages/react-native-editor/__device-tests__/helpers/utils.js b/packages/react-native-editor/__device-tests__/helpers/utils.js index cc1585892dfb96..538df4e57172ce 100644 --- a/packages/react-native-editor/__device-tests__/helpers/utils.js +++ b/packages/react-native-editor/__device-tests__/helpers/utils.js @@ -516,6 +516,7 @@ const waitForMediaLibrary = async ( driver ) => { * @param {string} driver * @param {string} elementLocator * @param {number} maxIteration - Default value is 25 + * @param {string} elementToReturn - Options are allElements, lastElement, firstElement. Defaults to "firstElement" * @param {number} iteration - Default value is 0 * @return {string} - Returns the first element found, empty string if not found */ @@ -523,6 +524,7 @@ const waitForVisible = async ( driver, elementLocator, maxIteration = 25, + elementToReturn = 'firstElement', iteration = 0 ) => { const timeout = 1000; @@ -539,35 +541,47 @@ const waitForVisible = async ( await driver.sleep( timeout ); } - const element = await driver.elementsByXPath( elementLocator ); - if ( element.length === 0 ) { + const elements = await driver.elementsByXPath( elementLocator ); + if ( elements.length === 0 ) { // if locator is not visible, try again return waitForVisible( driver, elementLocator, maxIteration, + elementToReturn, iteration + 1 ); } - return element[ 0 ]; + switch ( elementToReturn ) { + case 'allElements': + return elements; + case 'lastElement': + return elements[ elements.length - 1 ]; + default: + // Default is to return first element + return elements[ 0 ]; + } }; /** * @param {string} driver * @param {string} elementLocator * @param {number} maxIteration - Default value is 25, can be adjusted to be less to wait for element to not be visible + * @param {string} elementToReturn - Options are allElements, lastElement, firstElement. Defaults to "firstElement" * @return {boolean} - Returns true if element is found, false otherwise */ const isElementVisible = async ( driver, elementLocator, - maxIteration = 25 + maxIteration = 25, + elementToReturn = 'firstElement' ) => { const element = await waitForVisible( driver, elementLocator, - maxIteration + maxIteration, + elementToReturn ); // if there is no element, return false @@ -582,12 +596,14 @@ const clickIfClickable = async ( driver, elementLocator, maxIteration = 25, + elementToReturn = 'firstElement', iteration = 0 ) => { const element = await waitForVisible( driver, elementLocator, maxIteration, + elementToReturn, iteration ); @@ -606,6 +622,7 @@ const clickIfClickable = async ( driver, elementLocator, maxIteration, + elementToReturn, iteration + 1 ); } 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 92829551843ec4..5f3130e4992116 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -57,11 +57,7 @@ class EditorPage { if ( ! isAndroid() ) { const textBlockLocator = `(//XCUIElementTypeButton[contains(@name, "${ blockName } Block. Row ${ position }")])`; - const textBlock = await waitForVisible( - this.driver, - textBlockLocator - ); - await textBlock.click(); + await clickIfClickable( this.driver, textBlockLocator ); } const blockLocator = isAndroid() @@ -151,9 +147,12 @@ class EditorPage { async getLastBlockVisible() { const firstBlockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, " Block. Row ")]`; - await waitForVisible( this.driver, firstBlockLocator ); - const elements = await this.driver.elementsByXPath( firstBlockLocator ); - return elements[ elements.length - 1 ]; + return await waitForVisible( + this.driver, + firstBlockLocator, + 25, + 'lastElement' + ); } async hasBlockAtPosition( position = 1, blockName = '' ) { @@ -243,12 +242,10 @@ class EditorPage { // Sometimes double tap is not enough for paste menu to appear, so we also long press. await longPressMiddleOfElement( this.driver, htmlContentView ); - const pasteButton = await waitForVisible( + await clickIfClickable( this.driver, '//XCUIElementTypeMenuItem[@name="Paste"]' ); - - await pasteButton.click(); } await toggleHtmlMode( this.driver, false ); @@ -262,10 +259,11 @@ class EditorPage { if ( isAndroid() ) { return await this.driver.hideDeviceKeyboard(); } - const hideKeyboardToolbarButton = await this.driver.elementByXPath( + + await clickIfClickable( + this.driver, '//XCUIElementTypeButton[@name="Hide keyboard"]' ); - await hideKeyboardToolbarButton.click(); } async dismissAndroidClipboardSmartSuggestion() { @@ -645,11 +643,7 @@ class EditorPage { ? `//android.widget.Button[@content-desc="WordPress Media Library"]` : `//XCUIElementTypeButton[@name="WordPress Media Library"]`; - const mediaLibraryButton = await waitForVisible( - this.driver, - mediaLibraryLocator - ); - await mediaLibraryButton.click(); + await clickIfClickable( this.driver, mediaLibraryLocator ); } async enterCaptionToSelectedImageBlock( caption, clear = true ) { @@ -669,11 +663,10 @@ class EditorPage { await swipeDown( this.driver ); } else { - const cancelButton = await waitForVisible( + await clickIfClickable( this.driver, '//XCUIElementTypeButton[@name="Cancel"]' ); - await cancelButton.click(); } } @@ -703,13 +696,11 @@ class EditorPage { const elementName = isAndroid() ? '//*' : '//XCUIElementTypeOther'; - const locator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Hide search heading")]`; - const hideSearchHeadingToggle = await waitForVisible( + const hideSearchHeadingToggleLocator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Hide search heading")]`; + return await clickIfClickable( this.driver, - locator + hideSearchHeadingToggleLocator ); - - return await hideSearchHeadingToggle.click(); } async changeSearchButtonPositionSetting( block, buttonPosition ) { @@ -717,17 +708,11 @@ class EditorPage { const elementName = isAndroid() ? '//*' : '//XCUIElementTypeButton'; - const locator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Button position")]`; - let optionMenuButton = await waitForVisible( this.driver, locator ); - await optionMenuButton.click(); + const optionMenuLocator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Button position")]`; + await clickIfClickable( this.driver, optionMenuLocator ); const optionMenuButtonLocator = `${ elementName }[contains(@${ this.accessibilityIdXPathAttrib }, "${ buttonPosition }")]`; - optionMenuButton = await waitForVisible( - this.driver, - optionMenuButtonLocator - ); - - return await optionMenuButton.click(); + return await clickIfClickable( this.driver, optionMenuButtonLocator ); } async toggleSearchIconOnlySetting( block ) { @@ -735,10 +720,8 @@ class EditorPage { const elementName = isAndroid() ? '//*' : '//XCUIElementTypeOther'; - const locator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Use icon button")]`; - const useIconButton = await waitForVisible( this.driver, locator ); - - return await useIconButton.click(); + const useIconButtonLocator = `${ elementName }[starts-with(@${ this.accessibilityIdXPathAttrib }, "Use icon button")]`; + return await clickIfClickable( this.driver, useIconButtonLocator ); } async isSearchSettingsVisible() { From 83444ecf681fc0f55f96a271cdcd5fae4a74ca61 Mon Sep 17 00:00:00 2001 From: Derek Blank <derekpblank@gmail.com> Date: Fri, 10 Jun 2022 02:00:05 +1000 Subject: [PATCH 43/49] [RNMobile] Add 'Insert from URL' option to Video block (#41493) * Add onSelectURL function to mobile Video block edit * Add notices store to mobile Video block * Display Insert from URL in Video block media options menu * Allow native video block to use Embed block for embeddable URLs * Update native Video block src onSelectURL attribute * Update CHANGELOG --- .../components/media-upload/index.native.js | 2 +- .../block-library/src/video/edit.native.js | 31 ++++++++++++++++++- packages/react-native-editor/CHANGELOG.md | 1 + 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js index 8f793e4c0fa3f6..d8303e44e39d3d 100644 --- a/packages/block-editor/src/components/media-upload/index.native.js +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -127,7 +127,7 @@ export class MediaUpload extends Component { id: URL_MEDIA_SOURCE, value: URL_MEDIA_SOURCE, label: __( 'Insert from URL' ), - types: [ MEDIA_TYPE_AUDIO, MEDIA_TYPE_IMAGE ], + types: [ MEDIA_TYPE_AUDIO, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ], icon: globe, }; diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 4cca498f957d2f..e8e8d7af91c091 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -36,11 +36,13 @@ import { __, sprintf } from '@wordpress/i18n'; import { isURL, getProtocol } from '@wordpress/url'; import { doAction, hasAction } from '@wordpress/hooks'; import { video as SvgIcon, replace } from '@wordpress/icons'; -import { withSelect } from '@wordpress/data'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ +import { createUpgradedEmbedBlock } from '../embed/util'; import style from './style.scss'; import SvgIconRetry from './icon-retry'; import VideoCommonSettings from './edit-common-settings'; @@ -64,6 +66,7 @@ class VideoEdit extends Component { this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( this ); + this.onSelectURL = this.onSelectURL.bind( this ); this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); @@ -160,6 +163,25 @@ class VideoEdit extends Component { setAttributes( { id, src: url } ); } + onSelectURL( url ) { + const { createErrorNotice, onReplace, setAttributes } = this.props; + + if ( isURL( url ) ) { + // Check if there's an embed block that handles this URL. + const embedBlock = createUpgradedEmbedBlock( { + attributes: { url }, + } ); + if ( undefined !== embedBlock ) { + onReplace( embedBlock ); + return; + } + + setAttributes( { id: url, src: url } ); + } else { + createErrorNotice( __( 'Invalid URL.' ) ); + } + } + onVideoContanerLayout( event ) { const { width } = event.nativeEvent.layout; const height = width / VIDEO_ASPECT_RATIO; @@ -205,6 +227,7 @@ class VideoEdit extends Component { allowedTypes={ [ MEDIA_TYPE_VIDEO ] } isReplacingMedia={ true } onSelect={ this.onSelectMediaUploadOption } + onSelectURL={ this.onSelectURL } render={ ( { open, getMediaOptions } ) => { return ( <ToolbarGroup> @@ -226,6 +249,7 @@ class VideoEdit extends Component { <MediaPlaceholder allowedTypes={ [ MEDIA_TYPE_VIDEO ] } onSelect={ this.onSelectMediaUploadOption } + onSelectURL={ this.onSelectURL } icon={ this.getIcon( ICON_TYPE.PLACEHOLDER ) } onFocus={ this.props.onFocus } autoOpenMediaUpload={ @@ -378,5 +402,10 @@ export default compose( [ 'inserter_menu' ), } ) ), + withDispatch( ( dispatch ) => { + const { createErrorNotice } = dispatch( noticesStore ); + + return { createErrorNotice }; + } ), withPreferredColorScheme, ] )( VideoEdit ); diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 1a79d579c4a7f3..31c3a2249db3d9 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,6 +10,7 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased +- [*] Add 'Insert from URL' option to Video block [#41493] ## 1.78.1 From d407e8fc562481e47f32742eaaefeb74c802fba5 Mon Sep 17 00:00:00 2001 From: Derek Blank <derekpblank@gmail.com> Date: Wed, 22 Jun 2022 16:21:32 +1000 Subject: [PATCH 44/49] Upgrade Aztec to v1.5.9 (#41828) --- packages/react-native-aztec/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-aztec/android/build.gradle b/packages/react-native-aztec/android/build.gradle index 856f5d337d094c..ff48b4738d7bc1 100644 --- a/packages/react-native-aztec/android/build.gradle +++ b/packages/react-native-aztec/android/build.gradle @@ -9,7 +9,7 @@ buildscript { jSoupVersion = '1.10.3' wordpressUtilsVersion = '2.3.0' espressoVersion = '3.0.1' - aztecVersion = 'v1.5.8' + aztecVersion = 'v1.5.9' } } From 720843b1f69d6d685d48827d1af4c8e2340c3401 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Thu, 23 Jun 2022 13:09:44 -0500 Subject: [PATCH 45/49] fix: Image block preserves alt text from media library (#41839) * fix: Image block preserves alt text from media library When the alt text for a media item is present in the media library, that value should be copied into the Image block when inserted. This behavior avoids the need to re-enter the alt text for each image inserted into the post content. * fix: Allow Media with and without alt text Previous code required an alt text value, which broken existing code. This overrides the method to support both contexts. It also updates existing code to use the same method of generating media throughout the source. * test: Update alt text for demo editor test data Consistently setting the alt text for all platforms will likely help avoid confusion. * test: Update outdated test fixture data The demo editor now returns an alt text for the test image media. * docs: Update changelog --- .../block-library/src/image/edit.native.js | 1 + .../src/image/test/edit.native.js | 44 ++++++++++++++++--- .../ReactNativeGutenbergBridge/RNMedia.kt | 1 + .../wordpress/mobile/WPAndroidGlue/Media.kt | 37 ++++++++++++---- .../ios/GutenbergBridgeDelegate.swift | 4 +- packages/react-native-editor/CHANGELOG.md | 1 + .../__device-tests__/helpers/test-data.js | 2 +- .../java/com/gutenberg/MainApplication.java | 12 ++--- .../GutenbergViewController.swift | 4 +- 9 files changed, 82 insertions(+), 24 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 24df9fce2866a4..b0a5d05e6219b1 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -436,6 +436,7 @@ export class ImageEdit extends Component { id: media.id, url: media.url, caption: media.caption, + alt: media.alt, }; let additionalAttributes; diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/block-library/src/image/test/edit.native.js index 214c953be323b2..2803060847bb92 100644 --- a/packages/block-library/src/image/test/edit.native.js +++ b/packages/block-library/src/image/test/edit.native.js @@ -16,6 +16,7 @@ import Clipboard from '@react-native-clipboard/clipboard'; */ import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; import { + requestMediaPicker, setFeaturedImage, sendMediaUpload, subscribeMediaUpload, @@ -49,6 +50,10 @@ jest.mock( 'lodash', () => { return { ...actual, delay: ( cb ) => cb() }; } ); +function mockGetMedia( media ) { + jest.spyOn( select( coreStore ), 'getMedia' ).mockReturnValue( media ); +} + const apiFetchPromise = Promise.resolve( {} ); const clipboardPromise = Promise.resolve( '' ); @@ -292,12 +297,6 @@ describe( 'Image Block', () => { ); } - function mockGetMedia( media ) { - jest.spyOn( select( coreStore ), 'getMedia' ).mockReturnValueOnce( - media - ); - } - it( 'does not prompt to replace featured image during a new image upload', () => { // Arrange const INITIAL_IMAGE = { id: 1, url: 'mock-url-1' }; @@ -406,4 +405,37 @@ describe( 'Image Block', () => { ); } ); } ); + + it( 'sets src and alt attributes when selecting media', async () => { + const IMAGE = { id: 1, url: 'mock-image', alt: 'A beautiful mountain' }; + requestMediaPicker.mockImplementationOnce( + ( source, filter, multiple, callback ) => { + callback( { + id: IMAGE.id, + url: IMAGE.url, + alt: IMAGE.alt, + } ); + } + ); + mockGetMedia( { + id: IMAGE.id, + source_url: IMAGE.url, + } ); + + const initialHtml = ` + <!-- wp:image --> + <figure class="wp-block-image"> + <img alt="" /> + </figure> + <!-- /wp:image -->`; + const screen = await initializeEditor( { initialHtml } ); + + fireEvent.press( screen.getByText( 'ADD IMAGE' ) ); + fireEvent.press( screen.getByText( 'WordPress Media Library' ) ); + + const expectedHtml = `<!-- wp:image {"id":${ IMAGE.id },"sizeSlug":"large","linkDestination":"none"} --> +<figure class="wp-block-image size-large"><img src="${ IMAGE.url }" alt="${ IMAGE.alt }" class="wp-image-${ IMAGE.id }"/></figure> +<!-- /wp:image -->`; + expect( getEditorHtml() ).toBe( expectedHtml ); + } ); } ); diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNMedia.kt b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNMedia.kt index 8f1b3bb367e86c..9c3a25a5ed0762 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNMedia.kt +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNMedia.kt @@ -8,5 +8,6 @@ interface RNMedia { val type: String val caption: String val title: String + val alt: String fun toMap(): WritableMap } diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/Media.kt b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/Media.kt index fc835284805ae2..d225593d8eef4d 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/Media.kt +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/Media.kt @@ -14,7 +14,8 @@ data class Media( override val url: String, override val type: String, override val caption: String = "", - override val title: String = "" + override val title: String = "", + override val alt: String = "" ) : RNMedia { override fun toMap(): WritableMap = WritableNativeMap().apply { putInt("id", id) @@ -22,17 +23,11 @@ data class Media( putString("type", type) putString("caption", caption) putString("title", title) + putString("alt", alt) } companion object { - @JvmStatic - fun createRNMediaUsingMimeType( - id: Int, - url: String, - mimeType: String?, - caption: String?, - title: String? - ): Media { + private fun convertToType(mimeType: String?): String { val isMediaType = { mediaType: MediaType -> mimeType?.startsWith(mediaType.name.toLowerCase(Locale.ROOT)) == true } @@ -41,6 +36,30 @@ data class Media( isMediaType(VIDEO) -> VIDEO else -> OTHER }.name.toLowerCase(Locale.ROOT) + return type; + } + + @JvmStatic + fun createRNMediaUsingMimeType( + id: Int, + url: String, + mimeType: String?, + caption: String?, + title: String?, + alt: String?, + ): Media { + val type = convertToType(mimeType) + return Media(id, url, type, caption ?: "", title ?: "", alt ?: "") + } + @JvmStatic + fun createRNMediaUsingMimeType( + id: Int, + url: String, + mimeType: String?, + caption: String?, + title: String?, + ): Media { + val type = convertToType(mimeType) return Media(id, url, type, caption ?: "", title ?: "") } } diff --git a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift index 98b4c721caff1b..ae03570d6f038a 100644 --- a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift +++ b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift @@ -4,13 +4,15 @@ public struct MediaInfo: Encodable { public let type: String? public let title: String? public let caption: String? + public let alt: String? - public init(id: Int32?, url: String?, type: String?, caption: String? = nil, title: String? = nil) { + public init(id: Int32?, url: String?, type: String?, caption: String? = nil, title: String? = nil, alt: String? = nil) { self.id = id self.url = url self.type = type self.caption = caption self.title = title + self.alt = alt } } diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 31c3a2249db3d9..03c8d5ba9c6ebb 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,7 @@ For each user feature we should also add a importance categorization label to i ## Unreleased - [*] Add 'Insert from URL' option to Video block [#41493] +- [*] Image block copies the alt text from the media library when selecting an item [#41839] ## 1.78.1 diff --git a/packages/react-native-editor/__device-tests__/helpers/test-data.js b/packages/react-native-editor/__device-tests__/helpers/test-data.js index 87c4ae8ab51074..b234271843653d 100644 --- a/packages/react-native-editor/__device-tests__/helpers/test-data.js +++ b/packages/react-native-editor/__device-tests__/helpers/test-data.js @@ -96,7 +96,7 @@ exports.imageCompletehtml = `<!-- wp:image {"id":1,"sizeslug":"large"} --> <!-- /wp:paragraph -->`; exports.imageShorteHtml = `<!-- wp:image {"id":1,"sizeslug":"large"} --> -<figure class="wp-block-image size-large"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/><figcaption>C'est la vie my friends</figcaption></figure> +<figure class="wp-block-image size-large"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="A snow-capped mountain top in a cloudy sky with red-leafed trees in the foreground" class="wp-image-1"/><figcaption>C'est la vie my friends</figcaption></figure> <!-- /wp:image --> <!-- wp:paragraph --> diff --git a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java index b94e581a736cbe..354cb09e953ecd 100644 --- a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java +++ b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java @@ -1,5 +1,7 @@ package com.gutenberg; +import static org.wordpress.mobile.WPAndroidGlue.Media.createRNMediaUsingMimeType; + import android.app.Application; import android.content.Intent; import android.content.res.Configuration; @@ -83,17 +85,17 @@ public void requestMediaPickFromMediaLibrary(MediaSelectedCallback mediaSelected switch (mediaType) { case IMAGE: - rnMediaList.add(new Media(1, "https://cldup.com/cXyG__fTLN.jpg", "image", "Mountain", "")); + rnMediaList.add(createRNMediaUsingMimeType(1, "https://cldup.com/cXyG__fTLN.jpg", "image", "Mountain", "", "A snow-capped mountain top in a cloudy sky with red-leafed trees in the foreground")); break; case VIDEO: - rnMediaList.add(new Media(2, "https://i.cloudup.com/YtZFJbuQCE.mov", "video", "Cloudup", "")); + rnMediaList.add(createRNMediaUsingMimeType(2, "https://i.cloudup.com/YtZFJbuQCE.mov", "video", "Cloudup", "")); break; case ANY: case OTHER: - rnMediaList.add(new Media(3, "https://wordpress.org/latest.zip", "zip", "WordPress latest version", "WordPress.zip")); + rnMediaList.add(createRNMediaUsingMimeType(3, "https://wordpress.org/latest.zip", "zip", "WordPress latest version", "WordPress.zip")); break; case AUDIO: - rnMediaList.add(new Media(5, "https://cldup.com/59IrU0WJtq.mp3", "audio", "Summer presto", "")); + rnMediaList.add(createRNMediaUsingMimeType(5, "https://cldup.com/59IrU0WJtq.mp3", "audio", "Summer presto", "")); break; } mediaSelectedCallback.onMediaFileSelected(rnMediaList); @@ -145,7 +147,7 @@ public void getOtherMediaPickerOptions(OtherMediaOptionsReceivedCallback otherMe public void requestMediaPickFrom(String mediaSource, MediaSelectedCallback mediaSelectedCallback, Boolean allowMultipleSelection) { if (mediaSource.equals("1")) { List<RNMedia> rnMediaList = new ArrayList<>(); - rnMediaList.add(new Media(1, "https://grad.illinois.edu/sites/default/files/pdfs/cvsamples.pdf", "other", "","cvsamples.pdf")); + rnMediaList.add(createRNMediaUsingMimeType(1, "https://grad.illinois.edu/sites/default/files/pdfs/cvsamples.pdf", "other", "","cvsamples.pdf")); mediaSelectedCallback.onMediaFileSelected(rnMediaList); } } diff --git a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift index 9c722452c19d9b..928310f3f76439 100644 --- a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift +++ b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift @@ -88,9 +88,9 @@ extension GutenbergViewController: GutenbergBridgeDelegate { case .image: if(allowMultipleSelection) { callback([MediaInfo(id: 1, url: "https://cldup.com/cXyG__fTLN.jpg", type: "image"), - MediaInfo(id: 3, url: "https://cldup.com/cXyG__fTLN.jpg", type: "image", caption: "Mountain")]) + MediaInfo(id: 3, url: "https://cldup.com/cXyG__fTLN.jpg", type: "image", caption: "Mountain", alt: "A snow-capped mountain top in a cloudy sky with red-leafed trees in the foreground")]) } else { - callback([MediaInfo(id: 1, url: "https://cldup.com/cXyG__fTLN.jpg", type: "image", caption: "Mountain")]) + callback([MediaInfo(id: 1, url: "https://cldup.com/cXyG__fTLN.jpg", type: "image", caption: "Mountain", alt: "A snow-capped mountain top in a cloudy sky with red-leafed trees in the foreground")]) } case .video: if(allowMultipleSelection) { From 94e8c688d4f146d31c70d59e76adcb799260f450 Mon Sep 17 00:00:00 2001 From: Siobhan Bamber <siobhan@automattic.com> Date: Mon, 4 Jul 2022 11:02:47 +0100 Subject: [PATCH 46/49] [RNMobile] Implement recovery option for invalid blocks (#41988) If a block's validation fails within the mobile app, users are shown an error but not provided with any options for recovering the block. This PR introduces an option for users to attempt block recovery. They'll be prompted to tap on a block if they wish to attempt recovery. --- .../block-invalid-warning.native.js | 49 ++++++++++++++++--- .../src/components/block-list/block.native.js | 1 + packages/react-native-editor/CHANGELOG.md | 1 + 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block-invalid-warning.native.js b/packages/block-editor/src/components/block-list/block-invalid-warning.native.js index 38a03da3cf5eeb..83f4513ba643eb 100644 --- a/packages/block-editor/src/components/block-list/block-invalid-warning.native.js +++ b/packages/block-editor/src/components/block-list/block-invalid-warning.native.js @@ -1,27 +1,62 @@ +/** + * External dependencies + */ +import { TouchableWithoutFeedback } from 'react-native'; + /** * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies */ import Warning from '../warning'; +import { store as blockEditorStore } from '../../store'; -export default function BlockInvalidWarning( { blockTitle, icon } ) { +export default function BlockInvalidWarning( { blockTitle, icon, clientId } ) { const accessibilityLabel = sprintf( /* translators: accessibility text for blocks with invalid content. %d: localized block title */ __( '%s block. This block has invalid content' ), blockTitle ); + const selector = ( select ) => { + const { getBlock } = select( blockEditorStore ); + const block = getBlock( clientId ); + return { + block, + }; + }; + + const { block } = useSelect( selector, [ clientId ] ); + + const { replaceBlock } = useDispatch( blockEditorStore ); + + const recoverBlock = ( { name, attributes, innerBlocks } ) => + createBlock( name, attributes, innerBlocks ); + + const attemptBlockRecovery = () => { + replaceBlock( block.clientId, recoverBlock( block ) ); + }; + return ( - <Warning - title={ blockTitle } - message={ __( 'Problem displaying block' ) } - icon={ icon } + <TouchableWithoutFeedback + onPress={ attemptBlockRecovery } accessible={ true } - accessibilityLabel={ accessibilityLabel } - /> + accessibilityRole={ 'button' } + > + <Warning + title={ blockTitle } + // eslint-disable-next-line @wordpress/i18n-no-collapsible-whitespace + message={ __( + 'Problem displaying block. \nTap to attempt block recovery.' + ) } + icon={ icon } + accessibilityLabel={ accessibilityLabel } + /> + </TouchableWithoutFeedback> ); } diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 4ea24537e2e87c..c8b57443061c06 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -272,6 +272,7 @@ class BlockListBlock extends Component { <BlockInvalidWarning blockTitle={ title } icon={ icon } + clientId={ clientId } /> ) } diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 03c8d5ba9c6ebb..558259ee781aa7 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -12,6 +12,7 @@ For each user feature we should also add a importance categorization label to i ## Unreleased - [*] Add 'Insert from URL' option to Video block [#41493] - [*] Image block copies the alt text from the media library when selecting an item [#41839] +- [*] Introduce "block recovery" option for invalid blocks [#41988] ## 1.78.1 From 95a1894b134fc6bf5762df539956d9a64e3206b6 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:18:32 -0500 Subject: [PATCH 47/49] docs: Update change log --- packages/react-native-editor/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 558259ee781aa7..a31b4fa5dad2c9 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,6 +10,8 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased + +## 1.79.0 - [*] Add 'Insert from URL' option to Video block [#41493] - [*] Image block copies the alt text from the media library when selecting an item [#41839] - [*] Introduce "block recovery" option for invalid blocks [#41988] From c9c1e6b78cfb2416086773691e2536aecd00c684 Mon Sep 17 00:00:00 2001 From: Oguz Kocer <oguzkocer@users.noreply.github.com> Date: Thu, 7 Jul 2022 12:44:12 -0400 Subject: [PATCH 48/49] [RNMobile] Upgrades Gradle to 7.4.2 & Android Gradle Plugin to 7.2.1 (#42136) * Update Gradle to 7.4.1 * Update AGP to 7.2.1 --- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/settings.gradle | 2 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/settings.gradle | 2 +- .../react-native-editor/android/build.gradle | 2 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- 9 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-native-aztec/android/gradle/wrapper/gradle-wrapper.jar b/packages/react-native-aztec/android/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 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 R_{@Vu|29OF7ypyz{{qB}Ob!45 delta 8722 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 diff --git a/packages/react-native-aztec/android/gradle/wrapper/gradle-wrapper.properties b/packages/react-native-aztec/android/gradle/wrapper/gradle-wrapper.properties index b1159fc54f39b3..92f06b50fd65b4 100644 --- a/packages/react-native-aztec/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/react-native-aztec/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/packages/react-native-aztec/android/settings.gradle b/packages/react-native-aztec/android/settings.gradle index e405489a45de49..8b0c3d17fa200d 100644 --- a/packages/react-native-aztec/android/settings.gradle +++ b/packages/react-native-aztec/android/settings.gradle @@ -2,7 +2,7 @@ pluginManagement { gradle.ext.kotlinVersion = '1.5.32' plugins { - id "com.android.library" version "7.1.1" + id "com.android.library" version "7.2.1" id "org.jetbrains.kotlin.android" version gradle.ext.kotlinVersion id "com.automattic.android.publish-to-s3" version "0.7.0" } diff --git a/packages/react-native-bridge/android/gradle/wrapper/gradle-wrapper.jar b/packages/react-native-bridge/android/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 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 R_{@Vu|29OF7ypyz{{qB}Ob!45 delta 8722 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 diff --git a/packages/react-native-bridge/android/gradle/wrapper/gradle-wrapper.properties b/packages/react-native-bridge/android/gradle/wrapper/gradle-wrapper.properties index b1159fc54f39b3..92f06b50fd65b4 100644 --- a/packages/react-native-bridge/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/react-native-bridge/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/packages/react-native-bridge/android/settings.gradle b/packages/react-native-bridge/android/settings.gradle index 01c59d04f9ba65..d7374ea3743bbf 100644 --- a/packages/react-native-bridge/android/settings.gradle +++ b/packages/react-native-bridge/android/settings.gradle @@ -2,7 +2,7 @@ pluginManagement { gradle.ext.kotlinVersion = '1.5.32' plugins { - id "com.android.library" version "7.1.1" + id "com.android.library" version "7.2.1" id "org.jetbrains.kotlin.android" version gradle.ext.kotlinVersion id "com.automattic.android.publish-to-s3" version "0.7.0" } diff --git a/packages/react-native-editor/android/build.gradle b/packages/react-native-editor/android/build.gradle index cb4d6531d35447..848c57073453a7 100644 --- a/packages/react-native-editor/android/build.gradle +++ b/packages/react-native-editor/android/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - gradlePluginVersion = '7.1.1' + gradlePluginVersion = '7.2.1' kotlinVersion = '1.5.32' buildToolsVersion = "30.0.2" minSdkVersion = 21 diff --git a/packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.jar b/packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 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 R_{@Vu|29OF7ypyz{{qB}Ob!45 delta 8722 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 diff --git a/packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.properties b/packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.properties index b1159fc54f39b3..92f06b50fd65b4 100644 --- a/packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/react-native-editor/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 581fc704a1d129b4efa10dc1a590c5cef7bbc687 Mon Sep 17 00:00:00 2001 From: Oguz Kocer <oguzkocer@users.noreply.github.com> Date: Thu, 7 Jul 2022 14:40:22 -0400 Subject: [PATCH 49/49] Update Aztec-Android version to v1.6.0 (#42243) --- packages/react-native-aztec/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-aztec/android/build.gradle b/packages/react-native-aztec/android/build.gradle index ff48b4738d7bc1..39383624f2bf71 100644 --- a/packages/react-native-aztec/android/build.gradle +++ b/packages/react-native-aztec/android/build.gradle @@ -9,7 +9,7 @@ buildscript { jSoupVersion = '1.10.3' wordpressUtilsVersion = '2.3.0' espressoVersion = '3.0.1' - aztecVersion = 'v1.5.9' + aztecVersion = 'v1.6.0' } }