diff --git a/changelog.txt b/changelog.txt
index 2a1e7d62ed6568..9f6e6f652c2b02 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,7 +1,6 @@
== Changelog ==
-= 20.3.0-rc.1 =
-
+= 20.3.0 =
## Changelog
@@ -90,6 +89,7 @@
#### Interactivity API
- iAPI Router: Fix CSS rule order in some constructed style sheets. ([68923](https://github.com/WordPress/gutenberg/pull/68923))
+- iAPI Router: Revert "Handle styles assets on region-based navigation" ([69222](https://github.com/WordPress/gutenberg/pull/69222))
### Accessibility
diff --git a/docs/reference-guides/block-api/block-variations.md b/docs/reference-guides/block-api/block-variations.md
index 8a0c6b1dd5bd6c..8c223e54eea299 100644
--- a/docs/reference-guides/block-api/block-variations.md
+++ b/docs/reference-guides/block-api/block-variations.md
@@ -60,6 +60,44 @@ wp.blocks.registerBlockVariation( 'core/embed', {
} );
```
+## Registering block variations in PHP
+
+Block variations can also be registered from PHP using the `get_block_type_variations` filter hook. This approach is particularly useful when you need to dynamically generate variations based on registered post types, taxonomies, or other WordPress data.
+
+Here's an example of how to register a custom variation for the `core/image` block:
+
+```php
+function my_custom_image_variation( $variations, $block_type ) {
+ // Only modify variations for the image block
+ if ( 'core/image' !== $block_type->name ) {
+ return $variations;
+ }
+
+ // Add a custom variation
+ $variations[] = array(
+ 'name' => 'wide-image',
+ 'title' => __( 'Wide image', 'textdomain' ),
+ 'description' => __( 'A wide image', 'textdomain' ),
+ 'scope' => array( 'inserter' ),
+ 'isDefault' => false,
+ 'attributes' => array(
+ 'align' => 'wide', // Identifies the link type as custom
+ ),
+ );
+
+ return $variations;
+}
+add_filter( 'get_block_type_variations', 'my_custom_image_variation', 10, 2 );
+```
+
+The `get_block_type_variations` filter is called when variations are requested for a block type. It receives two parameters:
+- `$variations`: An array of currently registered variations for the block type
+- `$block_type`: The full block type object
+
+Note that variations registered through PHP will be merged with any variations registered through JavaScript using `registerBlockVariation()`.
+
+
+
## Removing a block variation
Block variations can also be easily removed. To do so, use `wp.blocks.unregisterBlockVariation()`. This function accepts the name of the block and the `name` of the variation that should be unregistered.
diff --git a/gutenberg.php b/gutenberg.php
index 58e04e0b9e4f54..58146500737685 100644
--- a/gutenberg.php
+++ b/gutenberg.php
@@ -5,7 +5,7 @@
* Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality.
* Requires at least: 6.6
* Requires PHP: 7.2
- * Version: 20.3.0-rc.1
+ * Version: 20.3.0
* Author: Gutenberg Team
* Text Domain: gutenberg
*
diff --git a/package-lock.json b/package-lock.json
index b5bd7ed6c1b7ef..a8bbafbfc2eb1f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "gutenberg",
- "version": "20.3.0-rc.1",
+ "version": "20.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gutenberg",
- "version": "20.3.0-rc.1",
+ "version": "20.3.0",
"hasInstallScript": true,
"license": "GPL-2.0-or-later",
"workspaces": [
diff --git a/package.json b/package.json
index 5459c72eb14728..0737c415f71c10 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "20.3.0-rc.1",
+ "version": "20.3.0",
"private": true,
"description": "A new WordPress editor experience.",
"author": "The WordPress Contributors",
diff --git a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
index 3d082a14a92bff..eb665ced62079f 100644
--- a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
+++ b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
@@ -203,7 +203,7 @@ exports[`ColorPaletteControl matches the snapshot 1`] = `
class="components-circular-option-picker"
>
diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js
index 3ae01525a80109..8411430dfbdadd 100644
--- a/packages/block-editor/src/components/iframe/index.js
+++ b/packages/block-editor/src/components/iframe/index.js
@@ -131,8 +131,23 @@ function Iframe( {
function preventFileDropDefault( event ) {
event.preventDefault();
}
+
+ const { ownerDocument } = node;
+
+ // Ideally ALL classes that are added through get_body_class should
+ // be added in the editor too, which we'll somehow have to get from
+ // the server in the future (which will run the PHP filters).
+ setBodyClasses(
+ Array.from( ownerDocument.body.classList ).filter(
+ ( name ) =>
+ name.startsWith( 'admin-color-' ) ||
+ name.startsWith( 'post-type-' ) ||
+ name === 'wp-embed-responsive'
+ )
+ );
+
function onLoad() {
- const { contentDocument, ownerDocument } = node;
+ const { contentDocument } = node;
const { documentElement } = contentDocument;
iFrameDocument = contentDocument;
@@ -140,18 +155,6 @@ function Iframe( {
clearerRef( documentElement );
- // Ideally ALL classes that are added through get_body_class should
- // be added in the editor too, which we'll somehow have to get from
- // the server in the future (which will run the PHP filters).
- setBodyClasses(
- Array.from( ownerDocument.body.classList ).filter(
- ( name ) =>
- name.startsWith( 'admin-color-' ) ||
- name.startsWith( 'post-type-' ) ||
- name === 'wp-embed-responsive'
- )
- );
-
contentDocument.dir = ownerDocument.dir;
for ( const compatStyle of getCompatibilityStyles() ) {
diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss
index 20aa1c288103d4..9051441af2cef6 100644
--- a/packages/block-editor/src/components/inserter/style.scss
+++ b/packages/block-editor/src/components/inserter/style.scss
@@ -644,5 +644,10 @@ $block-inserter-tabs-height: 44px;
}
.block-editor-tabbed-sidebar__tabpanel .block-editor-inserter__help-text {
+ display: none;
padding: 0 $grid-unit-30 $grid-unit-20;
+
+ @include break-mobile {
+ display: block;
+ }
}
diff --git a/packages/block-library/src/cover/edit/index.js b/packages/block-library/src/cover/edit/index.js
index 1eafe99e283eb4..b09093e312211d 100644
--- a/packages/block-library/src/cover/edit/index.js
+++ b/packages/block-library/src/cover/edit/index.js
@@ -514,6 +514,8 @@ function CoverEdit( {
value={ overlayColor.color }
onChange={ onSetOverlayColor }
clearable={ false }
+ asButtons
+ aria-label={ __( 'Overlay color' ) }
/>
diff --git a/packages/block-library/src/cover/test/edit.js b/packages/block-library/src/cover/test/edit.js
index 0a18d2cf3f9f8e..16695f53f67466 100644
--- a/packages/block-library/src/cover/test/edit.js
+++ b/packages/block-library/src/cover/test/edit.js
@@ -47,7 +47,7 @@ async function setup( attributes, useCoreBlocks, customSettings ) {
async function createAndSelectBlock() {
await userEvent.click(
- screen.getByRole( 'option', {
+ screen.getByRole( 'button', {
name: 'Black',
} )
);
@@ -72,7 +72,7 @@ describe( 'Cover block', () => {
test( 'can set overlay color using color picker on block placeholder', async () => {
const { container } = await setup();
- const colorPicker = screen.getByRole( 'option', {
+ const colorPicker = screen.getByRole( 'button', {
name: 'Black',
} );
await userEvent.click( colorPicker );
@@ -96,7 +96,7 @@ describe( 'Cover block', () => {
await setup();
await userEvent.click(
- screen.getByRole( 'option', {
+ screen.getByRole( 'button', {
name: 'Black',
} )
);
@@ -389,7 +389,7 @@ describe( 'Cover block', () => {
describe( 'isDark settings', () => {
test( 'should toggle is-light class if background changed from light to dark', async () => {
await setup();
- const colorPicker = screen.getByRole( 'option', {
+ const colorPicker = screen.getByRole( 'button', {
name: 'White',
} );
await userEvent.click( colorPicker );
@@ -413,7 +413,7 @@ describe( 'Cover block', () => {
} );
test( 'should remove is-light class if overlay color is removed', async () => {
await setup();
- const colorPicker = screen.getByRole( 'option', {
+ const colorPicker = screen.getByRole( 'button', {
name: 'White',
} );
await userEvent.click( colorPicker );
@@ -426,7 +426,7 @@ describe( 'Cover block', () => {
} )
);
await userEvent.click( screen.getByText( 'Overlay' ) );
- // The default color is black, so clicking the black color option will remove the background color,
+ // The default color is black, so clicking the black color button will remove the background color,
// which should remove the isDark setting and assign the is-light class.
const popupColorPicker = screen.getByRole( 'option', {
name: 'White',
diff --git a/packages/block-library/src/navigation-link/editor.scss b/packages/block-library/src/navigation-link/editor.scss
index b27c4520921fd4..5034c6f909ecdc 100644
--- a/packages/block-library/src/navigation-link/editor.scss
+++ b/packages/block-library/src/navigation-link/editor.scss
@@ -82,30 +82,9 @@
// Draw a wavy underline.
.wp-block-navigation-link__placeholder-text {
span {
- $blur: 10%;
- $width: 6%;
- $stop1: 30%;
- $stop2: 64%;
-
- --wp-underline-color: var(--wp-admin-theme-color);
-
- background-image:
- linear-gradient(45deg, transparent ($stop1 - $blur), var(--wp-underline-color) $stop1, var(--wp-underline-color) ($stop1 + $width), transparent ($stop1 + $width + $blur)),
- linear-gradient(135deg, transparent ($stop2 - $blur), var(--wp-underline-color) $stop2, var(--wp-underline-color) ($stop2 + $width), transparent ($stop2 + $width + $blur));
- background-position: 0 100%;
- background-size: 6px 3px;
- background-repeat: repeat-x;
-
- // Since applied to a span, it doesn't change the footprint of the item,
- // but it does vertically shift the underline to better align.
- padding-bottom: 0.1em;
- }
-
- &.is-invalid,
- &.is-draft {
- span {
- --wp-underline-color: #{$alert-red};
- }
+ text-decoration: wavy underline;
+ text-decoration-skip-ink: none;
+ text-underline-offset: 0.25rem;
}
}
diff --git a/packages/block-library/src/query-total/index.php b/packages/block-library/src/query-total/index.php
index ff2ac486727b92..ba99b324601331 100644
--- a/packages/block-library/src/query-total/index.php
+++ b/packages/block-library/src/query-total/index.php
@@ -10,6 +10,8 @@
*
* @since 6.8.0
*
+ * @global WP_Query $wp_query WordPress Query object.
+ *
* @param array $attributes Block attributes.
* @param string $content Block default content.
* @param WP_Block $block Block instance.
diff --git a/packages/components/src/border-box-control/test/index.tsx b/packages/components/src/border-box-control/test/index.tsx
index fb536656453f4d..74501f7fccc7ac 100644
--- a/packages/components/src/border-box-control/test/index.tsx
+++ b/packages/components/src/border-box-control/test/index.tsx
@@ -202,7 +202,7 @@ describe( 'BorderBoxControl', () => {
await waitFor( () =>
expect(
screen.getByRole( 'button', {
- name: 'Custom color picker.',
+ name: 'Custom color picker',
} )
).toBeVisible()
);
diff --git a/packages/components/src/border-control/test/index.js b/packages/components/src/border-control/test/index.js
index ff9007be28f1a2..c3e3987ed13517 100644
--- a/packages/components/src/border-control/test/index.js
+++ b/packages/components/src/border-control/test/index.js
@@ -138,7 +138,7 @@ describe( 'BorderControl', () => {
const customColorPicker = getButton( /Custom color picker/ );
const circularOptionPicker = screen.getByRole( 'listbox', {
- name: 'Custom color picker.',
+ name: 'Custom color picker',
} );
const colorSwatchButtons =
within( circularOptionPicker ).getAllByRole( 'option' );
diff --git a/packages/components/src/circular-option-picker/README.md b/packages/components/src/circular-option-picker/README.md
index b6db6f06daf456..8a4d5ac3cf5ca3 100644
--- a/packages/components/src/circular-option-picker/README.md
+++ b/packages/components/src/circular-option-picker/README.md
@@ -93,6 +93,19 @@ Prevents keyboard interaction from wrapping around. Only used when `asButtons` i
- Required: No
- Default: `true`
+### `aria-labelledby`: `string`
+
+The ID reference list of one or more elements that label the wrapper element.
+
+- Required: No
+
+### `aria-label`: `string`
+
+The label for the wrapper element. Not used if an 'aria-labelledby' is provided.
+
+- Required: No
+- Default: `Custom color picker`
+
## Subcomponents
### `CircularOptionPicker.ButtonAction`
diff --git a/packages/components/src/circular-option-picker/circular-option-picker.tsx b/packages/components/src/circular-option-picker/circular-option-picker.tsx
index 8b6be8cd2215f0..c4309ecf4dda3d 100644
--- a/packages/components/src/circular-option-picker/circular-option-picker.tsx
+++ b/packages/components/src/circular-option-picker/circular-option-picker.tsx
@@ -132,7 +132,7 @@ function ButtonsCircularOptionPicker(
);
return (
-
+
{ options }
{ children }
diff --git a/packages/components/src/circular-option-picker/index.tsx b/packages/components/src/circular-option-picker/index.tsx
index ef975c21ee6545..ef379994b476f5 100644
--- a/packages/components/src/circular-option-picker/index.tsx
+++ b/packages/components/src/circular-option-picker/index.tsx
@@ -9,5 +9,6 @@ export {
ButtonAction,
DropdownLinkAction,
} from './circular-option-picker-actions';
+export { getComputeCircularOptionPickerCommonProps } from './utils';
export default CircularOptionPicker;
diff --git a/packages/components/src/circular-option-picker/stories/index.story.tsx b/packages/components/src/circular-option-picker/stories/index.story.tsx
index 9d45c9bb92f7d0..6b564929fd8eb9 100644
--- a/packages/components/src/circular-option-picker/stories/index.story.tsx
+++ b/packages/components/src/circular-option-picker/stories/index.story.tsx
@@ -131,7 +131,7 @@ WithLoopingDisabled.parameters = {
docs: {
source: {
code: `}
/>`,
diff --git a/packages/components/src/circular-option-picker/test/index.tsx b/packages/components/src/circular-option-picker/test/index.tsx
index a6e9f2c45a05ce..7d58ed3920f9bd 100644
--- a/packages/components/src/circular-option-picker/test/index.tsx
+++ b/packages/components/src/circular-option-picker/test/index.tsx
@@ -57,6 +57,7 @@ describe( 'CircularOptionPicker', () => {
expect( screen.queryByRole( 'listbox' ) ).not.toBeInTheDocument();
expect( screen.queryByRole( 'option' ) ).not.toBeInTheDocument();
+ expect( screen.getByRole( 'group' ) ).toBeInTheDocument();
expect( screen.getByRole( 'button' ) ).toBeInTheDocument();
} );
} );
diff --git a/packages/components/src/circular-option-picker/types.ts b/packages/components/src/circular-option-picker/types.ts
index 411782aed575b1..54fae3ab2e798a 100644
--- a/packages/components/src/circular-option-picker/types.ts
+++ b/packages/components/src/circular-option-picker/types.ts
@@ -40,6 +40,16 @@ type CommonCircularOptionPickerProps = {
* The child elements.
*/
children?: ReactNode;
+ /**
+ * The ID reference list of one or more elements that label the wrapper
+ * element.
+ */
+ 'aria-labelledby'?: string;
+ /**
+ * The label for the wrapper element. Defaults to 'Custom color picker'. Not
+ * used if an 'aria-labelledby' is provided.
+ */
+ 'aria-label'?: string;
};
type WithBaseId = {
@@ -59,16 +69,7 @@ type FullListboxCircularOptionPickerProps = CommonCircularOptionPickerProps & {
* @default true
*/
loop?: boolean;
-} & (
- | {
- 'aria-label': string;
- 'aria-labelledby'?: never;
- }
- | {
- 'aria-label'?: never;
- 'aria-labelledby': string;
- }
- );
+};
export type ListboxCircularOptionPickerProps = WithBaseId &
Omit< FullListboxCircularOptionPickerProps, 'asButtons' >;
diff --git a/packages/components/src/circular-option-picker/utils.tsx b/packages/components/src/circular-option-picker/utils.tsx
new file mode 100644
index 00000000000000..fcb3b2bcac3695
--- /dev/null
+++ b/packages/components/src/circular-option-picker/utils.tsx
@@ -0,0 +1,27 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Computes the common props for the CircularOptionPicker.
+ */
+export function getComputeCircularOptionPickerCommonProps(
+ asButtons?: boolean,
+ loop?: boolean,
+ ariaLabel?: string,
+ ariaLabelledby?: string
+) {
+ const metaProps = asButtons
+ ? { asButtons: true }
+ : { asButtons: false, loop };
+
+ const labelProps = {
+ 'aria-labelledby': ariaLabelledby,
+ 'aria-label': ariaLabelledby
+ ? undefined
+ : ariaLabel || __( 'Custom color picker' ),
+ };
+
+ return { metaProps, labelProps };
+}
diff --git a/packages/components/src/color-palette/index.tsx b/packages/components/src/color-palette/index.tsx
index de4e4f4206fe3a..eb981e8b9acc70 100644
--- a/packages/components/src/color-palette/index.tsx
+++ b/packages/components/src/color-palette/index.tsx
@@ -19,7 +19,9 @@ import { useCallback, useMemo, useState, forwardRef } from '@wordpress/element';
*/
import Dropdown from '../dropdown';
import { ColorPicker } from '../color-picker';
-import CircularOptionPicker from '../circular-option-picker';
+import CircularOptionPicker, {
+ getComputeCircularOptionPickerCommonProps,
+} from '../circular-option-picker';
import { VStack } from '../v-stack';
import { Truncate } from '../truncate';
import { ColorHeading } from './styles';
@@ -233,7 +235,7 @@ function UnforwardedColorPalette(
buttonLabelName,
displayValue
)
- : __( 'Custom color picker.' );
+ : __( 'Custom color picker' );
const paletteCommonProps = {
clearColor,
@@ -251,33 +253,12 @@ function UnforwardedColorPalette(
);
- let metaProps:
- | { asButtons: false; loop?: boolean; 'aria-label': string }
- | { asButtons: false; loop?: boolean; 'aria-labelledby': string }
- | { asButtons: true };
-
- if ( asButtons ) {
- metaProps = { asButtons: true };
- } else {
- const _metaProps: { asButtons: false; loop?: boolean } = {
- asButtons: false,
- loop,
- };
-
- if ( ariaLabel ) {
- metaProps = { ..._metaProps, 'aria-label': ariaLabel };
- } else if ( ariaLabelledby ) {
- metaProps = {
- ..._metaProps,
- 'aria-labelledby': ariaLabelledby,
- };
- } else {
- metaProps = {
- ..._metaProps,
- 'aria-label': __( 'Custom color picker.' ),
- };
- }
- }
+ const { metaProps, labelProps } = getComputeCircularOptionPickerCommonProps(
+ asButtons,
+ loop,
+ ariaLabel,
+ ariaLabelledby
+ );
return (
@@ -335,6 +316,7 @@ function UnforwardedColorPalette(
{ ( colors.length > 0 || actions ) && (
{
expect( screen.queryByText( colorCode ) ).not.toBeInTheDocument();
expect(
screen.getByRole( 'button', {
- name: /^Custom color picker.$/,
+ name: /^Custom color picker$/,
} )
).toBeInTheDocument();
} );
diff --git a/packages/components/src/duotone-picker/duotone-picker.tsx b/packages/components/src/duotone-picker/duotone-picker.tsx
index 8764b401c38296..a21d12b73a65c4 100644
--- a/packages/components/src/duotone-picker/duotone-picker.tsx
+++ b/packages/components/src/duotone-picker/duotone-picker.tsx
@@ -13,7 +13,9 @@ import { __, sprintf } from '@wordpress/i18n';
* Internal dependencies
*/
import ColorListPicker from './color-list-picker';
-import CircularOptionPicker from '../circular-option-picker';
+import CircularOptionPicker, {
+ getComputeCircularOptionPickerCommonProps,
+} from '../circular-option-picker';
import { VStack } from '../v-stack';
import CustomDuotoneBar from './custom-duotone-bar';
@@ -127,33 +129,12 @@ function DuotonePicker( {
);
} );
- let metaProps:
- | { asButtons: false; loop?: boolean; 'aria-label': string }
- | { asButtons: false; loop?: boolean; 'aria-labelledby': string }
- | { asButtons: true };
-
- if ( asButtons ) {
- metaProps = { asButtons: true };
- } else {
- const _metaProps: { asButtons: false; loop?: boolean } = {
- asButtons: false,
- loop,
- };
-
- if ( ariaLabel ) {
- metaProps = { ..._metaProps, 'aria-label': ariaLabel };
- } else if ( ariaLabelledby ) {
- metaProps = {
- ..._metaProps,
- 'aria-labelledby': ariaLabelledby,
- };
- } else {
- metaProps = {
- ..._metaProps,
- 'aria-label': __( 'Custom color picker.' ),
- };
- }
- }
+ const { metaProps, labelProps } = getComputeCircularOptionPickerCommonProps(
+ asButtons,
+ loop,
+ ariaLabel,
+ ariaLabelledby
+ );
const options = unsetable
? [ unsetOption, ...duotoneOptions ]
@@ -163,6 +144,7 @@ function DuotonePicker( {
) {
);
- let metaProps:
- | { asButtons: false; loop?: boolean; 'aria-label': string }
- | { asButtons: false; loop?: boolean; 'aria-labelledby': string }
- | { asButtons: true };
-
- if ( asButtons ) {
- metaProps = { asButtons: true };
- } else {
- const _metaProps: { asButtons: false; loop?: boolean } = {
- asButtons: false,
- loop,
- };
-
- if ( ariaLabel ) {
- metaProps = { ..._metaProps, 'aria-label': ariaLabel };
- } else if ( ariaLabelledby ) {
- metaProps = {
- ..._metaProps,
- 'aria-labelledby': ariaLabelledby,
- };
- } else {
- metaProps = {
- ..._metaProps,
- 'aria-label': __( 'Custom color picker.' ),
- };
- }
- }
+ const { metaProps, labelProps } = getComputeCircularOptionPickerCommonProps(
+ asButtons,
+ loop,
+ ariaLabel,
+ ariaLabelledby
+ );
return (
diff --git a/packages/edit-site/src/components/site-editor-routes/patterns.js b/packages/edit-site/src/components/site-editor-routes/patterns.js
index db97c4b5c080fd..785528f09afb04 100644
--- a/packages/edit-site/src/components/site-editor-routes/patterns.js
+++ b/packages/edit-site/src/components/site-editor-routes/patterns.js
@@ -1,8 +1,27 @@
+/**
+ * WordPress dependencies
+ */
+import { privateApis as routerPrivateApis } from '@wordpress/router';
+
/**
* Internal dependencies
*/
import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns';
import PagePatterns from '../page-patterns';
+import { unlock } from '../../lock-unlock';
+
+const { useLocation } = unlock( routerPrivateApis );
+
+function MobilePatternsView() {
+ const { query = {} } = useLocation();
+ const { categoryId } = query;
+
+ return !! categoryId ? (
+
+ ) : (
+
+ );
+}
export const patternsRoute = {
name: 'patterns',
@@ -10,6 +29,6 @@ export const patternsRoute = {
areas: {
sidebar: ,
content: ,
- mobile: ,
+ mobile: ,
},
};
diff --git a/test/e2e/specs/editor/blocks/buttons.spec.js b/test/e2e/specs/editor/blocks/buttons.spec.js
index 554bd8947f0bf5..7830a934529aa4 100644
--- a/test/e2e/specs/editor/blocks/buttons.spec.js
+++ b/test/e2e/specs/editor/blocks/buttons.spec.js
@@ -324,13 +324,13 @@ test.describe( 'Buttons', () => {
await page.click(
'role=region[name="Editor settings"i] >> role=button[name="Text"i]'
);
- await page.click( 'role=button[name="Custom color picker."i]' );
+ await page.click( 'role=button[name="Custom color picker"i]' );
await page.fill( 'role=textbox[name="Hex color"i]', 'ff0000' );
await page.click(
'role=region[name="Editor settings"i] >> role=button[name="Background"i]'
);
- await page.click( 'role=button[name="Custom color picker."i]' );
+ await page.click( 'role=button[name="Custom color picker"i]' );
await page.fill( 'role=textbox[name="Hex color"i]', '00ff00' );
// Check the content.
diff --git a/test/e2e/specs/editor/blocks/cover.spec.js b/test/e2e/specs/editor/blocks/cover.spec.js
index 87c244a7306dc6..bee2548c2305d2 100644
--- a/test/e2e/specs/editor/blocks/cover.spec.js
+++ b/test/e2e/specs/editor/blocks/cover.spec.js
@@ -33,7 +33,7 @@ test.describe( 'Cover', () => {
} );
// Locate the Black color swatch.
- const blackColorSwatch = coverBlock.getByRole( 'option', {
+ const blackColorSwatch = coverBlock.getByRole( 'button', {
name: 'Black',
} );
await expect( blackColorSwatch ).toBeVisible();
@@ -105,7 +105,7 @@ test.describe( 'Cover', () => {
// Choose a color swatch to transform the placeholder block into
// a functioning block.
await coverBlock
- .getByRole( 'option', {
+ .getByRole( 'button', {
name: 'Black',
} )
.click();
@@ -128,7 +128,7 @@ test.describe( 'Cover', () => {
name: 'Block: Cover',
} );
await coverBlock
- .getByRole( 'option', {
+ .getByRole( 'button', {
name: 'Black',
} )
.click();
@@ -240,7 +240,7 @@ test.describe( 'Cover', () => {
// Choose a color swatch to transform the placeholder block into
// a functioning block.
await coverBlock
- .getByRole( 'option', {
+ .getByRole( 'button', {
name: 'Black',
} )
.click();
@@ -266,7 +266,7 @@ test.describe( 'Cover', () => {
// Choose a color swatch to transform the placeholder block into
// a functioning block.
await secondCoverBlock
- .getByRole( 'option', {
+ .getByRole( 'button', {
name: 'Black',
} )
.click();
diff --git a/test/e2e/specs/editor/blocks/heading.spec.js b/test/e2e/specs/editor/blocks/heading.spec.js
index 906095cad9d080..6ff7e11bb334e0 100644
--- a/test/e2e/specs/editor/blocks/heading.spec.js
+++ b/test/e2e/specs/editor/blocks/heading.spec.js
@@ -184,7 +184,7 @@ test.describe( 'Heading', () => {
await textColor.click();
await page
- .getByRole( 'button', { name: /Custom color picker./i } )
+ .getByRole( 'button', { name: /Custom color picker/i } )
.click();
await page
diff --git a/test/e2e/specs/editor/various/block-editor-dark-background.spec.js b/test/e2e/specs/editor/various/block-editor-dark-background.spec.js
new file mode 100644
index 00000000000000..dc333f31c7ed9d
--- /dev/null
+++ b/test/e2e/specs/editor/various/block-editor-dark-background.spec.js
@@ -0,0 +1,52 @@
+/**
+ * WordPress dependencies
+ */
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+
+test.describe( 'Block editor with dark background theme', () => {
+ test.beforeAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'darktheme' );
+ } );
+
+ test.beforeEach( async ( { admin } ) => {
+ await admin.createNewPost();
+ } );
+
+ test.afterAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'twentytwentyone' );
+ } );
+
+ test.describe( 'Block editor iframe body', () => {
+ test( 'Should have the is-dark-theme CSS class', async ( {
+ editor,
+ } ) => {
+ const canvasBody = editor.canvas.locator( 'body' );
+
+ await expect( canvasBody ).toHaveClass( /is-dark-theme/ );
+ } );
+ } );
+} );
+
+test.describe( 'Block editor with light background theme', () => {
+ test.beforeAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'twentytwentyfour' );
+ } );
+
+ test.beforeEach( async ( { admin } ) => {
+ await admin.createNewPost();
+ } );
+
+ test.afterAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'twentytwentyone' );
+ } );
+
+ test.describe( 'Block editor iframe body', () => {
+ test( 'Should not have the is-dark-theme CSS class', async ( {
+ editor,
+ } ) => {
+ const canvasBody = editor.canvas.locator( 'body' );
+
+ await expect( canvasBody ).not.toHaveClass( /is-dark-theme/ );
+ } );
+ } );
+} );
diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js
index 988683c8d11aa3..98dfe5e304f802 100644
--- a/test/e2e/specs/editor/various/list-view.spec.js
+++ b/test/e2e/specs/editor/various/list-view.spec.js
@@ -162,10 +162,10 @@ test.describe( 'List View', () => {
// make the inner blocks appear.
await editor.canvas
.getByRole( 'document', { name: 'Block: Cover' } )
- .getByRole( 'listbox', {
- name: 'Custom color picker.',
+ .getByRole( 'group', {
+ name: 'Overlay color',
} )
- .getByRole( 'option' )
+ .getByRole( 'button' )
.first()
.click();
diff --git a/test/e2e/specs/site-editor/preload.spec.js b/test/e2e/specs/site-editor/preload.spec.js
index b6f9f49aedeb7c..e618d70ca20b90 100644
--- a/test/e2e/specs/site-editor/preload.spec.js
+++ b/test/e2e/specs/site-editor/preload.spec.js
@@ -46,9 +46,6 @@ 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/e2e/specs/site-editor/site-editor-dark-background.spec.js b/test/e2e/specs/site-editor/site-editor-dark-background.spec.js
new file mode 100644
index 00000000000000..1bf49c196bdfc6
--- /dev/null
+++ b/test/e2e/specs/site-editor/site-editor-dark-background.spec.js
@@ -0,0 +1,75 @@
+/**
+ * WordPress dependencies
+ */
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+
+test.describe( 'Site editor with dark background theme', () => {
+ test.beforeAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'darktheme' );
+ } );
+
+ test.beforeEach( async ( { admin } ) => {
+ await admin.visitSiteEditor();
+ } );
+
+ test.afterAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'twentytwentyone' );
+ } );
+
+ test.describe( 'Site editor iframe body', () => {
+ test( 'Should have the is-dark-theme CSS class', async ( {
+ editor,
+ } ) => {
+ const canvasBody = editor.canvas.locator( 'body' );
+
+ await expect( canvasBody ).toHaveClass( /is-dark-theme/ );
+ } );
+ } );
+} );
+
+test.describe( 'Site editor with light background theme and theme variations', () => {
+ test.beforeAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'twentytwentyfour' );
+ } );
+
+ test.beforeEach( async ( { admin } ) => {
+ await admin.visitSiteEditor();
+ } );
+
+ test.afterAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'twentytwentyone' );
+ } );
+
+ test.describe( 'Site editor iframe body', () => {
+ test( 'Should not have the is-dark-theme CSS class', async ( {
+ editor,
+ } ) => {
+ const canvasBody = editor.canvas.locator( 'body' );
+
+ await expect( canvasBody ).not.toHaveClass( /is-dark-theme/ );
+ } );
+
+ test( 'Should add and remove the is-dark-theme CSS class with dark and light theme variation', async ( {
+ page,
+ editor,
+ } ) => {
+ // Click "Styles"
+ await page.getByRole( 'button', { name: 'Styles' } ).click();
+
+ // Click "Browse styles"
+ await page.getByRole( 'button', { name: 'Browse styles' } ).click();
+
+ const canvasBody = editor.canvas.locator( 'body' );
+
+ // Activate "Maelstrom" Theme Variation.
+ await page.getByRole( 'button', { name: 'Maelstrom' } ).click();
+
+ await expect( canvasBody ).toHaveClass( /is-dark-theme/ );
+
+ // Activate "Ember" Theme Variation.
+ await page.getByRole( 'button', { name: 'Ember' } ).click();
+
+ await expect( canvasBody ).not.toHaveClass( /is-dark-theme/ );
+ } );
+ } );
+} );
diff --git a/test/gutenberg-test-themes/darktheme/block-templates/index.html b/test/gutenberg-test-themes/darktheme/block-templates/index.html
new file mode 100644
index 00000000000000..0283daeb54c6f4
--- /dev/null
+++ b/test/gutenberg-test-themes/darktheme/block-templates/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+My awesome paragraph
+
diff --git a/test/gutenberg-test-themes/darktheme/block-templates/singular.html b/test/gutenberg-test-themes/darktheme/block-templates/singular.html
new file mode 100644
index 00000000000000..cd05d5fe917fea
--- /dev/null
+++ b/test/gutenberg-test-themes/darktheme/block-templates/singular.html
@@ -0,0 +1,2 @@
+
+
diff --git a/test/gutenberg-test-themes/darktheme/index.php b/test/gutenberg-test-themes/darktheme/index.php
new file mode 100644
index 00000000000000..0c6530acc1aaff
--- /dev/null
+++ b/test/gutenberg-test-themes/darktheme/index.php
@@ -0,0 +1,9 @@
+