diff --git a/docs/explanations/architecture/key-concepts.md b/docs/explanations/architecture/key-concepts.md index 1ba009f7823140..a041b86effdc3b 100644 --- a/docs/explanations/architecture/key-concepts.md +++ b/docs/explanations/architecture/key-concepts.md @@ -65,6 +65,6 @@ More on [Site editing templates](/docs/explanations/architecture/full-site-editi ## Styles -Styles, formerly known as Global Styles and as such referenced in the code, is both an interface that users access through the editor and a configuration system done through [a `theme.json` file](/docs/how-to-guides/themes/theme-json.md). This file absorbs most of the configuration aspects usually scattered through various `add_theme_support` calls to simplify communicating with the editor. It thus aims to improve declaring what settings should be enabled, what specific tools a theme offers (like a custom color palette), the available design tools present, and an infrastructure that allows to coordinate the styles coming from WordPress, the active theme, and the user. +Styles, formerly known as Global Styles and as such referenced in the code, is both an interface that users access through the editor and a configuration system done through [a `theme.json` file](/docs/how-to-guides/themes/global-settings-and-styles.md). This file absorbs most of the configuration aspects usually scattered through various `add_theme_support` calls to simplify communicating with the editor. It thus aims to improve declaring what settings should be enabled, what specific tools a theme offers (like a custom color palette), the available design tools present, and an infrastructure that allows to coordinate the styles coming from WordPress, the active theme, and the user. Learn more about [Global Styles](/docs/explanations/architecture/styles.md#global-styles). diff --git a/docs/explanations/architecture/styles.md b/docs/explanations/architecture/styles.md index d62171a0622055..94a8e91f94edbe 100644 --- a/docs/explanations/architecture/styles.md +++ b/docs/explanations/architecture/styles.md @@ -15,7 +15,7 @@ The final HTML document is the result of a few things: The stylesheets loaded in the front end include: - **Blocks**. The stylesheets that come with the block. In the front end, you can find a single stylesheet with all block styles defined by WordPress (`wp-block-library-*` ) or separate stylesheets per block in use (as in `wp-block-group-*`, `wp-block-columns-*`, etc). See [this note](https://make.wordpress.org/core/2021/07/01/block-styles-loading-enhancements-in-wordpress-5-8/) for the full details. -- **Global styles**. These styles are generated on the fly by using data coming from a theme.json file: see [note](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/), [reference](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/), and [how to guide](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json/). Specifically, it merges the contents of the theme.json from WordPress, the theme.json from the theme (if it has one), and the user data provided via the global styles sidebar in the site editor. The result of processing this data is an embedded stylesheet whose id is `global-styles-inline-css`. +- **Global styles**. These styles are generated on the fly by using data coming from a theme.json file: see [note](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/), [reference](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/), and [how to guide](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/). Specifically, it merges the contents of the theme.json from WordPress, the theme.json from the theme (if it has one), and the user data provided via the global styles sidebar in the site editor. The result of processing this data is an embedded stylesheet whose id is `global-styles-inline-css`. - **Theme**. Historically, themes have enqueued their own stylesheets, where the id is based on the theme name, as in `twentytwentytwo-style-css`. In addition to having their own stylesheets they can now declare a theme.json file containing styles that will be part of the stylesheet generated by global styles. - **User**. Some of the user actions in the editor will generate style content. This is the case for features such as duotone, layout, or link color. - **Other**. WordPress and plugins can also enqueue stylesheets. @@ -515,7 +515,7 @@ There are currently four layout types in use: - Flex: Items are displayed using a Flexbox layout. Defaults to a horizontal orientation. Spacing between children is handled via the `gap` CSS property. - Grid: Items are displayed using a Grid layout. Defaults to an `auto-fill` approach to column generation but can also be set to a fixed number of columns. Spacing between children is handled via the `gap` CSS property. -For controlling spacing between blocks, and enabling block spacing controls see: [What is blockGap and how can I use it?](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json/#what-is-blockgap-and-how-can-i-use-it). +For controlling spacing between blocks, and enabling block spacing controls see: [What is blockGap and how can I use it?](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/#what-is-blockgap-and-how-can-i-use-it). ### Targeting layout or container blocks from themes diff --git a/docs/getting-started/glossary.md b/docs/getting-started/glossary.md index bff8925e4619e1..ca509f6a321dba 100644 --- a/docs/getting-started/glossary.md +++ b/docs/getting-started/glossary.md @@ -70,7 +70,7 @@ This refers to a collection of features that ultimately allows users to edit the The CSS styles generated by WordPress and enqueued as an embedded stylesheet in the front end of the site. The stylesheet ID is `global-styles-inline-css`. The contents of this stylesheet come from the default `theme.json` of WordPress, the theme's `theme.json`, and the styles provided by the user via the global styles sidebar in the site editor. -See [theme.json reference docs](/docs/reference-guides/theme-json-reference.md), the [how to guide](/docs/how-to-guides/themes/theme-json.md), and an introduction to [styles in the block editor](/docs/explanations/architecture/styles.md). +See [theme.json reference docs](/docs/reference-guides/theme-json-reference.md), the [how to guide](/docs/how-to-guides/themes/global-settings-and-styles.md), and an introduction to [styles in the block editor](/docs/explanations/architecture/styles.md). Compare to block styles. diff --git a/docs/getting-started/tutorial.md b/docs/getting-started/tutorial.md index 1f3fd2b2aca9d7..392e905c443474 100644 --- a/docs/getting-started/tutorial.md +++ b/docs/getting-started/tutorial.md @@ -36,9 +36,9 @@ The first step in creating the Copyright Date Block is to scaffold the initial b Review the Get started with create-block documentation for an introduction to using this package. -You can use `create-block` from just about any directory on your computer and then use `wp-env` to create a local WordPress development environment with your new block plugin installed and activated. +You can use `create-block` from just about any directory (folder) on your computer and then use `wp-env` to create a local WordPress development environment with your new block plugin installed and activated. -Therefore, create a new directory (folder) on your computer called "Block Tutorial". Open your terminal and `cd` to this directory. Then run the following command. +Therefore, choose a directory to place the block plugin or optionally create a new folder called "Block Tutorial". Open your terminal and `cd` to this directory. Then run the following command.
If you are not using wp-env, instead, navigate to the plugins/ folder in your local WordPress installation using the terminal and run the following command. @@ -351,9 +351,10 @@ To enable this starting year functionality, you will need one attribute to store ### Updating block.json -Block attributes are generally specified in the [`block.json`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#data-storage-in-the-block-with-attributes) file. So open up the file and add the following section after the `example` in line 9. +Block attributes are generally specified in the [`block.json`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#data-storage-in-the-block-with-attributes) file. So open up the file and add the following section after the `example` property. ```json +"example": {}, "attributes": { "showStartingYear": { "type": "boolean" @@ -483,7 +484,7 @@ export default function Edit( { attributes, setAttributes } ) { 'Starting year', 'copyright-date-block' ) } - value={ startingYear } + value={ startingYear || '' } onChange={ ( value ) => setAttributes( { startingYear: value } ) } @@ -496,6 +497,10 @@ export default function Edit( { attributes, setAttributes } ) { } ``` +
+ You may have noticed that the value property has a value of startingYear || ''. The symbol || is called the Logical OR (logical disjunction) operator. This prevents warnings in React when the startingYear is empty. See Controlled and uncontrolled components for details. +
+ Save the file and refresh the Editor. Confirm that a text field now exists in the Settings panel. Add a starting year and confirm that when you update the page, the value is saved. ![A live look at editing the new Starting Year field in the Settings Sidebar](https://developer.wordpress.org/files/2023/12/block-tutorial-11.gif) @@ -522,7 +527,7 @@ export default function Edit( { attributes, setAttributes } ) { setAttributes( { startingYear: value } ) } @@ -601,7 +606,7 @@ export default function Edit( { attributes, setAttributes } ) { setAttributes( { startingYear: value } ) } diff --git a/docs/how-to-guides/curating-the-editor-experience/disable-editor-functionality.md b/docs/how-to-guides/curating-the-editor-experience/disable-editor-functionality.md index 34b23776914804..23803888f95221 100644 --- a/docs/how-to-guides/curating-the-editor-experience/disable-editor-functionality.md +++ b/docs/how-to-guides/curating-the-editor-experience/disable-editor-functionality.md @@ -1,4 +1,4 @@ -## Disable Editor functionality +# Disable Editor functionality This page is dedicated to the many ways you can disable specific functionality in the Post Editor and Site Editor that are not covered in other areas of the curation documentation. diff --git a/docs/how-to-guides/curating-the-editor-experience/theme-json.md b/docs/how-to-guides/curating-the-editor-experience/theme-json.md index c8f51ea3e91106..d373e0e81e345a 100644 --- a/docs/how-to-guides/curating-the-editor-experience/theme-json.md +++ b/docs/how-to-guides/curating-the-editor-experience/theme-json.md @@ -159,7 +159,7 @@ Continuing the examples with duotone, this means you could allow full access to } ``` -You can read more about how best to [turn on/off options with theme.json here](/docs/how-to-guides/themes/theme-json.md). +You can read more about how best to [turn on/off options with theme.json here](/docs/how-to-guides/themes/global-settings-and-styles.md). ### Disable inherit default layout diff --git a/docs/how-to-guides/themes/README.md b/docs/how-to-guides/themes/README.md index 708ecba03d264d..1510b20c30047f 100644 --- a/docs/how-to-guides/themes/README.md +++ b/docs/how-to-guides/themes/README.md @@ -6,7 +6,7 @@ The block editor provides a number of options for theme designers and developers ### Classic theme -In terms of block editor terminology this is any theme that defines its templates in the traditional `.php` file format, and that doesn't have an `index.html` format template in the `/block-templates` or `/templates` folders. A `Classic` theme has the ability to provide configuration and styling options to the block editor, and block content, via [Theme Supports](/docs/how-to-guides/themes/theme-support.md), or by including a [theme.json](/docs/how-to-guides/themes/theme-json.md) file. A theme does not have to be a `Block` theme in order to take advantage of some of the flexibility provided by the use of a `theme.json` file. +In terms of block editor terminology this is any theme that defines its templates in the traditional `.php` file format, and that doesn't have an `index.html` format template in the `/block-templates` or `/templates` folders. A `Classic` theme has the ability to provide configuration and styling options to the block editor, and block content, via [Theme Supports](/docs/how-to-guides/themes/theme-support.md), or by including a [theme.json](/docs/how-to-guides/themes/global-settings-and-styles.md) file. A theme does not have to be a `Block` theme in order to take advantage of some of the flexibility provided by the use of a `theme.json` file. ### Block theme @@ -18,5 +18,5 @@ There isn't an FSE specific theme type. In WordPress > 5.9 FSE is enabled for an **Contents** -- [Global Settings (theme.json)](/docs/how-to-guides/themes/theme-json.md) +- [Global Settings (theme.json)](/docs/how-to-guides/themes/global-settings-and-styles.md) - [Theme Support](/docs/how-to-guides/themes/theme-support.md) diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/global-settings-and-styles.md similarity index 98% rename from docs/how-to-guides/themes/theme-json.md rename to docs/how-to-guides/themes/global-settings-and-styles.md index 1f7480649f6ab1..130b6271d13bdf 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/global-settings-and-styles.md @@ -2,32 +2,6 @@ WordPress 5.8 comes with [a new mechanism](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/) to configure the editor that enables a finer-grained control and introduces the first step in managing styles for future WordPress releases: the `theme.json` file. Then `theme.json` [evolved to a v2](https://make.wordpress.org/core/2022/01/08/updates-for-settings-styles-and-theme-json/) with WordPress 5.9 release. This page documents its format. -- Rationale - - Settings for the block editor - - Settings can be controlled per block - - Styles are managed - - CSS Custom Properties: presets & custom -- Specification - - version - - settings - - Backward compatibility with add_theme_support - - Presets - - Custom - - Setting examples - - styles - - Top-level - - Block-level - - Elements - - Variations - - customTemplates - - templateParts - - patterns -- FAQ - - The naming schema of CSS Custom Properties - - Why using -- as a separator? - - How settings under "custom" create new CSS Custom Properties - - Why does it take so long to update the styles in the browser? - ## Rationale The Block Editor API has evolved at different velocities and there are some growing pains, specially in areas that affect themes. Examples of this are: the ability to [control the editor programmatically](https://make.wordpress.org/core/2020/01/23/controlling-the-block-editor/), or [a block style system](https://github.com/WordPress/gutenberg/issues/9534) that facilitates user, theme, and core style preferences. diff --git a/docs/how-to-guides/themes/theme-support.md b/docs/how-to-guides/themes/theme-support.md index b978ede928b83d..88e69938737b7a 100644 --- a/docs/how-to-guides/themes/theme-support.md +++ b/docs/how-to-guides/themes/theme-support.md @@ -315,7 +315,7 @@ Themes can opt out of generated block layout styles that provide default structu add_theme_support( 'disable-layout-styles' ); ``` -For themes looking to customize `blockGap` styles or block spacing, see [the developer docs on Global Settings & Styles](/docs/how-to-guides/themes/theme-json/#what-is-blockgap-and-how-can-i-use-it). +For themes looking to customize `blockGap` styles or block spacing, see [the developer docs on Global Settings & Styles](/docs/how-to-guides/themes/global-settings-and-styles.md#what-is-blockgap-and-how-can-i-use-it). ### Supporting custom line heights @@ -434,7 +434,7 @@ add_theme_support( 'custom-spacing' ); ## Link color control -Link support has been made stable as part of WordPress 5.8. It's `false` by default and themes can enable it via the [theme.json file](./theme-json.md): +Link support has been made stable as part of WordPress 5.8. It's `false` by default and themes can enable it via the [theme.json file](/docs/how-to-guides/curating-the-editor-experience/theme-json.md): ```json { diff --git a/docs/manifest.json b/docs/manifest.json index b17343872ea71d..5629675c0b57e3 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -240,7 +240,7 @@ "parent": "curating-the-editor-experience" }, { - "title": "DisableEditorFunctionality", + "title": "Disable Editor functionality", "slug": "disable-editor-functionality", "markdown_source": "../docs/how-to-guides/curating-the-editor-experience/disable-editor-functionality.md", "parent": "curating-the-editor-experience" @@ -301,8 +301,8 @@ }, { "title": "Global Settings & Styles (theme.json)", - "slug": "theme-json", - "markdown_source": "../docs/how-to-guides/themes/theme-json.md", + "slug": "global-settings-and-styles", + "markdown_source": "../docs/how-to-guides/themes/global-settings-and-styles.md", "parent": "themes" }, { diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index 7fd0e68c9bd8c0..4a59c34813448f 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -437,7 +437,7 @@ _**Note:** Since WordPress 6.2._ - Subproperties: - `minHeight`: type `boolean`, default value `false` -This value signals that a block supports some of the CSS style properties related to dimensions. When it does, the block editor will show UI controls for the user to set their values if [the theme declares support](/docs/how-to-guides/themes/theme-json/#opt-in-into-ui-controls). +This value signals that a block supports some of the CSS style properties related to dimensions. When it does, the block editor will show UI controls for the user to set their values if [the theme declares support](/docs/how-to-guides/themes/global-settings-and-styles.md#opt-in-into-ui-controls). ```js supports: { @@ -491,7 +491,7 @@ selectors: { The filter can be applied to an element inside the block by setting the `selectors.filter.duotone` selector. -Duotone presets are sourced from `color.duotone` in [theme.json](/docs/how-to-guides/themes/theme-json.md). +Duotone presets are sourced from `color.duotone` in [theme.json](/docs/how-to-guides/themes/global-settings-and-styles.md). When the block declares support for `filter.duotone`, the attributes definition is extended to include the attribute `style`: @@ -675,7 +675,7 @@ _**Note:** Since WordPress 6.2._ - Subproperties: - `sticky`: type `boolean`, default value `false` -This value signals that a block supports some of the CSS style properties related to position. When it does, the block editor will show UI controls for the user to set their values if [the theme declares support](/docs/how-to-guides/themes/theme-json/#opt-in-into-ui-controls). +This value signals that a block supports some of the CSS style properties related to position. When it does, the block editor will show UI controls for the user to set their values if [the theme declares support](/docs/how-to-guides/themes/global-settings-and-styles.md#opt-in-into-ui-controls). Note that sticky position controls are currently only available for blocks set at the root level of the document. Setting a block to the `sticky` position will stick the block to its most immediate parent when the user scrolls the page. diff --git a/docs/reference-guides/theme-json-reference/README.md b/docs/reference-guides/theme-json-reference/README.md index 92f6f77e298c00..11605b21625ad2 100644 --- a/docs/reference-guides/theme-json-reference/README.md +++ b/docs/reference-guides/theme-json-reference/README.md @@ -1,6 +1,6 @@ # Theme.json Reference -This reference guide lists the settings and style properties defined in the theme.json schema. See the [theme.json how to guide](/docs/how-to-guides/themes/theme-json.md) for examples and guide on how to use the theme.json file in your theme. +This reference guide lists the settings and style properties defined in the theme.json schema. See the [theme.json how to guide](/docs/how-to-guides/themes/global-settings-and-styles.md) for examples and guide on how to use the theme.json file in your theme. - [Version 2 (living reference)](/docs/reference-guides/theme-json-reference/theme-json-living.md) diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 627fee6071816f..4baa5a6009ded6 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -6,7 +6,7 @@ > - the [theme.json v1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) specification, and > - the [reference to migrate from theme.json v1 to v2](/docs/reference-guides/theme-json-reference/theme-json-migrations.md). -This reference guide lists the settings and style properties defined in the `theme.json` schema. See the [theme.json how to guide](/docs/how-to-guides/themes/theme-json.md) for examples and guidance on how to use the `theme.json` file in your theme. +This reference guide lists the settings and style properties defined in the `theme.json` schema. See the [theme.json how to guide](/docs/how-to-guides/themes/global-settings-and-styles.md) for examples and guidance on how to use the `theme.json` file in your theme. ## Schema diff --git a/docs/toc.json b/docs/toc.json index 2a0eb6470bd082..49110f8bed9579 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -130,7 +130,9 @@ { "docs/how-to-guides/propagating-updates.md": [] }, { "docs/how-to-guides/themes/README.md": [ - { "docs/how-to-guides/themes/theme-json.md": [] }, + { + "docs/how-to-guides/themes/global-settings-and-styles.md": [] + }, { "docs/how-to-guides/themes/theme-support.md": [] } ] }, diff --git a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php b/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php index 189e5a695c23a7..ea94128e1dde29 100644 --- a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php +++ b/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php @@ -428,11 +428,11 @@ private static function get_responsive_container_markup( $attributes, $inner_blo $responsive_dialog_directives = ''; $close_button_directives = ''; if ( $should_load_view_script ) { - $open_button_directives = ' + $open_button_directives = ' data-wp-on--click="actions.openMenuOnClick" data-wp-on--keydown="actions.handleMenuKeydown" '; - $responsive_container_directives = ' + $responsive_container_directives = ' data-wp-class--has-modal-open="state.isMenuOpen" data-wp-class--is-menu-open="state.isMenuOpen" data-wp-watch="callbacks.initMenu" @@ -440,15 +440,17 @@ private static function get_responsive_container_markup( $attributes, $inner_blo data-wp-on--focusout="actions.handleMenuFocusout" tabindex="-1" '; - $responsive_dialog_directives = ' + $responsive_dialog_directives = ' data-wp-bind--aria-modal="state.ariaModal" data-wp-bind--aria-label="state.ariaLabel" data-wp-bind--role="state.roleAttribute" - data-wp-watch="callbacks.focusFirstElement" '; - $close_button_directives = ' + $close_button_directives = ' data-wp-on--click="actions.closeMenuOnClick" '; + $responsive_container_content_directives = ' + data-wp-watch="callbacks.focusFirstElement" + '; } return sprintf( @@ -457,7 +459,7 @@ private static function get_responsive_container_markup( $attributes, $inner_blo
-
+
%2$s
@@ -475,7 +477,8 @@ private static function get_responsive_container_markup( $attributes, $inner_blo $open_button_directives, $responsive_container_directives, $responsive_dialog_directives, - $close_button_directives + $close_button_directives, + $responsive_container_content_directives ); } diff --git a/lib/experimental/fonts/font-library/class-wp-font-family-utils.php b/lib/experimental/fonts/font-library/class-wp-font-family-utils.php index 7d954e79e96a3c..35e6856e50aad8 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-family-utils.php +++ b/lib/experimental/fonts/font-library/class-wp-font-family-utils.php @@ -76,21 +76,6 @@ public static function merge_fonts_data( $font1, $font2 ) { return $merged_font; } - /** - * Returns whether the given file has a font MIME type. - * - * @since 6.5.0 - * - * @param string $filepath The file to check. - * @return bool True if the file has a font MIME type, false otherwise. - */ - public static function has_font_mime_type( $filepath ) { - $allowed_mime_types = WP_Font_Library::get_expected_font_mime_types_per_php_version(); - $filetype = wp_check_filetype( $filepath, $allowed_mime_types ); - - return in_array( $filetype['type'], $allowed_mime_types, true ); - } - /** * Format font family to make it valid CSS. * diff --git a/lib/experimental/fonts/font-library/class-wp-font-family.php b/lib/experimental/fonts/font-library/class-wp-font-family.php index 58d4f476e834d1..a4204dfe1fa2c7 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-family.php +++ b/lib/experimental/fonts/font-library/class-wp-font-family.php @@ -202,11 +202,6 @@ private function get_upload_overrides( $filename ) { * False if the download failed. */ private function download_asset( $url, $filename ) { - // Checks if the file to be downloaded has a font mime type. - if ( ! WP_Font_Family_Utils::has_font_mime_type( $filename ) ) { - return false; - } - // Include file with download_url() if function doesn't exist. if ( ! function_exists( 'download_url' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; @@ -263,12 +258,6 @@ private function move_font_face_asset( $font_face, $file ) { // because it is no longer needed. unset( $new_font_face['uploadedFile'] ); - // If the filename has no font mime type, don't move the file and - // return the font face definition without src to be ignored later. - if ( ! WP_Font_Family_Utils::has_font_mime_type( $filename ) ) { - return $new_font_face; - } - // Move the uploaded font asset from the temp folder to the fonts directory. if ( ! function_exists( 'wp_handle_upload' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index cf55a048bb9fa5..bb70068aa9482b 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -13,47 +13,42 @@ /** * This processor is built on top of the HTML Tag Processor and augments its * capabilities to process the Interactivity API directives. - * - * IMPORTANT DISCLAIMER: This code is highly experimental and its only purpose - * is to provide a way to test the server-side rendering of the Interactivity - * API. Most of this code will be discarded once the HTML Processor is - * available. Please restrain from investing unnecessary time and effort trying - * to improve this code. */ class WP_Directive_Processor extends Gutenberg_HTML_Tag_Processor_6_5 { + /** + * String containing the current root block. + * + * @var string + */ + public static $root_block = null; /** - * An array of root blocks. + * Array containing the direct children of interactive blocks. * * @var array */ - public static $root_block = null; + public static $children_of_interactive_block = array(); /** - * Add a root block to the variable. + * Sets the current root block. * * @param array $block The block to add. - * - * @return void */ public static function mark_root_block( $block ) { self::$root_block = md5( serialize( $block ) ); } /** - * Remove a root block to the variable. - * - * @return void + * Resets the root block. */ public static function unmark_root_block() { self::$root_block = null; } /** - * Check if block is a root block. + * Checks if block is a root block. * * @param array $block The block to check. - * * @return bool True if block is a root block, false otherwise. */ public static function is_marked_as_root_block( $block ) { @@ -61,17 +56,36 @@ public static function is_marked_as_root_block( $block ) { } /** - * Check if a root block has already been defined. + * Checks if a root block has already been defined. * - * @return bool True if block is a root block, false otherwise. + * @return bool True if there is a root block, false otherwise. */ public static function has_root_block() { return isset( self::$root_block ); } + /** + * Stores a reference to a direct children of an interactive block to be able + * to identify it later. + * + * @param array $block The block to add. + */ + public static function mark_children_of_interactive_block( $block ) { + self::$children_of_interactive_block[] = md5( serialize( $block ) ); + } /** - * Find the matching closing tag for an opening tag. + * Checks if block is marked as children of an interactive block. + * + * @param array $block The block to check. + * @return bool True if block is a children of an interactive block, false otherwise. + */ + public static function is_marked_as_children_of_interactive_block( $block ) { + return in_array( md5( serialize( $block ) ), self::$children_of_interactive_block, true ); + } + + /** + * Finds the matching closing tag for an opening tag. * * When called while on an open tag, traverse the HTML until we find the * matching closing tag, respecting any in-between content, including nested @@ -111,76 +125,7 @@ public function next_balanced_closer() { } /** - * Traverses the HTML searching for Interactivity API directives and processing - * them. - * - * @param WP_Directive_Processor $tags An instance of the WP_Directive_Processor. - * @param string $prefix Attribute prefix. - * @param string[] $directives Directives. - * - * @return WP_Directive_Processor The modified instance of the - * WP_Directive_Processor. - */ - public function process_rendered_html( $tags, $prefix, $directives ) { - $context = new WP_Directive_Context(); - $tag_stack = array(); - - while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - $tag_name = $tags->get_tag(); - - // Is this a tag that closes the latest opening tag? - if ( $tags->is_tag_closer() ) { - if ( 0 === count( $tag_stack ) ) { - continue; - } - - list( $latest_opening_tag_name, $attributes ) = end( $tag_stack ); - if ( $latest_opening_tag_name === $tag_name ) { - array_pop( $tag_stack ); - - // If the matching opening tag didn't have any directives, we move on. - if ( 0 === count( $attributes ) ) { - continue; - } - } - } else { - $attributes = array(); - foreach ( $tags->get_attribute_names_with_prefix( $prefix ) as $name ) { - /* - * Removes the part after the double hyphen before looking for - * the directive processor inside `$directives`, e.g., "wp-bind" - * from "wp-bind--src" and "wp-context" from "wp-context" etc... - */ - list( $type ) = WP_Directive_Processor::parse_attribute_name( $name ); - if ( array_key_exists( $type, $directives ) ) { - $attributes[] = $type; - } - } - - /* - * If this is an open tag, and if it either has directives, or if - * we're inside a tag that does, take note of this tag and its - * directives so we can call its directive processor once we - * encounter the matching closing tag. - */ - if ( - ! WP_Directive_Processor::is_html_void_element( $tags->get_tag() ) && - ( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) ) - ) { - $tag_stack[] = array( $tag_name, $attributes ); - } - } - - foreach ( $attributes as $attribute ) { - call_user_func( $directives[ $attribute ], $tags, $context ); - } - } - - return $tags; - } - - /** - * Return the content between two balanced tags. + * Returns the content between two balanced tags. * * When called on an opening tag, return the HTML content found between that * opening tag and its matching closing tag. @@ -206,14 +151,13 @@ public function get_inner_html() { } /** - * Set the content between two balanced tags. + * Sets the content between two balanced tags. * * When called on an opening tag, set the HTML content found between that * opening tag and its matching closing tag. * * @param string $new_html The string to replace the content between the * matching tags with. - * * @return bool Whether the content was successfully replaced. */ public function set_inner_html( $new_html ) { @@ -237,7 +181,7 @@ public function set_inner_html( $new_html ) { } /** - * Return a pair of bookmarks for the current opening tag and the matching + * Returns a pair of bookmarks for the current opening tag and the matching * closing tag. * * @return array|false A pair of bookmarks, or false if there's no matching @@ -267,12 +211,12 @@ public function get_balanced_tag_bookmarks() { } /** - * Whether a given HTML element is void (e.g.
). + * Checks whether a given HTML element is void (e.g.
). + * + * @see https://html.spec.whatwg.org/#elements-2 * * @param string $tag_name The element in question. * @return bool True if the element is void. - * - * @see https://html.spec.whatwg.org/#elements-2 */ public static function is_html_void_element( $tag_name ) { switch ( $tag_name ) { @@ -297,7 +241,7 @@ public static function is_html_void_element( $tag_name ) { } /** - * Extract and return the directive type and the the part after the double + * Extracts and return the directive type and the the part after the double * hyphen from an attribute name (if present), in an array format. * * Examples: @@ -307,7 +251,7 @@ public static function is_html_void_element( $tag_name ) { * 'wp-thing--and--thang' => array( 'wp-thing', 'and--thang' ) * * @param string $name The attribute name. - * @return array The resulting array + * @return array The resulting array. */ public static function parse_attribute_name( $name ) { return explode( '--', $name, 2 ); diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 064fc8ea62cbb2..075d31d577634c 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -1,16 +1,16 @@ 'gutenberg_interactivity_process_wp_bind', - 'data-wp-context' => 'gutenberg_interactivity_process_wp_context', - 'data-wp-class' => 'gutenberg_interactivity_process_wp_class', - 'data-wp-style' => 'gutenberg_interactivity_process_wp_style', - 'data-wp-text' => 'gutenberg_interactivity_process_wp_text', - ); - $tags = new WP_Directive_Processor( $block_content ); - $tags = $tags->process_rendered_html( $tags, 'data-wp-', $directives ); - return $tags->get_updated_html(); + // Parse our own block delimiters for interactive and non-interactive blocks. + $parsed_blocks = parse_blocks( $block_content ); + $context = new WP_Directive_Context(); + $processed_content = ''; + + foreach ( $parsed_blocks as $parsed_block ) { + if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) { + $processed_content .= gutenberg_process_interactive_block( $parsed_block, $context ); + } elseif ( 'core/non-interactivity-wrapper' === $parsed_block['blockName'] ) { + $processed_content .= gutenberg_process_non_interactive_block( $parsed_block, $context ); + } else { + $processed_content .= $parsed_block['innerHTML']; + } + } + return $processed_content; + } + + return $block_content; +} +add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 20, 2 ); +/** + * Marks the block as a children of an interactive block. + * + * @param array $parsed_block The parsed block. + * @param array $source_block The source block. + * @param WP_Block $parent_block The parent block. + */ +function gutenberg_mark_chidren_of_interactive_block( $parsed_block, $source_block, $parent_block ) { + if ( + isset( $parent_block ) && + isset( $parent_block->block_type->supports['interactivity'] ) && + $parent_block->block_type->supports['interactivity'] + ) { + WP_Directive_Processor::mark_children_of_interactive_block( $source_block ); } + return $parsed_block; +} +add_filter( 'render_block_data', 'gutenberg_mark_chidren_of_interactive_block', 100, 3 ); +/** + * Adds a comment delimiter to mark if the block is interactive or not. + * + * @param string $block_content The block content. + * @param array $block The full block, including name and attributes. + * @param WP_Block $block_instance The block instance. + */ +function gutenberg_mark_block_interactivity( $block_content, $block, $block_instance ) { + if ( + isset( $block_instance->block_type->supports['interactivity'] ) && + $block_instance->block_type->supports['interactivity'] + ) { + // Wraps the interactive block with a comment delimiter to be able to + // process it later. + return get_comment_delimited_block_content( + 'core/interactivity-wrapper', + array(), + $block_content + ); + } elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) { + // Wraps the non-interactive block with a comment delimiter to be able to + // skip it later. + return get_comment_delimited_block_content( + 'core/non-interactivity-wrapper', + array(), + $block_content + ); + } return $block_content; } -add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 10, 2 ); +add_filter( 'render_block', 'gutenberg_mark_block_interactivity', 10, 3 ); + +/** + * Traverses the HTML of an interactive block, searching for Interactivity API + * directives and processing them. For the inner blocks, it calls the + * corresponding function depending on the wrapper type. + * + * @param array $interactive_block The interactive block to process. + * @param WP_Directive_Context $context The context to use when processing. + * + * @return string The processed HTML. + */ +function gutenberg_process_interactive_block( $interactive_block, $context ) { + $block_index = 0; + $content = ''; + $interactive_inner_blocks = array(); + + foreach ( $interactive_block['innerContent'] as $inner_content ) { + if ( is_string( $inner_content ) ) { + $content .= $inner_content; + } else { + // This is an inner block. It may be an interactive block or a + // non-interactive block. + $content .= ''; + $interactive_inner_blocks[] = $interactive_block['innerBlocks'][ $block_index++ ]; + } + } + + return gutenberg_process_interactive_html( $content, $context, $interactive_inner_blocks ); +} + +/** + * Returns the HTML of a non-interactive block without processing the + * directives. For the inner blocks, it calls the corresponding function + * depending on the wrapper type. + * + * @param array $non_interactive_block The non-interactive block to process. + * @param WP_Directive_Context $context The context to use when processing. + * + * @return string The processed HTML. + */ +function gutenberg_process_non_interactive_block( $non_interactive_block, $context ) { + $block_index = 0; + $content = ''; + foreach ( $non_interactive_block['innerContent'] as $inner_content ) { + if ( is_string( $inner_content ) ) { + // This content belongs to a non interactive block and therefore it cannot + // contain directives. We add the HTML directly to the final output. + $content .= $inner_content; + } else { + // This is an inner block. It may be an interactive block or a + // non-interactive block. + $inner_block = $non_interactive_block['innerBlocks'][ $block_index++ ]; + + if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { + $content .= gutenberg_process_interactive_block( $inner_block, $context ); + } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { + $content .= gutenberg_process_non_interactive_block( $inner_block, $context ); + } + } + } + return $content; +} + +/** + * Processes interactive HTML by applying directives to the HTML tags. + * + * It uses the WP_Directive_Processor class to parse the HTML and apply the + * directives. If a tag contains a 'WP-INNER-BLOCKS' string and there are inner + * blocks to process, the function processes these inner blocks and replaces the + * 'WP-INNER-BLOCKS' tag in the HTML with those blocks. + * + * @param string $html The HTML to process. + * @param mixed $context The context to use when processing. + * @param array $inner_blocks The inner blocks to process. + * + * @return string The processed HTML. + */ +function gutenberg_process_interactive_html( $html, $context, $inner_blocks = array() ) { + static $directives = array( + 'data-wp-context' => 'gutenberg_interactivity_process_wp_context', + 'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind', + 'data-wp-class' => 'gutenberg_interactivity_process_wp_class', + 'data-wp-style' => 'gutenberg_interactivity_process_wp_style', + 'data-wp-text' => 'gutenberg_interactivity_process_wp_text', + ); + + $tags = new WP_Directive_Processor( $html ); + $prefix = 'data-wp-'; + $tag_stack = array(); + $inner_processed_blocks = array(); + $inner_blocks_index = 0; + while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { + $tag_name = $tags->get_tag(); + + // Processes the inner blocks. + if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) { + if ( 'core/interactivity-wrapper' === $inner_blocks[ $inner_blocks_index ]['blockName'] ) { + $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context ); + } elseif ( 'core/non-interactivity-wrapper' === $inner_blocks[ $inner_blocks_index ]['blockName'] ) { + $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_non_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context ); + } + } + if ( $tags->is_tag_closer() ) { + if ( 0 === count( $tag_stack ) ) { + continue; + } + list( $latest_opening_tag_name, $attributes ) = end( $tag_stack ); + if ( $latest_opening_tag_name === $tag_name ) { + array_pop( $tag_stack ); + // If the matching opening tag didn't have any directives, we move on. + if ( 0 === count( $attributes ) ) { + continue; + } + } + } else { + $attributes = array(); + foreach ( $tags->get_attribute_names_with_prefix( $prefix ) as $name ) { + /* + * Removes the part after the double hyphen before looking for + * the directive processor inside `$directives`, e.g., "wp-bind" + * from "wp-bind--src" and "wp-context" from "wp-context" etc... + */ + list( $type ) = $tags::parse_attribute_name( $name ); + if ( array_key_exists( $type, $directives ) ) { + $attributes[] = $type; + } + } + + /* + * If this is an open tag, and if it either has directives, or if + * we're inside a tag that does, take note of this tag and its + * directives so we can call its directive processor once we + * encounter the matching closing tag. + */ + if ( + ! $tags::is_html_void_element( $tag_name ) && + ( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) ) + ) { + $tag_stack[] = array( $tag_name, $attributes ); + } + } + + // Extract all directive names. They'll be used later on. + $directive_names = array_keys( $directives ); + $directive_names_rev = array_reverse( $directive_names ); + + /* + * Sort attributes by the order they appear in the `$directives` + * argument, considering it as the priority order in which + * directives should be processed. Note that the order is reversed + * for tag closers. + */ + $sorted_attrs = array_intersect( + $tags->is_tag_closer() + ? $directive_names_rev + : $directive_names, + $attributes + ); + + foreach ( $sorted_attrs as $attribute ) { + call_user_func( $directives[ $attribute ], $tags, $context ); + } + } + + $processed_html = $tags->get_updated_html(); + + // Replaces the inner block tags with the content of each inner block + // processed. + if ( ! empty( $inner_processed_blocks ) ) { + foreach ( $inner_processed_blocks as $inner_block_tag => $inner_block_content ) { + if ( str_contains( $processed_html, $inner_block_tag ) ) { + $processed_html = str_replace( '<' . $inner_block_tag . '>', $inner_block_content, $processed_html ); + } + } + } + return $processed_html; +} /** - * Resolve the reference using the store and the context from the provided path. + * Resolves the reference using the store and the context from the provided + * path. * * @param string $path Path. * @param array $context Context data. @@ -71,15 +304,14 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr ); /* - * Check first if the directive path is preceded by a negator operator (!), + * Checks first if the directive path is preceded by a negator operator (!), * indicating that the value obtained from the Interactivity Store (or the * passed context) using the subsequent path should be negated. */ $should_negate_value = '!' === $path[0]; - - $path = $should_negate_value ? substr( $path, 1 ) : $path; - $path_segments = explode( '.', $path ); - $current = $store; + $path = $should_negate_value ? substr( $path, 1 ) : $path; + $path_segments = explode( '.', $path ); + $current = $store; foreach ( $path_segments as $p ) { if ( isset( $current[ $p ] ) ) { $current = $current[ $p ]; @@ -89,7 +321,7 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr } /* - * Check if $current is an anonymous function or an arrow function, and if + * Checks if $current is an anonymous function or an arrow function, and if * so, call it passing the store. Other types of callables are ignored on * purpose, as arbitrary strings or arrays could be wrongly evaluated as * "callables". @@ -100,6 +332,6 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr $current = call_user_func( $current, $store ); } - // Return the opposite if it has a negator operator (!). + // Returns the opposite if it has a negator operator (!). return $should_negate_value ? ! $current : $current; } diff --git a/lib/experimental/interactivity-api/directives/wp-style.php b/lib/experimental/interactivity-api/directives/wp-style.php index 9c37f9082c2c0b..e5d7b269ace7cf 100644 --- a/lib/experimental/interactivity-api/directives/wp-style.php +++ b/lib/experimental/interactivity-api/directives/wp-style.php @@ -28,7 +28,7 @@ function gutenberg_interactivity_process_wp_style( $tags, $context ) { $expr = $tags->get_attribute( $attr ); $style_value = gutenberg_interactivity_evaluate_reference( $expr, $context->get_context() ); if ( $style_value ) { - $style_attr = $tags->get_attribute( 'style' ); + $style_attr = $tags->get_attribute( 'style' ) ?? ''; $style_attr = gutenberg_interactivity_set_style( $style_attr, $style_name, $style_value ); $tags->set_attribute( 'style', $style_attr ); } else { diff --git a/packages/block-editor/src/components/height-control/README.md b/packages/block-editor/src/components/height-control/README.md index 8853f9ef89321e..67b52f1d56f9b2 100644 --- a/packages/block-editor/src/components/height-control/README.md +++ b/packages/block-editor/src/components/height-control/README.md @@ -2,7 +2,7 @@ The `HeightControl` component adds a linked unit control and slider component for controlling the height of a block within the block editor. It supports passing a label, and is used for controlling the minimum height dimensions of Group blocks. -_Note:_ It is worth noting that the minimum height option is an opt-in feature. Themes need to declare support for it before it'll be available, and a convenient way to do that is via opting in to the [appearanceTools](/docs/how-to-guides/themes/theme-json/#opt-in-into-ui-controls) UI controls. +_Note:_ It is worth noting that the minimum height option is an opt-in feature. Themes need to declare support for it before it'll be available, and a convenient way to do that is via opting in to the [appearanceTools](/docs/how-to-guides/themes/global-settings-and-styles.md#opt-in-into-ui-controls) UI controls. ## Development guidelines diff --git a/packages/block-editor/src/components/rich-text/use-mark-persistent.js b/packages/block-editor/src/components/rich-text/use-mark-persistent.js index 9a564dfb7f97e8..10e157452fbe22 100644 --- a/packages/block-editor/src/components/rich-text/use-mark-persistent.js +++ b/packages/block-editor/src/components/rich-text/use-mark-persistent.js @@ -11,8 +11,7 @@ import { store as blockEditorStore } from '../../store'; export function useMarkPersistent( { html, value } ) { const previousText = useRef(); - const hasActiveFormats = - value.activeFormats && !! value.activeFormats.length; + const hasActiveFormats = !! value.activeFormats?.length; const { __unstableMarkLastChangeAsPersistent } = useDispatch( blockEditorStore ); diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index ba8e6d1a6683a4..fb3919168a2677 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -180,9 +180,9 @@ const { state, actions } = store( 'core/navigation', { focusFirstElement() { const { ref } = getElement(); if ( state.isMenuOpen ) { - ref.querySelector( - '.wp-block-navigation-item > *:first-child' - ).focus(); + const focusableElements = + ref.querySelectorAll( focusableSelectors ); + focusableElements?.[ 0 ]?.focus(); } }, }, diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 026ad72de90eae..3e3a29728490bd 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,7 +4,8 @@ ### Bug Fix -- `DropdownMenu V2 `: better fallback on browsers that don't support CSS subgrid([#57327](https://github.com/WordPress/gutenberg/pull/57327)). +- `NumberControl`: Make increment and decrement buttons keyboard accessible. ([#57402](https://github.com/WordPress/gutenberg/pull/57402)). +- `DropdownMenu V2`: better fallback on browsers that don't support CSS subgrid([#57327](https://github.com/WordPress/gutenberg/pull/57327)). - `FontSizePicker`: Use Button API for keeping focus on reset ([#57221](https://github.com/WordPress/gutenberg/pull/57221)). - `FontSizePicker`: Fix Reset button focus loss ([#57196](https://github.com/WordPress/gutenberg/pull/57196)). - `PaletteEdit`: Consider digits when generating kebab-cased slug ([#56713](https://github.com/WordPress/gutenberg/pull/56713)). diff --git a/packages/components/src/number-control/README.md b/packages/components/src/number-control/README.md index 1a78cd25bb457c..3a54351cd29258 100644 --- a/packages/components/src/number-control/README.md +++ b/packages/components/src/number-control/README.md @@ -46,9 +46,9 @@ If `isDragEnabled` is true, this controls the amount of `px` to have been dragge ### spinControls - The type of spin controls to display. These are butons that allow the user to + The type of spin controls to display. These are buttons that allow the user to quickly increment and decrement the number. - + - 'none' - Do not show spin controls. - 'native' - Use browser's native HTML `input` controls. - 'custom' - Use plus and minus icon buttons. diff --git a/packages/components/src/number-control/index.tsx b/packages/components/src/number-control/index.tsx index 320ef4cb87d1da..57d811d69939b4 100644 --- a/packages/components/src/number-control/index.tsx +++ b/packages/components/src/number-control/index.tsx @@ -247,9 +247,7 @@ function UnforwardedNumberControl( className={ spinButtonClasses } icon={ plusIcon } size="small" - aria-hidden="true" - aria-label={ __( 'Increment' ) } - tabIndex={ -1 } + label={ __( 'Increment' ) } onClick={ buildSpinButtonClickHandler( 'up' ) } @@ -258,9 +256,7 @@ function UnforwardedNumberControl( className={ spinButtonClasses } icon={ resetIcon } size="small" - aria-hidden="true" - aria-label={ __( 'Decrement' ) } - tabIndex={ -1 } + label={ __( 'Decrement' ) } onClick={ buildSpinButtonClickHandler( 'down' ) } diff --git a/packages/components/src/number-control/types.ts b/packages/components/src/number-control/types.ts index 98ee8e0a672f08..8d198e777bd557 100644 --- a/packages/components/src/number-control/types.ts +++ b/packages/components/src/number-control/types.ts @@ -15,7 +15,7 @@ export type NumberControlProps = Omit< */ hideHTMLArrows?: boolean; /** - * The type of spin controls to display. These are butons that allow the + * The type of spin controls to display. These are buttons that allow the * user to quickly increment and decrement the number. * * - 'none' - Do not show spin controls. diff --git a/packages/dataviews/src/add-filter.js b/packages/dataviews/src/add-filter.js index a9d8d78509dc46..2f001050cc1e51 100644 --- a/packages/dataviews/src/add-filter.js +++ b/packages/dataviews/src/add-filter.js @@ -117,7 +117,7 @@ export default function AddFilter( { filters, view, onChangeView } ) { return ( { @@ -172,7 +172,7 @@ export default function AddFilter( { filters, view, onChangeView } ) { ] ) => ( { + function DropdownMenuRadioItemCustom( + { checked, name, value, hideOnClick, onChange, onClick, ...props }, + ref + ) { const onClickHandler = ( e ) => { onClick?.( e ); onChange?.( { ...e, target: { ...e.target, value } } ); @@ -39,7 +42,7 @@ export const DropdownMenuRadioItemCustom = forwardRef( role="menuitemradio" name={ name } aria-checked={ checked } - hideOnClick={ false } + hideOnClick={ !! hideOnClick } prefix={ checked ? ( diff --git a/packages/dataviews/src/filter-summary.js b/packages/dataviews/src/filter-summary.js index e255561660648c..f09c15c8eb1374 100644 --- a/packages/dataviews/src/filter-summary.js +++ b/packages/dataviews/src/filter-summary.js @@ -103,7 +103,7 @@ export default function FilterSummary( { filter, view, onChangeView } ) { return ( @@ -150,7 +150,7 @@ export default function FilterSummary( { filter, view, onChangeView } ) { ( [ operator, { label, key } ] ) => ( { diff --git a/packages/dataviews/src/view-actions.js b/packages/dataviews/src/view-actions.js index 458032d68ac70c..9130f72694b289 100644 --- a/packages/dataviews/src/view-actions.js +++ b/packages/dataviews/src/view-actions.js @@ -54,6 +54,7 @@ function ViewTypeMenu( { view, onChangeView, supportedLayouts } ) { value={ availableView.type } name="view-actions-available-view" checked={ availableView.type === view.type } + hideOnClick={ true } onChange={ ( e ) => { onChangeView( { ...view, diff --git a/packages/dataviews/src/view-table.js b/packages/dataviews/src/view-table.js index 200da945a101a1..6ca851c5d5690d 100644 --- a/packages/dataviews/src/view-table.js +++ b/packages/dataviews/src/view-table.js @@ -167,7 +167,7 @@ function HeaderMenu( { field, view, onChangeView } ) { return ( { @@ -222,7 +222,7 @@ function HeaderMenu( { field, view, onChangeView } ) { ] ) => ( { - await searchForPattern( name ); - const patternElement = await page.waitForXPath( - `//div[@role = 'option']//div[contains(text(), '${ name }')]`, - { timeout: 5000, visible: available, hidden: ! available } - ); - const patternExists = !! patternElement; - await toggleGlobalBlockInserter(); - return patternExists; -}; - -const TEST_PATTERNS = [ - [ 'Test: Single heading', true ], - [ 'Test: Single paragraph', false ], - [ 'Test: Paragraph inside group', false ], -]; - -describe( 'Allowed Patterns', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-allowed-patterns' ); - await createNewPost(); - } ); - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-allowed-patterns' ); - } ); - - describe( 'Disable blocks plugin disabled', () => { - for ( const [ patternName ] of TEST_PATTERNS ) { - it( `should show test pattern "${ patternName }"`, async () => { - expect( await checkPatternExistence( patternName, true ) ).toBe( - true - ); - } ); - } - } ); - - describe( 'Disable blocks plugin enabled', () => { - beforeAll( async () => { - await activatePlugin( - 'gutenberg-test-allowed-patterns-disable-blocks' - ); - await createNewPost(); - } ); - afterAll( async () => { - await deactivatePlugin( - 'gutenberg-test-allowed-patterns-disable-blocks' - ); - } ); - - for ( const [ patternName, shouldBeAvailable ] of TEST_PATTERNS ) { - it( `should${ - shouldBeAvailable ? '' : ' not' - } show test "pattern ${ patternName }"`, async () => { - expect( - await checkPatternExistence( - patternName, - shouldBeAvailable - ) - ).toBe( shouldBeAvailable ); - } ); - } - } ); -} ); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/confirm-delete-dialog.js b/packages/edit-site/src/components/global-styles/font-library-modal/confirm-delete-dialog.js index 259b6900dd16d5..b87a921bd35e59 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/confirm-delete-dialog.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/confirm-delete-dialog.js @@ -13,8 +13,8 @@ function ConfirmDeleteDialog( { return ( @@ -22,7 +22,7 @@ function ConfirmDeleteDialog( { sprintf( /* translators: %s: Name of the font. */ __( - 'Would you like to remove %s and all its variants and assets?' + 'Are you sure you want to delete "%s" font and all its variants and assets?' ), font.name ) } diff --git a/packages/style-engine/docs/using-the-style-engine-with-block-supports.md b/packages/style-engine/docs/using-the-style-engine-with-block-supports.md index 42350a17ffcd6d..27d80df189cf3d 100644 --- a/packages/style-engine/docs/using-the-style-engine-with-block-supports.md +++ b/packages/style-engine/docs/using-the-style-engine-with-block-supports.md @@ -231,4 +231,4 @@ array( */ ``` -Read more about [global styles](https://developer.wordpress.org/block-editor/explanations/architecture/styles/#global-styles) and [preset CSS custom properties](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json/#css-custom-properties-presets-custom) and [theme supports](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-support/). +Read more about [global styles](https://developer.wordpress.org/block-editor/explanations/architecture/styles/#global-styles) and [preset CSS custom properties](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles.md#css-custom-properties-presets-custom) and [theme supports](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-support/). diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 46ef0284df15d9..99218f69a8fb79 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -4,81 +4,97 @@ * * @package Gutenberg * @subpackage Interactivity API - * - * @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound */ +class Tests_Process_Directives extends WP_UnitTestCase { + public function set_up() { + parent::set_up(); -class Helper_Class { - // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - public function process_foo_test( $tags, $context ) { - } - - public function increment( $store ) { - return $store['state']['count'] + $store['context']['count']; - } - - public static function static_increment( $store ) { - return $store['state']['count'] + $store['context']['count']; - } -} + register_block_type( + 'test/context-level-1', + array( + 'render_callback' => function ( $attributes, $content ) { + return '
' . $content . '
'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); -function gutenberg_test_process_directives_helper_increment( $store ) { - return $store['state']['count'] + $store['context']['count']; -} + register_block_type( + 'test/context-level-2', + array( + 'render_callback' => function ( $attributes, $content ) { + return '
' . $content . '
'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); -/** - * Tests for the gutenberg_interactivity_process_rendered_html function. - * - * @group interactivity-api - * @covers gutenberg_interactivity_process_rendered_html - */ -class Tests_Process_Directives extends WP_UnitTestCase { - public function test_correctly_call_attribute_directive_processor_on_closing_tag() { - - // PHPUnit cannot stub functions, only classes. - $test_helper = $this->createMock( Helper_Class::class ); - - $test_helper->expects( $this->exactly( 2 ) ) - ->method( 'process_foo_test' ) - ->with( - $this->callback( - function ( $p ) { - return 'DIV' === $p->get_tag() && ( - // Either this is a closing tag... - $p->is_tag_closer() || - // ...or it is an open tag, and has the directive attribute set. - ( ! $p->is_tag_closer() && 'abc' === $p->get_attribute( 'foo-test' ) ) - ); - } - ) - ); - - $directives = array( - 'foo-test' => array( $test_helper, 'process_foo_test' ), + register_block_type( + 'test/context-read-only', + array( + 'render_callback' => function () { + return '
'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) ); - $markup = '
Example:
This is a test>
Here is a nested div
'; - $tags = new WP_Directive_Processor( $markup ); - $tags->process_rendered_html( $tags, 'foo-', $directives ); - } + register_block_type( + 'test/non-interactive-with-directive', + array( + 'render_callback' => function () { + return ''; + }, + ) + ); - public function test_directives_with_double_hyphen_processed_correctly() { - $test_helper = $this->createMock( Helper_Class::class ); - $test_helper->expects( $this->atLeastOnce() ) - ->method( 'process_foo_test' ); + register_block_type( + 'test/context-level-with-manual-inner-block-rendering', + array( + 'render_callback' => function ( $attributes, $content, $block ) { + $inner_blocks_html = ''; + foreach ( $block->inner_blocks as $inner_block ) { + $inner_blocks_html .= $inner_block->render(); + } + return '
' . $inner_blocks_html . '
'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); - $directives = array( - 'foo-test' => array( $test_helper, 'process_foo_test' ), + register_block_type( + 'test/directives-ordering', + array( + 'render_callback' => function () { + return ''; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) ); + } - $markup = '
'; - $tags = new WP_Directive_Processor( $markup ); - $tags->process_rendered_html( $tags, 'foo-', $directives ); + public function tear_down() { + unregister_block_type( 'test/context-level-1' ); + unregister_block_type( 'test/context-level-2' ); + unregister_block_type( 'test/context-read-only' ); + unregister_block_type( 'test/non-interactive-with-directive' ); + unregister_block_type( 'test/context-level-with-manual-inner-block-rendering' ); + unregister_block_type( 'test/directives-ordering' ); + parent::tear_down(); } public function test_interactivity_process_directives_in_root_blocks() { - $block_content = '' . '

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

' . @@ -87,15 +103,11 @@ public function test_interactivity_process_directives_in_root_blocks() { '

Welcome to WordPress.

' . ''; - $parsed_block = parse_blocks( $block_content )[0]; - - $source_block = $parsed_block; - - $rendered_content = render_block( $parsed_block ); - + $parsed_block = parse_blocks( $block_content )[0]; + $source_block = $parsed_block; + $rendered_content = render_block( $parsed_block ); $parsed_block_second = parse_blocks( $block_content )[1]; - - $fake_parent_block = array(); + $fake_parent_block = array(); // Test that root block is intially emtpy. $this->assertEmpty( WP_Directive_Processor::$root_block ); @@ -117,16 +129,103 @@ public function test_interactivity_process_directives_in_root_blocks() { gutenberg_process_directives_in_root_blocks( $rendered_content, $parsed_block ); $this->assertEmpty( WP_Directive_Processor::$root_block ); } -} + public function test_directive_processing_of_interactive_block() { + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-1-input-2' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + } + + public function test_directive_processing_two_interactive_blocks_at_same_level() { + $post_content = '
'; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-1-input-2' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-2-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-2', $value ); + } + + public function test_directives_are_processed_at_tag_end() { + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-2-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-2', $value ); + $p->next_tag( array( 'class_name' => 'read-only-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-1-input-2' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + } + + public function test_non_interactive_children_of_interactive_is_rendered() { + $post_content = '

Welcome

'; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'read-only-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag(); + $this->assertSame( 'P', $p->get_tag() ); + $p->next_tag( array( 'class_name' => 'level-1-input-2' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + } + + public function test_non_interactive_blocks_are_not_processed() { + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'non-interactive-with-directive' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( null, $value ); + } + + public function test_non_interactive_blocks_with_manual_inner_block_rendering_are_not_processed() { + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'non-interactive-with-directive' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( null, $value ); + } + + public function test_directives_ordering() { + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag(); + + $value = $p->get_attribute( 'class' ); + $this->assertSame( 'other-class some-class', $value ); + + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'some-value', $value ); + + $value = $p->get_attribute( 'style' ); + $this->assertSame( 'display: none;', $value ); + } -/** - * Tests for the gutenberg_interactivity_evaluate_reference function. - * - * @group interactivity-api - * @covers gutenberg_interactivity_evaluate_reference - */ -class Tests_Utils_Evaluate extends WP_UnitTestCase { public function test_evaluate_function_should_access_state() { // Init a simple store. wp_store( @@ -142,6 +241,7 @@ public function test_evaluate_function_should_access_state() { ), ) ); + $this->assertSame( 1, gutenberg_interactivity_evaluate_reference( 'state.core.number' ) ); $this->assertTrue( gutenberg_interactivity_evaluate_reference( 'state.core.bool' ) ); $this->assertSame( 'hi', gutenberg_interactivity_evaluate_reference( 'state.core.nested.string' ) ); @@ -158,10 +258,12 @@ public function test_evaluate_function_should_access_passed_context() { ), ), ); + $this->assertSame( 2, gutenberg_interactivity_evaluate_reference( 'context.local.number', $context ) ); $this->assertFalse( gutenberg_interactivity_evaluate_reference( 'context.local.bool', $context ) ); $this->assertTrue( gutenberg_interactivity_evaluate_reference( '!context.local.bool', $context ) ); $this->assertSame( 'bye', gutenberg_interactivity_evaluate_reference( 'context.local.nested.string', $context ) ); + // Previously defined state is also accessible. $this->assertSame( 1, gutenberg_interactivity_evaluate_reference( 'state.core.number' ) ); $this->assertTrue( gutenberg_interactivity_evaluate_reference( 'state.core.bool' ) ); @@ -174,7 +276,6 @@ public function test_evaluate_function_should_return_null_for_unresolved_paths() public function test_evaluate_function_should_execute_anonymous_functions() { $context = new WP_Directive_Context( array( 'count' => 2 ) ); - $helper = new Helper_Class(); wp_store( array( @@ -182,14 +283,13 @@ public function test_evaluate_function_should_execute_anonymous_functions() { 'count' => 3, ), 'selectors' => array( - 'anonymous_function' => function ( $store ) { + 'anonymous_function' => function ( $store ) { return $store['state']['count'] + $store['context']['count']; }, // Other types of callables should not be executed. - 'function_name' => 'gutenberg_test_process_directives_helper_increment', - 'class_method' => array( $helper, 'increment' ), - 'class_static_method' => 'Helper_Class::static_increment', - 'class_static_method_as_array' => array( 'Helper_Class', 'static_increment' ), + 'function_name' => 'gutenberg_test_process_directives_helper_increment', + 'class_method' => array( $this, 'increment' ), + 'class_static_method' => array( 'Tests_Process_Directives', 'static_increment' ), ), ) ); @@ -200,16 +300,12 @@ public function test_evaluate_function_should_execute_anonymous_functions() { gutenberg_interactivity_evaluate_reference( 'selectors.function_name', $context->get_context() ) ); $this->assertSame( - array( $helper, 'increment' ), + array( $this, 'increment' ), gutenberg_interactivity_evaluate_reference( 'selectors.class_method', $context->get_context() ) ); $this->assertSame( - 'Helper_Class::static_increment', + array( 'Tests_Process_Directives', 'static_increment' ), gutenberg_interactivity_evaluate_reference( 'selectors.class_static_method', $context->get_context() ) ); - $this->assertSame( - array( 'Helper_Class', 'static_increment' ), - gutenberg_interactivity_evaluate_reference( 'selectors.class_static_method_as_array', $context->get_context() ) - ); } } diff --git a/phpunit/experimental/interactivity-api/directives/wp-style-test.php b/phpunit/experimental/interactivity-api/directives/wp-style-test.php index 8942559b2fe89f..51468bd8a28141 100644 --- a/phpunit/experimental/interactivity-api/directives/wp-style-test.php +++ b/phpunit/experimental/interactivity-api/directives/wp-style-test.php @@ -14,16 +14,16 @@ */ class Tests_Directives_WpStyle extends WP_UnitTestCase { public function test_directive_adds_style() { - $markup = '
Test
'; + $markup = '
Test
'; $tags = new WP_HTML_Tag_Processor( $markup ); $tags->next_tag(); - $context_before = new WP_Directive_Context( array( 'myblock' => array( 'color' => 'green' ) ) ); + $context_before = new WP_Directive_Context( array( 'color' => 'green' ) ); $context = $context_before; gutenberg_interactivity_process_wp_style( $tags, $context ); $this->assertSame( - '
Test
', + '
Test
', $tags->get_updated_html() ); $this->assertStringContainsString( 'color: green;', $tags->get_attribute( 'style' ) ); @@ -31,11 +31,11 @@ public function test_directive_adds_style() { } public function test_directive_ignores_empty_style() { - $markup = '
Test
'; + $markup = '
Test
'; $tags = new WP_HTML_Tag_Processor( $markup ); $tags->next_tag(); - $context_before = new WP_Directive_Context( array( 'myblock' => array( 'color' => 'green' ) ) ); + $context_before = new WP_Directive_Context( array( 'color' => 'green' ) ); $context = $context_before; gutenberg_interactivity_process_wp_style( $tags, $context ); @@ -43,4 +43,21 @@ public function test_directive_ignores_empty_style() { $this->assertStringNotContainsString( 'color: green;', $tags->get_attribute( 'style' ) ); $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-style directive changed context' ); } + + public function test_directive_works_without_style_attribute() { + $markup = '
Test
'; + $tags = new WP_HTML_Tag_Processor( $markup ); + $tags->next_tag(); + + $context_before = new WP_Directive_Context( array( 'color' => 'green' ) ); + $context = $context_before; + gutenberg_interactivity_process_wp_style( $tags, $context ); + + $this->assertSame( + '
Test
', + $tags->get_updated_html() + ); + $this->assertSame( 'color: green;', $tags->get_attribute( 'style' ) ); + $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-style directive changed context' ); + } } diff --git a/phpunit/tests/fonts/font-library/wpFontFamilyUtils/hasFontMimeType.php b/phpunit/tests/fonts/font-library/wpFontFamilyUtils/hasFontMimeType.php deleted file mode 100644 index e30c199612b8a9..00000000000000 --- a/phpunit/tests/fonts/font-library/wpFontFamilyUtils/hasFontMimeType.php +++ /dev/null @@ -1,61 +0,0 @@ -assertTrue( WP_Font_Family_Utils::has_font_mime_type( $font_file ) ); - } - - /** - * Data provider. - * - * @return array[] - */ - public function data_should_succeed_when_has_mime_type() { - return array( - 'ttf' => array( '/temp/piazzolla_400_italic.ttf' ), - 'otf' => array( '/temp/piazzolla_400_italic.otf' ), - 'woff' => array( '/temp/piazzolla_400_italic.woff' ), - 'woff2' => array( '/temp/piazzolla_400_italic.woff2' ), - ); - } - - /** - * @dataProvider data_should_fail_when_mime_not_supported - * - * @param string $font_file Font file path. - */ - public function test_should_fail_when_mime_not_supported( $font_file ) { - $this->assertFalse( WP_Font_Family_Utils::has_font_mime_type( $font_file ) ); - } - - /** - * Data provider. - * - * @return array[] - */ - public function data_should_fail_when_mime_not_supported() { - return array( - 'exe' => array( '/temp/test.exe' ), - 'md' => array( '/temp/license.md' ), - 'php' => array( '/temp/test.php' ), - 'txt' => array( '/temp/test.txt' ), - 'zip' => array( '/temp/lato.zip' ), - ); - } -} diff --git a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js index ac6094c3d3eac3..1d54f25b1a7cc9 100644 --- a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js +++ b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js @@ -112,7 +112,11 @@ test.describe( 'Navigation block - Frontend interactivity', () => { // Test: overlay menu focuses on first element after opening await expect( overlayMenuFirstElement ).toBeFocused(); - // Not Tested: overlay menu traps focus + // Test: overlay menu traps focus + await pageUtils.pressKeys( 'Tab', { times: 2, delay: 50 } ); + await expect( closeMenuButton ).toBeFocused(); + await pageUtils.pressKeys( 'Shift+Tab', { times: 2, delay: 50 } ); + await expect( overlayMenuFirstElement ).toBeFocused(); // Test: overlay menu closes on click on close menu button await closeMenuButton.click(); diff --git a/test/e2e/specs/editor/various/allowed-patterns.spec.js b/test/e2e/specs/editor/various/allowed-patterns.spec.js new file mode 100644 index 00000000000000..78407217442de5 --- /dev/null +++ b/test/e2e/specs/editor/various/allowed-patterns.spec.js @@ -0,0 +1,79 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Allowed Patterns', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( 'gutenberg-test-allowed-patterns' ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await Promise.all( [ + requestUtils.deactivatePlugin( 'gutenberg-test-allowed-patterns' ), + requestUtils.deactivatePlugin( + 'gutenberg-test-allowed-patterns-disable-blocks' + ), + ] ); + } ); + + test( 'should show all patterns when blocks are not disabled', async ( { + admin, + page, + } ) => { + await admin.createNewPost(); + await page + .getByRole( 'toolbar', { name: 'Document tools' } ) + .getByRole( 'button', { name: 'Toggle block inserter' } ) + .click(); + + await page + .getByRole( 'region', { + name: 'Block Library', + } ) + .getByRole( 'searchbox', { + name: 'Search for blocks and patterns', + } ) + .fill( 'Test:' ); + + await expect( + page + .getByRole( 'listbox', { name: 'Block patterns' } ) + .getByRole( 'option' ) + ).toHaveText( [ + 'Test: Single heading', + 'Test: Single paragraph', + 'Test: Paragraph inside group', + ] ); + } ); + + test( 'should show only allowed patterns when blocks are disabled', async ( { + admin, + page, + requestUtils, + } ) => { + await requestUtils.activatePlugin( + 'gutenberg-test-allowed-patterns-disable-blocks' + ); + await admin.createNewPost(); + await page + .getByRole( 'toolbar', { name: 'Document tools' } ) + .getByRole( 'button', { name: 'Toggle block inserter' } ) + .click(); + + await page + .getByRole( 'region', { + name: 'Block Library', + } ) + .getByRole( 'searchbox', { + name: 'Search for blocks and patterns', + } ) + .fill( 'Test:' ); + + await expect( + page + .getByRole( 'listbox', { name: 'Block patterns' } ) + .getByRole( 'option' ) + ).toHaveText( [ 'Test: Single heading' ] ); + } ); +} );