diff --git a/changelog.txt b/changelog.txt index 0b70b68c9377e..58b1b833d2c71 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ == Changelog == += 17.2.4 = + +## Changelog + +### Bug Fixes + +- Site editor: fix image upload bug ([57040](https://github.com/WordPress/gutenberg/pull/57040)) + +## Contributors + +The following contributors merged PRs in this release: + +@glendaviesnz + + + + = 17.3.0 = diff --git a/docs/assets/text-decoration-component.png b/docs/assets/text-decoration-component.png new file mode 100644 index 0000000000000..4626f953cf9c1 Binary files /dev/null and b/docs/assets/text-decoration-component.png differ diff --git a/packages/block-editor/src/components/offline-status/index.native.js b/packages/block-editor/src/components/offline-status/index.native.js index ae6007e75103c..0447791e69a7e 100644 --- a/packages/block-editor/src/components/offline-status/index.native.js +++ b/packages/block-editor/src/components/offline-status/index.native.js @@ -6,11 +6,13 @@ import { Text, View } from 'react-native'; /** * WordPress dependencies */ -import { usePreferredColorSchemeStyle } from '@wordpress/compose'; +import { + usePreferredColorSchemeStyle, + useNetworkConnectivity, +} from '@wordpress/compose'; import { Icon } from '@wordpress/components'; import { offline as offlineIcon } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; -import { useIsConnected } from '@wordpress/react-native-bridge'; /** * Internal dependencies @@ -18,7 +20,7 @@ import { useIsConnected } from '@wordpress/react-native-bridge'; import styles from './style.native.scss'; const OfflineStatus = () => { - const { isConnected } = useIsConnected(); + const { isConnected } = useNetworkConnectivity(); const containerStyle = usePreferredColorSchemeStyle( styles.offline, diff --git a/packages/block-editor/src/components/text-decoration-control/README.md b/packages/block-editor/src/components/text-decoration-control/README.md new file mode 100644 index 0000000000000..a606140baa330 --- /dev/null +++ b/packages/block-editor/src/components/text-decoration-control/README.md @@ -0,0 +1,40 @@ +# TextDecorationControl + +
+This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. +
+
+ +![TextDecorationControl Element in Inspector Control](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/text-decoration-component.png?raw=true) + + +## Usage + +```jsx +import { __experimentalTextDecorationControl as TextDecorationControl } from '@wordpress/block-editor'; +``` + +Then, you can use the component in your block editor UI: + +```jsx + setAttributes({ textDecoration: newValue })} +/> +``` + +### Props + +### `value` + +- **Type:** `String` +- **Default:** `none` +- **Options:** `none`, `underline`, `line-through` + +The current value of the Text Decoration setting. You may only choose from the `Options` listed above. + +### `onChange` + +- **Type:** `Function` + +A callback function invoked when the Text Decoration value is changed via an interaction with any of the buttons. Called with the Text Decoration value (`none`, `underline`, `line-through`) as the only argument. \ No newline at end of file diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 13fe6aa7ba53a..773000ad7c152 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -47,9 +47,19 @@ function AudioEdit( { } ) { const { id, autoplay, loop, preload, src } = attributes; const isTemporaryAudio = ! id && isBlobURL( src ); - const mediaUpload = useSelect( ( select ) => { - const { getSettings } = select( blockEditorStore ); - return getSettings().mediaUpload; + const { mediaUpload, multiAudioSelection } = useSelect( ( select ) => { + const { getSettings, getMultiSelectedBlockClientIds, getBlockName } = + select( blockEditorStore ); + const multiSelectedClientIds = getMultiSelectedBlockClientIds(); + + return { + mediaUpload: getSettings().mediaUpload, + multiAudioSelection: + multiSelectedClientIds.length && + multiSelectedClientIds.every( + ( _clientId ) => getBlockName( _clientId ) === 'core/audio' + ), + }; }, [] ); useEffect( () => { @@ -146,17 +156,19 @@ function AudioEdit( { return ( <> - - - + { ! multiAudioSelection && ( + + + + ) } diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 03fc15d19eedc..371a13b1bf5ad 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -104,23 +104,38 @@ function GalleryEdit( props ) { getSettings, preferredStyle, innerBlockImages, - wasBlockJustInserted, + blockWasJustInserted, + multiGallerySelection, } = useSelect( ( select ) => { - const settings = select( blockEditorStore ).getSettings(); + const { + getBlockName, + getMultiSelectedBlockClientIds, + getSettings: _getSettings, + getBlock: _getBlock, + wasBlockJustInserted, + } = select( blockEditorStore ); const preferredStyleVariations = - settings.__experimentalPreferredStyleVariations; + _getSettings().__experimentalPreferredStyleVariations; + const multiSelectedClientIds = getMultiSelectedBlockClientIds(); + return { - getBlock: select( blockEditorStore ).getBlock, - getSettings: select( blockEditorStore ).getSettings, + getBlock: _getBlock, + getSettings: _getSettings, preferredStyle: preferredStyleVariations?.value?.[ 'core/image' ], innerBlockImages: - select( blockEditorStore ).getBlock( clientId ) - ?.innerBlocks ?? EMPTY_ARRAY, - wasBlockJustInserted: select( - blockEditorStore - ).wasBlockJustInserted( clientId, 'inserter_menu' ), + _getBlock( clientId )?.innerBlocks ?? EMPTY_ARRAY, + blockWasJustInserted: wasBlockJustInserted( + clientId, + 'inserter_menu' + ), + multiGallerySelection: + multiSelectedClientIds.length && + multiSelectedClientIds.every( + ( _clientId ) => + getBlockName( _clientId ) === 'core/gallery' + ), }; }, [ clientId ] @@ -461,7 +476,7 @@ function GalleryEdit( props ) { ( hasImages && ! isSelected ) || imagesUploading, value: hasImageIds ? images : {}, autoOpenMediaUpload: - ! hasImages && isSelected && wasBlockJustInserted, + ! hasImages && isSelected && blockWasJustInserted, onFocus, }, } ); @@ -583,20 +598,22 @@ function GalleryEdit( props ) { { Platform.isWeb && ( <> - - image.id ) - .map( ( image ) => image.id ) } - addToGallery={ hasImageIds } - /> - + { ! multiGallerySelection && ( + + image.id ) + .map( ( image ) => image.id ) } + addToGallery={ hasImageIds } + /> + + ) } ); diff --git a/packages/block-library/src/gallery/gallery.js b/packages/block-library/src/gallery/gallery.js index e898ae2e9fdcb..10c05eb8cc401 100644 --- a/packages/block-library/src/gallery/gallery.js +++ b/packages/block-library/src/gallery/gallery.js @@ -24,6 +24,7 @@ export default function Gallery( props ) { blockProps, __unstableLayoutClassNames: layoutClassNames, isContentLocked, + multiGallerySelection, } = props; const { align, columns, imageCrop } = attributes; @@ -54,7 +55,9 @@ export default function Gallery( props ) { setAttributes={ setAttributes } isSelected={ isSelected } insertBlocksAfter={ insertBlocksAfter } - showToolbarButton={ ! isContentLocked } + showToolbarButton={ + ! multiGallerySelection && ! isContentLocked + } className="blocks-gallery-caption" label={ __( 'Gallery caption text' ) } placeholder={ __( 'Add gallery caption' ) } diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index e76a99e424a51..ea12457c2585d 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -134,17 +134,49 @@ export default function Image( { const { allowResize = true } = context; const { getBlock } = useSelect( blockEditorStore ); - const { image, multiImageSelection } = useSelect( + const { image } = useSelect( ( select ) => { const { getMedia } = select( coreStore ); - const { getMultiSelectedBlockClientIds, getBlockName } = - select( blockEditorStore ); - const multiSelectedClientIds = getMultiSelectedBlockClientIds(); return { image: id && isSelected ? getMedia( id, { context: 'view' } ) : null, + }; + }, + [ id, isSelected ] + ); + + const { + canInsertCover, + imageEditing, + imageSizes, + maxWidth, + mediaUpload, + multiImageSelection, + } = useSelect( + ( select ) => { + const { + getBlockRootClientId, + getMultiSelectedBlockClientIds, + getBlockName, + getSettings, + canInsertBlockType, + } = select( blockEditorStore ); + + const rootClientId = getBlockRootClientId( clientId ); + const settings = getSettings(); + const multiSelectedClientIds = getMultiSelectedBlockClientIds(); + + return { + imageEditing: settings.imageEditing, + imageSizes: settings.imageSizes, + maxWidth: settings.maxWidth, + mediaUpload: settings.mediaUpload, + canInsertCover: canInsertBlockType( + 'core/cover', + rootClientId + ), multiImageSelection: multiSelectedClientIds.length && multiSelectedClientIds.every( @@ -153,33 +185,8 @@ export default function Image( { ), }; }, - [ id, isSelected ] + [ clientId ] ); - const { canInsertCover, imageEditing, imageSizes, maxWidth, mediaUpload } = - useSelect( - ( select ) => { - const { - getBlockRootClientId, - getSettings, - canInsertBlockType, - } = select( blockEditorStore ); - - const rootClientId = getBlockRootClientId( clientId ); - const settings = getSettings(); - - return { - imageEditing: settings.imageEditing, - imageSizes: settings.imageSizes, - maxWidth: settings.maxWidth, - mediaUpload: settings.mediaUpload, - canInsertCover: canInsertBlockType( - 'core/cover', - rootClientId - ), - }; - }, - [ clientId ] - ); const { replaceBlocks, toggleSelection } = useDispatch( blockEditorStore ); const { createErrorNotice, createSuccessNotice } = @@ -755,7 +762,9 @@ export default function Image( { isSelected={ isSelected } insertBlocksAfter={ insertBlocksAfter } label={ __( 'Image caption text' ) } - showToolbarButton={ hasNonContentControls } + showToolbarButton={ + ! multiImageSelection && hasNonContentControls + } /> ); diff --git a/packages/block-library/src/social-link/index.php b/packages/block-library/src/social-link/index.php index b203a662822f5..fe256879fa4ff 100644 --- a/packages/block-library/src/social-link/index.php +++ b/packages/block-library/src/social-link/index.php @@ -33,7 +33,7 @@ function render_block_core_social_link( $attributes, $content, $block ) { * The `is_email` returns false for emails with schema. */ if ( is_email( $url ) ) { - $url = 'mailto:' . $url; + $url = 'mailto:' . antispambot( $url ); } /** diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 88b2669e66f56..db1fbb197126a 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -75,10 +75,20 @@ function VideoEdit( { const posterImageButton = useRef(); const { id, controls, poster, src, tracks } = attributes; const isTemporaryVideo = ! id && isBlobURL( src ); - const mediaUpload = useSelect( - ( select ) => select( blockEditorStore ).getSettings().mediaUpload, - [] - ); + const { mediaUpload, multiVideoSelection } = useSelect( ( select ) => { + const { getSettings, getMultiSelectedBlockClientIds, getBlockName } = + select( blockEditorStore ); + const multiSelectedClientIds = getMultiSelectedBlockClientIds(); + + return { + mediaUpload: getSettings().mediaUpload, + multiVideoSelection: + multiSelectedClientIds.length && + multiSelectedClientIds.every( + ( _clientId ) => getBlockName( _clientId ) === 'core/video' + ), + }; + }, [] ); useEffect( () => { if ( ! id && isBlobURL( src ) ) { @@ -185,25 +195,29 @@ function VideoEdit( { return ( <> - - { - setAttributes( { tracks: newTracks } ); - } } - /> - - - - + { ! multiVideoSelection && ( + <> + + { + setAttributes( { tracks: newTracks } ); + } } + /> + + + + + + ) } diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index acf5a57bfe6d6..026ad72de90ea 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -15,6 +15,7 @@ - `Truncate`: improve handling of non-string `children` ([#57261](https://github.com/WordPress/gutenberg/pull/57261)). - `PaletteEdit`: Don't discard colors with default name and slug ([#54332](https://github.com/WordPress/gutenberg/pull/54332)). - `RadioControl`: Fully encapsulate styles ([#57347](https://github.com/WordPress/gutenberg/pull/57347)). +- `GradientPicker`: Use slug while iterating over gradient entries to avoid React "duplicated key" warning ([#57361](https://github.com/WordPress/gutenberg/pull/57361)). ### Enhancements diff --git a/packages/components/src/gradient-picker/index.tsx b/packages/components/src/gradient-picker/index.tsx index b435ded2bcd0d..52e7e716642da 100644 --- a/packages/components/src/gradient-picker/index.tsx +++ b/packages/components/src/gradient-picker/index.tsx @@ -44,9 +44,9 @@ function SingleOrigin( { ...additionalProps }: PickerProps< GradientObject > ) { const gradientOptions = useMemo( () => { - return gradients.map( ( { gradient, name }, index ) => ( + return gradients.map( ( { gradient, name, slug }, index ) => ( { - return ( props ) => { - const { isConnected } = useIsConnected(); - return ; - }; -}, 'withIsConnected' ); - -export default withIsConnected; diff --git a/packages/compose/src/higher-order/with-network-connectivity/README.md b/packages/compose/src/higher-order/with-network-connectivity/README.md new file mode 100644 index 0000000000000..7ae64c0949973 --- /dev/null +++ b/packages/compose/src/higher-order/with-network-connectivity/README.md @@ -0,0 +1,20 @@ +# withNetworkConnectivity + +`withNetworkConnectivity` provides a true/false mobile connectivity status based on the `useNetworkConnectivity` hook. + +## Usage + +```jsx +/** + * WordPress dependencies + */ +import { withNetworkConnectivity } from '@wordpress/compose'; + +export class MyComponent extends Component { + if ( this.props.isConnected !== true ) { + console.log( 'You are currently offline.' ) + } +} + +export default withNetworkConnectivity( MyComponent ) +``` diff --git a/packages/compose/src/higher-order/with-network-connectivity/index.native.js b/packages/compose/src/higher-order/with-network-connectivity/index.native.js new file mode 100644 index 0000000000000..1a416966d45be --- /dev/null +++ b/packages/compose/src/higher-order/with-network-connectivity/index.native.js @@ -0,0 +1,19 @@ +/** + * Internal dependencies + */ +import { createHigherOrderComponent } from '../../utils/create-higher-order-component'; +import useNetworkConnectivity from '../../hooks/use-network-connectivity'; + +const withNetworkConnectivity = createHigherOrderComponent( + ( WrappedComponent ) => { + return ( props ) => { + const { isConnected } = useNetworkConnectivity(); + return ( + + ); + }; + }, + 'withNetworkConnectivity' +); + +export default withNetworkConnectivity; diff --git a/packages/compose/src/hooks/use-network-connectivity/index.native.js b/packages/compose/src/hooks/use-network-connectivity/index.native.js new file mode 100644 index 0000000000000..1a806cc99a5a7 --- /dev/null +++ b/packages/compose/src/hooks/use-network-connectivity/index.native.js @@ -0,0 +1,59 @@ +/** + * WordPress dependencies + */ +import { useEffect, useState } from '@wordpress/element'; +import { + requestConnectionStatus, + subscribeConnectionStatus, +} from '@wordpress/react-native-bridge'; + +/** + * @typedef {Object} NetworkInformation + * + * @property {boolean} [isConnected] Whether the device is connected to a network. + */ + +/** + * Returns the current network connectivity status provided by the native bridge. + * + * @example + * + * ```jsx + * const { isConnected } = useNetworkConnectivity(); + * ``` + * + * @return {NetworkInformation} Network information. + */ +export default function useNetworkConnectivity() { + const [ isConnected, setIsConnected ] = useState( true ); + + useEffect( () => { + let isCurrent = true; + + requestConnectionStatus( ( isBridgeConnected ) => { + if ( ! isCurrent ) { + return; + } + + setIsConnected( isBridgeConnected ); + } ); + + return () => { + isCurrent = false; + }; + }, [] ); + + useEffect( () => { + const subscription = subscribeConnectionStatus( + ( { isConnected: isBridgeConnected } ) => { + setIsConnected( isBridgeConnected ); + } + ); + + return () => { + subscription.remove(); + }; + }, [] ); + + return { isConnected }; +} diff --git a/packages/compose/src/hooks/use-network-connectivity/test/index.native.js b/packages/compose/src/hooks/use-network-connectivity/test/index.native.js new file mode 100644 index 0000000000000..82dce664b8b9c --- /dev/null +++ b/packages/compose/src/hooks/use-network-connectivity/test/index.native.js @@ -0,0 +1,87 @@ +/** + * External dependencies + */ +import { act, renderHook } from 'test/helpers'; + +/** + * WordPress dependencies + */ +import { + requestConnectionStatus, + subscribeConnectionStatus, +} from '@wordpress/react-native-bridge'; + +/** + * Internal dependencies + */ +import useNetworkConnectivity from '../index'; + +describe( 'useNetworkConnectivity', () => { + it( 'should optimisitically presume network connectivity', () => { + const { result } = renderHook( () => useNetworkConnectivity() ); + + expect( result.current.isConnected ).toBe( true ); + } ); + + describe( 'when network connectivity is available', () => { + beforeAll( () => { + requestConnectionStatus.mockImplementation( ( callback ) => { + callback( true ); + return { remove: jest.fn() }; + } ); + } ); + + it( 'should return true', () => { + const { result } = renderHook( () => useNetworkConnectivity() ); + + expect( result.current.isConnected ).toBe( true ); + } ); + + it( 'should update the status when network connectivity changes', () => { + let subscriptionCallback; + subscribeConnectionStatus.mockImplementation( ( callback ) => { + subscriptionCallback = callback; + return { remove: jest.fn() }; + } ); + + const { result } = renderHook( () => useNetworkConnectivity() ); + + expect( result.current.isConnected ).toBe( true ); + + act( () => subscriptionCallback( { isConnected: false } ) ); + + expect( result.current.isConnected ).toBe( false ); + } ); + } ); + + describe( 'when network connectivity is unavailable', () => { + beforeAll( () => { + requestConnectionStatus.mockImplementation( ( callback ) => { + callback( false ); + return { remove: jest.fn() }; + } ); + } ); + + it( 'should return false', () => { + const { result } = renderHook( () => useNetworkConnectivity() ); + + expect( result.current.isConnected ).toBe( false ); + } ); + + it( 'should update the status when network connectivity changes', () => { + let subscriptionCallback; + subscribeConnectionStatus.mockImplementation( ( callback ) => { + subscriptionCallback = callback; + return { remove: jest.fn() }; + } ); + + const { result } = renderHook( () => useNetworkConnectivity() ); + + expect( result.current.isConnected ).toBe( false ); + + act( () => subscriptionCallback( { isConnected: true } ) ); + + expect( result.current.isConnected ).toBe( true ); + } ); + } ); +} ); diff --git a/packages/compose/src/index.native.js b/packages/compose/src/index.native.js index 00e0a66a36034..8d0953b81a14e 100644 --- a/packages/compose/src/index.native.js +++ b/packages/compose/src/index.native.js @@ -17,7 +17,7 @@ export { default as withInstanceId } from './higher-order/with-instance-id'; export { default as withSafeTimeout } from './higher-order/with-safe-timeout'; export { default as withState } from './higher-order/with-state'; export { default as withPreferredColorScheme } from './higher-order/with-preferred-color-scheme'; -export { default as withIsConnected } from './higher-order/with-is-connected'; +export { default as withNetworkConnectivity } from './higher-order/with-network-connectivity'; // Hooks. export { default as useConstrainedTabbing } from './hooks/use-constrained-tabbing'; @@ -38,3 +38,4 @@ export { default as useDebouncedInput } from './hooks/use-debounced-input'; export { default as useThrottle } from './hooks/use-throttle'; export { default as useMergeRefs } from './hooks/use-merge-refs'; export { default as useRefEffect } from './hooks/use-ref-effect'; +export { default as useNetworkConnectivity } from './hooks/use-network-connectivity'; diff --git a/packages/dataviews/src/add-filter.js b/packages/dataviews/src/add-filter.js index acc4c86534014..a9d8d78509dc4 100644 --- a/packages/dataviews/src/add-filter.js +++ b/packages/dataviews/src/add-filter.js @@ -209,16 +209,14 @@ export default function AddFilter( { filters, view, onChangeView } ) { disabled={ ! activeElement } hideOnClick={ false } onClick={ () => { - onChangeView( ( currentView ) => ( { - ...currentView, + onChangeView( { + ...view, page: 1, - filters: - currentView.filters.filter( - ( f ) => - f.field !== - filter.field - ), - } ) ); + filters: view.filters.filter( + ( f ) => + f.field !== filter.field + ), + } ); } } > @@ -240,11 +238,11 @@ export default function AddFilter( { filters, view, onChangeView } ) { } hideOnClick={ false } onClick={ () => { - onChangeView( ( currentView ) => ( { - ...currentView, + onChangeView( { + ...view, page: 1, filters: [], - } ) ); + } ); } } > diff --git a/packages/dataviews/src/filters.js b/packages/dataviews/src/filters.js index 6195eefe8fe47..0ba9d1d50a969 100644 --- a/packages/dataviews/src/filters.js +++ b/packages/dataviews/src/filters.js @@ -68,7 +68,7 @@ export default function Filters( { fields, view, onChangeView } ) { return ( { +export default function ResetFilter( { view, onChangeView } ) { return ( ); -}; +} diff --git a/packages/dataviews/src/search.js b/packages/dataviews/src/search.js index 2e58b721d6e2e..10a578b49aab2 100644 --- a/packages/dataviews/src/search.js +++ b/packages/dataviews/src/search.js @@ -18,11 +18,11 @@ export default function Search( { label, view, onChangeView } ) { onChangeViewRef.current = onChangeView; }, [ onChangeView ] ); useEffect( () => { - onChangeViewRef.current( ( currentView ) => ( { - ...currentView, + onChangeViewRef.current( { + ...view, page: 1, search: debouncedSearch, - } ) ); + } ); }, [ debouncedSearch ] ); const searchLabel = label || __( 'Filter list' ); return ( diff --git a/packages/e2e-tests/specs/editor/various/block-switcher.test.js b/packages/e2e-tests/specs/editor/various/block-switcher.test.js deleted file mode 100644 index 61278881077ba..0000000000000 --- a/packages/e2e-tests/specs/editor/various/block-switcher.test.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * WordPress dependencies - */ -import { - hasBlockSwitcher, - getAvailableBlockTransforms, - createNewPost, - insertBlock, - pressKeyWithModifier, -} from '@wordpress/e2e-test-utils'; - -describe( 'Block Switcher', () => { - beforeEach( async () => { - await createNewPost(); - } ); - - it( 'Should show the expected block transforms on the list block when the blocks are removed', async () => { - // Insert a list block. - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( '- List content' ); - await page.keyboard.press( 'ArrowUp' ); - await pressKeyWithModifier( 'alt', 'F10' ); - - // Verify the block switcher exists. - expect( await hasBlockSwitcher() ).toBeTruthy(); - - // Verify the correct block transforms appear. - expect( await getAvailableBlockTransforms() ).toEqual( - expect.arrayContaining( [ - 'Group', - 'Paragraph', - 'Heading', - 'Quote', - 'Columns', - ] ) - ); - } ); - - it( 'Should show the expected block transforms on the list block when the quote block is removed', async () => { - // Remove the quote block from the list of registered blocks. - await page.evaluate( () => { - wp.blocks.unregisterBlockType( 'core/quote' ); - } ); - - // Insert a list block. - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( '- List content' ); - await page.keyboard.press( 'ArrowUp' ); - await pressKeyWithModifier( 'alt', 'F10' ); - - // Verify the block switcher exists. - expect( await hasBlockSwitcher() ).toBeTruthy(); - - // Verify the correct block transforms appear. - expect( await getAvailableBlockTransforms() ).toEqual( - expect.arrayContaining( [ - 'Group', - 'Paragraph', - 'Heading', - 'Columns', - ] ) - ); - } ); - - it( 'Should not show the block switcher if all the blocks the list block transforms into are removed', async () => { - // Insert a list block. - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( '- List content' ); - - // Remove the paragraph and quote block from the list of registered blocks. - await page.evaluate( () => { - [ - 'core/quote', - 'core/pullquote', - 'core/paragraph', - 'core/group', - 'core/heading', - 'core/columns', - ].forEach( ( block ) => wp.blocks.unregisterBlockType( block ) ); - } ); - - await page.keyboard.press( 'ArrowUp' ); - await pressKeyWithModifier( 'alt', 'F10' ); - - // Verify the block switcher exists. - expect( await hasBlockSwitcher() ).toBeFalsy(); - // Verify the correct block transforms appear. - expect( await getAvailableBlockTransforms() ).toHaveLength( 0 ); - } ); - - describe( 'Conditional tranformation options', () => { - describe( 'Columns tranforms', () => { - it( 'Should show Columns block only if selected blocks are between limits (1-6)', async () => { - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( '- List content' ); - await page.keyboard.press( 'ArrowUp' ); - await insertBlock( 'Heading' ); - await page.keyboard.type( 'I am a header' ); - await page.keyboard.down( 'Shift' ); - await page.keyboard.press( 'ArrowUp' ); - await page.keyboard.up( 'Shift' ); - expect( await getAvailableBlockTransforms() ).toEqual( - expect.arrayContaining( [ 'Columns' ] ) - ); - } ); - it( 'Should NOT show Columns transform only if selected blocks are more than max limit(6)', async () => { - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( '- List content' ); - await page.keyboard.press( 'ArrowUp' ); - await insertBlock( 'Heading' ); - await page.keyboard.type( 'I am a header' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'First paragraph' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'Second paragraph' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'Third paragraph' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'Fourth paragraph' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'Fifth paragraph' ); - await pressKeyWithModifier( 'primary', 'a' ); - await pressKeyWithModifier( 'primary', 'a' ); - expect( await getAvailableBlockTransforms() ).not.toEqual( - expect.arrayContaining( [ 'Columns' ] ) - ); - } ); - } ); - } ); -} ); diff --git a/packages/e2e-tests/specs/experiments/blocks/post-comments-form.test.js b/packages/e2e-tests/specs/experiments/blocks/post-comments-form.test.js deleted file mode 100644 index 0a26788b2d442..0000000000000 --- a/packages/e2e-tests/specs/experiments/blocks/post-comments-form.test.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * WordPress dependencies - */ -import { - insertBlock, - activateTheme, - setOption, - visitSiteEditor, - enterEditMode, - deleteAllTemplates, - canvas, -} from '@wordpress/e2e-test-utils'; - -describe( 'Comments Form', () => { - let previousCommentStatus; - - beforeAll( async () => { - await activateTheme( 'emptytheme' ); - await deleteAllTemplates( 'wp_template' ); - previousCommentStatus = await setOption( - 'default_comment_status', - 'closed' - ); - } ); - - afterAll( async () => { - await setOption( 'default_comment_status', previousCommentStatus ); - } ); - - describe( 'placeholder', () => { - it( 'displays in site editor even when comments are closed by default', async () => { - // Navigate to "Singular" post template - await visitSiteEditor(); - await expect( page ).toClick( - '.edit-site-sidebar-navigation-item', - { text: /templates/i } - ); - await expect( page ).toClick( - '.edit-site-sidebar-navigation-item', - { text: /single entries/i } - ); - await enterEditMode(); - - // Insert post comments form - await insertBlock( 'Comments Form' ); - - // Ensure the placeholder is there - await expect( canvas() ).toMatchElement( - '.wp-block-post-comments-form .comment-form' - ); - } ); - } ); -} ); diff --git a/packages/e2e-tests/specs/site-editor/settings-sidebar.test.js b/packages/e2e-tests/specs/site-editor/settings-sidebar.test.js deleted file mode 100644 index 6b589ec7ea33d..0000000000000 --- a/packages/e2e-tests/specs/site-editor/settings-sidebar.test.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * WordPress dependencies - */ -import { - deleteAllTemplates, - activateTheme, - getAllBlocks, - selectBlockByClientId, - insertBlock, - visitSiteEditor, - enterEditMode, -} from '@wordpress/e2e-test-utils'; - -async function toggleSidebar() { - await page.click( - '.edit-site-header-edit-mode__actions button[aria-label="Settings"]' - ); -} - -async function getActiveTabLabel() { - return await page.$eval( - '.edit-site-sidebar-edit-mode__panel-tab.is-active', - ( element ) => element.getAttribute( 'aria-label' ) - ); -} - -async function getTemplateCard() { - return { - title: await page.$eval( - '.edit-site-sidebar-card__title', - ( element ) => element.innerText - ), - description: await page.$eval( - '.edit-site-sidebar-card__description', - ( element ) => element.innerText - ), - }; -} - -describe( 'Settings sidebar', () => { - beforeAll( async () => { - await activateTheme( 'emptytheme' ); - await deleteAllTemplates( 'wp_template' ); - await deleteAllTemplates( 'wp_template_part' ); - } ); - afterAll( async () => { - await deleteAllTemplates( 'wp_template' ); - await deleteAllTemplates( 'wp_template_part' ); - await activateTheme( 'twentytwentyone' ); - } ); - beforeEach( async () => { - await visitSiteEditor(); - await enterEditMode(); - } ); - - describe( 'Template tab', () => { - it( 'should open template tab by default if no block is selected', async () => { - await toggleSidebar(); - - expect( await getActiveTabLabel() ).toEqual( - 'Template (selected)' - ); - } ); - - it( "should show the currently selected template's title and description", async () => { - await toggleSidebar(); - - const templateCardBeforeNavigation = await getTemplateCard(); - await visitSiteEditor( { - postId: 'emptytheme//singular', - postType: 'wp_template', - } ); - await enterEditMode(); - const templateCardAfterNavigation = await getTemplateCard(); - - expect( templateCardBeforeNavigation ).toMatchObject( { - title: 'Index', - description: - 'Used as a fallback template for all pages when a more specific template is not defined.', - } ); - expect( templateCardAfterNavigation ).toMatchObject( { - title: 'Single Entries', - description: - 'Displays any single entry, such as a post or a page. This template will serve as a fallback when a more specific template (e.g. Single Post, Page, or Attachment) cannot be found.', - } ); - } ); - } ); - - describe( 'Block tab', () => { - it( 'should open block tab by default if a block is selected', async () => { - const allBlocks = await getAllBlocks(); - await selectBlockByClientId( allBlocks[ 0 ].clientId ); - - await toggleSidebar(); - - expect( await getActiveTabLabel() ).toEqual( 'Block (selected)' ); - } ); - } ); - - describe( 'Tab switch based on selection', () => { - it( 'should switch to block tab if we select a block, when Template is selected', async () => { - await toggleSidebar(); - expect( await getActiveTabLabel() ).toEqual( - 'Template (selected)' - ); - // By inserting the block is also selected. - await insertBlock( 'Heading' ); - expect( await getActiveTabLabel() ).toEqual( 'Block (selected)' ); - } ); - it( 'should switch to Template tab when a block was selected and we select the Template', async () => { - await insertBlock( 'Heading' ); - await toggleSidebar(); - expect( await getActiveTabLabel() ).toEqual( 'Block (selected)' ); - await page.evaluate( () => { - wp.data.dispatch( 'core/block-editor' ).clearSelectedBlock(); - } ); - expect( await getActiveTabLabel() ).toEqual( - 'Template (selected)' - ); - } ); - } ); -} ); diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 56fda6e6154b2..33884d1c5659d 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -133,8 +133,10 @@ export default function PagePages() { const [ pageId, setPageId ] = useState( null ); const history = useHistory(); - const onSelectionChange = ( items ) => - setPageId( items?.length === 1 ? items[ 0 ].id : null ); + const onSelectionChange = useCallback( + ( items ) => setPageId( items?.length === 1 ? items[ 0 ].id : null ), + [ setPageId ] + ); const queryArgs = useMemo( () => { const filters = {}; @@ -303,23 +305,19 @@ export default function PagePages() { [ permanentlyDeletePostAction, restorePostAction, editPostAction ] ); const onChangeView = useCallback( - ( viewUpdater ) => { - let updatedView = - typeof viewUpdater === 'function' - ? viewUpdater( view ) - : viewUpdater; - if ( updatedView.type !== view.type ) { - updatedView = { - ...updatedView, + ( newView ) => { + if ( newView.type !== view.type ) { + newView = { + ...newView, layout: { - ...defaultConfigPerViewType[ updatedView.type ], + ...defaultConfigPerViewType[ newView.type ], }, }; } - setView( updatedView ); + setView( newView ); }, - [ view, setView ] + [ view.type, setView ] ); // TODO: we need to handle properly `data={ data || EMPTY_ARRAY }` for when `isLoading`. diff --git a/packages/edit-site/src/components/page-patterns/grid-item.js b/packages/edit-site/src/components/page-patterns/grid-item.js index b394ef8eb6e76..bacb0f3190863 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -200,6 +200,7 @@ function GridItem( { categoryId, item, ...props } ) { ) } diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index d97a5e42fb612..9c52aaa7f12f6 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -170,8 +170,11 @@ export default function DataviewsTemplates() { } ); const history = useHistory(); - const onSelectionChange = ( items ) => - setTemplateId( items?.length === 1 ? items[ 0 ].id : null ); + const onSelectionChange = useCallback( + ( items ) => + setTemplateId( items?.length === 1 ? items[ 0 ].id : null ), + [ setTemplateId ] + ); const authors = useMemo( () => { if ( ! allTemplates ) { @@ -341,25 +344,23 @@ export default function DataviewsTemplates() { ], [ resetTemplateAction ] ); + const onChangeView = useCallback( - ( viewUpdater ) => { - let updatedView = - typeof viewUpdater === 'function' - ? viewUpdater( view ) - : viewUpdater; - if ( updatedView.type !== view.type ) { - updatedView = { - ...updatedView, + ( newView ) => { + if ( newView.type !== view.type ) { + newView = { + ...newView, layout: { - ...defaultConfigPerViewType[ updatedView.type ], + ...defaultConfigPerViewType[ newView.type ], }, }; } - setView( updatedView ); + setView( newView ); }, - [ view, setView ] + [ view.type, setView ] ); + return ( <> { - let isCurrent = true; - - RNReactNativeGutenbergBridge.requestConnectionStatus( - ( isBridgeConnected ) => { - if ( ! isCurrent ) { - return; - } - - setIsConnected( isBridgeConnected ); - } - ); - - return () => { - isCurrent = false; - }; - }, [] ); - - useEffect( () => { - const subscription = subscribeConnectionStatus( - ( { isConnected: isBridgeConnected } ) => { - setIsConnected( isBridgeConnected ); - } - ); - - return () => { - subscription.remove(); - }; - }, [] ); - - return { isConnected }; -} - -function subscribeConnectionStatus( callback ) { +export function subscribeConnectionStatus( callback ) { return gutenbergBridgeEvents.addListener( 'connectionStatusChange', callback ); } +export function requestConnectionStatus( callback ) { + return RNReactNativeGutenbergBridge.requestConnectionStatus( callback ); +} + /** * Request media picker for the given media source. * diff --git a/test/e2e/specs/editor/blocks/post-comments-form.spec.js b/test/e2e/specs/editor/blocks/post-comments-form.spec.js new file mode 100644 index 0000000000000..db75771dc0915 --- /dev/null +++ b/test/e2e/specs/editor/blocks/post-comments-form.spec.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Comments Form', () => { + let originalCommentStatus; + + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'emptytheme' ); + await requestUtils.deleteAllTemplates( 'wp_template' ); + } ); + + test.beforeEach( async ( { requestUtils } ) => { + const siteSettings = await requestUtils.getSiteSettings(); + originalCommentStatus = siteSettings.default_comment_status; + } ); + + test.afterEach( async ( { requestUtils } ) => { + await requestUtils.updateSiteSettings( { + default_comment_status: originalCommentStatus, + } ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyone' ); + } ); + + test( 'placeholder displays in the site editor even when comments are closed by default', async ( { + admin, + editor, + } ) => { + // Navigate to "Singular" post template + await admin.visitSiteEditor( { + postId: 'emptytheme//singular', + postType: 'wp_template', + canvas: 'edit', + } ); + + // Insert post comments form + await editor.insertBlock( { name: 'core/post-comments-form' } ); + + // Ensure the placeholder is there + const postCommentsFormBlock = editor.canvas.locator( + 'role=document[name="Block: Comments Form"i]' + ); + await expect( postCommentsFormBlock.locator( 'form' ) ).toBeVisible(); + } ); +} ); diff --git a/test/e2e/specs/editor/various/block-switcher.spec.js b/test/e2e/specs/editor/various/block-switcher.spec.js index 12fd843ed2ed1..e68844ac4a9f6 100644 --- a/test/e2e/specs/editor/various/block-switcher.spec.js +++ b/test/e2e/specs/editor/various/block-switcher.spec.js @@ -8,7 +8,183 @@ test.describe( 'Block Switcher', () => { await admin.createNewPost(); } ); - test( 'Block variation transforms', async ( { editor, page } ) => { + test( 'Should show the expected block transforms on the list block when the blocks are removed', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); + await page.keyboard.type( '- List content' ); + await page.keyboard.press( 'ArrowUp' ); + await pageUtils.pressKeys( 'alt+F10' ); + + const blockSwitcher = page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'List' } ); + + // Verify the block switcher exists. + await expect( blockSwitcher ).toHaveAttribute( + 'aria-haspopup', + 'true' + ); + + // Verify the correct block transforms appear. + await blockSwitcher.click(); + await expect( + page.getByRole( 'menu', { name: 'List' } ).getByRole( 'menuitem' ) + ).toHaveText( [ 'Paragraph', 'Heading', 'Quote', 'Columns', 'Group' ] ); + } ); + + test( 'Should show the expected block transforms on the list block when the quote block is removed', async ( { + editor, + page, + pageUtils, + } ) => { + // Remove the quote block from the list of registered blocks. + await page.waitForFunction( () => { + try { + window.wp.blocks.unregisterBlockType( 'core/quote' ); + return true; + } catch { + return false; + } + } ); + + // Insert a list block. + await editor.canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); + await page.keyboard.type( '- List content' ); + await page.keyboard.press( 'ArrowUp' ); + await pageUtils.pressKeys( 'alt+F10' ); + + const blockSwitcher = page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'List' } ); + + // Verify the block switcher exists. + await expect( blockSwitcher ).toHaveAttribute( + 'aria-haspopup', + 'true' + ); + + // Verify the correct block transforms appear. + await blockSwitcher.click(); + await expect( + page.getByRole( 'menu', { name: 'List' } ).getByRole( 'menuitem' ) + ).toHaveText( [ 'Paragraph', 'Heading', 'Columns', 'Group' ] ); + } ); + + test( 'Should not show the block switcher if all the blocks the list block transforms into are removed', async ( { + editor, + page, + pageUtils, + } ) => { + // Insert a list block. + await editor.canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); + await page.keyboard.type( '- List content' ); + + // Remove blocks. + await page.waitForFunction( () => { + try { + window.wp.data + .dispatch( 'core/blocks' ) + .removeBlockTypes( [ + 'core/quote', + 'core/pullquote', + 'core/paragraph', + 'core/group', + 'core/heading', + 'core/columns', + ] ); + return true; + } catch { + return false; + } + } ); + + await page.keyboard.press( 'ArrowUp' ); + await pageUtils.pressKeys( 'alt+F10' ); + + // Verify the block switcher isn't enabled. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'List' } ) + ).toBeDisabled(); + } ); + + test( 'Should show Columns block only if selected blocks are between limits (1-6)', async ( { + editor, + page, + } ) => { + await editor.canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); + await page.keyboard.type( '- List content' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '## I am a header' ); + await page.keyboard.down( 'Shift' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.up( 'Shift' ); + + await page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Multiple blocks selected' } ) + .click(); + + await expect( + page + .getByRole( 'menu', { name: 'Multiple blocks selected' } ) + .getByRole( 'menuitem', { name: 'Columns' } ) + ).toBeVisible(); + } ); + + test( 'Should NOT show Columns transform only if selected blocks are more than max limit(6)', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); + await page.keyboard.type( '- List content' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '## I am a header' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'First paragraph' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'Second paragraph' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'Third paragraph' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'Fourth paragraph' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'Fifth paragraph' ); + await pageUtils.pressKeys( 'primary+a', { times: 2 } ); + + await page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Multiple blocks selected' } ) + .click(); + + await expect( + page + .getByRole( 'menu', { name: 'Multiple blocks selected' } ) + .getByRole( 'menuitem', { name: 'Columns' } ) + ).toBeHidden(); + } ); + + test( 'should be able to transform to block variations', async ( { + editor, + page, + } ) => { // This is the `stack` Group variation. await editor.insertBlock( { name: 'core/group', diff --git a/test/e2e/specs/site-editor/settings-sidebar.spec.js b/test/e2e/specs/site-editor/settings-sidebar.spec.js new file mode 100644 index 0000000000000..f063603deacca --- /dev/null +++ b/test/e2e/specs/site-editor/settings-sidebar.spec.js @@ -0,0 +1,148 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Settings sidebar', () => { + test.beforeAll( async ( { requestUtils } ) => { + await Promise.all( [ + requestUtils.activateTheme( 'emptytheme' ), + requestUtils.deleteAllTemplates( 'wp_template' ), + requestUtils.deleteAllTemplates( 'wp_template_part' ), + ] ); + } ); + + test.beforeEach( async ( { admin } ) => { + await admin.visitSiteEditor( { + postId: 'emptytheme//index', + postType: 'wp_template', + canvas: 'edit', + } ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await Promise.all( [ + requestUtils.activateTheme( 'twentytwentyone' ), + requestUtils.deleteAllTemplates( 'wp_template' ), + requestUtils.deleteAllTemplates( 'wp_template_part' ), + ] ); + } ); + + test.describe( 'Template tab', () => { + test( 'should open template tab by default if no block is selected', async ( { + editor, + page, + } ) => { + await editor.openDocumentSettingsSidebar(); + + await expect( + page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Template (selected)' } ) + ).toHaveClass( /is-active/ ); + } ); + + test( `should show the currently selected template's title and description`, async ( { + admin, + editor, + page, + } ) => { + await editor.openDocumentSettingsSidebar(); + + const settingsSideber = page.getByRole( 'region', { + name: 'Editor settings', + } ); + const templateTitle = settingsSideber.locator( + '.edit-site-sidebar-card__title' + ); + const templateDescription = settingsSideber.locator( + '.edit-site-sidebar-card__description' + ); + + await expect( templateTitle ).toHaveText( 'Index' ); + await expect( templateDescription ).toHaveText( + 'Used as a fallback template for all pages when a more specific template is not defined.' + ); + + await admin.visitSiteEditor( { + postId: 'emptytheme//singular', + postType: 'wp_template', + canvas: 'edit', + } ); + + await expect( templateTitle ).toHaveText( 'Single Entries' ); + await expect( templateDescription ).toHaveText( + 'Displays any single entry, such as a post or a page. This template will serve as a fallback when a more specific template (e.g. Single Post, Page, or Attachment) cannot be found.' + ); + } ); + } ); + + test.describe( 'Block tab', () => { + test( 'should open block tab by default if a block is selected', async ( { + editor, + page, + } ) => { + await editor.selectBlocks( + editor.canvas.getByRole( 'document', { name: 'Block' } ).first() + ); + await editor.openDocumentSettingsSidebar(); + + await expect( + page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Block (selected)' } ) + ).toHaveClass( /is-active/ ); + } ); + } ); + + test.describe( 'Tab switch based on selection', () => { + test( 'should switch to block tab if we select a block, when Template is selected', async ( { + editor, + page, + } ) => { + await editor.openDocumentSettingsSidebar(); + + await expect( + page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Template (selected)' } ) + ).toHaveClass( /is-active/ ); + + // By inserting the block is also selected. + await editor.insertBlock( { name: 'core/heading' } ); + await expect( + page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Block (selected)' } ) + ).toHaveClass( /is-active/ ); + } ); + + test( 'should switch to Template tab when a block was selected and we select the Template', async ( { + editor, + page, + } ) => { + await editor.selectBlocks( + editor.canvas.getByRole( 'document', { name: 'Block' } ).first() + ); + await editor.openDocumentSettingsSidebar(); + + await expect( + page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Block (selected)' } ) + ).toHaveClass( /is-active/ ); + + await page.evaluate( () => { + window.wp.data + .dispatch( 'core/block-editor' ) + .clearSelectedBlock(); + } ); + + await expect( + page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Template (selected)' } ) + ).toHaveClass( /is-active/ ); + } ); + } ); +} ); diff --git a/test/native/setup.js b/test/native/setup.js index 8bfa8fb0626f2..bf6c6d970aa32 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -107,7 +107,8 @@ jest.mock( '@wordpress/react-native-bridge', () => { subscribeShowEditorHelp: jest.fn(), subscribeOnUndoPressed: jest.fn(), subscribeOnRedoPressed: jest.fn(), - useIsConnected: jest.fn( () => ( { isConnected: true } ) ), + subscribeConnectionStatus: jest.fn( () => ( { remove: jest.fn() } ) ), + requestConnectionStatus: jest.fn( ( callback ) => callback( true ) ), editorDidMount: jest.fn(), showAndroidSoftKeyboard: jest.fn(), hideAndroidSoftKeyboard: jest.fn(),