diff --git a/changelog.txt b/changelog.txt index bd7375a67e9d8..8159928bd84fb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,26 @@ == Changelog == += 17.0.2 = + + + +## Changelog + +### Bug Fixes + +#### Typography +- Fix another fatal error in WP_Fonts_Resolver::Get_settings(). ([56067](https://github.com/WordPress/gutenberg/pull/56067)) + + + + +## Contributors + +The following contributors merged PRs in this release: + +@arthur791004 + + = 17.0.1 = ## Changelog diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index e65853a6fbdf7..0ae5979b79704 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -771,7 +771,7 @@ Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Ju - **Name:** core/quote - **Category:** text -- **Supports:** anchor, color (background, gradients, heading, link, text), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** anchor, color (background, gradients, heading, link, text), layout (~~allowEditing~~), spacing (blockGap), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** align, citation, value ## Read More diff --git a/lib/experimental/fonts-api/class-wp-fonts-resolver.php b/lib/experimental/fonts-api/class-wp-fonts-resolver.php index f2299c7c36831..efa66839cd39c 100644 --- a/lib/experimental/fonts-api/class-wp-fonts-resolver.php +++ b/lib/experimental/fonts-api/class-wp-fonts-resolver.php @@ -200,11 +200,6 @@ private static function get_settings() { if ( $set_theme_structure ) { $set_theme_structure = false; $settings = static::set_tyopgraphy_settings_array_structure( $settings ); - - // Initialize the font families from settings if set and is an array, otherwise default to an empty array. - if ( ! isset( $settings['typography']['fontFamilies']['theme'] ) || ! is_array( $settings['typography']['fontFamilies']['theme'] ) ) { - $settings['typography']['fontFamilies']['theme'] = array(); - } } // Initialize the font families from variation if set and is an array, otherwise default to an empty array. @@ -219,7 +214,12 @@ private static function get_settings() { ); // Make sure there are no duplicates. - $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'] ); + $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'], SORT_REGULAR ); + + // The font families from settings might become null after running the `array_unique`. + if ( ! isset( $settings['typography']['fontFamilies']['theme'] ) || ! is_array( $settings['typography']['fontFamilies']['theme'] ) ) { + $settings['typography']['fontFamilies']['theme'] = array(); + } } return $settings; diff --git a/package-lock.json b/package-lock.json index 8b64b911fae9f..9439981c29d7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "17.0.1", + "version": "17.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "17.0.1", + "version": "17.0.2", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -95,7 +95,7 @@ "@octokit/rest": "16.26.0", "@octokit/types": "6.34.0", "@octokit/webhooks-types": "5.6.0", - "@playwright/test": "1.32.0", + "@playwright/test": "1.39.0", "@pmmmwh/react-refresh-webpack-plugin": "0.5.11", "@storybook/addon-a11y": "7.2.2", "@storybook/addon-actions": "7.2.2", @@ -7136,22 +7136,18 @@ } }, "node_modules/@playwright/test": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz", - "integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", + "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", "dev": true, "dependencies": { - "@types/node": "*", - "playwright-core": "1.32.0" + "playwright": "1.39.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "fsevents": "2.3.2" + "node": ">=16" } }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { @@ -43229,16 +43225,34 @@ "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", "dev": true }, - "node_modules/playwright-core": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.0.tgz", - "integrity": "sha512-Z9Ij17X5Z3bjpp6XKujGBp9Gv4eViESac9aDmwgQFUEJBW0K80T21m/Z+XJQlu4cNsvPygw33b6V1Va6Bda5zQ==", + "node_modules/playwright": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", + "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", "dev": true, + "dependencies": { + "playwright-core": "1.39.0" + }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright/node_modules/playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" } }, "node_modules/please-upgrade-node": { @@ -52344,6 +52358,12 @@ "node": ">= 8" } }, + "node_modules/web-vitals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.0.tgz", + "integrity": "sha512-f5YnCHVG9Y6uLCePD4tY8bO/Ge15NPEQWtvm3tPzDKygloiqtb4SVqRHBcrIAqo2ztqX5XueqDn97zHF0LdT6w==", + "dev": true + }, "node_modules/webdriver": { "version": "8.16.20", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.20.tgz", @@ -55196,7 +55216,8 @@ "form-data": "^4.0.0", "get-port": "^5.1.1", "lighthouse": "^10.4.0", - "mime": "^3.0.0" + "mime": "^3.0.0", + "web-vitals": "^3.5.0" }, "engines": { "node": ">=12" @@ -56443,7 +56464,7 @@ "minimist": "^1.2.0", "npm-package-json-lint": "^6.4.0", "npm-packlist": "^3.0.0", - "playwright-core": "1.32.0", + "playwright-core": "1.39.0", "postcss": "^8.4.5", "postcss-loader": "^6.2.1", "prettier": "npm:wp-prettier@3.0.3", @@ -56470,11 +56491,23 @@ "npm": ">=6.14.4" }, "peerDependencies": { - "@playwright/test": "^1.32.0", + "@playwright/test": "^1.39.0", "react": "^18.0.0", "react-dom": "^18.0.0" } }, + "packages/scripts/node_modules/playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "packages/server-side-render": { "name": "@wordpress/server-side-render", "version": "4.22.0", @@ -61619,14 +61652,12 @@ } }, "@playwright/test": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz", - "integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", + "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", "dev": true, "requires": { - "@types/node": "*", - "fsevents": "2.3.2", - "playwright-core": "1.32.0" + "playwright": "1.39.0" } }, "@pmmmwh/react-refresh-webpack-plugin": { @@ -70293,7 +70324,8 @@ "form-data": "^4.0.0", "get-port": "^5.1.1", "lighthouse": "^10.4.0", - "mime": "^3.0.0" + "mime": "^3.0.0", + "web-vitals": "^3.5.0" } }, "@wordpress/e2e-tests": { @@ -71116,7 +71148,7 @@ "minimist": "^1.2.0", "npm-package-json-lint": "^6.4.0", "npm-packlist": "^3.0.0", - "playwright-core": "1.32.0", + "playwright-core": "1.39.0", "postcss": "^8.4.5", "postcss-loader": "^6.2.1", "prettier": "npm:wp-prettier@3.0.3", @@ -71134,6 +71166,14 @@ "webpack-bundle-analyzer": "^4.9.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" + }, + "dependencies": { + "playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true + } } }, "@wordpress/server-side-render": { @@ -90185,11 +90225,23 @@ "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", "dev": true }, - "playwright-core": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.0.tgz", - "integrity": "sha512-Z9Ij17X5Z3bjpp6XKujGBp9Gv4eViESac9aDmwgQFUEJBW0K80T21m/Z+XJQlu4cNsvPygw33b6V1Va6Bda5zQ==", - "dev": true + "playwright": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", + "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.39.0" + }, + "dependencies": { + "playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true + } + } }, "please-upgrade-node": { "version": "3.2.0", @@ -97126,6 +97178,12 @@ "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", "dev": true }, + "web-vitals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.0.tgz", + "integrity": "sha512-f5YnCHVG9Y6uLCePD4tY8bO/Ge15NPEQWtvm3tPzDKygloiqtb4SVqRHBcrIAqo2ztqX5XueqDn97zHF0LdT6w==", + "dev": true + }, "webdriver": { "version": "8.16.20", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.20.tgz", diff --git a/package.json b/package.json index 266cbcc802d14..9f456f359a741 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "17.0.1", + "version": "17.0.2", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", @@ -107,7 +107,7 @@ "@octokit/rest": "16.26.0", "@octokit/types": "6.34.0", "@octokit/webhooks-types": "5.6.0", - "@playwright/test": "1.32.0", + "@playwright/test": "1.39.0", "@pmmmwh/react-refresh-webpack-plugin": "0.5.11", "@storybook/addon-a11y": "7.2.2", "@storybook/addon-actions": "7.2.2", diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index a9d5f15f12f81..ab57605563e1d 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -17,12 +17,7 @@ import { useMergeRefs, useDebounce, } from '@wordpress/compose'; -import { - createContext, - useState, - useMemo, - useCallback, -} from '@wordpress/element'; +import { createContext, useMemo, useCallback } from '@wordpress/element'; /** * Internal dependencies @@ -40,13 +35,10 @@ import { } from '../block-edit/context'; import { useTypingObserver } from '../observe-typing'; -const elementContext = createContext(); - export const IntersectionObserver = createContext(); const pendingBlockVisibilityUpdatesPerRegistry = new WeakMap(); function Root( { className, ...settings } ) { - const [ element, setElement ] = useState(); const isLargeViewport = useViewportMatch( 'medium' ); const { isOutlineMode, isFocusMode, editorMode } = useSelect( ( select ) => { @@ -115,13 +107,9 @@ function Root( { className, ...settings } ) { settings ); return ( - - -
- { /* Ensure element and layout styles are always at the end of the document */ } -
- - + +
+ ); } @@ -133,8 +121,6 @@ export default function BlockList( settings ) { ); } -BlockList.__unstableElementContext = elementContext; - function Items( { placeholder, rootClientId, diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index f598b35f890f1..03a6f8939c181 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -13,7 +13,6 @@ import { __experimentalTruncate as Truncate, Popover, } from '@wordpress/components'; -import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -120,12 +119,3 @@ function BlockStyles( { clientId, onSwitch = noop, onHoverClassName = noop } ) { } export default BlockStyles; - -BlockStyles.Slot = () => { - deprecated( 'BlockStyles.Slot', { - version: '6.4', - since: '6.2', - } ); - - return null; -}; diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js b/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js index 73fef4aed68e2..04a0f27d162dd 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js @@ -9,7 +9,7 @@ import { MenuItemsChoice, ExternalLink, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useMemo, createInterpolateElement } from '@wordpress/element'; @@ -55,15 +55,24 @@ export function PatternsFilter( { const patternSyncMenuOptions = useMemo( () => [ - { value: SYNC_TYPES.all, label: __( 'All' ) }, + { + value: SYNC_TYPES.all, + label: _x( 'All', 'Option that shows all patterns' ), + }, { value: SYNC_TYPES.full, - label: __( 'Synced' ), + label: _x( + 'Synced', + 'Option that shows all synchronized patterns' + ), disabled: shouldDisableSyncFilter, }, { value: SYNC_TYPES.unsynced, - label: __( 'Not synced' ), + label: _x( + 'Not synced', + 'Option that shows all patterns that are not synchronized' + ), disabled: shouldDisableSyncFilter, }, ], diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index edab6b4ad488c..5c31c4c14371c 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -275,14 +275,14 @@ If passed, children are rendered after the input. ```jsx -
+
+
``` diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index c25ed5cd1187a..a208fa20d242f 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -6,7 +6,13 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Button, Spinner, Notice, TextControl } from '@wordpress/components'; +import { + Button, + Spinner, + Notice, + TextControl, + __experimentalHStack as HStack, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useRef, useState, useEffect } from '@wordpress/element'; import { focus } from '@wordpress/dom'; @@ -467,7 +473,13 @@ function LinkControl( { ) } { showActions && ( -
+ + - -
+ ) } { renderControlBottom && renderControlBottom() } diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index fae3f03786dfe..7b6bbff0700a3 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -96,12 +96,7 @@ $preview-image-height: 140px; } .block-editor-link-control__search-actions { - display: flex; - flex-direction: row-reverse; // put "Cancel" on the left but retain DOM order. - justify-content: flex-start; - gap: $grid-unit-10; padding: $grid-unit-10 $grid-unit-20 $grid-unit-20; - order: 20; } .block-editor-link-control__search-results-wrapper { diff --git a/packages/block-editor/src/components/media-replace-flow/style.scss b/packages/block-editor/src/components/media-replace-flow/style.scss index dd3b0563c3ca8..61df542cf5840 100644 --- a/packages/block-editor/src/components/media-replace-flow/style.scss +++ b/packages/block-editor/src/components/media-replace-flow/style.scss @@ -17,6 +17,7 @@ &.has-siblings { border-top: $border-width solid $gray-900; margin-top: $grid-unit-10; + padding-bottom: $grid-unit-10; } .block-editor-media-replace-flow__image-url-label { @@ -55,8 +56,7 @@ } .block-editor-link-control__search-actions { - top: 0; // cancel default top positioning - right: 4px; + padding: $grid-unit-10 0 0; } } } diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 32bd1afd3d540..aab10e9ab6547 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -17,7 +17,6 @@ import { } from '@wordpress/blocks'; import { useInstanceId, useMergeRefs } from '@wordpress/compose'; import { - __experimentalRichText as RichText, __unstableCreateElement, isEmpty, insert, @@ -46,6 +45,7 @@ import { } from './utils'; import EmbedHandlerPicker from './embed-handler-picker'; import { Content } from './content'; +import RichText from './native'; const classes = 'block-editor-rich-text__editable'; @@ -688,6 +688,8 @@ ForwardedRichTextContainer.Content.defaultProps = { value: '', }; +ForwardedRichTextContainer.Raw = RichText; + /** * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/rich-text/README.md */ diff --git a/packages/rich-text/src/component/format-edit.js b/packages/block-editor/src/components/rich-text/native/format-edit.js similarity index 86% rename from packages/rich-text/src/component/format-edit.js rename to packages/block-editor/src/components/rich-text/native/format-edit.js index 1867c1ef2e4f7..75b077ab321d4 100644 --- a/packages/rich-text/src/component/format-edit.js +++ b/packages/block-editor/src/components/rich-text/native/format-edit.js @@ -1,8 +1,7 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { getActiveFormat } from '../get-active-format'; -import { getActiveObject } from '../get-active-object'; +import { getActiveFormat, getActiveObject } from '@wordpress/rich-text'; export default function FormatEdit( { formatTypes, diff --git a/packages/rich-text/src/get-format-colors.native.js b/packages/block-editor/src/components/rich-text/native/get-format-colors.native.js similarity index 92% rename from packages/rich-text/src/get-format-colors.native.js rename to packages/block-editor/src/components/rich-text/native/get-format-colors.native.js index f9b3a9187ca2b..a54d3e10f78a0 100644 --- a/packages/rich-text/src/get-format-colors.native.js +++ b/packages/block-editor/src/components/rich-text/native/get-format-colors.native.js @@ -1,7 +1,7 @@ /** - * WordPress dependencies + * Internal dependencies */ -import { getColorObjectByAttributeValues } from '@wordpress/block-editor'; +import { getColorObjectByAttributeValues } from '../../../components/colors'; const FORMAT_TYPE = 'core/text-color'; const REGEX_TO_MATCH = /^has-(.*)-color$/; diff --git a/packages/block-editor/src/components/rich-text/native/index.js b/packages/block-editor/src/components/rich-text/native/index.js new file mode 100644 index 0000000000000..2d1ec238274a0 --- /dev/null +++ b/packages/block-editor/src/components/rich-text/native/index.js @@ -0,0 +1 @@ +export default () => {}; diff --git a/packages/rich-text/src/component/index.native.js b/packages/block-editor/src/components/rich-text/native/index.native.js similarity index 98% rename from packages/rich-text/src/component/index.native.js rename to packages/block-editor/src/components/rich-text/native/index.native.js index 47b47d58c5988..2381b9809eca8 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/index.native.js @@ -29,23 +29,25 @@ import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes'; import { isURL } from '@wordpress/url'; import { atSymbol, plus } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; +import { + applyFormat, + getActiveFormat, + getActiveFormats, + insert, + getTextContent, + isEmpty, + create, + toHTMLString, + isCollapsed, + remove, +} from '@wordpress/rich-text'; /** * Internal dependencies */ import { useFormatTypes } from './use-format-types'; import FormatEdit from './format-edit'; -import { applyFormat } from '../apply-format'; -import { getActiveFormat } from '../get-active-format'; -import { getActiveFormats } from '../get-active-formats'; -import { insert } from '../insert'; -import { getTextContent } from '../get-text-content'; -import { isEmpty } from '../is-empty'; -import { create } from '../create'; -import { toHTMLString } from '../to-html-string'; -import { isCollapsed } from '../is-collapsed'; -import { remove } from '../remove'; -import { getFormatColors } from '../get-format-colors'; +import { getFormatColors } from './get-format-colors'; import styles from './style.scss'; import ToolbarButtonWithOptions from './toolbar-button-with-options'; diff --git a/packages/rich-text/src/component/style.native.scss b/packages/block-editor/src/components/rich-text/native/style.native.scss similarity index 100% rename from packages/rich-text/src/component/style.native.scss rename to packages/block-editor/src/components/rich-text/native/style.native.scss diff --git a/packages/rich-text/src/test/__snapshots__/index.native.js.snap b/packages/block-editor/src/components/rich-text/native/test/__snapshots__/index.native.js.snap similarity index 100% rename from packages/rich-text/src/test/__snapshots__/index.native.js.snap rename to packages/block-editor/src/components/rich-text/native/test/__snapshots__/index.native.js.snap diff --git a/packages/rich-text/src/test/index.native.js b/packages/block-editor/src/components/rich-text/native/test/index.native.js similarity index 98% rename from packages/rich-text/src/test/index.native.js rename to packages/block-editor/src/components/rich-text/native/test/index.native.js index e0ce7ff78d6cc..64bfb3b183c6b 100644 --- a/packages/rich-text/src/test/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/test/index.native.js @@ -8,7 +8,7 @@ import { getEditorHtml, render, initializeEditor } from 'test/helpers'; * WordPress dependencies */ import { select } from '@wordpress/data'; -import { store as blockEditorStore } from '@wordpress/block-editor'; +import { store as richTextStore } from '@wordpress/rich-text'; import { coreBlocks } from '@wordpress/block-library'; import { getBlockTypes, @@ -19,8 +19,8 @@ import { /** * Internal dependencies */ -import { store as richTextStore } from '../store'; -import RichText from '../component/index.native'; +import { store as blockEditorStore } from '../../../../store'; +import RichText from '../index.native'; /** * Mock `useSelect` with various global application settings, e.g., styles. diff --git a/packages/rich-text/src/test/performance/rich-text.native.js b/packages/block-editor/src/components/rich-text/native/test/performance/rich-text.native.js similarity index 94% rename from packages/rich-text/src/test/performance/rich-text.native.js rename to packages/block-editor/src/components/rich-text/native/test/performance/rich-text.native.js index aaaf42c90137f..7be9981d04bce 100644 --- a/packages/rich-text/src/test/performance/rich-text.native.js +++ b/packages/block-editor/src/components/rich-text/native/test/performance/rich-text.native.js @@ -11,7 +11,7 @@ import { /** * Internal dependencies */ -import RichText from '../../component/index.native'; +import RichText from '../../index.native'; describe( 'RichText Performance', () => { const onCreateUndoLevel = jest.fn(); diff --git a/packages/rich-text/src/component/toolbar-button-with-options.native.js b/packages/block-editor/src/components/rich-text/native/toolbar-button-with-options.native.js similarity index 100% rename from packages/rich-text/src/component/toolbar-button-with-options.native.js rename to packages/block-editor/src/components/rich-text/native/toolbar-button-with-options.native.js diff --git a/packages/rich-text/src/component/use-format-types.js b/packages/block-editor/src/components/rich-text/native/use-format-types.js similarity index 97% rename from packages/rich-text/src/component/use-format-types.js rename to packages/block-editor/src/components/rich-text/native/use-format-types.js index 637be62b4361e..ff65d7421ae5c 100644 --- a/packages/rich-text/src/component/use-format-types.js +++ b/packages/block-editor/src/components/rich-text/native/use-format-types.js @@ -3,10 +3,7 @@ */ import { useMemo } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; -/** - * Internal dependencies - */ -import { store as richTextStore } from '../store'; +import { store as richTextStore } from '@wordpress/rich-text'; function formatTypesSelector( select ) { return select( richTextStore ).getFormatTypes(); diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 5470ea5789f3a..5442e394e68c7 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -16,7 +16,6 @@ import { import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; import { useMemo, useEffect } from '@wordpress/element'; -import { useDispatch } from '@wordpress/data'; /** * Internal dependencies @@ -34,12 +33,10 @@ import { } from '../components/duotone/utils'; import { getBlockCSSSelector } from '../components/global-styles/get-block-css-selector'; import { scopeSelector } from '../components/global-styles/utils'; -import { useBlockSettings } from './utils'; +import { useBlockSettings, useStyleOverride } from './utils'; import { default as StylesFiltersPanel } from '../components/global-styles/filters-panel'; import { useBlockEditingMode } from '../components/block-editing-mode'; import { __unstableUseBlockElement as useBlockElement } from '../components/block-list/use-block-props/use-block-refs'; -import { store as blockEditorStore } from '../store'; -import { unlock } from '../lock-unlock'; const EMPTY_ARRAY = []; @@ -291,8 +288,27 @@ function DuotoneStyles( { const isValidFilter = Array.isArray( colors ) || colors === 'unset'; - const { setStyleOverride, deleteStyleOverride } = unlock( - useDispatch( blockEditorStore ) + useStyleOverride( + isValidFilter + ? { + css: + colors !== 'unset' + ? getDuotoneStylesheet( selector, filterId ) + : getDuotoneUnsetStylesheet( selector ), + __unstableType: 'presets', + } + : undefined + ); + useStyleOverride( + isValidFilter + ? { + assets: + colors !== 'unset' + ? getDuotoneFilter( filterId, colors ) + : '', + __unstableType: 'svgs', + } + : undefined ); const blockElement = useBlockElement( clientId ); @@ -300,19 +316,6 @@ function DuotoneStyles( { useEffect( () => { if ( ! isValidFilter ) return; - setStyleOverride( filterId, { - css: - colors !== 'unset' - ? getDuotoneStylesheet( selector, filterId ) - : getDuotoneUnsetStylesheet( selector ), - __unstableType: 'presets', - } ); - setStyleOverride( `duotone-${ filterId }`, { - assets: - colors !== 'unset' ? getDuotoneFilter( filterId, colors ) : '', - __unstableType: 'svgs', - } ); - // Safari does not always update the duotone filter when the duotone colors // are changed. When using Safari, force the block element to be repainted by // the browser to ensure any changes are reflected visually. This logic matches @@ -329,20 +332,7 @@ function DuotoneStyles( { blockElement.offsetHeight; blockElement.style.display = display; } - - return () => { - deleteStyleOverride( filterId ); - deleteStyleOverride( `duotone-${ filterId }` ); - }; - }, [ - isValidFilter, - blockElement, - colors, - selector, - filterId, - setStyleOverride, - deleteStyleOverride, - ] ); + }, [ isValidFilter, blockElement ] ); return null; } diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index e6113484f8a2c..f4730702e9adb 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -9,7 +9,7 @@ import classnames from 'classnames'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { Button, ButtonGroup, @@ -17,7 +17,6 @@ import { PanelBody, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useEffect } from '@wordpress/element'; /** * Internal dependencies @@ -29,8 +28,7 @@ import { getLayoutType, getLayoutTypes } from '../layouts'; import { useBlockEditingMode } from '../components/block-editing-mode'; import { LAYOUT_DEFINITIONS } from '../layouts/definitions'; import { kebabCase } from '../utils/object'; -import { useBlockSettings } from './utils'; -import { unlock } from '../lock-unlock'; +import { useBlockSettings, useStyleOverride } from './utils'; const layoutBlockSupportKey = 'layout'; @@ -381,17 +379,7 @@ function BlockWithLayoutStyles( { block: BlockListBlock, props } ) { layoutClasses ); - const { setStyleOverride, deleteStyleOverride } = unlock( - useDispatch( blockEditorStore ) - ); - - useEffect( () => { - if ( ! css ) return; - setStyleOverride( selector, { css } ); - return () => { - deleteStyleOverride( selector ); - }; - }, [ selector, css, setStyleOverride, deleteStyleOverride ] ); + useStyleOverride( { css } ); return ( { - if ( ! css ) return; - setStyleOverride( selector, { css } ); - return () => { - deleteStyleOverride( selector ); - }; - }, [ selector, css, setStyleOverride, deleteStyleOverride ] ); + useStyleOverride( { css } ); return ; } diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index bdd8095e9b250..710dbfaf5ace0 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -14,22 +14,16 @@ import { } from '@wordpress/components'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; -import { - useContext, - useMemo, - createPortal, - Platform, -} from '@wordpress/element'; +import { useMemo, Platform } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; /** * Internal dependencies */ -import BlockList from '../components/block-list'; import { useSettings } from '../components/use-settings'; import InspectorControls from '../components/inspector-controls'; import useBlockDisplayInformation from '../components/use-block-display-information'; -import { cleanEmptyObject } from './utils'; +import { cleanEmptyObject, useStyleOverride } from './utils'; import { unlock } from '../lock-unlock'; import { store as blockEditorStore } from '../store'; @@ -368,7 +362,6 @@ export const withPositionStyles = createHigherOrderComponent( hasPositionBlockSupport && ! isPositionDisabled; const id = useInstanceId( BlockListBlock ); - const element = useContext( BlockList.__unstableElementContext ); // Higher specificity to override defaults in editor UI. const positionSelector = `.wp-container-${ id }.wp-container-${ id }`; @@ -392,15 +385,9 @@ export const withPositionStyles = createHigherOrderComponent( !! attributes?.style?.position?.type, } ); - return ( - <> - { allowPositionStyles && - element && - !! css && - createPortal( , element ) } - - - ); + useStyleOverride( { css } ); + + return ; }, 'withPositionStyles' ); diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 356042e3017ca..d74e10b0208f1 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useContext, useMemo, createPortal } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { getBlockSupport, @@ -19,7 +19,6 @@ import { getCSSRules, compileCSS } from '@wordpress/style-engine'; /** * Internal dependencies */ -import BlockList from '../components/block-list'; import { BACKGROUND_SUPPORT_KEY, BackgroundImagePanel } from './background'; import { BORDER_SUPPORT_KEY, BorderPanel } from './border'; import { COLOR_SUPPORT_KEY, ColorEdit } from './color'; @@ -34,7 +33,7 @@ import { DimensionsPanel, } from './dimensions'; import useDisplayBlockControls from '../components/use-display-block-controls'; -import { shouldSkipSerialization } from './utils'; +import { shouldSkipSerialization, useStyleOverride } from './utils'; import { scopeSelector } from '../components/global-styles/utils'; import { useBlockEditingMode } from '../components/block-editing-mode'; @@ -484,33 +483,20 @@ const withElementsStyles = createHigherOrderComponent( : undefined; }, [ baseElementSelector, blockElementStyles, props.name ] ); - const element = useContext( BlockList.__unstableElementContext ); + useStyleOverride( { css: styles } ); return ( - <> - { styles && - element && - createPortal( - ; - }; + useStyleOverride( { css: gap } ); - return gap && styleElement - ? createPortal( , styleElement ) - : null; + return null; } diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php index 5333ab6ea3dc9..1165ce94b5921 100644 --- a/packages/block-library/src/navigation-link/index.php +++ b/packages/block-library/src/navigation-link/index.php @@ -322,6 +322,25 @@ function build_variation_for_navigation_link( $entity, $kind ) { return $variation; } +/** + * Register a variation for a post type / taxonomy for the navigation link block + * + * @param array $variation Variation array from build_variation_for_navigation_link. + * @return void + */ +function register_block_core_navigation_link_variation( $variation ) { + // Directly set the variations on the registered block type + // because there's no server side registration for variations (see #47170). + $navigation_block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/navigation-link' ); + // If the block is not registered yet, bail early. + // Variation will be registered in register_block_core_navigation_link then. + if ( ! $navigation_block_type ) { + return; + } + + $navigation_block_type->variations[] = $variation; +} + /** * Register the navigation link block. * @@ -329,6 +348,9 @@ function build_variation_for_navigation_link( $entity, $kind ) { * @throws WP_Error An WP_Error exception parsing the block definition. */ function register_block_core_navigation_link() { + // This will only handle post types and taxonomies registered until this point (init on priority 9). + // See action hooks below for other post types and taxonomies. + // See https://github.com/WordPress/gutenberg/issues/53826 for details. $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' ); $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' ); @@ -369,3 +391,38 @@ function register_block_core_navigation_link() { ); } add_action( 'init', 'register_block_core_navigation_link' ); +// Register actions for all post types and taxonomies, to add variations when they are registered. +// All post types/taxonomies registered before register_block_core_navigation_link, will be handled by that function. +add_action( 'registered_post_type', 'register_block_core_navigation_link_post_type_variation', 10, 2 ); +add_action( 'registered_taxonomy', 'register_block_core_navigation_link_taxonomy_variation', 10, 3 ); + +/** + * Register custom post type variations for navigation link on post type registration + * Handles all post types registered after the block is registered in register_navigation_link_post_type_variations + * + * @param string $post_type The post type name passed from registered_post_type filter. + * @param WP_Post_Type $post_type_object The post type object passed from registered_post_type. + * @return void + */ +function register_block_core_navigation_link_post_type_variation( $post_type, $post_type_object ) { + if ( $post_type_object->show_in_nav_menus ) { + $variation = build_variation_for_navigation_link( $post_type_object, 'post-type' ); + register_block_core_navigation_link_variation( $variation ); + } +} + +/** + * Register a custom taxonomy variation for navigation link on taxonomy registration + * Handles all taxonomies registered after the block is registered in register_navigation_link_post_type_variations + * + * @param string $taxonomy Taxonomy slug. + * @param array|string $object_type Object type or array of object types. + * @param array $args Array of taxonomy registration arguments. + * @return void + */ +function register_block_core_navigation_link_taxonomy_variation( $taxonomy, $object_type, $args ) { + if ( isset( $args['show_in_nav_menus'] ) && $args['show_in_nav_menus'] ) { + $variation = build_variation_for_navigation_link( (object) $args, 'post-type' ); + register_block_core_navigation_link_variation( $variation ); + } +} diff --git a/packages/block-library/src/quote/block.json b/packages/block-library/src/quote/block.json index eff4649230a58..d0bb9005f8e63 100644 --- a/packages/block-library/src/quote/block.json +++ b/packages/block-library/src/quote/block.json @@ -54,6 +54,12 @@ "background": true, "text": true } + }, + "layout": { + "allowEditing": false + }, + "spacing": { + "blockGap": true } }, "styles": [ diff --git a/packages/block-library/src/quote/style.scss b/packages/block-library/src/quote/style.scss index e453e407a0875..8688294bc5594 100644 --- a/packages/block-library/src/quote/style.scss +++ b/packages/block-library/src/quote/style.scss @@ -18,5 +18,9 @@ font-size: 1.125em; text-align: right; } + + } + > cite { + display: block; } } diff --git a/packages/block-library/src/read-more/style.scss b/packages/block-library/src/read-more/style.scss index f0e0291eb6ffc..3a15de37adf68 100644 --- a/packages/block-library/src/read-more/style.scss +++ b/packages/block-library/src/read-more/style.scss @@ -1,7 +1,7 @@ .wp-block-read-more { display: block; width: fit-content; - &:not([style*="text-decoration"]) { + &:where(:not([style*="text-decoration"])) { text-decoration: none; &:focus, diff --git a/packages/e2e-test-utils-playwright/package.json b/packages/e2e-test-utils-playwright/package.json index 775e1da771331..f8d9b7cd2f017 100644 --- a/packages/e2e-test-utils-playwright/package.json +++ b/packages/e2e-test-utils-playwright/package.json @@ -37,7 +37,8 @@ "form-data": "^4.0.0", "get-port": "^5.1.1", "lighthouse": "^10.4.0", - "mime": "^3.0.0" + "mime": "^3.0.0", + "web-vitals": "^3.5.0" }, "peerDependencies": { "@playwright/test": ">=1" diff --git a/packages/e2e-test-utils-playwright/src/metrics/index.ts b/packages/e2e-test-utils-playwright/src/metrics/index.ts index 68343f6d7c482..fac05d9004bd8 100644 --- a/packages/e2e-test-utils-playwright/src/metrics/index.ts +++ b/packages/e2e-test-utils-playwright/src/metrics/index.ts @@ -2,6 +2,11 @@ * External dependencies */ import type { Page, Browser } from '@playwright/test'; +import { join } from 'path'; +// resolution-mode support in TypeScript 5.3 will resolve this. +// See https://devblogs.microsoft.com/typescript/announcing-typescript-5-3-beta/ +// @ts-expect-error +import type { Metric } from 'web-vitals'; type EventType = | 'click' @@ -32,11 +37,22 @@ type MetricsConstructorProps = { page: Page; }; +interface WebVitalsMeasurements { + CLS?: number; + FCP?: number; + FID?: number; + INP?: number; + LCP?: number; + TTFB?: number; +} + export class Metrics { browser: Browser; page: Page; trace: Trace; + webVitals: WebVitalsMeasurements = {}; + constructor( { page }: MetricsConstructorProps ) { this.page = page; this.browser = page.context().browser()!; @@ -273,4 +289,95 @@ export class Metrics { ) .map( ( item ) => ( item.dur ? item.dur / 1000 : 0 ) ); } + + /** + * Initializes the web-vitals library upon next page navigation. + * + * Defaults to automatically triggering the navigation, + * but it can also be done manually. + * + * @example + * ```js + * await metrics.initWebVitals(); + * console.log( await metrics.getWebVitals() ); + * ``` + * + * @example + * ```js + * await metrics.initWebVitals( false ); + * await page.goto( '/some-other-page' ); + * console.log( await metrics.getWebVitals() ); + * ``` + * + * @param reload Whether to force navigation by reloading the current page. + */ + async initWebVitals( reload = true ) { + await this.page.addInitScript( { + path: join( + __dirname, + '../../../../node_modules/web-vitals/dist/web-vitals.umd.cjs' + ), + } ); + + await this.page.exposeFunction( + '__reportVitals__', + ( data: string ) => { + const measurement: Metric = JSON.parse( data ); + this.webVitals[ measurement.name ] = measurement.value; + } + ); + + await this.page.addInitScript( () => { + const reportVitals = ( measurement: unknown ) => + window.__reportVitals__( JSON.stringify( measurement ) ); + + window.addEventListener( 'DOMContentLoaded', () => { + // @ts-ignore + window.webVitals.onCLS( reportVitals ); + // @ts-ignore + window.webVitals.onFCP( reportVitals ); + // @ts-ignore + window.webVitals.onFID( reportVitals ); + // @ts-ignore + window.webVitals.onINP( reportVitals ); + // @ts-ignore + window.webVitals.onLCP( reportVitals ); + // @ts-ignore + window.webVitals.onTTFB( reportVitals ); + } ); + } ); + + if ( reload ) { + // By reloading the page the script will be applied. + await this.page.reload(); + } + } + + /** + * Returns web vitals as collected by the web-vitals library. + * + * If the web-vitals library hasn't been loaded on the current page yet, + * it will be initialized with a page reload. + * + * Reloads the page to force web-vitals to report all collected metrics. + * + * @return {WebVitalsMeasurements} Web vitals measurements. + */ + async getWebVitals() { + // Reset values. + this.webVitals = {}; + + const hasScript = await this.page.evaluate( + () => typeof window.webVitals !== 'undefined' + ); + + if ( ! hasScript ) { + await this.initWebVitals(); + } + + // Trigger navigation so the web-vitals library reports values on unload. + await this.page.reload(); + + return this.webVitals; + } } diff --git a/packages/e2e-test-utils-playwright/src/page-utils/drag-files.ts b/packages/e2e-test-utils-playwright/src/page-utils/drag-files.ts index fa43fc76d27c3..d0af0b499f286 100644 --- a/packages/e2e-test-utils-playwright/src/page-utils/drag-files.ts +++ b/packages/e2e-test-utils-playwright/src/page-utils/drag-files.ts @@ -9,7 +9,7 @@ import { getType } from 'mime'; * Internal dependencies */ import type { PageUtils } from './index'; -import type { ElementHandle, Locator } from '@playwright/test'; +import type { Locator } from '@playwright/test'; type FileObject = { name: string; @@ -118,25 +118,29 @@ async function dragFiles( /** * Drop the files at the current position. - * - * @param locator */ - drop: async ( locator: Locator | ElementHandle | null ) => { - if ( ! locator ) { - const topMostElement = await this.page.evaluateHandle( - ( { x, y } ) => { - return document.elementFromPoint( x, y ); - }, - position - ); - locator = topMostElement.asElement(); - } + drop: async () => { + const topMostElement = await this.page.evaluateHandle( + ( { x, y } ) => { + const element = document.elementFromPoint( x, y ); + if ( element instanceof HTMLIFrameElement ) { + const offsetBox = element.getBoundingClientRect(); + return element.contentDocument!.elementFromPoint( + x - offsetBox.x, + y - offsetBox.y + ); + } + return element; + }, + position + ); + const elementHandle = topMostElement.asElement(); - if ( ! locator ) { + if ( ! elementHandle ) { throw new Error( 'Element not found.' ); } - const dataTransfer = await locator.evaluateHandle( + const dataTransfer = await elementHandle.evaluateHandle( async ( _node, _fileObjects ) => { const dt = new DataTransfer(); const fileInstances = await Promise.all( @@ -159,7 +163,7 @@ async function dragFiles( fileObjects ); - await locator.dispatchEvent( 'drop', { dataTransfer } ); + await elementHandle.dispatchEvent( 'drop', { dataTransfer } ); await cdpSession.detach(); }, diff --git a/packages/e2e-test-utils-playwright/src/types.ts b/packages/e2e-test-utils-playwright/src/types.ts index 62e38033b73e8..3dd6b0683aa33 100644 --- a/packages/e2e-test-utils-playwright/src/types.ts +++ b/packages/e2e-test-utils-playwright/src/types.ts @@ -1,29 +1,12 @@ +/** + * External dependencies + */ declare global { interface Window { // Silence the warning for `window.wp` in Playwright's evaluate functions. wp: any; - } - - // Experimental API that is subject to change. - // See https://developer.mozilla.org/en-US/docs/Web/API/LayoutShiftAttribution - interface LayoutShiftAttribution { - readonly node: Node; - readonly previousRect: DOMRectReadOnly; - readonly currentRect: DOMRectReadOnly; - readonly toJSON: () => string; - } - - // Experimental API that is subject to change. - // See https://developer.mozilla.org/en-US/docs/Web/API/LayoutShift - interface LayoutShift extends PerformanceEntry { - readonly duration: number; - readonly entryType: 'layout-shift'; - readonly name: 'layout-shift'; - readonly startTime: DOMHighResTimeStamp; - readonly value: number; - readonly hadRecentInput: boolean; - readonly lastInputTime: DOMHighResTimeStamp; - readonly sources: LayoutShiftAttribution[]; + // Helper function added by Metrics fixture for web-vitals.js. + __reportVitals__: ( data: string ) => void; } } diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap deleted file mode 100644 index 5c6867e75ad7a..0000000000000 --- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap +++ /dev/null @@ -1,147 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`cpt locking template_lock all should insert line breaks when using enter and shift-enter 1`] = ` -" -
- - - -

First line
Second line
Third line

- - - -
-

-
- - - -
-" -`; - -exports[`cpt locking template_lock all should not error when deleting the cotents of a paragraph 1`] = ` -" -
- - - -

- - - -
-

-
- - - -
-" -`; - -exports[`cpt locking template_lock all unlocked group should allow blocks to be moved 1`] = ` -" -
-

p1

- - - -
-

-
-
-" -`; - -exports[`cpt locking template_lock all unlocked group should allow blocks to be removed 1`] = ` -" -
-
-

-
-
-" -`; - -exports[`cpt locking template_lock false should allow blocks to be inserted 1`] = ` -" -
- - - -

- - - -
-

-
- - - -
- - - -
    -
  • List content
  • -
-" -`; - -exports[`cpt locking template_lock false should allow blocks to be moved 1`] = ` -" -

p1

- - - -
- - - -
-

-
- - - -
-" -`; - -exports[`cpt locking template_lock false should allow blocks to be removed 1`] = ` -" -
- - - -
-

-
- - - -
-" -`; - -exports[`cpt locking template_lock insert should allow blocks to be moved 1`] = ` -" -

p1

- - - -
- - - -
-

-
- - - -
-" -`; diff --git a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js deleted file mode 100644 index 447be0793fafb..0000000000000 --- a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js +++ /dev/null @@ -1,251 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - clickBlockToolbarButton, - clickMenuItem, - createNewPost, - deactivatePlugin, - getEditedPostContent, - insertBlock, - pressKeyTimes, - pressKeyWithModifier, - setPostContent, - canvas, -} from '@wordpress/e2e-test-utils'; - -describe( 'cpt locking', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-plugin-cpt-locking' ); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-plugin-cpt-locking' ); - } ); - - const shouldDisableTheInserter = async () => { - expect( - await page.evaluate( () => { - const inserter = document.querySelector( - '.edit-post-header [aria-label="Add block"], .edit-post-header [aria-label="Toggle block inserter"]' - ); - return inserter.getAttribute( 'disabled' ); - } ) - ).not.toBeNull(); - }; - - const shouldNotAllowBlocksToBeRemoved = async () => { - await canvas().type( - '.block-editor-rich-text__editable[data-type="core/paragraph"]', - 'p1' - ); - await clickBlockToolbarButton( 'Options' ); - expect( - await page.$x( '//button/span[contains(text(), "Delete")]' ) - ).toHaveLength( 0 ); - }; - - const shouldAllowBlocksToBeMoved = async () => { - await canvas().click( - 'div > .block-editor-rich-text__editable[data-type="core/paragraph"]' - ); - expect( await page.$( 'button[aria-label="Move up"]' ) ).not.toBeNull(); - await page.click( 'button[aria-label="Move up"]' ); - await canvas().type( - 'div > .block-editor-rich-text__editable[data-type="core/paragraph"]', - 'p1' - ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - }; - - describe( 'template_lock all', () => { - beforeEach( async () => { - await createNewPost( { postType: 'locked-all-post' } ); - } ); - - it( 'should disable the inserter', shouldDisableTheInserter ); - - it( - 'should not allow blocks to be removed', - shouldNotAllowBlocksToBeRemoved - ); - - it( 'should not allow blocks to be moved', async () => { - await canvas().click( - '.block-editor-rich-text__editable[data-type="core/paragraph"]' - ); - expect( await page.$( 'button[aria-label="Move up"]' ) ).toBeNull(); - } ); - - it( 'should not error when deleting the cotents of a paragraph', async () => { - await canvas().click( - '.block-editor-block-list__block[data-type="core/paragraph"]' - ); - const textToType = 'Paragraph'; - await page.keyboard.type( 'Paragraph' ); - await pressKeyTimes( 'Backspace', textToType.length + 1 ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should insert line breaks when using enter and shift-enter', async () => { - await canvas().click( - '.block-editor-block-list__block[data-type="core/paragraph"]' - ); - await page.keyboard.type( 'First line' ); - await pressKeyTimes( 'Enter', 1 ); - await page.keyboard.type( 'Second line' ); - await pressKeyWithModifier( 'shift', 'Enter' ); - await page.keyboard.type( 'Third line' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should show invalid template notice if the blocks do not match the templte', async () => { - const content = await getEditedPostContent(); - const [ , contentWithoutImage ] = - content.split( '' ); - await setPostContent( contentWithoutImage ); - const noticeContent = await page.waitForSelector( - '.editor-template-validation-notice .components-notice__content' - ); - expect( - await page.evaluate( - ( _noticeContent ) => _noticeContent.firstChild.nodeValue, - noticeContent - ) - ).toEqual( - 'The content of your post doesn’t match the template assigned to your post type.' - ); - } ); - - it( 'should not allow blocks to be inserted in inner blocks', async () => { - await canvas().click( - 'button[aria-label="Two columns; equal split"]' - ); - await page.evaluate( - () => new Promise( window.requestIdleCallback ) - ); - expect( - await canvas().$( - '.wp-block-column .block-editor-button-block-appender' - ) - ).toBeNull(); - - expect( - await page.evaluate( () => { - const inserter = document.querySelector( - '.edit-post-header [aria-label="Add block"], .edit-post-header [aria-label="Toggle block inserter"]' - ); - return inserter.getAttribute( 'disabled' ); - } ) - ).not.toBeNull(); - } ); - } ); - - describe( 'template_lock insert', () => { - beforeEach( async () => { - await createNewPost( { postType: 'locked-insert-post' } ); - } ); - - it( 'should disable the inserter', shouldDisableTheInserter ); - - it( - 'should not allow blocks to be removed', - shouldNotAllowBlocksToBeRemoved - ); - - it( 'should allow blocks to be moved', shouldAllowBlocksToBeMoved ); - } ); - - describe( 'template_lock false', () => { - beforeEach( async () => { - await createNewPost( { postType: 'not-locked-post' } ); - } ); - - it( 'should allow blocks to be inserted', async () => { - expect( - // "Add block" selector is required to make sure performance comparison - // doesn't fail on older branches where we still had "Add block" as label. - await page.$( - '.edit-post-header [aria-label="Add block"], .edit-post-header [aria-label="Toggle block inserter"]' - ) - ).not.toBeNull(); - await insertBlock( 'List' ); - await page.keyboard.type( 'List content' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should allow blocks to be removed', async () => { - await canvas().type( - '.block-editor-rich-text__editable[data-type="core/paragraph"]', - 'p1' - ); - await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Delete' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should allow blocks to be moved', shouldAllowBlocksToBeMoved ); - } ); - - describe( 'template_lock all unlocked group', () => { - beforeEach( async () => { - await createNewPost( { - postType: 'l-post-ul-group', - } ); - } ); - - it( 'should allow blocks to be removed', async () => { - await canvas().type( - 'div > .block-editor-rich-text__editable[data-type="core/paragraph"]', - 'p1' - ); - await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Delete' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should allow blocks to be moved', shouldAllowBlocksToBeMoved ); - } ); - - describe( 'template_lock all locked group', () => { - beforeEach( async () => { - await createNewPost( { - postType: 'l-post-l-group', - } ); - } ); - - it( - 'should not allow blocks to be removed', - shouldNotAllowBlocksToBeRemoved - ); - - it( 'should not allow blocks to be moved', async () => { - await canvas().click( - '.block-editor-rich-text__editable[data-type="core/paragraph"]' - ); - expect( await page.$( 'button[aria-label="Move up"]' ) ).toBeNull(); - } ); - } ); - - describe( 'template_lock all inherited group', () => { - beforeEach( async () => { - await createNewPost( { - postType: 'l-post-i-group', - } ); - } ); - - it( - 'should not allow blocks to be removed', - shouldNotAllowBlocksToBeRemoved - ); - - it( 'should not allow blocks to be moved', async () => { - await canvas().click( - '.block-editor-rich-text__editable[data-type="core/paragraph"]' - ); - expect( await page.$( 'button[aria-label="Move up"]' ) ).toBeNull(); - } ); - } ); -} ); diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss index fa61cc9889cf9..237bbf25f2c79 100644 --- a/packages/edit-post/src/components/visual-editor/style.scss +++ b/packages/edit-post/src/components/visual-editor/style.scss @@ -21,7 +21,6 @@ font-size: $default-font-size; padding: 6px 12px; - &.is-tertiary, &.has-icon { padding: 6px; } diff --git a/packages/edit-site/src/components/dataviews/in-filter.js b/packages/edit-site/src/components/dataviews/in-filter.js index 06e6360e9d5a8..9642169335aaf 100644 --- a/packages/edit-site/src/components/dataviews/in-filter.js +++ b/packages/edit-site/src/components/dataviews/in-filter.js @@ -5,6 +5,7 @@ import { __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper, SelectControl, } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; export const OPERATOR_IN = 'in'; @@ -30,7 +31,11 @@ export default ( { filter, view, onChangeView } ) => { htmlFor={ id } className="dataviews__select-control-prefix" > - { filter.name + ':' } + { sprintf( + /* translators: filter name. */ + __( '%s:' ), + filter.name + ) } } options={ filter.elements } diff --git a/packages/edit-site/src/components/page-patterns/patterns-list.js b/packages/edit-site/src/components/page-patterns/patterns-list.js index bbcb7e7910211..eb56fdded9060 100644 --- a/packages/edit-site/src/components/page-patterns/patterns-list.js +++ b/packages/edit-site/src/components/page-patterns/patterns-list.js @@ -12,7 +12,7 @@ import { __experimentalHeading as Heading, __experimentalText as Text, } from '@wordpress/components'; -import { __, isRTL } from '@wordpress/i18n'; +import { __, _x, isRTL } from '@wordpress/i18n'; import { chevronLeft, chevronRight } from '@wordpress/icons'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { useAsyncList, useViewportMatch } from '@wordpress/compose'; @@ -33,9 +33,15 @@ import Pagination from './pagination'; const { useLocation, useHistory } = unlock( routerPrivateApis ); const SYNC_FILTERS = { - all: __( 'All' ), - [ PATTERN_SYNC_TYPES.full ]: __( 'Synced' ), - [ PATTERN_SYNC_TYPES.unsynced ]: __( 'Not synced' ), + all: _x( 'All', 'Option that shows all patterns' ), + [ PATTERN_SYNC_TYPES.full ]: _x( + 'Synced', + 'Option that shows all synchronized patterns' + ), + [ PATTERN_SYNC_TYPES.unsynced ]: _x( + 'Not synced', + 'Option that shows all patterns that are not synchronized' + ), }; const SYNC_DESCRIPTIONS = { diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js index 95fd2a4814d82..0f29292274546 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js @@ -3,7 +3,8 @@ */ import { MenuGroup, MenuItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useEntityRecord } from '@wordpress/core-data'; +import { useDispatch } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -18,7 +19,7 @@ export default function ResetDefaultTemplate( { onClick } ) { const currentTemplateSlug = useCurrentTemplateSlug(); const isPostsPage = useIsPostsPage(); const { postType, postId } = useEditedPostContext(); - const entity = useEntityRecord( 'postType', postType, postId ); + const { editEntityRecord } = useDispatch( coreStore ); // The default template in a post is indicated by an empty string. if ( ! currentTemplateSlug || isPostsPage ) { return null; @@ -27,7 +28,13 @@ export default function ResetDefaultTemplate( { onClick } ) { { - entity.edit( { template: '' }, { undoIgnore: true } ); + editEntityRecord( + 'postType', + postType, + postId, + { template: '' }, + { undoIgnore: true } + ); onClick(); } } > diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js index f8e550d37ef06..40eb1c5c4bd62 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js @@ -6,7 +6,8 @@ import { decodeEntities } from '@wordpress/html-entities'; import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor'; import { MenuItem, Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useEntityRecord } from '@wordpress/core-data'; +import { useDispatch } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; import { parse } from '@wordpress/blocks'; import { useAsyncList } from '@wordpress/compose'; @@ -22,12 +23,18 @@ export default function SwapTemplateButton( { onClick } ) { setShowModal( false ); }, [] ); const { postType, postId } = useEditedPostContext(); - const entitiy = useEntityRecord( 'postType', postType, postId ); + const { editEntityRecord } = useDispatch( coreStore ); if ( ! availableTemplates?.length ) { return null; } const onTemplateSelect = async ( template ) => { - entitiy.edit( { template: template.name }, { undoIgnore: true } ); + editEntityRecord( + 'postType', + postType, + postId, + { template: template.name }, + { undoIgnore: true } + ); onClose(); // Close the template suggestions modal first. onClick(); }; diff --git a/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js b/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js index f90bf30ce0211..0407b85c4e8bc 100644 --- a/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js +++ b/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js @@ -17,8 +17,9 @@ import { hasBlockSupport, } from '@wordpress/blocks'; import { useContext, useMemo, useCallback } from '@wordpress/element'; -import { useDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -391,6 +392,10 @@ function PushChangesToGlobalStylesControl( { const withPushChangesToGlobalStyles = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { const blockEditingMode = useBlockEditingMode(); + const isBlockBasedTheme = useSelect( + ( select ) => select( coreStore ).getCurrentTheme()?.is_block_theme, + [] + ); const supportsStyles = SUPPORTED_STYLES.some( ( feature ) => hasBlockSupport( props.name, feature ) ); @@ -398,11 +403,13 @@ const withPushChangesToGlobalStyles = createHigherOrderComponent( return ( <> - { blockEditingMode === 'default' && supportsStyles && ( - - - - ) } + { blockEditingMode === 'default' && + supportsStyles && + isBlockBasedTheme && ( + + + + ) } ); } diff --git a/packages/editor/src/components/post-sync-status/index.js b/packages/editor/src/components/post-sync-status/index.js index 15e322b343f40..abc45146c36af 100644 --- a/packages/editor/src/components/post-sync-status/index.js +++ b/packages/editor/src/components/post-sync-status/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { PanelRow, Modal, @@ -109,7 +109,10 @@ export function PostSyncStatusModal() { - {} } - > + /> ); } diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js index 22d20fd037265..f5e6e85b8602d 100644 --- a/packages/patterns/src/components/create-pattern-modal.js +++ b/packages/patterns/src/components/create-pattern-modal.js @@ -9,7 +9,7 @@ import { __experimentalVStack as VStack, ToggleControl, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { useState, useMemo } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; @@ -179,7 +179,10 @@ export default function CreatePatternModal( { categoryMap={ categoryMap } /> { /** * External dependencies */ +const path = require( 'path' ); const { resolve } = require( 'node:path' ); const { sync: spawn } = require( 'cross-spawn' ); @@ -27,7 +28,10 @@ const { const result = spawn( 'node', - [ require.resolve( 'playwright-core/cli' ), 'install' ], + [ + path.resolve( require.resolve( 'playwright-core' ), '..', 'cli.js' ), + 'install', + ], { stdio: 'inherit', } diff --git a/phpunit/blocks/block-navigation-link-variations-test.php b/phpunit/blocks/block-navigation-link-variations-test.php new file mode 100644 index 0000000000000..c5082e0f4c878 --- /dev/null +++ b/phpunit/blocks/block-navigation-link-variations-test.php @@ -0,0 +1,89 @@ + array( + 'item_link' => 'Custom Book', + ), + 'public' => true, + 'show_in_rest' => true, + 'show_in_nav_menus' => true, + ) + ); + register_taxonomy( + 'book_type', + 'custom_book', + array( + 'labels' => array( + 'item_link' => 'Book Type', + ), + 'show_in_nav_menus' => true, + ) + ); + } + + public function tear_down() { + unregister_post_type( 'custom_book' ); + unregister_taxonomy( 'book_type' ); + parent::tear_down(); + } + + /** + * @covers ::register_block_core_navigation_link_post_type_variation + */ + public function test_navigation_link_variations_custom_post_type() { + $registry = WP_Block_Type_Registry::get_instance(); + $nav_link_block = $registry->get_registered( 'core/navigation-link' ); + $this->assertNotEmpty( $nav_link_block->variations, 'Block has no variations' ); + $variation = $this->get_variation_by_name( 'custom_book', $nav_link_block->variations ); + $this->assertIsArray( $variation, 'Block variation is not an array' ); + $this->assertArrayHasKey( 'title', $variation, 'Block variation has no title' ); + $this->assertEquals( 'Custom Book', $variation['title'], 'Variation title is different than the post type label' ); + } + + /** + * @covers ::register_block_core_navigation_link_taxonomy_variation + */ + public function test_navigation_link_variations_custom_taxonomy() { + $registry = WP_Block_Type_Registry::get_instance(); + $nav_link_block = $registry->get_registered( 'core/navigation-link' ); + $this->assertNotEmpty( $nav_link_block->variations, 'Block has no variations' ); + $variation = $this->get_variation_by_name( 'book_type', $nav_link_block->variations ); + $this->assertIsArray( $variation, 'Block variation is not an array' ); + $this->assertArrayHasKey( 'title', $variation, 'Block variation has no title' ); + $this->assertEquals( 'Book Type', $variation['title'], 'Variation title is different than the post type label' ); + } + + /** + * Get a variation by its name from an array of variations. + * + * @param string $variation_name The name (= slug) of the variation. + * @param array $variations An array of variations. + * @return array|null The found variation or null. + */ + private function get_variation_by_name( $variation_name, $variations ) { + $found_variation = null; + foreach ( $variations as $variation ) { + if ( $variation['name'] === $variation_name ) { + $found_variation = $variation; + } + } + + return $found_variation; + } +} diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js index db3ff72e3ab6e..85590e9ec1596 100644 --- a/test/e2e/specs/editor/blocks/image.spec.js +++ b/test/e2e/specs/editor/blocks/image.spec.js @@ -47,7 +47,9 @@ test.describe( 'Image', () => { imageBlock.locator( 'data-testid=form-file-upload-input' ) ); - const image = imageBlock.locator( 'role=img' ); + const image = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); await expect( image ).toBeVisible(); await expect( image ).toHaveAttribute( 'src', new RegExp( filename ) ); @@ -69,7 +71,9 @@ test.describe( 'Image', () => { const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' ); - const image = imageBlock.locator( 'role=img' ); + const image = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); const filename = await imageBlockUtils.upload( imageBlock.locator( 'data-testid=form-file-upload-input' ) @@ -98,7 +102,9 @@ test.describe( 'Image', () => { const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' ); - const image = imageBlock.locator( 'role=img' ); + const image = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); const fileName = await imageBlockUtils.upload( imageBlock.locator( 'data-testid=form-file-upload-input' ) @@ -129,7 +135,9 @@ test.describe( 'Image', () => { const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' ); - const image = imageBlock.locator( 'role=img' ); + const image = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); const fileName = await imageBlockUtils.upload( imageBlock.locator( 'data-testid=form-file-upload-input' ) @@ -182,7 +190,9 @@ test.describe( 'Image', () => { const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' ); - const image = imageBlock.locator( 'role=img' ); + const image = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); const tmpInput = await page.evaluateHandle( () => { const input = document.createElement( 'input' ); @@ -226,7 +236,9 @@ test.describe( 'Image', () => { const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' ); - const image = imageBlock.locator( 'role=img' ); + const image = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); const filename = await imageBlockUtils.upload( imageBlock.locator( 'data-testid=form-file-upload-input' ) @@ -292,7 +304,9 @@ test.describe( 'Image', () => { const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' ); - const image = imageBlock.locator( 'role=img' ); + const image = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); const filename = await imageBlockUtils.upload( imageBlock.locator( 'data-testid=form-file-upload-input' ) @@ -349,7 +363,9 @@ test.describe( 'Image', () => { const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' ); - const image = imageBlock.locator( 'role=img' ); + const image = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); const filename = await imageBlockUtils.upload( imageBlock.locator( 'data-testid=form-file-upload-input' ) @@ -385,7 +401,9 @@ test.describe( 'Image', () => { const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' ); - const image = imageBlock.locator( 'role=img' ); + const image = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); const filename = await imageBlockUtils.upload( imageBlock.locator( 'data-testid=form-file-upload-input' ) @@ -572,7 +590,9 @@ test.describe( 'Image', () => { imageBlock.locator( 'data-testid=form-file-upload-input' ) ); - const imageInEditor = imageBlock.locator( 'role=img' ); + const imageInEditor = imageBlock.getByRole( 'img', { + name: 'This image has an empty alt attribute', + } ); await expect( imageInEditor ).toBeVisible(); await expect( imageInEditor ).toHaveAttribute( 'src', diff --git a/test/e2e/specs/editor/blocks/paragraph.spec.js b/test/e2e/specs/editor/blocks/paragraph.spec.js index 3cf3654870a35..142b01be282d4 100644 --- a/test/e2e/specs/editor/blocks/paragraph.spec.js +++ b/test/e2e/specs/editor/blocks/paragraph.spec.js @@ -83,9 +83,7 @@ test.describe( 'Paragraph', () => { await expect( draggingUtils.dropZone ).toBeVisible(); await expect( draggingUtils.insertionIndicator ).toBeHidden(); - await drop( - editor.canvas.locator( '[data-type="core/paragraph"]' ) - ); + await drop(); const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' diff --git a/test/e2e/specs/editor/plugins/post-type-locking.spec.js b/test/e2e/specs/editor/plugins/post-type-locking.spec.js new file mode 100644 index 0000000000000..5c1d6ebf06dac --- /dev/null +++ b/test/e2e/specs/editor/plugins/post-type-locking.spec.js @@ -0,0 +1,461 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Post-type locking', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( + 'gutenberg-test-plugin-cpt-locking' + ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deactivatePlugin( + 'gutenberg-test-plugin-cpt-locking' + ); + } ); + + test.describe( 'template_lock all', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { postType: 'locked-all-post' } ); + } ); + + test( 'should disable the inserter', async ( { page } ) => { + await expect( + page + .getByRole( 'toolbar', { name: 'Document tools' } ) + .getByRole( 'button', { name: 'Toggle block inserter' } ) + ).toBeDisabled(); + } ); + + test( 'should not allow blocks to be removed', async ( { + editor, + page, + } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .first() + .fill( 'p1' ); + + await editor.clickBlockToolbarButton( 'Options' ); + + await expect( + page + .getByRole( 'menu', { name: 'Options' } ) + .getByRole( 'menuitem', { name: 'Delete' } ) + ).toBeHidden(); + } ); + + test( 'should not allow blocks to be moved', async ( { + editor, + page, + } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .first() + .click(); + + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Move up' } ) + ).toBeHidden(); + } ); + + test( 'should not error when deleting the contents of a paragraph', async ( { + editor, + page, + pageUtils, + } ) => { + const firstParagraph = editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .first(); + await firstParagraph.click(); + + const textToType = 'Paragraph'; + await page.keyboard.type( textToType ); + await pageUtils.pressKeys( 'Backspace', textToType.length + 1 ); + + await expect( firstParagraph ).toHaveText( '' ); + } ); + + test( 'should insert line breaks when using enter and shift-enter', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .first() + .click(); + + await page.keyboard.type( 'First line' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'Second line' ); + await pageUtils.pressKeys( 'shift+Enter' ); + await page.keyboard.type( 'Third line' ); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/image', + }, + { + name: 'core/paragraph', + attributes: { + content: 'First line
Second line
Third line', + }, + }, + { + name: 'core/quote', + }, + { + name: 'core/columns', + }, + ] ); + } ); + + test( 'should show invalid template notice if the blocks do not match the templte', async ( { + page, + pageUtils, + } ) => { + // Open the code editor. + await pageUtils.pressKeys( 'secondary+M' ); + + // Modify template. + await page.getByRole( 'textbox', { name: 'Type text or HTML' } ) + .fill( ` +

+` ); + + // Go back to the visual editor. + await pageUtils.pressKeys( 'secondary+M' ); + + await expect( + page.locator( + '.editor-template-validation-notice .components-notice__content' + ) + ).toContainText( + 'The content of your post doesn’t match the template assigned to your post type.' + ); + } ); + + test( 'should not allow blocks to be inserted in inner blocks', async ( { + editor, + page, + } ) => { + await editor.canvas + .getByRole( 'button', { + name: 'Two columns; equal split', + } ) + .click(); + + await expect( + page + .getByRole( 'document', { + name: 'Block: Column (1 of 2)', + } ) + .getByRole( 'button', { name: 'Add block' } ) + ).toBeHidden(); + + await expect( + page + .getByRole( 'toolbar', { name: 'Document tools' } ) + .getByRole( 'button', { name: 'Toggle block inserter' } ) + ).toBeDisabled(); + } ); + } ); + + test.describe( 'template_lock insert', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { postType: 'locked-insert-post' } ); + } ); + + test( 'should disable the inserter', async ( { page } ) => { + await expect( + page + .getByRole( 'toolbar', { name: 'Document tools' } ) + .getByRole( 'button', { name: 'Toggle block inserter' } ) + ).toBeDisabled(); + } ); + + test( 'should not allow blocks to be removed', async ( { + editor, + page, + } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .first() + .fill( 'p1' ); + + await editor.clickBlockToolbarButton( 'Options' ); + + await expect( + page + .getByRole( 'menu', { name: 'Options' } ) + .getByRole( 'menuitem', { name: 'Delete' } ) + ).toBeHidden(); + } ); + + test( 'should allow blocks to be moved', async ( { editor, page } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .first() + .click(); + + await page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Move up' } ) + .click(); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/paragraph', + }, + { + name: 'core/image', + }, + { + name: 'core/quote', + }, + { + name: 'core/columns', + }, + ] ); + } ); + } ); + + test.describe( 'template_lock false', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { postType: 'not-locked-post' } ); + } ); + + test( 'should allow blocks to be inserted', async ( { + editor, + page, + } ) => { + await expect( + page + .getByRole( 'toolbar', { name: 'Document tools' } ) + .getByRole( 'button', { name: 'Toggle block inserter' } ) + ).toBeEnabled(); + + await editor.insertBlock( { name: 'core/list' } ); + await editor.canvas + .getByRole( 'textbox', { name: 'List text' } ) + .fill( 'List content' ); + + await expect( + editor.canvas.getByRole( 'document', { + name: 'Block: List item', + } ) + ).toHaveText( 'List content' ); + } ); + + test( 'should allow blocks to be removed', async ( { editor } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .first() + .fill( 'p1' ); + + await editor.clickBlockOptionsMenuItem( 'Delete' ); + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/image', + }, + { + name: 'core/quote', + }, + { + name: 'core/columns', + }, + ] ); + } ); + + test( 'should allow blocks to be moved', async ( { editor, page } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .first() + .click(); + + await page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Move up' } ) + .click(); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/paragraph', + }, + { + name: 'core/image', + }, + { + name: 'core/quote', + }, + { + name: 'core/columns', + }, + ] ); + } ); + } ); + + test.describe( 'template_lock all unlocked group', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { postType: 'l-post-ul-group' } ); + } ); + + test( 'should allow blocks to be removed', async ( { editor } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .last() + .fill( 'p1' ); + + await editor.clickBlockOptionsMenuItem( 'Delete' ); + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/group', + innerBlocks: [ + { + name: 'core/quote', + }, + ], + }, + ] ); + } ); + + test( 'should allow blocks to be moved', async ( { editor, page } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .last() + .click(); + + await page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Move up' } ) + .click(); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/group', + innerBlocks: [ + { + name: 'core/paragraph', + }, + { + name: 'core/quote', + }, + ], + }, + ] ); + } ); + } ); + + test.describe( 'template_lock all locked group', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { postType: 'l-post-l-group' } ); + } ); + + test( 'should not allow blocks to be removed', async ( { + editor, + page, + } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .last() + .fill( 'p1' ); + + await editor.clickBlockToolbarButton( 'Options' ); + + await expect( + page + .getByRole( 'menu', { name: 'Options' } ) + .getByRole( 'menuitem', { name: 'Delete' } ) + ).toBeHidden(); + } ); + + test( 'should not allow blocks to be moved', async ( { + editor, + page, + } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .last() + .click(); + + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Move up' } ) + ).toBeHidden(); + } ); + } ); + + test.describe( 'template_lock all inherited group', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { postType: 'l-post-i-group' } ); + } ); + + test( 'should not allow blocks to be removed', async ( { + editor, + page, + } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .last() + .fill( 'p1' ); + + await editor.clickBlockToolbarButton( 'Options' ); + + await expect( + page + .getByRole( 'menu', { name: 'Options' } ) + .getByRole( 'menuitem', { name: 'Delete' } ) + ).toBeHidden(); + } ); + + test( 'should not allow blocks to be moved', async ( { + editor, + page, + } ) => { + await editor.canvas + .getByRole( 'document', { + name: 'Empty block', + } ) + .last() + .click(); + + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Move up' } ) + ).toBeHidden(); + } ); + } ); +} ); diff --git a/test/e2e/specs/editor/various/draggable-blocks.spec.js b/test/e2e/specs/editor/various/draggable-blocks.spec.js index fb56b43dc6e03..29a81f57540e0 100644 --- a/test/e2e/specs/editor/various/draggable-blocks.spec.js +++ b/test/e2e/specs/editor/various/draggable-blocks.spec.js @@ -414,7 +414,7 @@ test.describe( 'Draggable block', () => { 'Dragging over the empty group block but outside the appender should still show the blue background' ).toHaveCSS( 'background-color', 'rgb(0, 124, 186)' ); - await drop( rowBlock ); + await drop(); await expect( rowAppender ).toBeHidden(); await expect.poll( editor.getBlocks ).toMatchObject( [ { @@ -446,7 +446,7 @@ test.describe( 'Draggable block', () => { 'rgb(0, 124, 186)' ); - await drop( columnAppender ); + await drop(); await expect( columnAppender ).toBeHidden(); await expect.poll( editor.getBlocks ).toMatchObject( [ { name: 'core/group' }, diff --git a/test/e2e/specs/editor/various/multi-block-selection.spec.js b/test/e2e/specs/editor/various/multi-block-selection.spec.js index 4fb39783954fa..585e4f1851373 100644 --- a/test/e2e/specs/editor/various/multi-block-selection.spec.js +++ b/test/e2e/specs/editor/various/multi-block-selection.spec.js @@ -247,15 +247,14 @@ test.describe( 'Multi-block selection', () => { editor, multiBlockSelectionUtils, } ) => { - // To do: run with iframe. - await editor.switchToLegacyCanvas(); - - await page.getByRole( 'button', { name: 'Add default block' } ).click(); + await editor.canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); await page.keyboard.type( '1' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( '2' ); - await page + await editor.canvas .getByRole( 'document', { name: 'Block: Paragraph' } ) .filter( { hasText: '1' } ) .click( { modifiers: [ 'Shift' ] } ); @@ -272,11 +271,11 @@ test.describe( 'Multi-block selection', () => { .getByRole( 'toolbar', { name: 'Block tools' } ) .getByRole( 'button', { name: 'Group' } ) .click(); - await page + await editor.canvas .getByRole( 'document', { name: 'Block: Paragraph' } ) .filter( { hasText: '1' } ) .click(); - await page + await editor.canvas .getByRole( 'document', { name: 'Block: Paragraph' } ) .filter( { hasText: '2' } ) .click( { modifiers: [ 'Shift' ] } );