diff --git a/packages/editor/src/components/post-actions/index.js b/packages/editor/src/components/post-actions/index.js
index d6adf6c072166..219fb19e5eb83 100644
--- a/packages/editor/src/components/post-actions/index.js
+++ b/packages/editor/src/components/post-actions/index.js
@@ -20,57 +20,39 @@ import { usePostActions } from './actions';
const { Menu, kebabCase } = unlock( componentsPrivateApis );
-function useEditedEntityRecordsWithPermissions( postType, postIds ) {
- const { items, permissions } = useSelect(
+export default function PostActions( { postType, postId, onActionPerformed } ) {
+ const [ activeModalAction, setActiveModalAction ] = useState( null );
+
+ const { item, permissions } = useSelect(
( select ) => {
const { getEditedEntityRecord, getEntityRecordPermissions } =
unlock( select( coreStore ) );
return {
- items: postIds.map( ( postId ) =>
- getEditedEntityRecord( 'postType', postType, postId )
- ),
- permissions: postIds.map( ( postId ) =>
- getEntityRecordPermissions( 'postType', postType, postId )
+ item: getEditedEntityRecord( 'postType', postType, postId ),
+ permissions: getEntityRecordPermissions(
+ 'postType',
+ postType,
+ postId
),
};
},
- [ postIds, postType ]
+ [ postId, postType ]
);
-
- return useMemo( () => {
- return items.map( ( item, index ) => ( {
+ const itemWithPermissions = useMemo( () => {
+ return {
...item,
- permissions: permissions[ index ],
- } ) );
- }, [ items, permissions ] );
-}
-
-export default function PostActions( { postType, postId, onActionPerformed } ) {
- const [ activeModalAction, setActiveModalAction ] = useState( null );
- const _postIds = useMemo( () => {
- if ( Array.isArray( postId ) ) {
- return postId;
- }
- return postId ? [ postId ] : [];
- }, [ postId ] );
-
- const itemsWithPermissions = useEditedEntityRecordsWithPermissions(
- postType,
- _postIds
- );
+ permissions,
+ };
+ }, [ item, permissions ] );
const allActions = usePostActions( { postType, onActionPerformed } );
const actions = useMemo( () => {
return allActions.filter( ( action ) => {
return (
- ( ! action.isEligible ||
- itemsWithPermissions.some( ( itemWithPermissions ) =>
- action.isEligible( itemWithPermissions )
- ) ) &&
- ( itemsWithPermissions.length < 2 || action.supportsBulk )
+ ! action.isEligible || action.isEligible( itemWithPermissions )
);
} );
- }, [ allActions, itemsWithPermissions ] );
+ }, [ allActions, itemWithPermissions ] );
return (
<>
@@ -90,7 +72,7 @@ export default function PostActions( { postType, postId, onActionPerformed } ) {
@@ -98,7 +80,7 @@ export default function PostActions( { postType, postId, onActionPerformed } ) {
{ !! activeModalAction && (
setActiveModalAction( null ) }
/>
) }
diff --git a/packages/editor/src/components/post-card-panel/index.js b/packages/editor/src/components/post-card-panel/index.js
index 895545cb007f0..a2ed6f5adaad8 100644
--- a/packages/editor/src/components/post-card-panel/index.js
+++ b/packages/editor/src/components/post-card-panel/index.js
@@ -118,11 +118,13 @@ export default function PostCardPanel( {
{ pageTypeBadge }
) }
-
+ { postIds.length === 1 && (
+
+ ) }
{ postIds.length > 1 && (
diff --git a/packages/editor/src/components/post-template/block-theme.js b/packages/editor/src/components/post-template/block-theme.js
index 6d7f7f787b32f..8089e187233e0 100644
--- a/packages/editor/src/components/post-template/block-theme.js
+++ b/packages/editor/src/components/post-template/block-theme.js
@@ -53,7 +53,9 @@ export default function BlockThemeControl( { id } ) {
id
);
const { createSuccessNotice } = useDispatch( noticesStore );
- const { setRenderingMode } = useDispatch( editorStore );
+ const { setRenderingMode, setDefaultRenderingMode } = unlock(
+ useDispatch( editorStore )
+ );
const canCreateTemplate = useSelect(
( select ) =>
@@ -149,11 +151,11 @@ export default function BlockThemeControl( { id } ) {
isSelected={ ! isTemplateHidden }
role="menuitemcheckbox"
onClick={ () => {
- setRenderingMode(
- isTemplateHidden
- ? 'template-locked'
- : 'post-only'
- );
+ const newRenderingMode = isTemplateHidden
+ ? 'template-locked'
+ : 'post-only';
+ setRenderingMode( newRenderingMode );
+ setDefaultRenderingMode( newRenderingMode );
} }
>
{ __( 'Show template' ) }
diff --git a/packages/editor/src/components/post-text-editor/style.scss b/packages/editor/src/components/post-text-editor/style.scss
index 5391588d906d8..ad7b3383db2aa 100644
--- a/packages/editor/src/components/post-text-editor/style.scss
+++ b/packages/editor/src/components/post-text-editor/style.scss
@@ -41,8 +41,6 @@ textarea.editor-post-text-editor {
&::-moz-placeholder {
color: $dark-gray-placeholder;
- // Override Firefox default.
- opacity: 1;
}
&:-ms-input-placeholder {
diff --git a/packages/editor/src/components/preferences-modal/block-visibility.js b/packages/editor/src/components/preferences-modal/block-visibility.js
index 8726b114d9748..fc6deab6b5fb5 100644
--- a/packages/editor/src/components/preferences-modal/block-visibility.js
+++ b/packages/editor/src/components/preferences-modal/block-visibility.js
@@ -14,6 +14,7 @@ import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
const { BlockManager } = unlock( blockEditorPrivateApis );
+const EMPTY_ARRAY = [];
export default function BlockVisibility() {
const { showBlockTypes, hideBlockTypes } = unlock(
@@ -31,7 +32,7 @@ export default function BlockVisibility() {
select( editorStore ).getEditorSettings().allowedBlockTypes,
hiddenBlockTypes:
select( preferencesStore ).get( 'core', 'hiddenBlockTypes' ) ??
- [],
+ EMPTY_ARRAY,
};
}, [] );
diff --git a/packages/editor/src/components/preview-dropdown/index.js b/packages/editor/src/components/preview-dropdown/index.js
index a081564e48ea8..3fa1e211bb7d3 100644
--- a/packages/editor/src/components/preview-dropdown/index.js
+++ b/packages/editor/src/components/preview-dropdown/index.js
@@ -56,7 +56,9 @@ export default function PreviewDropdown( { forceIsAutosaveable, disabled } ) {
templateId: getCurrentTemplateId(),
};
}, [] );
- const { setDeviceType, setRenderingMode } = useDispatch( editorStore );
+ const { setDeviceType, setRenderingMode, setDefaultRenderingMode } = unlock(
+ useDispatch( editorStore )
+ );
const { resetZoomLevel } = unlock( useDispatch( blockEditorStore ) );
const handleDevicePreviewChange = ( newDeviceType ) => {
@@ -160,11 +162,11 @@ export default function PreviewDropdown( { forceIsAutosaveable, disabled } ) {
isSelected={ ! isTemplateHidden }
role="menuitemcheckbox"
onClick={ () => {
- setRenderingMode(
- isTemplateHidden
- ? 'template-locked'
- : 'post-only'
- );
+ const newRenderingMode = isTemplateHidden
+ ? 'template-locked'
+ : 'post-only';
+ setRenderingMode( newRenderingMode );
+ setDefaultRenderingMode( newRenderingMode );
} }
>
{ __( 'Show template' ) }
diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js
index 0e653228fee62..3e9afe3363b1a 100644
--- a/packages/editor/src/components/provider/index.js
+++ b/packages/editor/src/components/provider/index.js
@@ -56,11 +56,6 @@ const NON_CONTEXTUAL_POST_TYPES = [
'wp_template_part',
];
-/**
- * These are rendering modes that the editor supports.
- */
-const RENDERING_MODES = [ 'post-only', 'template-locked' ];
-
/**
* Depending on the post, template and template mode,
* returns the appropriate blocks and change handlers for the block editor provider.
@@ -183,37 +178,28 @@ export const ExperimentalEditorProvider = withRegistryProvider(
getEditorSelection,
getRenderingMode,
__unstableIsEditorReady,
- } = select( editorStore );
- const {
- getEntitiesConfig,
- getPostType,
- hasFinishedResolution,
- } = select( coreStore );
-
- const postTypeSupports = getPostType( post.type )?.supports;
- const hasLoadedPostObject = hasFinishedResolution(
- 'getPostType',
- [ post.type ]
- );
-
- const _defaultMode = Array.isArray( postTypeSupports?.editor )
- ? postTypeSupports.editor.find(
- ( features ) => 'default-mode' in features
- )?.[ 'default-mode' ]
- : undefined;
- const hasDefaultMode = RENDERING_MODES.includes( _defaultMode );
-
- // Wait for template resolution when rendering in a `template-locked` mode.
+ getDefaultRenderingMode,
+ } = unlock( select( editorStore ) );
+ const { getEntitiesConfig } = select( coreStore );
+
+ const _defaultMode = getDefaultRenderingMode( post.type );
+ /**
+ * To avoid content "flash", wait until rendering mode has been resolved.
+ * This is important for the initial render of the editor.
+ *
+ * - Wait for template to be resolved if the default mode is 'template-locked'.
+ * - Wait for default mode to be resolved otherwise.
+ */
const hasResolvedMode =
- hasLoadedPostObject && _defaultMode === 'template-locked'
+ _defaultMode === 'template-locked'
? hasTemplate
- : true;
+ : _defaultMode !== undefined;
return {
editorSettings: getEditorSettings(),
isReady: __unstableIsEditorReady() && hasResolvedMode,
mode: getRenderingMode(),
- defaultMode: hasDefaultMode ? _defaultMode : 'post-only',
+ defaultMode: _defaultMode,
selection: getEditorSelection(),
postTypeEntities:
post.type === 'wp_template'
@@ -224,7 +210,7 @@ export const ExperimentalEditorProvider = withRegistryProvider(
[ post.type, hasTemplate ]
);
- const shouldRenderTemplate = !! template && mode !== 'post-only';
+ const shouldRenderTemplate = hasTemplate && mode !== 'post-only';
const rootLevelPost = shouldRenderTemplate ? template : post;
const defaultBlockContext = useMemo( () => {
const postContext = {};
@@ -341,7 +327,9 @@ export const ExperimentalEditorProvider = withRegistryProvider(
// Sets the right rendering mode when loading the editor.
useEffect( () => {
- setRenderingMode( defaultMode );
+ if ( defaultMode ) {
+ setRenderingMode( defaultMode );
+ }
}, [ defaultMode, setRenderingMode ] );
useHideBlocksFromInserter( post.type, mode );
diff --git a/packages/editor/src/store/private-actions.js b/packages/editor/src/store/private-actions.js
index 6a83b3ca0b403..f1aa458fdab98 100644
--- a/packages/editor/src/store/private-actions.js
+++ b/packages/editor/src/store/private-actions.js
@@ -492,3 +492,36 @@ export const removeTemplates =
.createErrorNotice( errorMessage, { type: 'snackbar' } );
}
};
+
+/**
+ * Set the default rendering mode preference for the current post type.
+ *
+ * @param {string} mode The rendering mode to set as default.
+ */
+export const setDefaultRenderingMode =
+ ( mode ) =>
+ ( { select, registry } ) => {
+ const postType = select.getCurrentPostType();
+ const theme = registry
+ .select( coreStore )
+ .getCurrentTheme()?.stylesheet;
+ const renderingModes =
+ registry
+ .select( preferencesStore )
+ .get( 'core', 'renderingModes' )?.[ theme ] ?? {};
+
+ if ( renderingModes[ postType ] === mode ) {
+ return;
+ }
+
+ const newModes = {
+ [ theme ]: {
+ ...renderingModes,
+ [ postType ]: mode,
+ },
+ };
+
+ registry
+ .dispatch( preferencesStore )
+ .set( 'core', 'renderingModes', newModes );
+ };
diff --git a/packages/editor/src/store/private-selectors.js b/packages/editor/src/store/private-selectors.js
index 73bf19f3d9993..d381d4caa7492 100644
--- a/packages/editor/src/store/private-selectors.js
+++ b/packages/editor/src/store/private-selectors.js
@@ -16,6 +16,7 @@ import {
verse,
} from '@wordpress/icons';
import { store as coreStore } from '@wordpress/core-data';
+import { store as preferencesStore } from '@wordpress/preferences';
/**
* Internal dependencies
@@ -34,6 +35,11 @@ const EMPTY_INSERTION_POINT = {
filterValue: undefined,
};
+/**
+ * These are rendering modes that the editor supports.
+ */
+const RENDERING_MODES = [ 'post-only', 'template-locked' ];
+
/**
* Get the inserter.
*
@@ -215,3 +221,54 @@ export const getPostBlocksByName = createRegistrySelector( ( select ) =>
() => [ select( blockEditorStore ).getBlocks() ]
)
);
+
+/**
+ * Returns the default rendering mode for a post type by user preference or post type configuration.
+ *
+ * @param {Object} state Global application state.
+ * @param {string} postType The post type.
+ *
+ * @return {string} The default rendering mode. Returns `undefined` while resolving value.
+ */
+export const getDefaultRenderingMode = createRegistrySelector(
+ ( select ) => ( state, postType ) => {
+ const { getPostType, getCurrentTheme, hasFinishedResolution } =
+ select( coreStore );
+
+ // This needs to be called before `hasFinishedResolution`.
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
+ const currentTheme = getCurrentTheme();
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
+ const postTypeEntity = getPostType( postType );
+
+ // Wait for the post type and theme resolution.
+ if (
+ ! hasFinishedResolution( 'getPostType', [ postType ] ) ||
+ ! hasFinishedResolution( 'getCurrentTheme' )
+ ) {
+ return undefined;
+ }
+
+ const theme = currentTheme?.stylesheet;
+ const defaultModePreference = select( preferencesStore ).get(
+ 'core',
+ 'renderingModes'
+ )?.[ theme ]?.[ postType ];
+ const postTypeDefaultMode = Array.isArray(
+ postTypeEntity?.supports?.editor
+ )
+ ? postTypeEntity.supports.editor.find(
+ ( features ) => 'default-mode' in features
+ )?.[ 'default-mode' ]
+ : undefined;
+
+ const defaultMode = defaultModePreference || postTypeDefaultMode;
+
+ // Fallback gracefully to 'post-only' when rendering mode is not supported.
+ if ( ! RENDERING_MODES.includes( defaultMode ) ) {
+ return 'post-only';
+ }
+
+ return defaultMode;
+ }
+);
diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx
index 1a96f5d0cab74..eef5b0734c997 100644
--- a/packages/fields/src/fields/template/template-edit.tsx
+++ b/packages/fields/src/fields/template/template-edit.tsx
@@ -28,6 +28,8 @@ import { getItemTitle } from '../../actions/utils';
import type { BasePost } from '../../types';
import { unlock } from '../../lock-unlock';
+const EMPTY_ARRAY: [] = [];
+
export const TemplateEdit = ( {
data,
field,
@@ -39,7 +41,7 @@ export const TemplateEdit = ( {
typeof data.id === 'number' ? data.id : parseInt( data.id, 10 );
const slug = data.slug;
- const { availableTemplates, templates } = useSelect(
+ const { canSwitchTemplate, templates } = useSelect(
( select ) => {
const allTemplates =
select( coreStore ).getEntityRecords< WpTemplate >(
@@ -49,7 +51,7 @@ export const TemplateEdit = ( {
per_page: -1,
post_type: postType,
}
- ) ?? [];
+ ) ?? EMPTY_ARRAY;
const { getHomePage, getPostsPageId } = unlock(
select( coreStore )
@@ -63,40 +65,41 @@ export const TemplateEdit = ( {
return {
templates: allTemplates,
- availableTemplates: allowSwitchingTemplate
- ? allTemplates.filter(
- ( template ) =>
- template.is_custom &&
- template.slug !== data.template &&
- !! template.content.raw // Skip empty templates.
- )
- : [],
+ canSwitchTemplate: allowSwitchingTemplate,
};
},
- [ data.template, postId, postType ]
+ [ postId, postType ]
);
- const templatesAsPatterns = useMemo(
- () =>
- availableTemplates.map( ( template ) => ( {
+ const templatesAsPatterns = useMemo( () => {
+ if ( ! canSwitchTemplate ) {
+ return [];
+ }
+ return templates
+ .filter(
+ ( template ) =>
+ template.is_custom &&
+ template.slug !== data.template &&
+ // Skip empty templates.
+ !! template.content.raw
+ )
+ .map( ( template ) => ( {
name: template.slug,
blocks: parse( template.content.raw ),
title: decodeEntities( template.title.rendered ),
id: template.id,
- } ) ),
- [ availableTemplates ]
- );
+ } ) );
+ }, [ canSwitchTemplate, data.template, templates ] );
const shownTemplates = useAsyncList( templatesAsPatterns );
const value = field.getValue( { item: data } );
+ const foundTemplate = templates.find(
+ ( template ) => template.slug === value
+ );
const currentTemplate = useSelect(
( select ) => {
- const foundTemplate = templates?.find(
- ( template ) => template.slug === value
- );
-
if ( foundTemplate ) {
return foundTemplate;
}
@@ -128,7 +131,7 @@ export const TemplateEdit = ( {
);
}
},
- [ postType, slug, templates, value ]
+ [ foundTemplate, postType, slug ]
);
const [ showModal, setShowModal ] = useState( false );
diff --git a/packages/format-library/src/text-color/inline.js b/packages/format-library/src/text-color/inline.js
index bc1e0eef07b0c..a14240ed9f489 100644
--- a/packages/format-library/src/text-color/inline.js
+++ b/packages/format-library/src/text-color/inline.js
@@ -142,6 +142,8 @@ function ColorPicker( { name, property, value, onChange } ) {
setColors( value, name, colors, { [ property ]: color } )
);
} }
+ // Prevent the text and color picker from overlapping.
+ __experimentalIsRenderedInSidebar
/>
);
}
diff --git a/packages/format-library/src/text-color/style.scss b/packages/format-library/src/text-color/style.scss
index 439af6db38d0c..05f448d88d77b 100644
--- a/packages/format-library/src/text-color/style.scss
+++ b/packages/format-library/src/text-color/style.scss
@@ -1,6 +1,13 @@
+// Must equal $color-palette-circle-size from:
+// @wordpress/components/src/circular-option-picker/style.scss
+$swatch-size: 28px;
+$swatch-gap: 12px;
+$panelPadding: $grid-unit-20;
+
.format-library__inline-color-popover {
[role="tabpanel"] {
- padding: 16px;
+ padding: $panelPadding;
+ width: ( $swatch-size * 6 ) + ( $swatch-gap * 5 ) + ( $panelPadding * 2 );
}
}
diff --git a/packages/url/src/add-query-args.js b/packages/url/src/add-query-args.js
index c47c33813c16b..556a3fb706a44 100644
--- a/packages/url/src/add-query-args.js
+++ b/packages/url/src/add-query-args.js
@@ -3,6 +3,7 @@
*/
import { getQueryArgs } from './get-query-args';
import { buildQueryString } from './build-query-string';
+import { getFragment } from './get-fragment';
/**
* Appends arguments as querystring to the provided URL. If the URL already
@@ -26,7 +27,8 @@ export function addQueryArgs( url = '', args ) {
return url;
}
- let baseUrl = url;
+ const fragment = getFragment( url ) || '';
+ let baseUrl = url.replace( fragment, '' );
// Determine whether URL already had query arguments.
const queryStringIndex = url.indexOf( '?' );
@@ -38,5 +40,5 @@ export function addQueryArgs( url = '', args ) {
baseUrl = baseUrl.substr( 0, queryStringIndex );
}
- return baseUrl + '?' + buildQueryString( args );
+ return baseUrl + '?' + buildQueryString( args ) + fragment;
}
diff --git a/packages/url/src/remove-query-args.js b/packages/url/src/remove-query-args.js
index 07fd5467808e0..5d31313b08a74 100644
--- a/packages/url/src/remove-query-args.js
+++ b/packages/url/src/remove-query-args.js
@@ -18,14 +18,18 @@ import { buildQueryString } from './build-query-string';
* @return {string} Updated URL.
*/
export function removeQueryArgs( url, ...args ) {
+ const fragment = url.replace( /^[^#]*/, '' );
+ url = url.replace( /#.*/, '' );
+
const queryStringIndex = url.indexOf( '?' );
if ( queryStringIndex === -1 ) {
- return url;
+ return url + fragment;
}
const query = getQueryArgs( url );
const baseURL = url.substr( 0, queryStringIndex );
args.forEach( ( arg ) => delete query[ arg ] );
const queryString = buildQueryString( query );
- return queryString ? baseURL + '?' + queryString : baseURL;
+ const updatedUrl = queryString ? baseURL + '?' + queryString : baseURL;
+ return updatedUrl + fragment;
}
diff --git a/packages/url/src/test/index.js b/packages/url/src/test/index.js
index 3d622ad2d8db7..763c2e71c9fcb 100644
--- a/packages/url/src/test/index.js
+++ b/packages/url/src/test/index.js
@@ -636,7 +636,7 @@ describe( 'addQueryArgs', () => {
);
} );
- it( 'should encodes spaces by RFC 3986', () => {
+ it( 'should encode spaces by RFC 3986', () => {
const url = 'https://andalouses.example/beach';
const args = { activity: 'fun in the sun' };
@@ -652,6 +652,15 @@ describe( 'addQueryArgs', () => {
expect( addQueryArgs( url, args ) ).toBe( '?sun=true' );
} );
+ it( 'should add query args before the url fragment', () => {
+ const url = 'https://andalouses.example/beach/#fragment';
+ const args = { sun: 'true' };
+
+ expect( addQueryArgs( url, args ) ).toBe(
+ 'https://andalouses.example/beach/?sun=true#fragment'
+ );
+ } );
+
it( 'should return URL argument unaffected if no query arguments to append', () => {
[ '', 'https://example.com', 'https://example.com?' ].forEach(
( url ) => {
@@ -796,6 +805,12 @@ describe( 'getQueryArg', () => {
expect( getQueryArg( url, 'baz' ) ).toBeUndefined();
} );
+ it( 'should not return what looks like a query arg after the url fragment', () => {
+ const url = 'https://andalouses.example/beach#fragment?foo=bar&bar=baz';
+
+ expect( getQueryArg( url, 'foo' ) ).toBeUndefined();
+ } );
+
it( 'should get the value of an array query arg', () => {
const url = 'https://andalouses.example/beach?foo[]=bar&foo[]=baz';
@@ -823,6 +838,12 @@ describe( 'hasQueryArg', () => {
expect( hasQueryArg( url, 'baz' ) ).toBeFalsy();
} );
+ it( 'should return false if the query arg is after url fragment', () => {
+ const url = 'https://andalouses.example/beach#fragment?foo=bar&bar=baz';
+
+ expect( hasQueryArg( url, 'foo' ) ).toBeFalsy();
+ } );
+
it( 'should return true for an array query arg', () => {
const url = 'https://andalouses.example/beach?foo[]=bar&foo[]=baz';
@@ -867,6 +888,23 @@ describe( 'removeQueryArgs', () => {
'https://andalouses.example/beach?bar=foobar'
);
} );
+
+ it( 'should not remove the url fragment', () => {
+ const url =
+ 'https://andalouses.example/beach?foo=bar¶m=value#fragment';
+
+ expect( removeQueryArgs( url, 'foo' ) ).toEqual(
+ 'https://andalouses.example/beach?param=value#fragment'
+ );
+ } );
+
+ it( 'should not remove what looks like a query arg after url fragment', () => {
+ const url = 'https://andalouses.example/beach#fragment?foo=bar';
+
+ expect( removeQueryArgs( url, 'foo' ) ).toEqual(
+ 'https://andalouses.example/beach#fragment?foo=bar'
+ );
+ } );
} );
describe( 'prependHTTP', () => {
diff --git a/phpunit/block-supports/aria-label-test.php b/phpunit/block-supports/aria-label-test.php
new file mode 100644
index 0000000000000..52b14edd5b67c
--- /dev/null
+++ b/phpunit/block-supports/aria-label-test.php
@@ -0,0 +1,85 @@
+test_block_name = null;
+ }
+
+ public function tear_down() {
+ unregister_block_type( $this->test_block_name );
+ $this->test_block_name = null;
+ parent::tear_down();
+ }
+
+ /**
+ * Registers a new block for testing aria-label support.
+ *
+ * @param string $block_name Name for the test block.
+ * @param array $supports Array defining block support configuration.
+ *
+ * @return WP_Block_Type The block type for the newly registered test block.
+ */
+ private function register_aria_label_block_with_support( $block_name, $supports = array() ) {
+ $this->test_block_name = $block_name;
+ register_block_type(
+ $this->test_block_name,
+ array(
+ 'api_version' => 3,
+ 'supports' => $supports,
+ )
+ );
+ $registry = WP_Block_Type_Registry::get_instance();
+
+ return $registry->get_registered( $this->test_block_name );
+ }
+
+ /**
+ * Tests that position block support works as expected.
+ *
+ * @dataProvider data_aria_label_block_support
+ *
+ * @param boolean|array $support Aria label block support configuration.
+ * @param string $value Aria label value for attribute object.
+ * @param array $expected Expected aria label block support styles.
+ */
+ public function test_gutenberg_apply_aria_label_support( $support, $value, $expected ) {
+ $block_type = self::register_aria_label_block_with_support(
+ 'test/aria-label-block',
+ array( 'ariaLabel' => $support )
+ );
+ $block_attrs = array( 'ariaLabel' => $value );
+ $actual = gutenberg_apply_aria_label_support( $block_type, $block_attrs );
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array
+ */
+ public function data_aria_label_block_support() {
+ return array(
+ 'aria-label attribute is applied' => array(
+ 'support' => true,
+ 'value' => 'Label',
+ 'expected' => array( 'aria-label' => 'Label' ),
+ ),
+ 'aria-label attribute is not applied if block does not support it' => array(
+ 'support' => false,
+ 'value' => 'Label',
+ 'expected' => array(),
+ ),
+ );
+ }
+}
diff --git a/test/e2e/specs/site-editor/navigation.spec.js b/test/e2e/specs/site-editor/navigation.spec.js
index 18eb6c9904b44..2a886c2048f88 100644
--- a/test/e2e/specs/site-editor/navigation.spec.js
+++ b/test/e2e/specs/site-editor/navigation.spec.js
@@ -106,6 +106,23 @@ test.describe( 'Site editor navigation', () => {
// We should have our editor canvas button back
await expect( editorCanvasButton ).toBeVisible();
} );
+
+ test( 'Should show 404 page when navigating to non-existent template', async ( {
+ admin,
+ page,
+ } ) => {
+ // Navigate to a non-existent template.
+ await admin.visitAdminPage( 'site-editor.php', 'p=/template-foo-bar' );
+
+ // Verify the 404 error notice is displayed with the correct message.
+ await expect(
+ page.locator(
+ '.edit-site-layout__area .components-notice__content'
+ )
+ ).toHaveText(
+ 'The requested page could not be found. Please check the URL.'
+ );
+ } );
} );
class EditorNavigationUtils {
diff --git a/test/e2e/specs/site-editor/preload.spec.js b/test/e2e/specs/site-editor/preload.spec.js
index e618d70ca20b9..b6f9f49aedeb7 100644
--- a/test/e2e/specs/site-editor/preload.spec.js
+++ b/test/e2e/specs/site-editor/preload.spec.js
@@ -46,6 +46,9 @@ test.describe( 'Preload', () => {
expect( requests ).toEqual( [
// Seems to be coming from `enableComplementaryArea`.
'/wp/v2/users/me',
+ // There are two separate settings OPTIONS requests. We should fix
+ // so the one for canUser and getEntityRecord are reused.
+ '/wp/v2/settings',
] );
} );
} );
diff --git a/test/native/integration-test-helpers/initialize-editor.js b/test/native/integration-test-helpers/initialize-editor.js
index 3b89da979aee3..ffaea764a90ec 100644
--- a/test/native/integration-test-helpers/initialize-editor.js
+++ b/test/native/integration-test-helpers/initialize-editor.js
@@ -38,7 +38,7 @@ export async function initializeEditor( props, { component } = {} ) {
resolutionSpy.mockImplementation( ( selectorName, args ) => {
// The mobile editor only supports the `post-only` rendering mode, so we
// presume a resolved `getPostType` selector to unblock editor rendering.
- if ( 'getPostType' === selectorName ) {
+ if ( [ 'getPostType', 'getCurrentTheme' ].includes( selectorName ) ) {
return true;
}