From 8cd2a9da62122c0447e3057e2067ed9a8fdb5770 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 22 Jan 2024 14:23:28 +0000 Subject: [PATCH 01/68] Add: Footnotes support for other CPT's. (#57353) --- packages/block-library/src/footnotes/edit.js | 3 +- .../block-library/src/footnotes/format.js | 10 ++++-- .../block-library/src/footnotes/index.php | 31 ++++++++++++------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/packages/block-library/src/footnotes/edit.js b/packages/block-library/src/footnotes/edit.js index 111e0ba5d3a0e..88254657ced3c 100644 --- a/packages/block-library/src/footnotes/edit.js +++ b/packages/block-library/src/footnotes/edit.js @@ -14,10 +14,11 @@ export default function FootnotesEdit( { context: { postType, postId } } ) { 'meta', postId ); + const footnotesSupported = 'string' === typeof meta?.footnotes; const footnotes = meta?.footnotes ? JSON.parse( meta.footnotes ) : []; const blockProps = useBlockProps(); - if ( postType !== 'post' && postType !== 'page' ) { + if ( ! footnotesSupported ) { return (
0; }, [] ); + const [ meta ] = useEntityProp( 'postType', postType, 'meta', postId ); + const footnotesSupported = 'string' === typeof meta?.footnotes; + const { selectionChange, insertBlock } = useDispatch( blockEditorStore ); @@ -81,7 +85,7 @@ export const format = { return null; } - if ( postType !== 'post' && postType !== 'page' ) { + if ( ! footnotesSupported ) { return null; } diff --git a/packages/block-library/src/footnotes/index.php b/packages/block-library/src/footnotes/index.php index bc6291dd21c38..0cd2ad73ef3d4 100644 --- a/packages/block-library/src/footnotes/index.php +++ b/packages/block-library/src/footnotes/index.php @@ -68,17 +68,26 @@ function render_block_core_footnotes( $attributes, $content, $block ) { * @since 6.3.0 */ function register_block_core_footnotes() { - foreach ( array( 'post', 'page' ) as $post_type ) { - register_post_meta( - $post_type, - 'footnotes', - array( - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', - 'revisions_enabled' => true, - ) - ); + $post_types = get_post_types( + array( + 'show_in_rest' => true, + 'public' => true, + ) + ); + foreach ( $post_types as $post_type ) { + // Only register the meta field if the post type supports the editor, custom fields, and revisions. + if ( post_type_supports( $post_type, 'editor' ) && post_type_supports( $post_type, 'custom-fields' ) && post_type_supports( $post_type, 'revisions' ) ) { + register_post_meta( + $post_type, + 'footnotes', + array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'revisions_enabled' => true, + ) + ); + } } register_block_type_from_metadata( __DIR__ . '/footnotes', From c35e95aac2bce2fed37539ad11b0dd6908a2f0af Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 22 Jan 2024 14:34:46 +0000 Subject: [PATCH 02/68] Document files/directories requiring backmerging to WP Core for major release (#58064) * Initial docs stub * Remove phpunit from list * Flatten ignore list for absolute clarity * Correct title --- .../code/back-merging-to-wp-core.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 docs/contributors/code/back-merging-to-wp-core.md diff --git a/docs/contributors/code/back-merging-to-wp-core.md b/docs/contributors/code/back-merging-to-wp-core.md new file mode 100644 index 0000000000000..2b1ec77df1e55 --- /dev/null +++ b/docs/contributors/code/back-merging-to-wp-core.md @@ -0,0 +1,31 @@ +# Back-merging code to WordPress Core + +For major releases of the WordPress software, Gutenberg features need to be merged into WordPress Core. Typically this involves taking changes made in `.php` files within the Gutenberg repository and making the equivalent updates in the WP Core codebase. + +## Files/Directories + +Changes to files within the following files/directories will typically require back-merging to WP Core: + +- `lib/` +- `phpunit/` + +## Ignored directories/files + +The following directories/files do _not_ require back-merging to WP Core: + +- `lib/load.php` - Plugin specific code. +- `lib/experiments-page.php` - experiments are Plugin specific. +- `packages/block-library` - this is handled automatically during the packages sync process. +- `packages/e2e-tests/plugins` - PHP files related to e2e tests only. Mostly fixture data generators. +- `phpunit/blocks` - the code is maintained in Gutenberg so the test should be as well. + +Please note this list is not exhaustive. + +## Pull Request Criteria + +In general, all PHP code committed to the Gutenberg repository since the date of the final Gutenberg release that was included in [the _last_ stable WP Core release](https://developer.wordpress.org/block-editor/contributors/versions-in-wordpress/) should be considered for back merging to WP Core. + +There are however certain exceptions to that rule. PRs with the following criteria do _not_ require back-merging to WP Core: + +- Does not contain changes to PHP code. +- Has label `Backport from WordPress Core` - this code is already in WP Core. From f4c34a4656521fdb7f151d687d89679c6caebada Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+c4rl0sbr4v0@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:51:46 +0100 Subject: [PATCH 03/68] Interactivity: Export `withScope()` and allow to use it with asynchronous operations. (#58013) * Initial commit * Commit to change branches * Support generators in with scope * Update changelog --- .../interactive-blocks/with-scope/block.json | 15 ++++++++ .../interactive-blocks/with-scope/render.php | 14 +++++++ .../interactive-blocks/with-scope/view.js | 20 ++++++++++ packages/interactivity/CHANGELOG.md | 7 ++-- packages/interactivity/src/directives.js | 1 + packages/interactivity/src/hooks.tsx | 2 + packages/interactivity/src/index.js | 3 +- packages/interactivity/src/utils.js | 38 ++++++++++++++++++- .../specs/interactivity/with-scope.spec.ts | 27 +++++++++++++ 9 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 packages/e2e-tests/plugins/interactive-blocks/with-scope/block.json create mode 100644 packages/e2e-tests/plugins/interactive-blocks/with-scope/render.php create mode 100644 packages/e2e-tests/plugins/interactive-blocks/with-scope/view.js create mode 100644 test/e2e/specs/interactivity/with-scope.spec.ts diff --git a/packages/e2e-tests/plugins/interactive-blocks/with-scope/block.json b/packages/e2e-tests/plugins/interactive-blocks/with-scope/block.json new file mode 100644 index 0000000000000..b9d6a4fc3cf7c --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/with-scope/block.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/with-scope", + "title": "E2E Interactivity tests - with scope", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewScript": "with-scope-view", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/with-scope/render.php b/packages/e2e-tests/plugins/interactive-blocks/with-scope/render.php new file mode 100644 index 0000000000000..e1a844e33246a --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/with-scope/render.php @@ -0,0 +1,14 @@ + + +
+

0

+

0

+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/with-scope/view.js b/packages/e2e-tests/plugins/interactive-blocks/with-scope/view.js new file mode 100644 index 0000000000000..9df421a6f50b7 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/with-scope/view.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { store, getContext, withScope } from '@wordpress/interactivity'; + +store( 'with-scope', { + callbacks: { + asyncInit: () => { + setTimeout( withScope(function*() { + yield new Promise(resolve => setTimeout(resolve, 1)); + const context = getContext() + context.asyncCounter += 1; + }, 1 )); + }, + syncInit: () => { + const context = getContext() + context.syncCounter += 1; + } + }, +} ); diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index d660032cbc038..b37fff1bbb5c4 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -5,12 +5,13 @@ ### Enhancements - Prevent the usage of Preact components in `wp-text`. ([#57879](https://github.com/WordPress/gutenberg/pull/57879)) -- Update `preact`, `@preact/signals` and `deepsignal` dependencies. ([57891](https://github.com/WordPress/gutenberg/pull/57891)) +- Update `preact`, `@preact/signals` and `deepsignal` dependencies. ([#57891](https://github.com/WordPress/gutenberg/pull/57891)) +- Export `withScope()` and allow to use it with asynchronous operations. ([#58013](https://github.com/WordPress/gutenberg/pull/58013)) ### New Features -- Add the `data-wp-run` directive along with the `useInit` and `useWatch` hooks. ([57805](https://github.com/WordPress/gutenberg/pull/57805)) -- Add `wp-data-on-window` and `wp-data-on-document` directives. ([57931](https://github.com/WordPress/gutenberg/pull/57931)) +- Add the `data-wp-run` directive along with the `useInit` and `useWatch` hooks. ([#57805](https://github.com/WordPress/gutenberg/pull/57805)) +- Add `wp-data-on-window` and `wp-data-on-document` directives. ([#57931](https://github.com/WordPress/gutenberg/pull/57931)) ### Breaking Changes diff --git a/packages/interactivity/src/directives.js b/packages/interactivity/src/directives.js index 25ba92808614d..21ab2da29cf27 100644 --- a/packages/interactivity/src/directives.js +++ b/packages/interactivity/src/directives.js @@ -132,6 +132,7 @@ export default () => { // data-wp-init--[name] directive( 'init', ( { directives: { init }, evaluate } ) => { init.forEach( ( entry ) => { + // TODO: Replace with useEffect to prevent unneeded scopes. useInit( () => evaluate( entry ) ); } ); } ); diff --git a/packages/interactivity/src/hooks.tsx b/packages/interactivity/src/hooks.tsx index 0f3bb145b6559..4309f3f0bea7a 100644 --- a/packages/interactivity/src/hooks.tsx +++ b/packages/interactivity/src/hooks.tsx @@ -156,6 +156,8 @@ export const resetScope = () => { scopeStack.pop(); }; +export const getNamespace = () => namespaceStack.slice( -1 )[ 0 ]; + export const setNamespace = ( namespace: string ) => { namespaceStack.push( namespace ); }; diff --git a/packages/interactivity/src/index.js b/packages/interactivity/src/index.js index cf0b4c88cac4b..0864291455310 100644 --- a/packages/interactivity/src/index.js +++ b/packages/interactivity/src/index.js @@ -5,9 +5,10 @@ import registerDirectives from './directives'; import { init } from './router'; export { store } from './store'; -export { directive, getContext, getElement } from './hooks'; +export { directive, getContext, getElement, getNamespace } from './hooks'; export { navigate, prefetch } from './router'; export { + withScope, useWatch, useInit, useEffect, diff --git a/packages/interactivity/src/utils.js b/packages/interactivity/src/utils.js index 021df983cb4f0..84e04803cea4f 100644 --- a/packages/interactivity/src/utils.js +++ b/packages/interactivity/src/utils.js @@ -12,7 +12,14 @@ import { effect } from '@preact/signals'; /** * Internal dependencies */ -import { getScope, setScope, resetScope } from './hooks'; +import { + getScope, + setScope, + resetScope, + getNamespace, + setNamespace, + resetNamespace, +} from './hooks'; const afterNextFrame = ( callback ) => { return new Promise( ( resolve ) => { @@ -71,13 +78,40 @@ export function useSignalEffect( callback ) { * @param {Function} func The passed function. * @return {Function} The wrapped function. */ -const withScope = ( func ) => { +export const withScope = ( func ) => { const scope = getScope(); + const ns = getNamespace(); + if ( func?.constructor?.name === 'GeneratorFunction' ) { + return async ( ...args ) => { + const gen = func( ...args ); + let value; + let it; + while ( true ) { + setNamespace( ns ); + setScope( scope ); + try { + it = gen.next( value ); + } finally { + resetNamespace(); + resetScope(); + } + try { + value = await it.value; + } catch ( e ) { + gen.throw( e ); + } + if ( it.done ) break; + } + return value; + }; + } return ( ...args ) => { + setNamespace( ns ); setScope( scope ); try { return func( ...args ); } finally { + resetNamespace(); resetScope(); } }; diff --git a/test/e2e/specs/interactivity/with-scope.spec.ts b/test/e2e/specs/interactivity/with-scope.spec.ts new file mode 100644 index 0000000000000..1cb73cc915aca --- /dev/null +++ b/test/e2e/specs/interactivity/with-scope.spec.ts @@ -0,0 +1,27 @@ +/** + * Internal dependencies + */ +import { test, expect } from './fixtures'; + +test.describe( 'withScope', () => { + test.beforeAll( async ( { interactivityUtils: utils } ) => { + await utils.activatePlugins(); + await utils.addPostWithBlock( 'test/with-scope' ); + } ); + test.beforeEach( async ( { interactivityUtils: utils, page } ) => { + await page.goto( utils.getLink( 'test/with-scope' ) ); + } ); + test.afterAll( async ( { interactivityUtils: utils } ) => { + await utils.deactivatePlugins(); + await utils.deleteAllPosts(); + } ); + + test( 'directives using withScope should work with async and sync functions', async ( { + page, + } ) => { + const asyncCounter = page.getByTestId( 'asyncCounter' ); + await expect( asyncCounter ).toHaveText( '1' ); + const syncCounter = page.getByTestId( 'syncCounter' ); + await expect( syncCounter ).toHaveText( '1' ); + } ); +} ); From ba13f35f38cdf622ddfc065e146f11683762716d Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Mon, 22 Jan 2024 18:08:54 +0000 Subject: [PATCH 04/68] Bump plugin version to 17.5.1 --- gutenberg.php | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index febd63e0be030..e3ef3d1612722 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.3 * Requires PHP: 7.0 - * Version: 17.5.0 + * Version: 17.5.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 56374e1eeb89a..2a2fc30b96099 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "17.5.0", + "version": "17.5.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "17.5.0", + "version": "17.5.1", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 39f4ab82105e8..035c8d69cc56f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "17.5.0", + "version": "17.5.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 6f2eaab0aa7322119397a38c461596a48f43d81b Mon Sep 17 00:00:00 2001 From: Siobhan Bamber Date: Mon, 22 Jan 2024 18:42:30 +0000 Subject: [PATCH 05/68] RNMobile: Avoid crashes by ensuring RichText value exists prior to `toString` calls (#58088) --- .../rich-text/native/index.native.js | 20 +++++++++---------- packages/react-native-editor/CHANGELOG.md | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/native/index.native.js b/packages/block-editor/src/components/rich-text/native/index.native.js index 1f536011b35b6..7a775cad34648 100644 --- a/packages/block-editor/src/components/rich-text/native/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/index.native.js @@ -264,7 +264,7 @@ export class RichText extends Component { onCreateUndoLevel() { const { __unstableOnCreateUndoLevel: onCreateUndoLevel } = this.props; // If the content is the same, no level needs to be created. - if ( this.lastHistoryValue.toString() === this.value.toString() ) { + if ( this.lastHistoryValue?.toString() === this.value?.toString() ) { return; } @@ -317,7 +317,7 @@ export class RichText extends Component { event.nativeEvent.text ); // On iOS, onChange can be triggered after selection changes, even though there are no content changes. - if ( contentWithoutRootTag === this.value.toString() ) { + if ( contentWithoutRootTag === this.value?.toString() ) { return; } this.lastEventCount = event.nativeEvent.eventCount; @@ -333,7 +333,7 @@ export class RichText extends Component { ); this.debounceCreateUndoLevel(); - const refresh = this.value.toString() !== contentWithoutRootTag; + const refresh = this.value?.toString() !== contentWithoutRootTag; this.value = contentWithoutRootTag; // We don't want to refresh if our goal is just to create a record. @@ -564,7 +564,7 @@ export class RichText extends Component { // Check if value is up to date with latest state of native AztecView. if ( event.nativeEvent.text && - event.nativeEvent.text !== this.props.value.toString() + event.nativeEvent.text !== this.props.value?.toString() ) { this.onTextUpdate( event ); } @@ -589,7 +589,7 @@ export class RichText extends Component { // this approach is not perfectly reliable. const isManual = this.lastAztecEventType !== 'input' && - this.props.value.toString() === this.value.toString(); + this.props.value?.toString() === this.value?.toString(); if ( hasChanged && isManual ) { const value = this.createRecord(); const activeFormats = getActiveFormats( value ); @@ -659,7 +659,7 @@ export class RichText extends Component { event.nativeEvent.text ); if ( - contentWithoutRootTag === this.value.toString() && + contentWithoutRootTag === this.value?.toString() && realStart === this.selectionStart && realEnd === this.selectionEnd ) { @@ -756,7 +756,7 @@ export class RichText extends Component { typeof nextProps.value !== 'undefined' && typeof this.props.value !== 'undefined' && ( ! this.comesFromAztec || ! this.firedAfterTextChanged ) && - nextProps.value.toString() !== this.props.value.toString() + nextProps.value?.toString() !== this.props.value?.toString() ) { // Gutenberg seems to try to mirror the caret state even on events that only change the content so, // let's force caret update if state has selection set. @@ -824,7 +824,7 @@ export class RichText extends Component { const { style, tagName } = this.props; const { currentFontSize } = this.state; - if ( this.props.value.toString() !== this.value.toString() ) { + if ( this.props.value?.toString() !== this.value?.toString() ) { this.value = this.props.value; } const { __unstableIsSelected: prevIsSelected } = prevProps; @@ -884,8 +884,8 @@ export class RichText extends Component { // On android if content is empty we need to send no content or else the placeholder will not show. if ( ! this.isIOS && - ( value.toString() === '' || - value.toString() === EMPTY_PARAGRAPH_TAGS ) + ( value?.toString() === '' || + value?.toString() === EMPTY_PARAGRAPH_TAGS ) ) { return ''; } diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index fba0781e594fb..3013f9da005cd 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,7 @@ For each user feature we should also add a importance categorization label to i ## Unreleased - [**] Video block: Fix logic for displaying empty state based on source presence [#58015] +- [**] Fix crash when RichText values are not defined [#58088] ## 1.111.0 - [**] Image block media uploads display a custom error message when there is no internet connection [#56937] From b9e3c9f311b5ac3f85646cd24c741f4aa47e5a25 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Mon, 22 Jan 2024 18:55:44 +0000 Subject: [PATCH 06/68] Update Changelog for 17.5.1 --- changelog.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/changelog.txt b/changelog.txt index b24b8294744d6..193fe8dfe70f6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ == Changelog == += 17.5.1 = + +## Changelog + +### Bug Fixes + +- (Preferences)(hotfix)(17.5) Hotfix for missing preferences in the `core` scope([58031](https://github.com/WordPress/gutenberg/pull/58031)) + +## Contributors + +The following contributors merged PRs in this release: + +@youknowriad @fullofcaffeine + + + + = 17.5.0 = ## Changelog From b2c2a085e30a50f51414f819220c9ff974cdd328 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Mon, 22 Jan 2024 21:02:37 +0100 Subject: [PATCH 07/68] Fix php unit tests (after variations api change) (#58090) --- packages/block-library/src/navigation-link/index.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php index 1dcb2996a8e15..71ef26b630d51 100644 --- a/packages/block-library/src/navigation-link/index.php +++ b/packages/block-library/src/navigation-link/index.php @@ -338,7 +338,10 @@ function block_core_navigation_link_register_variation( $variation ) { return; } - $navigation_block_type->variations[] = $variation; + $navigation_block_type->variations = array_merge( + $navigation_block_type->variations, + array( $variation ) + ); } /** @@ -355,15 +358,16 @@ function block_core_navigation_link_unregister_variation( $name ) { if ( ! $navigation_block_type || empty( $navigation_block_type->variations ) ) { return; } + $variations = $navigation_block_type->variations; // Search for the variation and remove it from the array. - foreach ( $navigation_block_type->variations as $i => $variation ) { + foreach ( $variations as $i => $variation ) { if ( $variation['name'] === $name ) { - unset( $navigation_block_type->variations[ $i ] ); + unset( $variations[ $i ] ); break; } } // Reindex array after removing one variation. - $navigation_block_type->variations = array_values( $navigation_block_type->variations ); + $navigation_block_type->variations = array_values( $variations ); } /** From 6ab0f47f646fcd4be1d68b6e3458ac5fe94e3a48 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 23 Jan 2024 09:59:24 +1100 Subject: [PATCH 08/68] Outline editable blocks when in a pattern that has locked children (#57991) Use :has() to outline editable blocks that are within a pattern that has locked children but are themselves not locked. For example a pattern that has multiple paragraphs, not in a container, and some of those paragraphs allows an override. --- .../src/components/block-list/content.scss | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss index 8cd75f6855b62..2b778c0892cfa 100644 --- a/packages/block-editor/src/components/block-list/content.scss +++ b/packages/block-editor/src/components/block-list/content.scss @@ -303,27 +303,34 @@ _::-webkit-full-page-media, _:future, :root .has-multi-selection .block-editor-b } } -// Indicate which blocks are editable within a page editor or a content-locked -// pattern. Only show when user hovers over the page editor or pattern. +// Indicate which blocks are editable within a locked context. +// 1. User must be hovering an editor with renderingMode = 'template-lock'; or... .is-template-locked:hover, +// ...a container block. .block-editor-block-list__block:hover { - .block-editor-block-list__block.is-editing-disabled > .block-editor-block-list__block:not(.is-editing-disabled):not(.is-selected):not(.has-child-selected) { - &::after { - content: ""; - border-style: dotted; - position: absolute; - pointer-events: none; - top: $border-width; - left: $border-width; - right: $border-width; - bottom: $border-width; - border: 1px dotted var(--wp-admin-theme-color); - border-radius: $radius-block-ui - $border-width; - } + // 2. Look for locked blocks; or... + .block-editor-block-list__block.is-editing-disabled, + // ...container blocks that have locked children. + &:has(> .block-editor-block-list__block.is-editing-disabled) { + // 3. Highlight any unlocked children of that locked block. + & > .block-editor-block-list__block:not(.is-editing-disabled):not(.is-selected):not(.has-child-selected) { + &::after { + content: ""; + border-style: dotted; + position: absolute; + pointer-events: none; + top: $border-width; + left: $border-width; + right: $border-width; + bottom: $border-width; + border: 1px dotted var(--wp-admin-theme-color); + border-radius: $radius-block-ui - $border-width; + } - &.is-hovered::after { - background: rgba(var(--wp-admin-theme-color--rgb), 0.1); - border: none; + &.is-hovered::after { + background: rgba(var(--wp-admin-theme-color--rgb), 0.1); + border: none; + } } } } From 2997bad8c9119307d0a2767581bc39d7466d439b Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:00:14 +1100 Subject: [PATCH 09/68] List View: Displace list view items when dragging (a bit more WYSIWYG) (#56625) * Include earlier experiment to make drag chip resemble the list view item * Try displacing list view items via transform: translateY while dragging * Try adding a bit of styling when nesting * Try to fix tests * Offset the top of the table to match the displacement * Improve the experience when beginning to drag a block * Smoothly hide gap when dragging outside of the list view area * Merge in changes from drag to collapsed block to expand PR * Try collapsing selected blocks down to a single row space while dragging * Fix jumpiness in Safari * Try making styling match the selected state * Try smoothly dropping to the final position * Fix transitions above dragged items * Fix flickering issue in Safari * Try a transparent background color * Fix drag behaviour in navigation block * Add vertical drop indicator * Try using existing drop indicator line * Add reduced motion, fix e2e test * Tidy up classname for drag chip in prep for subsequent changes * Remove indent from drop chip, ensure indent is applied to the position * Try updating the drag chip to use a border * Try drop indicator line as preview of final list item * Try offsets for nesting level of drop indicator, and use darker color if adjacent block has selected branch * Use drag chip as on trunk, but with opacity of 0.8 * Consolidate drop indicator logic * Fix drop indicator when dragging files * Don't add nesting classname when dragging files as the editor thinks we can nest for any block * Move displacement logic to a helper function, add tests * Remove unneeded changes from useMovingAnimation * Add explanatory comments * Try a timeout for the list view drop zone updates of 50ms instead of 200ms --- .../src/components/block-draggable/index.js | 21 +- .../components/list-view/block-contents.js | 6 +- .../src/components/list-view/block.js | 9 + .../src/components/list-view/branch.js | 40 +++- .../components/list-view/drop-indicator.js | 185 +++++++++++---- .../src/components/list-view/index.js | 77 +++++- .../src/components/list-view/leaf.js | 4 +- .../src/components/list-view/style.scss | 115 +++++++-- .../list-view/test/use-list-view-drop-zone.js | 4 + .../src/components/list-view/test/utils.js | 219 +++++++++++++++++- .../list-view/use-list-view-block-indexes.js | 29 +++ .../list-view/use-list-view-drop-zone.js | 84 ++++++- .../src/components/list-view/utils.js | 116 ++++++++++ .../sidebar-navigation-item/style.scss | 2 +- .../specs/editor/various/list-view.spec.js | 31 ++- 15 files changed, 853 insertions(+), 89 deletions(-) create mode 100644 packages/block-editor/src/components/list-view/use-list-view-block-indexes.js diff --git a/packages/block-editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js index 7191a0f1428a9..c86bc55e8ed07 100644 --- a/packages/block-editor/src/components/block-draggable/index.js +++ b/packages/block-editor/src/components/block-draggable/index.js @@ -17,12 +17,15 @@ import { __unstableUseBlockRef as useBlockRef } from '../block-list/use-block-pr import { isDropTargetValid } from '../use-block-drop-zone'; const BlockDraggable = ( { + appendToOwnerDocument, children, clientIds, cloneClassname, + elementId, onDragStart, onDragEnd, fadeWhenDisabled = false, + dragComponent, } ) => { const { srcRootClientId, @@ -181,6 +184,7 @@ const BlockDraggable = ( { return ( + // Check against `undefined` so that `null` can be used to disable + // the default drag component. + dragComponent !== undefined ? ( + dragComponent + ) : ( + + ) } + elementId={ elementId } > { ( { onDraggableStart, onDraggableEnd } ) => { return children( { diff --git a/packages/block-editor/src/components/list-view/block-contents.js b/packages/block-editor/src/components/list-view/block-contents.js index 8d5b03395f3e2..0537a4b48cbe4 100644 --- a/packages/block-editor/src/components/list-view/block-contents.js +++ b/packages/block-editor/src/components/list-view/block-contents.js @@ -74,7 +74,11 @@ const ListViewBlockContents = forwardRef( setInsertedBlock={ setInsertedBlock } /> ) } - + { ( { draggable, onDragStart, onDragEnd } ) => ( @@ -188,17 +206,20 @@ function ListViewBranch( props ) { selectBlock={ selectBlock } isSelected={ isSelected } isBranchSelected={ isSelectedBranch } - isDragged={ isDragged || isBranchDragged } + isDragged={ isDragged } level={ level } position={ position } rowCount={ rowCount } siblingBlockCount={ blockCount } showBlockMovers={ showBlockMovers } path={ updatedPath } - isExpanded={ shouldExpand } + isExpanded={ isDragged ? false : shouldExpand } listPosition={ nextPosition } selectedClientIds={ selectedClientIds } isSyncedBranch={ syncedBranch } + displacement={ displacement } + isAfterDraggedBlocks={ isAfterDraggedBlocks } + isNesting={ isNesting } /> ) } { ! showBlock && ( @@ -206,7 +227,7 @@ function ListViewBranch( props ) { ) } - { hasNestedBlocks && shouldExpand && ( + { hasNestedBlocks && shouldExpand && ! isDragged && ( { @@ -35,7 +60,7 @@ export default function ListViewDropIndicator( { : undefined; return [ _rootBlockElement, _blockElement ]; - }, [ rootClientId, clientId ] ); + }, [ listViewRef, rootClientId, clientId ] ); // The targetElement is the element that the drop indicator will appear // before or after. When dropping into an empty block list, blockElement @@ -44,27 +69,6 @@ export default function ListViewDropIndicator( { const rtl = isRTL(); - const getDropIndicatorIndent = useCallback( - ( targetElementRect ) => { - if ( ! rootBlockElement ) { - return 0; - } - - // Calculate the indent using the block icon of the root block. - // Using a classname selector here might be flaky and could be - // improved. - const rootBlockIconElement = rootBlockElement.querySelector( - '.block-editor-block-icon' - ); - const rootBlockIconRect = - rootBlockIconElement.getBoundingClientRect(); - return rtl - ? targetElementRect.right - rootBlockIconRect.left - : rootBlockIconRect.right - targetElementRect.left; - }, - [ rootBlockElement, rtl ] - ); - const getDropIndicatorWidth = useCallback( ( targetElementRect, indent ) => { if ( ! targetElement ) { @@ -143,12 +147,69 @@ export default function ListViewDropIndicator( { } const targetElementRect = targetElement.getBoundingClientRect(); - const indent = getDropIndicatorIndent( targetElementRect ); return { - width: getDropIndicatorWidth( targetElementRect, indent ), + width: getDropIndicatorWidth( targetElementRect, 0 ), }; - }, [ getDropIndicatorIndent, getDropIndicatorWidth, targetElement ] ); + }, [ getDropIndicatorWidth, targetElement ] ); + + const horizontalScrollOffsetStyle = useMemo( () => { + if ( ! targetElement ) { + return {}; + } + + const scrollContainer = getScrollContainer( targetElement ); + const ownerDocument = targetElement.ownerDocument; + const windowScroll = + scrollContainer === ownerDocument.body || + scrollContainer === ownerDocument.documentElement; + + if ( scrollContainer && ! windowScroll ) { + const scrollContainerRect = scrollContainer.getBoundingClientRect(); + const targetElementRect = targetElement.getBoundingClientRect(); + + const distanceBetweenContainerAndTarget = rtl + ? scrollContainerRect.right - targetElementRect.right + : targetElementRect.left - scrollContainerRect.left; + + if ( ! rtl && scrollContainerRect.left > targetElementRect.left ) { + return { + transform: `translateX( ${ distanceBetweenContainerAndTarget }px )`, + }; + } + + if ( rtl && scrollContainerRect.right < targetElementRect.right ) { + return { + transform: `translateX( ${ + distanceBetweenContainerAndTarget * -1 + }px )`, + }; + } + } + + return {}; + }, [ rtl, targetElement ] ); + + const ariaLevel = useMemo( () => { + if ( ! rootBlockElement ) { + return 1; + } + + const _ariaLevel = parseInt( + rootBlockElement.getAttribute( 'aria-level' ), + 10 + ); + + return _ariaLevel ? _ariaLevel + 1 : 1; + }, [ rootBlockElement ] ); + + const hasAdjacentSelectedBranch = useMemo( () => { + if ( ! targetElement ) { + return false; + } + + return targetElement.classList.contains( 'is-branch-selected' ); + }, [ targetElement ] ); const popoverAnchor = useMemo( () => { const isValidDropPosition = @@ -163,16 +224,15 @@ export default function ListViewDropIndicator( { contextElement: targetElement, getBoundingClientRect() { const rect = targetElement.getBoundingClientRect(); - const indent = getDropIndicatorIndent( rect ); // In RTL languages, the drop indicator should be positioned // to the left of the target element, with the width of the // indicator determining the indent at the right edge of the // target element. In LTR languages, the drop indicator should // end at the right edge of the target element, with the indent // added to the position of the left edge of the target element. - let left = rtl ? rect.left : rect.left + indent; + // let left = rtl ? rect.left : rect.left + indent; + let left = rect.left; let top = 0; - let bottom = 0; // In deeply nested lists, where a scrollbar is present, // the width of the drop indicator should be the width of @@ -212,27 +272,19 @@ export default function ListViewDropIndicator( { } if ( dropPosition === 'top' ) { - top = rect.top; - bottom = rect.top; + top = rect.top - rect.height * 2; } else { // `dropPosition` is either `bottom` or `inside` - top = rect.bottom; - bottom = rect.bottom; + top = rect.top; } - const width = getDropIndicatorWidth( rect, indent ); - const height = bottom - top; + const width = getDropIndicatorWidth( rect, 0 ); + const height = rect.height; return new window.DOMRect( left, top, width, height ); }, }; - }, [ - targetElement, - dropPosition, - getDropIndicatorIndent, - getDropIndicatorWidth, - rtl, - ] ); + }, [ targetElement, dropPosition, getDropIndicatorWidth, rtl ] ); if ( ! targetElement ) { return null; @@ -243,13 +295,54 @@ export default function ListViewDropIndicator( { animate={ false } anchor={ popoverAnchor } focusOnMount={ false } - className="block-editor-list-view-drop-indicator" + className="block-editor-list-view-drop-indicator--preview" variant="unstyled" + flip={ false } + resize={ true } >
+ className={ classnames( + 'block-editor-list-view-drop-indicator__line', + { + 'block-editor-list-view-drop-indicator__line--darker': + hasAdjacentSelectedBranch, + } + ) } + > +
+
+ {} } /> + + + + + { blockTitle } + + + +
+
+
+
); } diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 315a6153839d1..5270a7af3a296 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -28,8 +33,9 @@ import { __ } from '@wordpress/i18n'; */ import ListViewBranch from './branch'; import { ListViewContext } from './context'; -import ListViewDropIndicator from './drop-indicator'; +import ListViewDropIndicatorPreview from './drop-indicator'; import useBlockSelection from './use-block-selection'; +import useListViewBlockIndexes from './use-list-view-block-indexes'; import useListViewClientIds from './use-list-view-client-ids'; import useListViewDropZone from './use-list-view-drop-zone'; import useListViewExpandSelectedItem from './use-list-view-expand-selected-item'; @@ -105,6 +111,7 @@ function ListViewComponent( const instanceId = useInstanceId( ListViewComponent ); const { clientIdsTree, draggedClientIds, selectedClientIds } = useListViewClientIds( { blocks, rootClientId } ); + const blockIndexes = useListViewBlockIndexes( clientIdsTree ); const { getBlock } = useSelect( blockEditorStore ); const { visibleBlockCount, shouldShowInnerBlocks } = useSelect( @@ -132,6 +139,8 @@ function ListViewComponent( const { ref: dropZoneRef, target: blockDropTarget } = useListViewDropZone( { dropZoneElement, + expandedState, + setExpandedState, } ); const elementRef = useRef(); const treeGridRef = useMergeRefs( [ elementRef, dropZoneRef, ref ] ); @@ -210,11 +219,55 @@ function ListViewComponent( [ updateBlockSelection ] ); + const firstDraggedBlockClientId = draggedClientIds?.[ 0 ]; + + // Convert a blockDropTarget into indexes relative to the blocks in the list view. + // These values are used to determine which blocks should be displaced to make room + // for the drop indicator. See `ListViewBranch` and `getDragDisplacementValues`. + const { blockDropTargetIndex, blockDropPosition, firstDraggedBlockIndex } = + useMemo( () => { + let _blockDropTargetIndex, _firstDraggedBlockIndex; + + if ( blockDropTarget?.clientId ) { + const foundBlockIndex = + blockIndexes[ blockDropTarget.clientId ]; + // If dragging below or inside the block, treat the drop target as the next block. + _blockDropTargetIndex = + foundBlockIndex === undefined || + blockDropTarget?.dropPosition === 'top' + ? foundBlockIndex + : foundBlockIndex + 1; + } else if ( blockDropTarget === null ) { + // A `null` value is used to indicate that the user is dragging outside of the list view. + _blockDropTargetIndex = null; + } + + if ( firstDraggedBlockClientId ) { + const foundBlockIndex = + blockIndexes[ firstDraggedBlockClientId ]; + _firstDraggedBlockIndex = + foundBlockIndex === undefined || + blockDropTarget?.dropPosition === 'top' + ? foundBlockIndex + : foundBlockIndex + 1; + } + + return { + blockDropTargetIndex: _blockDropTargetIndex, + blockDropPosition: blockDropTarget?.dropPosition, + firstDraggedBlockIndex: _firstDraggedBlockIndex, + }; + }, [ blockDropTarget, blockIndexes, firstDraggedBlockClientId ] ); + const contextValue = useMemo( () => ( { + blockDropPosition, + blockDropTargetIndex, + blockIndexes, draggedClientIds, expandedState, expand, + firstDraggedBlockIndex, collapse, BlockSettingsMenu, listViewInstanceId: instanceId, @@ -225,9 +278,13 @@ function ListViewComponent( rootClientId, } ), [ + blockDropPosition, + blockDropTargetIndex, + blockIndexes, draggedClientIds, expandedState, expand, + firstDraggedBlockIndex, collapse, BlockSettingsMenu, instanceId, @@ -267,7 +324,8 @@ function ListViewComponent( return ( - @@ -278,7 +336,11 @@ function ListViewComponent( ) } 0 && + blockDropTargetIndex !== undefined, + } ) } aria-label={ __( 'Block navigation structure' ) } ref={ treeGridRef } onCollapseRow={ collapseRow } @@ -286,6 +348,15 @@ function ListViewComponent( onFocusRow={ focusRow } applicationAriaLabel={ __( 'Block navigation structure' ) } aria-describedby={ describedById } + style={ { + '--wp-admin--list-view-dragged-items-height': + draggedClientIds?.length + ? `${ + BLOCK_LIST_ITEM_HEIGHT * + ( draggedClientIds.length - 1 ) + }px` + : null, + } } > { const animationRef = useMovingAnimation( { - isSelected, - adjustScrolling: false, + clientId: props[ 'data-block' ], enableAnimation: true, triggerAnimationOnChange: path, } ); diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 42423328d19e4..11cf1fafa0e14 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -9,11 +9,22 @@ margin: (-$grid-unit-15) (-$grid-unit-15 * 0.5) 0; width: calc(100% + #{ $grid-unit-15 }); } + + // Without setting `pointer-events: none`, when dragging over list view items, + // Safari calls onDropZoneLeave causing flickering in the position of the drop indicator. + // https://bugs.webkit.org/show_bug.cgi?id=66547 + // See: https://github.com/WordPress/gutenberg/pull/56625 + &.is-dragging tbody { + pointer-events: none; + } } .block-editor-list-view-leaf { // Use position relative for row animation. position: relative; + // Set the initial translate position to ensure the UI in Safari doesn't jump + // when rows later receive the is-displacement-up or is-displacement-down class. + transform: translateY(0); &.is-draggable, &.is-draggable .block-editor-list-view-block-contents { @@ -128,6 +139,66 @@ border-radius: 0; } + // List view items will be displaced up or down relative to their original position + // when a user is dragging a block within the list view. This creates a space for a + // visual indicator of where the block will be placed when dropped. The `normal` state + // is used to allow rows to smoothly transition back into their original position, + // without attaching the transition to the list view leaf itself. This prevents rows + // from animating too much once the user has dropped the block. + &.is-displacement-normal { + transition: transform 0.2s; + transform: translateY(0); + @include reduce-motion("transition"); + } + + &.is-displacement-up { + transition: transform 0.2s; + transform: translateY(-36px); + @include reduce-motion("transition"); + } + + &.is-displacement-down { + transition: transform 0.2s; + transform: translateY(36px); + @include reduce-motion("transition"); + } + + // Collapse multi-selections down into a single row space while dragging. The following + // rules ensure that during a multi-selection, the space for additional blocks is factored in + // when displacing up and down. The result is that there should only ever be a single row's + // worth of space for the visual indicator of where a block will be placed when dropped. + &.is-after-dragged-blocks { + transition: transform 0.2s; + transform: translateY(calc(var(--wp-admin--list-view-dragged-items-height, 36px) * -1)); + @include reduce-motion("transition"); + } + + &.is-after-dragged-blocks.is-displacement-up { + transition: transform 0.2s; + transform: translateY(calc(-36px + var(--wp-admin--list-view-dragged-items-height, 36px) * -1)); + @include reduce-motion("transition"); + } + + &.is-after-dragged-blocks.is-displacement-down { + transition: transform 0.2s; + transform: translateY(calc(36px + var(--wp-admin--list-view-dragged-items-height, 36px) * -1)); + @include reduce-motion("transition"); + } + + // To ensure displaced rows behave correctly, ensure that blocks that are currently being dragged + // are visually hidden. The below rules are used in favor of `display: none` to ensure that + // there is no flicker when a user begins to drag a block. + &.is-dragging { + opacity: 0; + // The row's left position is set to 0 to ensure that the animation + // when dropping a block animates from the correct position. + left: 0; + // Ensure the dragged element does not otherwise affect dropping to + // other positions. + pointer-events: none; + z-index: -9999; + } + // List View renders a fixed number of items and relies on each item having a fixed height of 36px. // If this value changes, we should also change the itemHeight value set in useFixedWindowList. // See: https://github.com/WordPress/gutenberg/pull/35230 for additional context. @@ -159,6 +230,7 @@ } } + &.is-nesting .block-editor-list-view-block-contents, .block-editor-list-view-block-contents:focus { box-shadow: none; @@ -173,11 +245,6 @@ box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); z-index: 2; pointer-events: none; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } } } @@ -186,14 +253,10 @@ right: 0; } + &.is-nesting .block-editor-list-view__menu, .block-editor-list-view-block__menu:focus { box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); z-index: 1; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } } &.is-visible .block-editor-list-view-block-contents { @@ -370,6 +433,14 @@ } } +// First level of indentation is aria-level 2, max indent is 8. +// Indent is a full icon size, plus 4px which optically aligns child icons to the text label above. +$block-navigation-max-indent: 8; + +.block-editor-list-view-draggable-chip { + opacity: 0.8; +} + .block-editor-list-view-block__contents-cell, .block-editor-list-view-appender__cell { .block-editor-list-view-block__contents-container, @@ -386,9 +457,6 @@ cursor: pointer; } -// First level of indentation is aria-level 2, max indent is 8. -// Indent is a full icon size, plus 4px which optically aligns child icons to the text label above. -$block-navigation-max-indent: 8; .block-editor-list-view-leaf[aria-level] .block-editor-list-view__expander { margin-left: ( $icon-size ) * $block-navigation-max-indent + 4 * ( $block-navigation-max-indent - 1 ); } @@ -443,6 +511,27 @@ $block-navigation-max-indent: 8; } } +.block-editor-list-view-drop-indicator--preview { + pointer-events: none; + + // If the drop indicator's popover ever extends to the edge of the screen, + // avoid any scrollbars by hiding the overflow. + .components-popover__content { + overflow: hidden !important; + } + + .block-editor-list-view-drop-indicator__line { + background: rgba(var(--wp-admin-theme-color--rgb), 0.04); + height: 36px; + border-radius: 4px; + overflow: hidden; + } + + .block-editor-list-view-drop-indicator__line--darker { + background: rgba(var(--wp-admin-theme-color--rgb), 0.09); + } +} + .block-editor-list-view-placeholder { padding: 0; margin: 0; diff --git a/packages/block-editor/src/components/list-view/test/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/test/use-list-view-drop-zone.js index 98c2b31132c60..f1180a9fa27ca 100644 --- a/packages/block-editor/src/components/list-view/test/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/test/use-list-view-drop-zone.js @@ -127,6 +127,7 @@ describe( 'getListViewDropTarget', () => { expect( target ).toEqual( { blockIndex: 0, + clientId: 'block-1', dropPosition: 'inside', rootClientId: 'block-1', } ); @@ -142,6 +143,7 @@ describe( 'getListViewDropTarget', () => { expect( target ).toEqual( { blockIndex: 0, + clientId: 'block-3', dropPosition: 'inside', rootClientId: 'block-3', } ); @@ -244,6 +246,7 @@ describe( 'getListViewDropTarget', () => { expect( target ).toEqual( { blockIndex: 1, + clientId: 'block-1', dropPosition: 'inside', rootClientId: 'block-1', } ); @@ -277,6 +280,7 @@ describe( 'getListViewDropTarget', () => { expect( target ).toEqual( { blockIndex: 0, + clientId: 'block-1', dropPosition: 'inside', rootClientId: 'block-1', } ); diff --git a/packages/block-editor/src/components/list-view/test/utils.js b/packages/block-editor/src/components/list-view/test/utils.js index 78d78a9d90069..d603d9ef1bb8b 100644 --- a/packages/block-editor/src/components/list-view/test/utils.js +++ b/packages/block-editor/src/components/list-view/test/utils.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { getCommonDepthClientIds } from '../utils'; +import { getCommonDepthClientIds, getDragDisplacementValues } from '../utils'; describe( 'getCommonDepthClientIds', () => { it( 'should return start and end when no depth is provided', () => { @@ -48,3 +48,220 @@ describe( 'getCommonDepthClientIds', () => { expect( result ).toEqual( { start: 'start-3', end: 'clicked-id' } ); } ); } ); + +describe( 'getDragDisplacementValues', () => { + const blockIndexes = { + 'block-a': 0, + 'block-b': 1, + 'block-c': 2, + 'block-d': 3, + 'block-e': 4, + 'block-f': 5, + 'block-g': 6, + 'block-h': 7, + }; + + it( 'should return displacement of normal when block is after dragged block and drop target', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: 3, + blockDropPosition: 'bottom', + clientId: 'block-h', + firstDraggedBlockIndex: 5, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'normal', + isAfterDraggedBlocks: true, + isNesting: false, + } ); + } ); + + it( 'should return displacement of up when block is after dragged block and before the drop target', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: 7, + blockDropPosition: 'bottom', + clientId: 'block-d', + firstDraggedBlockIndex: 2, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'up', + isAfterDraggedBlocks: true, + isNesting: false, + } ); + } ); + + it( 'should return displacement of down when block is before dragged block and after the drop target', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: 1, + blockDropPosition: 'bottom', + clientId: 'block-d', + firstDraggedBlockIndex: 6, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'down', + isAfterDraggedBlocks: false, + isNesting: false, + } ); + } ); + + it( 'should return isNesting of true when block is just before the drop target and drop position is inside', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: 1, + blockDropPosition: 'inside', + clientId: 'block-a', + firstDraggedBlockIndex: 6, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'normal', + isAfterDraggedBlocks: false, + isNesting: true, + } ); + } ); + + it( 'should return isNesting of false when block is not just before the drop target and drop position is inside', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: 1, + blockDropPosition: 'inside', + clientId: 'block-b', + firstDraggedBlockIndex: 6, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'down', + isAfterDraggedBlocks: false, + isNesting: false, + } ); + } ); + + it( 'should return displacement of up when drop target index is null and block is after dragged block', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: null, + blockDropPosition: 'bottom', + clientId: 'block-h', + firstDraggedBlockIndex: 5, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'up', + isAfterDraggedBlocks: true, + isNesting: false, + } ); + } ); + + it( 'should return displacement of normal when drop target index is null and block is before dragged block', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: null, + blockDropPosition: 'bottom', + clientId: 'block-c', + firstDraggedBlockIndex: 5, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'normal', + isAfterDraggedBlocks: false, + isNesting: false, + } ); + } ); + + it( 'should return displacement of normal when dragging a file (no dragged block) and the block is before the target index', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: 3, + blockDropPosition: 'bottom', + clientId: 'block-b', + firstDraggedBlockIndex: undefined, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'normal', + isAfterDraggedBlocks: false, + isNesting: false, + } ); + } ); + + it( 'should return displacement of down when dragging a file (no dragged block) and the block is after the target index', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: 3, + blockDropPosition: 'bottom', + clientId: 'block-h', + firstDraggedBlockIndex: undefined, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'down', + isAfterDraggedBlocks: false, + isNesting: false, + } ); + } ); + + it( 'should return displacement of normal when dragging a file (no dragged block) and dragging outside the list view (drop target of null)', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: null, + blockDropPosition: 'bottom', + clientId: 'block-h', + firstDraggedBlockIndex: undefined, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: 'normal', + isAfterDraggedBlocks: false, + isNesting: false, + } ); + } ); + + it( 'should return undefined displacement if the target index is undefined', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: undefined, + blockDropPosition: 'bottom', + clientId: 'block-h', + firstDraggedBlockIndex: undefined, + isDragged: false, + } ); + + expect( result ).toEqual( { + displacement: undefined, + isAfterDraggedBlocks: false, + isNesting: false, + } ); + } ); + + it( 'should return all undefined values if the block is dragged', () => { + const result = getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex: 3, + blockDropPosition: 'bottom', + clientId: 'block-h', + firstDraggedBlockIndex: 7, + isDragged: true, + } ); + + expect( result ).toEqual( { + displacement: undefined, + isAfterDraggedBlocks: undefined, + isNesting: undefined, + } ); + } ); +} ); diff --git a/packages/block-editor/src/components/list-view/use-list-view-block-indexes.js b/packages/block-editor/src/components/list-view/use-list-view-block-indexes.js new file mode 100644 index 0000000000000..5890e366d7ce9 --- /dev/null +++ b/packages/block-editor/src/components/list-view/use-list-view-block-indexes.js @@ -0,0 +1,29 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +export default function useListViewBlockIndexes( blocks ) { + const blockIndexes = useMemo( () => { + const indexes = {}; + + let currentGlobalIndex = 0; + + const traverseBlocks = ( blockList ) => { + blockList.forEach( ( block ) => { + indexes[ block.clientId ] = currentGlobalIndex; + currentGlobalIndex++; + + if ( block.innerBlocks.length > 0 ) { + traverseBlocks( block.innerBlocks ); + } + } ); + }; + + traverseBlocks( blocks ); + + return indexes; + }, [ blocks ] ); + + return blockIndexes; +} diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index a1a369d3f9408..3354b3f41d391 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -2,10 +2,11 @@ * WordPress dependencies */ import { useSelect } from '@wordpress/data'; -import { useState, useCallback } from '@wordpress/element'; +import { useState, useCallback, useEffect } from '@wordpress/element'; import { useThrottle, __experimentalUseDropZone as useDropZone, + usePrevious, } from '@wordpress/compose'; import { isRTL } from '@wordpress/i18n'; @@ -304,6 +305,7 @@ export function getListViewDropTarget( blocksData, position, rtl = false ) { return { rootClientId: candidateBlockData.clientId, + clientId: candidateBlockData.clientId, blockIndex: newBlockIndex, dropPosition: 'inside', }; @@ -396,15 +398,30 @@ export function getListViewDropTarget( blocksData, position, rtl = false ) { }; } +// Throttle options need to be defined outside of the hook to avoid +// re-creating the object on every render. This is due to a limitation +// of the `useThrottle` hook, where the options object is included +// in the dependency array for memoization. +const EXPAND_THROTTLE_OPTIONS = { + leading: false, // Don't call the function immediately on the first call. + trailing: true, // Do call the function on the last call. +}; + /** * A react hook for implementing a drop zone in list view. * - * @param {Object} props Named parameters. - * @param {?HTMLElement} [props.dropZoneElement] Optional element to be used as the drop zone. + * @param {Object} props Named parameters. + * @param {?HTMLElement} [props.dropZoneElement] Optional element to be used as the drop zone. + * @param {Object} [props.expandedState] The expanded state of the blocks in the list view. + * @param {Function} [props.setExpandedState] Function to set the expanded state of a list of block clientIds. * * @return {WPListViewDropZoneTarget} The drop target. */ -export default function useListViewDropZone( { dropZoneElement } ) { +export default function useListViewDropZone( { + dropZoneElement, + expandedState, + setExpandedState, +} ) { const { getBlockRootClientId, getBlockIndex, @@ -420,6 +437,55 @@ export default function useListViewDropZone( { dropZoneElement } ) { const rtl = isRTL(); + const previousRootClientId = usePrevious( targetRootClientId ); + + const maybeExpandBlock = useCallback( + ( _expandedState, _target ) => { + // If the user is attempting to drop a block inside a collapsed block, + // that is, using a nesting gesture flagged by 'inside' dropPosition, + // expand the block within the list view, if it isn't already. + const { rootClientId } = _target || {}; + if ( ! rootClientId ) { + return; + } + if ( + _target?.dropPosition === 'inside' && + ! _expandedState[ rootClientId ] + ) { + setExpandedState( { + type: 'expand', + clientIds: [ rootClientId ], + } ); + } + }, + [ setExpandedState ] + ); + + // Throttle the maybeExpandBlock function to avoid expanding the block + // too quickly when the user is dragging over the block. This is to + // avoid expanding the block when the user is just passing over it. + const throttledMaybeExpandBlock = useThrottle( + maybeExpandBlock, + 500, + EXPAND_THROTTLE_OPTIONS + ); + + useEffect( () => { + if ( + target?.dropPosition !== 'inside' || + previousRootClientId !== target?.rootClientId + ) { + throttledMaybeExpandBlock.cancel(); + return; + } + throttledMaybeExpandBlock( expandedState, target ); + }, [ + expandedState, + previousRootClientId, + target, + throttledMaybeExpandBlock, + ] ); + const draggedBlockClientIds = getDraggedBlockClientIds(); const throttled = useThrottle( useCallback( @@ -484,7 +550,7 @@ export default function useListViewDropZone( { dropZoneElement } ) { rtl, ] ), - 200 + 50 ); const ref = useDropZone( { @@ -496,6 +562,9 @@ export default function useListViewDropZone( { dropZoneElement } ) { }, onDragLeave() { throttled.cancel(); + // Use `null` value to indicate that the drop target is not valid, + // but that the drag is still active. This allows for styling rules + // that are active only when a user drags outside of the list view. setTarget( null ); }, onDragOver( event ) { @@ -506,7 +575,10 @@ export default function useListViewDropZone( { dropZoneElement } ) { }, onDragEnd() { throttled.cancel(); - setTarget( null ); + // Use `undefined` value to indicate that the drag has concluded. + // This allows styling rules that are active only when a user is + // dragging to be removed. + setTarget( undefined ); }, } ); diff --git a/packages/block-editor/src/components/list-view/utils.js b/packages/block-editor/src/components/list-view/utils.js index 632173e120691..ed7a321dea0c8 100644 --- a/packages/block-editor/src/components/list-view/utils.js +++ b/packages/block-editor/src/components/list-view/utils.js @@ -93,3 +93,119 @@ export function focusListItem( focusClientId, treeGridElementRef ) { } ); } } + +/** + * Get values for the block that flag whether the block should be displaced up or down, + * whether the block is being nested, and whether the block appears after the dragged + * blocks. These values are used to determine the class names to apply to the block. + * The list view rows are displaced visually via CSS rules. Displacement rules: + * - `normal`: no displacement — used to apply a translateY of `0` so that the block + * appears in its original position, and moves to that position smoothly when dragging + * outside of the list view area. + * - `up`: the block should be displaced up, creating room beneath the block for the drop indicator. + * - `down`: the block should be displaced down, creating room above the block for the drop indicator. + * + * @param {Object} props + * @param {Object} props.blockIndexes The indexes of all the blocks in the list view, keyed by clientId. + * @param {number|null|undefined} props.blockDropTargetIndex The index of the block that the user is dropping to. + * @param {?string} props.blockDropPosition The position relative to the block that the user is dropping to. + * @param {string} props.clientId The client id for the current block. + * @param {?number} props.firstDraggedBlockIndex The index of the first dragged block. + * @param {?boolean} props.isDragged Whether the current block is being dragged. Dragged blocks skip displacement. + * @return {Object} An object containing the `displacement`, `isAfterDraggedBlocks` and `isNesting` values. + */ +export function getDragDisplacementValues( { + blockIndexes, + blockDropTargetIndex, + blockDropPosition, + clientId, + firstDraggedBlockIndex, + isDragged, +} ) { + let displacement; + let isNesting; + let isAfterDraggedBlocks; + + if ( ! isDragged ) { + isNesting = false; + const thisBlockIndex = blockIndexes[ clientId ]; + isAfterDraggedBlocks = thisBlockIndex > firstDraggedBlockIndex; + + // Determine where to displace the position of the current block, relative + // to the blocks being dragged (in their original position) and the drop target + // (the position where a user is currently dragging the blocks to). + if ( + blockDropTargetIndex !== undefined && + blockDropTargetIndex !== null && + firstDraggedBlockIndex !== undefined + ) { + // If the block is being dragged and there is a valid drop target, + // determine if the block being rendered should be displaced up or down. + + if ( thisBlockIndex !== undefined ) { + if ( + thisBlockIndex >= firstDraggedBlockIndex && + thisBlockIndex < blockDropTargetIndex + ) { + // If the current block appears after the set of dragged blocks + // (in their original position), but is before the drop target, + // then the current block should be displaced up. + displacement = 'up'; + } else if ( + thisBlockIndex < firstDraggedBlockIndex && + thisBlockIndex >= blockDropTargetIndex + ) { + // If the current block appears before the set of dragged blocks + // (in their original position), but is after the drop target, + // then the current block should be displaced down. + displacement = 'down'; + } else { + displacement = 'normal'; + } + isNesting = + typeof blockDropTargetIndex === 'number' && + blockDropTargetIndex - 1 === thisBlockIndex && + blockDropPosition === 'inside'; + } + } else if ( + blockDropTargetIndex === null && + firstDraggedBlockIndex !== undefined + ) { + // A `null` value for `blockDropTargetIndex` indicates that the + // drop target is outside of the valid areas within the list view. + // In this case, the drag is still active, but as there is no + // valid drop target, we should remove the gap indicating where + // the block would be inserted. + if ( + thisBlockIndex !== undefined && + thisBlockIndex >= firstDraggedBlockIndex + ) { + displacement = 'up'; + } else { + displacement = 'normal'; + } + } else if ( + blockDropTargetIndex !== undefined && + blockDropTargetIndex !== null && + firstDraggedBlockIndex === undefined + ) { + // If the blockdrop target is defined, but there are no dragged blocks, + // then the block should be displaced relative to the drop target. + if ( thisBlockIndex !== undefined ) { + if ( thisBlockIndex < blockDropTargetIndex ) { + displacement = 'normal'; + } else { + displacement = 'down'; + } + } + } else if ( blockDropTargetIndex === null ) { + displacement = 'normal'; + } + } + + return { + displacement, + isNesting, + isAfterDraggedBlocks, + }; +} diff --git a/packages/edit-site/src/components/sidebar-navigation-item/style.scss b/packages/edit-site/src/components/sidebar-navigation-item/style.scss index 88ff27a9c1d2f..908056d52af48 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-item/style.scss @@ -33,5 +33,5 @@ .edit-site-sidebar-navigation-screen__content .block-editor-list-view-block-select-button { cursor: grab; - padding: $grid-unit-10; + padding: $grid-unit-10 $grid-unit-10 $grid-unit-10 0; } diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js index 674801cf94aba..00f21b4e51c5e 100644 --- a/test/e2e/specs/editor/various/list-view.spec.js +++ b/test/e2e/specs/editor/various/list-view.spec.js @@ -53,11 +53,40 @@ test.describe( 'List View', () => { name: 'Paragraph', exact: true, } ); + const imageBlockItem = listView.getByRole( 'gridcell', { + name: 'Image', + exact: true, + } ); const headingBlockItem = listView.getByRole( 'gridcell', { name: 'Heading', exact: true, } ); - await paragraphBlockItem.dragTo( headingBlockItem, { x: 0, y: 0 } ); + + await paragraphBlockItem.hover(); + await page.mouse.down(); + + // To work around a drag and drop bug in Safari, the list view applies + // `pointer-events: none` to the list view while dragging, so that + // `onDragLeave` is not fired when dragging within the list view. + // Without the `force: true` option, the `hover` action will fail + // as playwright will complain that pointer-events are intercepted. + // https://bugs.webkit.org/show_bug.cgi?id=66547 + // See: https://github.com/WordPress/gutenberg/pull/56625 + + // Hover over each block to mimic moving up the list view. + // Also, hover twice to ensure a dragover event is dispatched. + // See: https://playwright.dev/docs/input#dragging-manually + await imageBlockItem.hover( { force: true } ); + await imageBlockItem.hover( { force: true } ); + await headingBlockItem.hover( { force: true } ); + await headingBlockItem.hover( { force: true } ); + + // Disable reason: Need to wait until the throttle timeout of 250ms has passed. + /* eslint-disable playwright/no-wait-for-timeout */ + await editor.page.waitForTimeout( 300 ); + /* eslint-enable playwright/no-wait-for-timeout */ + + await page.mouse.up(); // Ensure the block was dropped correctly. await expect From 31d0943cbc244479b04362592d9b9e9f9bd35a35 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:51:22 +1000 Subject: [PATCH 10/68] Fix: Theme.json application of custom root selector for styles (#58050) --- lib/class-wp-theme-json-gutenberg.php | 4 ++-- phpunit/class-wp-theme-json-test.php | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index a063ab34d9069..f4266a7ef66dd 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1010,7 +1010,7 @@ protected static function get_blocks_metadata() { if ( $duotone_support ) { $root_selector = wp_get_block_css_selector( $block_type ); - $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); + $duotone_selector = static::scope_selector( $root_selector, $duotone_support ); } } @@ -1185,7 +1185,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' $setting_nodes[ $root_settings_key ]['selector'] = $options['root_selector']; } if ( false !== $root_style_key ) { - $setting_nodes[ $root_style_key ]['selector'] = $options['root_selector']; + $style_nodes[ $root_style_key ]['selector'] = $options['root_selector']; } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 89900d45893d9..95d7c92507a62 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -2451,4 +2451,28 @@ public function test_resolve_variables() { $this->assertEquals( $small_font, $styles['blocks']['core/quote']['variations']['plain']['typography']['fontSize'], 'Block variations: font-size' ); $this->assertEquals( $secondary_color, $styles['blocks']['core/quote']['variations']['plain']['color']['background'], 'Block variations: color' ); } + + public function test_get_stylesheet_custom_root_selector() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'text' => 'teal', + ), + ), + ), + 'default' + ); + + $options = array( 'root_selector' => '.custom' ); + $actual = $theme_json->get_stylesheet( array( 'styles' ), null, $options ); + + // Results also include root site blocks styles which hard code + // `body { margin: 0;}`. + $this->assertEquals( + 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.custom{color: teal;}', + $actual + ); + } } From d1d1921b43f5819ae0936e29af57bce54aa56a77 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Tue, 23 Jan 2024 01:50:28 -0300 Subject: [PATCH 11/68] [Fonts API] removing files and files loading no longer needed (#57972) * removing files and files loading no longer needed * redeclaration guard in load.php * format php * Removes all tests * Comment adjustments to load.php --------- Co-authored-by: hellofromtonya --- .../class-gutenberg-fonts-api-bc-layer.php | 96 -- .../fonts-api/bc-layer/class-wp-web-fonts.php | 703 --------- .../class-wp-webfonts-provider-local.php | 284 ---- .../bc-layer/class-wp-webfonts-provider.php | 66 - .../bc-layer/class-wp-webfonts-utils.php | 85 - .../fonts-api/bc-layer/class-wp-webfonts.php | 238 --- .../bc-layer/webfonts-deprecations.php | 260 --- .../class-wp-fonts-provider-local.php | 236 --- .../fonts-api/class-wp-fonts-provider.php | 129 -- .../fonts-api/class-wp-fonts-resolver.php | 408 ----- .../fonts-api/class-wp-fonts-utils.php | 122 -- lib/experimental/fonts-api/class-wp-fonts.php | 753 --------- lib/experimental/fonts-api/fonts-api.php | 259 --- lib/load.php | 105 +- phpunit/tests/fonts-api/base.php | 391 ----- phpunit/tests/fonts-api/bc-layer/base.php | 46 - .../bc-layer/bc-layer-tests-dataset.php | 483 ------ .../isDeprecatedStructure.php | 35 - .../migrateDeprecatedStructure.php | 38 - .../fonts-api/bc-layer/wpRegisterWebfonts.php | 62 - .../bc-layer/wpWebfonts/getAllWebfonts.php | 33 - .../bc-layer/wpWebfonts/getFontSlug.php | 134 -- .../wpWebfonts/getRegisteredWebfonts.php | 32 - .../bc-layer/wpWebfonts/registerWebfont.php | 113 -- .../fonts-api/wp-fonts-tests-dataset.php | 1396 ----------------- .../fonts-api/wpDeregisterFontFamily.php | 74 - .../fonts-api/wpDeregisterFontVariation.php | 298 ---- .../fonts-api/wpEnqueueFontVariations.php | 82 - phpunit/tests/fonts-api/wpEnqueueFonts.php | 122 -- phpunit/tests/fonts-api/wpFonts.php | 38 - phpunit/tests/fonts-api/wpFonts/add.php | 44 - .../tests/fonts-api/wpFonts/addFontFamily.php | 65 - .../tests/fonts-api/wpFonts/addVariation.php | 149 -- phpunit/tests/fonts-api/wpFonts/dequeue.php | 72 - phpunit/tests/fonts-api/wpFonts/doItem.php | 336 ---- phpunit/tests/fonts-api/wpFonts/doItems.php | 196 --- phpunit/tests/fonts-api/wpFonts/enqueue.php | 53 - .../tests/fonts-api/wpFonts/getEnqueued.php | 57 - .../tests/fonts-api/wpFonts/getProviders.php | 62 - .../tests/fonts-api/wpFonts/getRegistered.php | 90 -- phpunit/tests/fonts-api/wpFonts/query.php | 151 -- .../fonts-api/wpFonts/registerProvider.php | 116 -- phpunit/tests/fonts-api/wpFonts/remove.php | 118 -- .../fonts-api/wpFonts/removeFontFamily.php | 89 -- .../fonts-api/wpFonts/removeVariation.php | 278 ---- .../tests/fonts-api/wpFontsProviderLocal.php | 180 --- .../addMissingFontsToThemeJson.php | 252 --- .../enqueueUserSelectedFonts.php | 131 -- .../registerFontsFromThemeJson.php | 297 ---- .../convertFontFamilyIntoHandle.php | 84 - .../convertVariationIntoHandle.php | 122 -- .../getFontFamilyFromVariation.php | 134 -- .../fonts-api/wpFontsUtils/isDefined.php | 61 - phpunit/tests/fonts-api/wpPrintFonts.php | 230 --- .../fonts-api/wpRegisterFontProvider.php | 95 -- phpunit/tests/fonts-api/wpRegisterFonts.php | 104 -- 56 files changed, 35 insertions(+), 10652 deletions(-) delete mode 100644 lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php delete mode 100644 lib/experimental/fonts-api/bc-layer/class-wp-web-fonts.php delete mode 100644 lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider-local.php delete mode 100644 lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider.php delete mode 100644 lib/experimental/fonts-api/bc-layer/class-wp-webfonts-utils.php delete mode 100644 lib/experimental/fonts-api/bc-layer/class-wp-webfonts.php delete mode 100644 lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php delete mode 100644 lib/experimental/fonts-api/class-wp-fonts-provider-local.php delete mode 100644 lib/experimental/fonts-api/class-wp-fonts-provider.php delete mode 100644 lib/experimental/fonts-api/class-wp-fonts-resolver.php delete mode 100644 lib/experimental/fonts-api/class-wp-fonts-utils.php delete mode 100644 lib/experimental/fonts-api/class-wp-fonts.php delete mode 100644 lib/experimental/fonts-api/fonts-api.php delete mode 100644 phpunit/tests/fonts-api/base.php delete mode 100644 phpunit/tests/fonts-api/bc-layer/base.php delete mode 100644 phpunit/tests/fonts-api/bc-layer/bc-layer-tests-dataset.php delete mode 100644 phpunit/tests/fonts-api/bc-layer/gutenbergFontsApiBcLayer/isDeprecatedStructure.php delete mode 100644 phpunit/tests/fonts-api/bc-layer/gutenbergFontsApiBcLayer/migrateDeprecatedStructure.php delete mode 100644 phpunit/tests/fonts-api/bc-layer/wpRegisterWebfonts.php delete mode 100644 phpunit/tests/fonts-api/bc-layer/wpWebfonts/getAllWebfonts.php delete mode 100644 phpunit/tests/fonts-api/bc-layer/wpWebfonts/getFontSlug.php delete mode 100644 phpunit/tests/fonts-api/bc-layer/wpWebfonts/getRegisteredWebfonts.php delete mode 100644 phpunit/tests/fonts-api/bc-layer/wpWebfonts/registerWebfont.php delete mode 100644 phpunit/tests/fonts-api/wp-fonts-tests-dataset.php delete mode 100644 phpunit/tests/fonts-api/wpDeregisterFontFamily.php delete mode 100644 phpunit/tests/fonts-api/wpDeregisterFontVariation.php delete mode 100644 phpunit/tests/fonts-api/wpEnqueueFontVariations.php delete mode 100644 phpunit/tests/fonts-api/wpEnqueueFonts.php delete mode 100644 phpunit/tests/fonts-api/wpFonts.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/add.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/addFontFamily.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/addVariation.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/dequeue.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/doItem.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/doItems.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/enqueue.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/getEnqueued.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/getProviders.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/getRegistered.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/query.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/registerProvider.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/remove.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/removeFontFamily.php delete mode 100644 phpunit/tests/fonts-api/wpFonts/removeVariation.php delete mode 100644 phpunit/tests/fonts-api/wpFontsProviderLocal.php delete mode 100644 phpunit/tests/fonts-api/wpFontsResolver/addMissingFontsToThemeJson.php delete mode 100644 phpunit/tests/fonts-api/wpFontsResolver/enqueueUserSelectedFonts.php delete mode 100644 phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php delete mode 100644 phpunit/tests/fonts-api/wpFontsUtils/convertFontFamilyIntoHandle.php delete mode 100644 phpunit/tests/fonts-api/wpFontsUtils/convertVariationIntoHandle.php delete mode 100644 phpunit/tests/fonts-api/wpFontsUtils/getFontFamilyFromVariation.php delete mode 100644 phpunit/tests/fonts-api/wpFontsUtils/isDefined.php delete mode 100644 phpunit/tests/fonts-api/wpPrintFonts.php delete mode 100644 phpunit/tests/fonts-api/wpRegisterFontProvider.php delete mode 100644 phpunit/tests/fonts-api/wpRegisterFonts.php diff --git a/lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php b/lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php deleted file mode 100644 index 8c85976987523..0000000000000 --- a/lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php +++ /dev/null @@ -1,96 +0,0 @@ -variation_property_defaults = apply_filters( 'wp_webfont_variation_defaults', $this->variation_property_defaults ); - - /** - * Fires when the WP_Webfonts instance is initialized. - * - * @since X.X.X - * - * @param WP_Web_Fonts $wp_webfonts WP_Web_Fonts instance (passed by reference). - */ - do_action_ref_array( 'wp_default_webfonts', array( &$this ) ); - } - - /** - * Get the list of registered providers. - * - * @since X.X.X - * - * @return array $providers { - * An associative array of registered providers, keyed by their unique ID. - * - * @type string $provider_id => array { - * An associate array of provider's class name and fonts. - * - * @type string $class_name Fully qualified name of the provider's class. - * @type string[] $fonts An array of enqueued font handles for this provider. - * } - * } - */ - public function get_providers() { - return $this->providers; - } - - /** - * Register a provider. - * - * @since X.X.X - * - * @param string $provider_id The provider's unique ID. - * @param string $class_name The provider class name. - * @return bool True if successfully registered, else false. - */ - public function register_provider( $provider_id, $class_name ) { - if ( empty( $provider_id ) || empty( $class_name ) || ! class_exists( $class_name ) ) { - return false; - } - - $this->providers[ $provider_id ] = array( - 'class' => $class_name, - 'fonts' => array(), - ); - return true; - } - - /** - * Get the list of all registered font family handles. - * - * @since X.X.X - * - * @return string[] - */ - public function get_registered_font_families() { - $font_families = array(); - foreach ( $this->registered as $handle => $obj ) { - if ( $obj->extra['is_font_family'] ) { - $font_families[] = $handle; - } - } - return $font_families; - } - - /** - * Get the list of all registered font families and their variations. - * - * @since X.X.X - * - * @return string[] - */ - public function get_registered() { - return array_keys( $this->registered ); - } - - /** - * Get the list of enqueued font families and their variations. - * - * @since X.X.X - * - * @return array[] - */ - public function get_enqueued() { - return $this->queue; - } - - /** - * Registers a font family. - * - * @since X.X.X - * - * @param string $font_family Font family name to register. - * @return string|null Font family handle when registration successes. Null on failure. - */ - public function add_font_family( $font_family ) { - $font_family_handle = WP_Fonts_Utils::convert_font_family_into_handle( $font_family ); - if ( ! $font_family_handle ) { - return null; - } - - if ( isset( $this->registered[ $font_family_handle ] ) ) { - return $font_family_handle; - } - - $registered = $this->add( $font_family_handle, false ); - if ( ! $registered ) { - return null; - } - - $this->add_data( $font_family_handle, 'font-properties', array( 'font-family' => $font_family ) ); - $this->add_data( $font_family_handle, 'is_font_family', true ); - - return $font_family_handle; - } - - /** - * Removes a font family and all registered variations. - * - * @since X.X.X - * - * @param string $font_family_handle The font family to remove. - */ - public function remove_font_family( $font_family_handle ) { - if ( ! isset( $this->registered[ $font_family_handle ] ) ) { - return; - } - - $variations = $this->registered[ $font_family_handle ]->deps; - - foreach ( $variations as $variation ) { - $this->remove( $variation ); - } - - $this->remove( $font_family_handle ); - } - - /** - * Add a variation to an existing family or register family if none exists. - * - * @since X.X.X - * - * @param string $font_family_handle The font family's handle for this variation. - * @param array $variation An array of variation properties to add. - * @param string $variation_handle Optional. The variation's handle. When none is provided, the - * handle will be dynamically generated. - * Default empty string. - * @return string|null Variation handle on success. Else null. - */ - public function add_variation( $font_family_handle, array $variation, $variation_handle = '' ) { - if ( ! WP_Fonts_Utils::is_defined( $font_family_handle ) ) { - trigger_error( 'Font family handle must be a non-empty string.' ); - return null; - } - - // When there is a variation handle, check it. - if ( '' !== $variation_handle && ! WP_Fonts_Utils::is_defined( $variation_handle ) ) { - trigger_error( 'Variant handle must be a non-empty string.' ); - return null; - } - - // Register the font family when it does not yet exist. - if ( ! isset( $this->registered[ $font_family_handle ] ) ) { - if ( ! $this->add_font_family( $font_family_handle ) ) { - return null; - } - } - - $variation = $this->validate_variation( $variation ); - - // Variation validation failed. - if ( ! $variation ) { - return null; - } - - // When there's no variation handle, attempt to create one. - if ( '' === $variation_handle ) { - $variation_handle = WP_Fonts_Utils::convert_variation_into_handle( $font_family_handle, $variation ); - if ( is_null( $variation_handle ) ) { - return null; - } - } - - // Bail out if the variant is already registered. - if ( $this->is_variation_registered( $font_family_handle, $variation_handle ) ) { - return $variation_handle; - } - - $variation_src = array_key_exists( 'src', $variation ) ? $variation['src'] : false; - $result = $this->add( $variation_handle, $variation_src ); - - // Bail out if the registration failed. - if ( ! $result ) { - return null; - } - - $this->add_data( $variation_handle, 'font-properties', $variation ); - $this->add_data( $variation_handle, 'is_font_family', false ); - - // Add the font variation as a dependency to the registered font family. - $this->add_dependency( $font_family_handle, $variation_handle ); - - $this->providers[ $variation['provider'] ]['fonts'][] = $variation_handle; - - return $variation_handle; - } - - /** - * Removes a variation. - * - * @since X.X.X - * - * @param string $font_family_handle The font family for this variation. - * @param string $variation_handle The variation's handle to remove. - */ - public function remove_variation( $font_family_handle, $variation_handle ) { - if ( isset( $this->registered[ $variation_handle ] ) ) { - $this->remove( $variation_handle ); - } - - if ( ! $this->is_variation_registered( $font_family_handle, $variation_handle ) ) { - return; - } - - // Remove the variation as a dependency from its font family. - $this->registered[ $font_family_handle ]->deps = array_values( - array_diff( - $this->registered[ $font_family_handle ]->deps, - array( $variation_handle ) - ) - ); - } - - /** - * Checks if the variation is registered. - * - * @since X.X.X - * - * @param string $font_family_handle The font family's handle for this variation. - * @param string $variation_handle Variation's handle. - * @return bool True when registered to the given font family. Else false. - */ - private function is_variation_registered( $font_family_handle, $variation_handle ) { - if ( ! isset( $this->registered[ $font_family_handle ] ) ) { - return false; - } - - return in_array( $variation_handle, $this->registered[ $font_family_handle ]->deps, true ); - } - - /** - * Adds a variation as a dependency to the given font family. - * - * @since X.X.X - * - * @param string $font_family_handle The font family's handle for this variation. - * @param string $variation_handle The variation's handle. - */ - private function add_dependency( $font_family_handle, $variation_handle ) { - $this->registered[ $font_family_handle ]->deps[] = $variation_handle; - } - - /** - * Validates and sanitizes a variation. - * - * @since X.X.X - * - * @param array $variation Variation properties to add. - * @return false|array Validated variation on success. Else, false. - */ - private function validate_variation( $variation ) { - $variation = wp_parse_args( $variation, $this->variation_property_defaults ); - - // Check the font-family. - if ( empty( $variation['font-family'] ) || ! is_string( $variation['font-family'] ) ) { - trigger_error( 'Webfont font-family must be a non-empty string.' ); - return false; - } - - // Local fonts need a "src". - if ( 'local' === $variation['provider'] ) { - // Make sure that local fonts have 'src' defined. - if ( empty( $variation['src'] ) || ( ! is_string( $variation['src'] ) && ! is_array( $variation['src'] ) ) ) { - trigger_error( 'Webfont src must be a non-empty string or an array of strings.' ); - return false; - } - } elseif ( ! isset( $this->providers[ $variation['provider'] ] ) ) { - trigger_error( sprintf( 'The provider "%s" is not registered', $variation['provider'] ) ); - return false; - } elseif ( ! class_exists( $this->providers[ $variation['provider'] ]['class'] ) ) { - trigger_error( sprintf( 'The provider class "%s" does not exist', $variation['provider'] ) ); - return false; - } - - // Validate the 'src' property. - if ( ! empty( $variation['src'] ) ) { - foreach ( (array) $variation['src'] as $src ) { - if ( empty( $src ) || ! is_string( $src ) ) { - trigger_error( 'Each webfont src must be a non-empty string.' ); - return false; - } - } - } - - // Check the font-weight. - if ( ! is_string( $variation['font-weight'] ) && ! is_int( $variation['font-weight'] ) ) { - trigger_error( 'Webfont font-weight must be a properly formatted string or integer.' ); - return false; - } - - // Check the font-display. - if ( ! in_array( $variation['font-display'], array( 'auto', 'block', 'fallback', 'swap', 'optional' ), true ) ) { - $variation['font-display'] = 'fallback'; - } - - $valid_props = array( - 'ascent-override', - 'descent-override', - 'font-display', - 'font-family', - 'font-stretch', - 'font-style', - 'font-weight', - 'font-variant', - 'font-feature-settings', - 'font-variation-settings', - 'line-gap-override', - 'size-adjust', - 'src', - 'unicode-range', - - // Exceptions. - 'provider', - ); - - foreach ( $variation as $prop => $value ) { - if ( ! in_array( $prop, $valid_props, true ) ) { - unset( $variation[ $prop ] ); - } - } - - return $variation; - } - - /** - * Processes the items and dependencies. - * - * Processes the items passed to it or the queue, and their dependencies. - * - * @since X.X.X - * - * @param string|string[]|bool $handles Optional. Items to be processed: queue (false), - * single item (string), or multiple items (array of strings). - * Default false. - * @param int|false $group Optional. Group level: level (int), no group (false). - * - * @return array|string[] Array of web font handles that have been processed. - * An empty array if none were processed. - */ - public function do_items( $handles = false, $group = false ) { - $handles = $this->prepare_handles_for_printing( $handles ); - - if ( empty( $handles ) ) { - return $this->done; - } - - $this->all_deps( $handles ); - if ( empty( $this->to_do ) ) { - return $this->done; - } - - $this->to_do_keyed_handles = array_flip( $this->to_do ); - - foreach ( $this->get_providers() as $provider_id => $provider ) { - // Alert and skip if the provider class does not exist. - if ( ! class_exists( $provider['class'] ) ) { - /* translators: %s is the provider name. */ - trigger_error( - sprintf( - 'Class "%s" not found for "%s" web font provider', - $provider['class'], - $provider_id - ) - ); - continue; - } - - $this->do_item( $provider_id, $group ); - } - - $this->process_font_families_after_printing( $handles ); - - return $this->done; - } - - /** - * Prepares the given handles for printing. - * - * @since X.X.X - * - * @param string|string[]|bool $handles Optional. Handles to prepare. - * Default false. - * @return array Array of handles. - */ - private function prepare_handles_for_printing( $handles = false ) { - if ( false !== $handles ) { - $handles = $this->validate_handles( $handles ); - // Bail out when invalid. - if ( empty( $handles ) ) { - return array(); - } - } - - // Use the enqueued queue. - if ( empty( $handles ) ) { - if ( empty( $this->queue ) ) { - return array(); - } - $handles = $this->queue; - } - - return $handles; - } - - /** - * Validates handle(s) to ensure each is a non-empty string. - * - * @since X.X.X - * - * @param string|string[] $handles Handles to prepare. - * @return string[]|null Array of handles on success. Else null. - */ - private function validate_handles( $handles ) { - // Validate each element is a non-empty string handle. - $handles = array_filter( (array) $handles, array( WP_Fonts_Utils::class, 'is_defined' ) ); - - if ( empty( $handles ) ) { - trigger_error( 'Handles must be a non-empty string or array of non-empty strings' ); - return null; - } - - return $handles; - } - - /** - * Invokes each provider to process and print its styles. - * - * @since X.X.X - * - * @see WP_Dependencies::do_item() - * - * @param string $provider_id The provider to process. - * @param int|false $group Not used. - * @return bool - */ - public function do_item( $provider_id, $group = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - // Bail out if the provider is not registered. - if ( ! isset( $this->providers[ $provider_id ] ) ) { - return false; - } - - $font_handles = $this->get_enqueued_fonts_for_provider( $provider_id ); - if ( empty( $font_handles ) ) { - return false; - } - - $properties_by_font = $this->get_font_properties_for_provider( $font_handles ); - if ( empty( $properties_by_font ) ) { - return false; - } - - // Invoke provider to print its styles. - $provider = $this->get_provider_instance( $provider_id ); - $provider->set_fonts( $properties_by_font ); - $provider->print_styles(); - - // Clean up. - $this->update_queues_for_printed_fonts( $font_handles ); - - return true; - } - - /** - * Retrieves a list of enqueued web font variations for a provider. - * - * @since X.X.X - * - * @param string $provider_id The provider to process. - * @return array[] Webfonts organized by providers. - */ - private function get_enqueued_fonts_for_provider( $provider_id ) { - $providers = $this->get_providers(); - - if ( empty( $providers[ $provider_id ] ) ) { - return array(); - } - - return array_intersect( - $providers[ $provider_id ]['fonts'], - $this->to_do - ); - } - - /** - * Gets a list of font properties for each of the given font handles. - * - * @since X.X.X - * - * @param array $font_handles Font handles to get properties. - * @return array A list of fonts with each font's properties. - */ - private function get_font_properties_for_provider( array $font_handles ) { - $font_properties = array(); - - foreach ( $font_handles as $font_handle ) { - $properties = $this->get_data( $font_handle, 'font-properties' ); - if ( ! $properties ) { - continue; - } - $font_properties[ $font_handle ] = $properties; - } - - return $font_properties; - } - - /** - * Gets the instance of the provider from the WP_Webfonts::$provider_instance store. - * - * @since X.X.X - * - * @param string $provider_id The provider to get. - * @return object Instance of the provider. - */ - private function get_provider_instance( $provider_id ) { - if ( ! isset( $this->provider_instances[ $provider_id ] ) ) { - $this->provider_instances[ $provider_id ] = new $this->providers[ $provider_id ]['class'](); - } - return $this->provider_instances[ $provider_id ]; - } - - /** - * Update queues for the given printed fonts. - * - * @since X.X.X - * - * @param array $font_handles Font handles to get properties. - */ - private function update_queues_for_printed_fonts( array $font_handles ) { - foreach ( $font_handles as $font_handle ) { - $this->set_as_done( $font_handle ); - $this->remove_from_to_do_queues( $font_handle ); - } - } - - /** - * Processes the font families after printing the variations. - * - * For each queued font family: - * - * a. if any of their variations were printed, the font family is added to the `done` list. - * b. removes each from the to_do queues. - * - * @since X.X.X - * - * @param array $handles Handles to process. - */ - private function process_font_families_after_printing( array $handles ) { - foreach ( $handles as $handle ) { - if ( - ! $this->get_data( $handle, 'is_font_family' ) || - ! isset( $this->to_do_keyed_handles[ $handle ] ) - ) { - continue; - } - $font_family = $this->registered[ $handle ]; - - // Add the font family to `done` list if any of its variations were printed. - if ( ! empty( $font_family->deps ) ) { - $processed = array_intersect( $font_family->deps, $this->done ); - if ( ! empty( $processed ) ) { - $this->set_as_done( $handle ); - } - } - - $this->remove_from_to_do_queues( $handle ); - } - } - - /** - * Removes the handle from the `to_do` and `to_do_keyed_handles` lists. - * - * @since X.X.X - * - * @param string $handle Handle to remove. - */ - private function remove_from_to_do_queues( $handle ) { - unset( - $this->to_do[ $this->to_do_keyed_handles[ $handle ] ], - $this->to_do_keyed_handles[ $handle ] - ); - } - - /** - * Sets the given handle to done by adding it to the `done` list. - * - * @since X.X.X - * - * @param string $handle Handle to set as done. - */ - private function set_as_done( $handle ) { - if ( ! is_array( $this->done ) ) { - $this->done = array(); - } - $this->done[] = $handle; - } - - /** - * Converts the font family and its variations into theme.json structural format. - * - * @since X.X.X - * - * @param string $font_family_handle Font family to convert. - * @return array Webfonts in theme.json structural format. - */ - public function to_theme_json( $font_family_handle ) { - if ( ! isset( $this->registered[ $font_family_handle ] ) ) { - return array(); - } - - $font_family_name = $this->registered[ $font_family_handle ]->extra['font-properties']['font-family']; - $theme_json_format = array( - 'fontFamily' => str_contains( $font_family_name, ' ' ) ? "'{$font_family_name}'" : $font_family_name, - 'name' => $font_family_name, - 'slug' => $font_family_handle, - 'fontFace' => array(), - ); - - foreach ( $this->registered[ $font_family_handle ]->deps as $variation_handle ) { - if ( ! isset( $this->registered[ $variation_handle ] ) ) { - continue; - } - - $variation_obj = $this->registered[ $variation_handle ]; - $variation_properties = array( 'origin' => 'gutenberg_wp_fonts_api' ); - foreach ( $variation_obj->extra['font-properties'] as $property_name => $property_value ) { - $property_in_camelcase = lcfirst( str_replace( '-', '', ucwords( $property_name, '-' ) ) ); - $variation_properties[ $property_in_camelcase ] = $property_value; - } - $theme_json_format['fontFace'][ $variation_obj->handle ] = $variation_properties; - } - - return $theme_json_format; - } -} diff --git a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider-local.php b/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider-local.php deleted file mode 100644 index b6fd5d78a1435..0000000000000 --- a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider-local.php +++ /dev/null @@ -1,284 +0,0 @@ -style_tag_atts = array( 'type' => 'text/css' ); - } - } - - /** - * Gets the `@font-face` CSS styles for locally-hosted font files. - * - * This method does the following processing tasks: - * 1. Orchestrates an optimized `src` (with format) for browser support. - * 2. Generates the `@font-face` for all its webfonts. - * - * For example, when given these webfonts: - * - * array( - * 'source-serif-pro.normal.200 900' => array( - * 'provider' => 'local', - * 'font_family' => 'Source Serif Pro', - * 'font_weight' => '200 900', - * 'font_style' => 'normal', - * 'src' => 'https://example.com/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2' ), - * ), - * 'source-serif-pro.italic.400 900' => array( - * 'provider' => 'local', - * 'font_family' => 'Source Serif Pro', - * 'font_weight' => '200 900', - * 'font_style' => 'italic', - * 'src' => 'https://example.com/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2' ), - * ), - * ) - * - * - * the following `@font-face` styles are generated and returned: - * - * - * @font-face{ - * font-family:"Source Serif Pro"; - * font-style:normal; - * font-weight:200 900; - * font-stretch:normal; - * src:local("Source Serif Pro"), url('/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2') format('woff2'); - * } - * @font-face{ - * font-family:"Source Serif Pro"; - * font-style:italic; - * font-weight:200 900; - * font-stretch:normal; - * src:local("Source Serif Pro"), url('/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2') format('woff2'); - * } - * - * - * @since X.X.X - * - * @return string The `@font-face` CSS. - */ - public function get_css() { - $css = ''; - - foreach ( $this->webfonts as $webfont ) { - // Order the webfont's `src` items to optimize for browser support. - $webfont = $this->order_src( $webfont ); - - // Build the @font-face CSS for this webfont. - $css .= '@font-face{' . $this->build_font_face_css( $webfont ) . '}'; - } - - return $css; - } - - /** - * Order `src` items to optimize for browser support. - * - * @since 6.0.0 - * - * @param array $webfont Webfont to process. - * @return array - */ - private function order_src( array $webfont ) { - if ( ! is_array( $webfont['src'] ) ) { - $webfont['src'] = (array) $webfont['src']; - } - - $src = array(); - $src_ordered = array(); - - foreach ( $webfont['src'] as $url ) { - // Add data URIs first. - if ( str_starts_with( trim( $url ), 'data:' ) ) { - $src_ordered[] = array( - 'url' => $url, - 'format' => 'data', - ); - continue; - } - $format = pathinfo( $url, PATHINFO_EXTENSION ); - $src[ $format ] = $url; - } - - // Add woff2. - if ( ! empty( $src['woff2'] ) ) { - $src_ordered[] = array( - 'url' => $src['woff2'], - 'format' => 'woff2', - ); - } - - // Add woff. - if ( ! empty( $src['woff'] ) ) { - $src_ordered[] = array( - 'url' => $src['woff'], - 'format' => 'woff', - ); - } - - // Add ttf. - if ( ! empty( $src['ttf'] ) ) { - $src_ordered[] = array( - 'url' => $src['ttf'], - 'format' => 'truetype', - ); - } - - // Add eot. - if ( ! empty( $src['eot'] ) ) { - $src_ordered[] = array( - 'url' => $src['eot'], - 'format' => 'embedded-opentype', - ); - } - - // Add otf. - if ( ! empty( $src['otf'] ) ) { - $src_ordered[] = array( - 'url' => $src['otf'], - 'format' => 'opentype', - ); - } - $webfont['src'] = $src_ordered; - - return $webfont; - } - - /** - * Builds the font-family's CSS. - * - * @since 6.0.0 - * - * @param array $webfont Webfont to process. - * @return string This font-family's CSS. - */ - private function build_font_face_css( array $webfont ) { - $css = ''; - - // Wrap font-family in quotes if it contains spaces - // and is not already wrapped in quotes. - if ( - str_contains( $webfont['font-family'], ' ' ) && - ! str_contains( $webfont['font-family'], '"' ) && - ! str_contains( $webfont['font-family'], "'" ) - ) { - $webfont['font-family'] = '"' . $webfont['font-family'] . '"'; - } - - foreach ( $webfont as $key => $value ) { - - // Skip "provider", since it's for internal API use, - // and not a valid CSS property. - if ( 'provider' === $key ) { - continue; - } - - // Compile the "src" parameter. - if ( 'src' === $key ) { - $value = $this->compile_src( $webfont['font-family'], $value ); - } - - // If font-variation-settings is an array, convert it to a string. - if ( 'font-variation-settings' === $key && is_array( $value ) ) { - $value = $this->compile_variations( $value ); - } - - if ( ! empty( $value ) ) { - $css .= "$key:$value;"; - } - } - - return $css; - } - - /** - * Compiles the `src` into valid CSS. - * - * @since 6.0.0 - * - * @param string $font_family Font family. - * @param array $value Value to process. - * @return string The CSS. - */ - private function compile_src( $font_family, array $value ) { - $src = "local($font_family)"; - - foreach ( $value as $item ) { - - if ( str_starts_with( $item['url'], get_site_url() ) ) { - $item['url'] = wp_make_link_relative( $item['url'] ); - } - - $src .= ( 'data' === $item['format'] ) - ? ", url({$item['url']})" - : ", url('{$item['url']}') format('{$item['format']}')"; - } - return $src; - } - - /** - * Compiles the font variation settings. - * - * @since 6.0.0 - * - * @param array $font_variation_settings Array of font variation settings. - * @return string The CSS. - */ - private function compile_variations( array $font_variation_settings ) { - $variations = ''; - - foreach ( $font_variation_settings as $key => $value ) { - $variations .= "$key $value"; - } - - return $variations; - } -} diff --git a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider.php b/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider.php deleted file mode 100644 index 5b7f5ece335b1..0000000000000 --- a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider.php +++ /dev/null @@ -1,66 +0,0 @@ -webfonts = $this->fonts; - } - - /** - * This method is here to wire WP_Fonts_Provider::do_item() to this - * deprecated class to ensure the fonts get set. - * - * @param array[] $fonts Fonts to be processed. - */ - public function set_fonts( array $fonts ) { - parent::set_fonts( $fonts ); - $this->webfonts = $this->fonts; - } -} diff --git a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-utils.php b/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-utils.php deleted file mode 100644 index d0242acf9a2b6..0000000000000 --- a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-utils.php +++ /dev/null @@ -1,85 +0,0 @@ -wp_fonts = ! empty( $wp_fonts ) ? $wp_fonts : wp_fonts(); - } - - /** - * Gets the font slug. - * - * @since X.X.X - * @deprecated Use WP_Fonts_Utils::convert_font_family_into_handle() or WP_Fonts_Utils::get_font_family_from_variation(). - * - * @param array|string $to_convert The value to convert into a slug. Expected as the web font's array - * or a font-family as a string. - * @return string|false The font slug on success, or false if the font-family cannot be determined. - */ - public static function get_font_slug( $to_convert ) { - $message = is_array( $to_convert ) - ? 'Use WP_Fonts_Utils::get_font_family_from_variation() to get the font family from an array and then WP_Fonts_Utils::convert_font_family_into_handle() to convert the font-family name into a handle' - : 'Use WP_Fonts_Utils::convert_font_family_into_handle() to convert the font-family name into a handle'; - _deprecated_function( __METHOD__, 'GB 14.9.1', $message ); - - if ( empty( $to_convert ) ) { - return false; - } - - $font_family_name = is_array( $to_convert ) - ? WP_Fonts_Utils::get_font_family_from_variation( $to_convert ) - : $to_convert; - - $slug = false; - if ( ! empty( $font_family_name ) ) { - $slug = WP_Fonts_Utils::convert_font_family_into_handle( $font_family_name ); - } - - return $slug; - } - - /** - * Initializes the API. - * - * @since 6.0.0 - * @deprecated GB 14.9.1 Use wp_fonts(). - */ - public static function init() { - _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_fonts()' ); - } - - /** - * Get the list of all registered font family handles. - * - * @since X.X.X - * @deprecated GB 15.8.0 Use wp_fonts()->get_registered_font_families(). - * - * @return string[] - */ - public function get_registered_font_families() { - _deprecated_function( __METHOD__, 'GB 15.8.0', 'wp_fonts()->get_registered_font_families()' ); - - return $this->wp_fonts->get_registered_font_families(); - } - - /** - * Gets the list of registered fonts. - * - * @since 6.0.0 - * @deprecated 14.9.1 Use wp_fonts()->get_registered(). - * - * @return array[] - */ - public function get_registered_webfonts() { - _deprecated_function( __METHOD__, '14.9.1', 'wp_fonts()->get_registered()' ); - - return $this->_get_registered_webfonts(); - } - - /** - * Gets the list of enqueued fonts. - * - * @since 6.0.0 - * @deprecated GB 14.9.1 Use wp_fonts()->get_enqueued(). - * - * @return array[] - */ - public function get_enqueued_webfonts() { - _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_fonts()->get_enqueued()' ); - - return $this->wp_fonts->queue; - } - - /** - * Gets the list of all fonts. - * - * @since 6.0.0 - * @deprecated GB 14.9.1 Use wp_fonts()->get_registered(). - * - * @return array[] - */ - public function get_all_webfonts() { - _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_fonts()->get_registered()' ); - - return $this->_get_registered_webfonts(); - } - - /** - * Registers a webfont. - * - * @since 6.0.0 - * @deprecated GB 14.9.1 Use wp_register_fonts(). - * - * @param array $webfont Web font to register. - * @param string $font_family_handle Optional. Font family handle for the given variation. - * Default empty string. - * @param string $variation_handle Optional. Handle for the variation to register. - * @param bool $silence_deprecation Optional. Silences the deprecation notice. For internal use. - * @return string|false The font family slug if successfully registered, else false. - */ - public function register_webfont( array $webfont, $font_family_handle = '', $variation_handle = '', $silence_deprecation = false ) { - if ( ! $silence_deprecation ) { - _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_register_fonts()' ); - } - - // Bail out if no variation passed as there's not to register. - if ( empty( $webfont ) ) { - return false; - } - - // Restructure definition: keyed by font-family and array of variations. - $font = array( $webfont ); - if ( WP_Fonts_Utils::is_defined( $font_family_handle ) ) { - $font = array( $font_family_handle => $font ); - } else { - $font = Gutenberg_Fonts_API_BC_Layer::migrate_deprecated_structure( $font, true ); - $font_family_handle = array_key_first( $font ); - } - - if ( empty( $font ) || empty( $font_family_handle ) ) { - return false; - } - - // If the variation handle was passed, add it as variation key. - if ( WP_Fonts_Utils::is_defined( $variation_handle ) ) { - $font[ $font_family_handle ] = array( $variation_handle => $font[ $font_family_handle ][0] ); - } - - // Register with the Fonts API. - $handle = wp_register_fonts( $font ); - if ( empty( $handle ) ) { - return false; - } - return array_pop( $handle ); - } - - /** - * Enqueue a font-family that has been already registered. - * - * @since 6.0.0 - * @deprecated GB 14.9.1 Use wp_enqueue_fonts(). - * - * @param string $font_family_name The font family name to be enqueued. - * @return bool True if successfully enqueued, else false. - */ - public function enqueue_webfont( $font_family_name ) { - _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_enqueue_fonts()' ); - - wp_enqueue_fonts( array( $font_family_name ) ); - return true; - } - - /** - * Gets the registered webfonts in the original web font property structure keyed by each handle. - * - * @return array[] - */ - private function _get_registered_webfonts() { - $font_families = array(); - $registered = array(); - - // Find the registered font families. - foreach ( $this->wp_fonts->registered as $handle => $obj ) { - if ( ! $obj->extra['is_font_family'] ) { - continue; - } - - if ( ! isset( $registered[ $handle ] ) ) { - $registered[ $handle ] = array(); - } - - $font_families[ $handle ] = $obj->deps; - } - - // Build the return array structure. - foreach ( $font_families as $font_family_handle => $variations ) { - foreach ( $variations as $variation_handle ) { - $variation_obj = $this->wp_fonts->registered[ $variation_handle ]; - - $registered[ $font_family_handle ][ $variation_handle ] = $variation_obj->extra['font-properties']; - } - } - - return $registered; - } -} diff --git a/lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php b/lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php deleted file mode 100644 index 7a3a7bd013eea..0000000000000 --- a/lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php +++ /dev/null @@ -1,260 +0,0 @@ - array[] $variations { - * An array of web font variations for this font-family. - * Each variation has the following structure. - * - * @type array $variation { - * @type string $provider The provider ID. Default 'local'. - * @type string $font-style The font-style property. Default 'normal'. - * @type string $font-weight The font-weight property. Default '400'. - * @type string $font-display The font-display property. Default 'fallback'. - * } - * } - * } - * @return string[] Array of registered font family handles. - */ - function wp_register_webfonts( array $webfonts ) { - _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_register_fonts()' ); - - $webfonts = Gutenberg_Fonts_API_BC_Layer::migrate_deprecated_structure( $webfonts ); - - return wp_register_fonts( $webfonts ); - } -} - -if ( ! function_exists( 'wp_register_webfont' ) ) { - /** - * Registers a single webfont. - * - * Example of how to register Source Serif Pro font with font-weight range of 200-900: - * - * If the font file is contained within the theme: - * - * - * wp_register_webfont( - * array( - * 'provider' => 'local', - * 'font-family' => 'Source Serif Pro', - * 'font-weight' => '200 900', - * 'font-style' => 'normal', - * 'src' => get_theme_file_uri( 'assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2' ), - * ) - * ); - * - * - * @since 6.0.0 - * @deprecated 14.9.1 Use wp_register_fonts(). - * - * @param array $webfont Webfont to be registered. - * @return string|false The font family slug if successfully registered, else false. - */ - function wp_register_webfont( array $webfont ) { - _deprecated_function( __FUNCTION__, '14.9.1', 'wp_register_fonts()' ); - - return wp_fonts()->register_webfont( $webfont, '', '', false ); - } -} - -if ( ! function_exists( 'wp_enqueue_webfonts' ) ) { - /** - * Enqueues one or more font family and all of its variations. - * - * @since X.X.X - * @since GB 15.1 Use wp_enqueue_fonts() ihstead. - * - * @param string[] $font_families Font family(ies) to enqueue. - */ - function wp_enqueue_webfonts( array $font_families ) { - _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_enqueue_fonts()' ); - - wp_enqueue_fonts( $font_families ); - } -} - -if ( ! function_exists( 'wp_enqueue_webfont' ) ) { - /** - * Enqueue a single font family that has been registered beforehand. - * - * Example of how to enqueue Source Serif Pro font: - * - * - * wp_enqueue_webfont( 'Source Serif Pro' ); - * - * - * Font families should be enqueued from the `init` hook or later. - * - * @since 6.0.0 - * @deprecated GB 14.9.1 Use wp_enqueue_fonts() instead. - * - * @param string $font_family_name The font family name to be enqueued. - * @return bool True if successfully enqueued, else false. - */ - function wp_enqueue_webfont( $font_family_name ) { - _deprecated_function( __FUNCTION__, 'GB 14.9.1', 'wp_enqueue_fonts()' ); - - wp_enqueue_fonts( array( $font_family_name ) ); - return true; - } -} - -if ( ! function_exists( 'wp_enqueue_webfont_variations' ) ) { - /** - * Enqueues a specific set of web font variations. - * - * @since X.X.X - * @deprecated GB 15.1 Use wp_enqueue_font_variations() instead. - * - * @param string|string[] $variation_handles Variation handle (string) or handles (array of strings). - */ - function wp_enqueue_webfont_variations( $variation_handles ) { - _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_enqueue_font_variations()' ); - - wp_enqueue_font_variations( $variation_handles ); - } -} - -if ( ! function_exists( 'wp_deregister_webfont_variation' ) ) { - /** - * Deregisters a font variation. - * - * @since GB 14.9.1 - * @deprecated GB 15.1 Use wp_deregister_font_variation() instead. - * - * @param string $font_family_handle The font family for this variation. - * @param string $variation_handle The variation's handle to remove. - */ - function wp_deregister_webfont_variation( $font_family_handle, $variation_handle ) { - _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_deregister_font_variation()' ); - - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - } -} - -if ( ! function_exists( 'wp_get_webfont_providers' ) ) { - /** - * Gets all registered providers. - * - * Return an array of providers, each keyed by their unique - * ID (i.e. the `$id` property in the provider's object) with - * an instance of the provider (object): - * ID => provider instance - * - * Each provider contains the business logic for how to - * process its specific font service (i.e. local or remote) - * and how to generate the `@font-face` styles for its service. - * - * @since X.X.X - * @deprecated GB 14.9.1 Use wp_fonts()->get_providers(). - * - * @return string[] All registered providers, each keyed by their unique ID. - */ - function wp_get_webfont_providers() { - _deprecated_function( __FUNCTION__, '14.9.1', 'wp_fonts()->get_providers()' ); - - $providers = array(); - foreach ( wp_fonts()->get_providers() as $id => $config ) { - $providers[ $id ] = $config['class']; - } - - return $providers; - } -} - -if ( ! function_exists( 'wp_register_webfont_provider' ) ) { - /** - * Registers a custom font service provider. - * - * A webfont provider contains the business logic for how to - * interact with a remote font service and how to generate - * the `@font-face` styles for that remote service. - * - * How to register a custom font service provider: - * 1. Load its class file into memory before registration. - * 2. Pass the class' name to this function. - * - * For example, for a class named `My_Custom_Font_Service_Provider`: - * ``` - * wp_register_webfont_provider( My_Custom_Font_Service_Provider::class ); - * ``` - * - * @since X.X.X - * @deprecated GB 15.1 Use wp_register_font_provider() instead. - * - * @param string $name The provider's name. - * @param string $classname The provider's class name. - * The class should be a child of `WP_Webfonts_Provider`. - * See {@see WP_Webfonts_Provider}. - * - * @return bool True if successfully registered, else false. - */ - function wp_register_webfont_provider( $name, $classname ) { - _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_register_font_provider' ); - - return wp_register_font_provider( $name, $classname ); - } -} - -if ( ! function_exists( 'wp_print_webfonts' ) ) { - /** - * Invokes each provider to process and print its styles. - * - * @since GB 14.9.1 - * @deprecated GB 15.1 Use wp_print_fonts() instead. - * - * @param string|string[]|false $handles Optional. Items to be processed: queue (false), - * single item (string), or multiple items (array of strings). - * Default false. - * @return array|string[] Array of web font handles that have been processed. - * An empty array if none were processed. - */ - function wp_print_webfonts( $handles = false ) { - _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_print_fonts' ); - - return wp_print_fonts( $handles ); - } -} diff --git a/lib/experimental/fonts-api/class-wp-fonts-provider-local.php b/lib/experimental/fonts-api/class-wp-fonts-provider-local.php deleted file mode 100644 index 019861a9ae191..0000000000000 --- a/lib/experimental/fonts-api/class-wp-fonts-provider-local.php +++ /dev/null @@ -1,236 +0,0 @@ -style_tag_atts = array( 'type' => 'text/css' ); - } - } - - /** - * Gets the `@font-face` CSS styles for locally-hosted font files. - * - * This method does the following processing tasks: - * 1. Orchestrates an optimized `src` (with format) for browser support. - * 2. Generates the `@font-face` for all its fonts. - * - * @since X.X.X - * - * @return string The `@font-face` CSS. - */ - public function get_css() { - $css = ''; - - foreach ( $this->fonts as $font ) { - // Order the font's `src` items to optimize for browser support. - $font = $this->order_src( $font ); - - // Build the @font-face CSS for this font. - $css .= '@font-face{' . $this->build_font_face_css( $font ) . '}'; - } - - return $css; - } - - /** - * Order `src` items to optimize for browser support. - * - * @since X.X.X - * - * @param array $font Font to process. - * @return array - */ - private function order_src( array $font ) { - if ( ! is_array( $font['src'] ) ) { - $font['src'] = (array) $font['src']; - } - - $src = array(); - $src_ordered = array(); - - foreach ( $font['src'] as $url ) { - // Add data URIs first. - if ( str_starts_with( trim( $url ), 'data:' ) ) { - $src_ordered[] = array( - 'url' => $url, - 'format' => 'data', - ); - continue; - } - $format = pathinfo( $url, PATHINFO_EXTENSION ); - $src[ $format ] = $url; - } - - // Add woff2. - if ( ! empty( $src['woff2'] ) ) { - $src_ordered[] = array( - 'url' => $src['woff2'], - 'format' => 'woff2', - ); - } - - // Add woff. - if ( ! empty( $src['woff'] ) ) { - $src_ordered[] = array( - 'url' => $src['woff'], - 'format' => 'woff', - ); - } - - // Add ttf. - if ( ! empty( $src['ttf'] ) ) { - $src_ordered[] = array( - 'url' => $src['ttf'], - 'format' => 'truetype', - ); - } - - // Add eot. - if ( ! empty( $src['eot'] ) ) { - $src_ordered[] = array( - 'url' => $src['eot'], - 'format' => 'embedded-opentype', - ); - } - - // Add otf. - if ( ! empty( $src['otf'] ) ) { - $src_ordered[] = array( - 'url' => $src['otf'], - 'format' => 'opentype', - ); - } - $font['src'] = $src_ordered; - - return $font; - } - - /** - * Builds the font-family's CSS. - * - * @since X.X.X - * - * @param array $font Font to process. - * @return string This font-family's CSS. - */ - private function build_font_face_css( array $font ) { - $css = ''; - - // Wrap font-family in quotes if it contains spaces - // and is not already wrapped in quotes. - if ( - str_contains( $font['font-family'], ' ' ) && - ! str_contains( $font['font-family'], '"' ) && - ! str_contains( $font['font-family'], "'" ) - ) { - $font['font-family'] = '"' . $font['font-family'] . '"'; - } - - foreach ( $font as $key => $value ) { - - // Skip "provider", since it's for internal API use, - // and not a valid CSS property. - if ( 'provider' === $key ) { - continue; - } - - // Compile the "src" parameter. - if ( 'src' === $key ) { - $value = $this->compile_src( $value ); - } - - // If font-variation-settings is an array, convert it to a string. - if ( 'font-variation-settings' === $key && is_array( $value ) ) { - $value = $this->compile_variations( $value ); - } - - if ( ! empty( $value ) ) { - $css .= "$key:$value;"; - } - } - - return $css; - } - - /** - * Compiles the `src` into valid CSS. - * - * @since X.X.X - * - * @param array $value Value to process. - * @return string The CSS. - */ - private function compile_src( array $value ) { - $src = ''; - - foreach ( $value as $item ) { - $src .= ( 'data' === $item['format'] ) - ? ", url({$item['url']})" - : ", url('{$item['url']}') format('{$item['format']}')"; - } - - $src = ltrim( $src, ', ' ); - return $src; - } - - /** - * Compiles the font variation settings. - * - * @since X.X.X - * - * @param array $font_variation_settings Array of font variation settings. - * @return string The CSS. - */ - private function compile_variations( array $font_variation_settings ) { - $variations = ''; - - foreach ( $font_variation_settings as $key => $value ) { - $variations .= "$key $value"; - } - - return $variations; - } -} diff --git a/lib/experimental/fonts-api/class-wp-fonts-provider.php b/lib/experimental/fonts-api/class-wp-fonts-provider.php deleted file mode 100644 index b59c59d6b8938..0000000000000 --- a/lib/experimental/fonts-api/class-wp-fonts-provider.php +++ /dev/null @@ -1,129 +0,0 @@ -fonts = $fonts; - } - - /** - * Prints the generated styles. - * - * @since X.X.X - */ - public function print_styles() { - printf( - $this->get_style_element(), - $this->get_css() - ); - } - - /** - * Gets the `@font-face` CSS for the provider's fonts. - * - * This method is where the provider does it processing to build the - * needed `@font-face` CSS for all of its fonts. Specifics of how - * this processing is done is contained in each provider. - * - * @since X.X.X - * - * @return string The `@font-face` CSS. - */ - abstract public function get_css(); - - /** - * Gets the `\n"; - } - - /** - * Gets the defined \n", - $font_faces['merriweather-200-900-normal'] - ), - ), - 'print Source Serif Pro for local provider' => array( - 'setup' => array( - 'provider' => array( 'local' => $providers['local'] ), - 'provider_handles' => array( 'local' => $local_font_handles ), - 'registered' => $local_fonts, - 'enqueued' => array( 'source-serif-pro' ), - ), - 'expected_done' => array( 'source-serif-pro', 'Source Serif Pro-300-normal', 'Source Serif Pro-900-italic' ), - 'expected_output' => sprintf( - "\n", - $font_faces['Source Serif Pro-300-normal'], - $font_faces['Source Serif Pro-900-italic'] - ), - ), - 'print all fonts for local provider' => array( - 'setup' => array( - 'provider' => array( 'local' => $providers['local'] ), - 'provider_handles' => array( 'local' => $local_font_handles ), - 'registered' => $local_fonts, - 'enqueued' => array( 'merriweather', 'source-serif-pro' ), - ), - 'expected_done' => array( - 'merriweather', - 'merriweather-200-900-normal', - 'source-serif-pro', - 'Source Serif Pro-300-normal', - 'Source Serif Pro-900-italic', - ), - 'expected_output' => sprintf( - "\n", - $font_faces['merriweather-200-900-normal'], - $font_faces['Source Serif Pro-300-normal'], - $font_faces['Source Serif Pro-900-italic'] - ), - ), - - // All providers registered with multiple fonts. - - 'print font1 when all providers registered' => array( - 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'font1' ) ) ), - 'expected_done' => array( 'font1', 'font1-300-normal', 'font1-300-italic', 'font1-900-normal' ), - 'expected_output' => sprintf( - '%s; %s; %s\n', - $font_faces['font1-300-normal'], - $font_faces['font1-300-italic'], - $font_faces['font1-900-normal'] - ), - ), - 'print all mock fonts when all providers registered' => array( - 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'font1', 'font2', 'font3' ) ) ), - 'expected_done' => array( - 'font1', - 'font1-300-normal', - 'font1-300-italic', - 'font1-900-normal', - 'font2', - 'font2-200-900-normal', - 'font2-200-900-italic', - 'font3', - 'font3-bold-normal', - ), - 'expected_output' => sprintf( - '%s; %s; %s; %s; %s; %s\n', - $font_faces['font1-300-normal'], - $font_faces['font1-300-italic'], - $font_faces['font1-900-normal'], - $font_faces['font2-200-900-normal'], - $font_faces['font2-200-900-italic'], - $font_faces['font3-bold-normal'] - ), - ), - 'print merriweather when all providers registered' => array( - 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather' ) ) ), - 'expected_done' => array( 'merriweather', 'merriweather-200-900-normal' ), - 'expected_output' => sprintf( - "\n", - $font_faces['merriweather-200-900-normal'] - ), - ), - 'print all local fonts when all providers registered' => array( - 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather', 'source-serif-pro' ) ) ), - 'expected_done' => array( - 'merriweather', - 'merriweather-200-900-normal', - 'source-serif-pro', - 'Source Serif Pro-300-normal', - 'Source Serif Pro-900-italic', - ), - 'expected_output' => sprintf( - "\n", - $font_faces['merriweather-200-900-normal'], - $font_faces['Source Serif Pro-300-normal'], - $font_faces['Source Serif Pro-900-italic'] - ), - ), - - 'print all fonts for all providers' => array( - 'setup' => array_merge( $setup_all, array( 'enqueued' => $all_variation_handles ) ), - 'expected_done' => $all_variation_handles, - 'expected_output' => - sprintf( - "\n", - $font_faces['merriweather-200-900-normal'], - $font_faces['Source Serif Pro-300-normal'], - $font_faces['Source Serif Pro-900-italic'] - ) . - sprintf( - '%s; %s; %s; %s; %s; %s\n', - $font_faces['font1-300-normal'], - $font_faces['font1-300-italic'], - $font_faces['font1-900-normal'], - $font_faces['font2-200-900-normal'], - $font_faces['font2-200-900-italic'], - $font_faces['font3-bold-normal'] - ), - ), - - // Specific variations enqueued. - // Validates that only these specific variations print once. - - 'specific variation: 1 local' => array( - 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather-200-900-normal' ) ) ), - 'expected_done' => array( 'merriweather-200-900-normal' ), - 'expected_output' => sprintf( - "\n", - $font_faces['merriweather-200-900-normal'] - ), - ), - 'specific variation: 1 local from different font families' => array( - 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather-200-900-normal', 'Source Serif Pro-900-italic' ) ) ), - 'expected_done' => array( 'merriweather-200-900-normal', 'Source Serif Pro-900-italic' ), - 'expected_output' => sprintf( - "\n", - $font_faces['merriweather-200-900-normal'], - $font_faces['Source Serif Pro-900-italic'] - ), - ), - 'specific variation: 1 local and 1 mock' => array( - 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather-200-900-normal', 'font2-200-900-normal' ) ) ), - 'expected_done' => array( 'merriweather-200-900-normal', 'font2-200-900-normal' ), - 'expected_output' => sprintf( - "\n" . - '%s\n', - $font_faces['merriweather-200-900-normal'], - $font_faces['font2-200-900-normal'] - ), - ), - 'specific variation: 1 mock and 1 local' => array( - 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'font2-200-900-normal', 'Source Serif Pro-300-normal' ) ) ), - 'expected_done' => array( 'font2-200-900-normal', 'Source Serif Pro-300-normal' ), - 'expected_output' => sprintf( - "\n" . - '%s\n', - $font_faces['Source Serif Pro-300-normal'], - $font_faces['font2-200-900-normal'] - ), - ), - ); - } - - protected function get_data_registry() { - return array( - 'lato' => array(), - 'merriweather' => array( - 'merriweather-200-900-normal' => array( - 'font-family' => 'Merriweather', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', - ), - ), - 'Source Serif Pro' => array( - 'Source Serif Pro-300-normal' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'normal', - 'font-weight' => '300', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'font-display' => 'fallback', - ), - 'Source Serif Pro-900-italic' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'italic', - 'font-weight' => '900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', - 'font-display' => 'fallback', - ), - ), - 'my-font' => array( - 'my-font-300-normal' => array( - 'font-family' => 'My Font', - 'font-weight' => '300', - 'src' => 'https://example.com/assets/fonts/my-font.ttf.woff2', - ), - 'my-font-300-italic' => array( - 'font-family' => 'My Font', - 'font-weight' => '300', - 'font-style' => 'italic', - 'src' => 'https://example.com/assets/fonts/my-font.ttf.woff2', - ), - 'my-font-900-normal' => array( - 'font-family' => 'My Font', - 'font-weight' => '900', - 'src' => 'https://example.com/assets/fonts/my-font.ttf.woff2', - ), - ), - ); - } - - /** - * Gets the provider definitions. - * - * @since X.X.X - * - * @param string $provider_id Optional. Provider ID to get. Default empty string. - * @return array - */ - protected function get_provider_definitions( $provider_id = '' ) { - $providers = array( - 'mock' => array( - 'id' => 'mock', - 'class' => Mock_Provider::class, - ), - 'local' => array( - 'id' => 'local', - 'class' => WP_Fonts_Provider_Local::class, - ), - ); - - if ( '' === $provider_id ) { - return $providers; - } - - if ( isset( $providers[ $provider_id ] ) ) { - return $providers[ $provider_id ]; - } - - return array( - 'id' => $provider_id, - 'class' => '', - ); - } - - /** - * Gets font definitions for both local and mock providers. - * - * @since X.X.X - * - * @return array|string[][][] - */ - protected function get_registered_fonts() { - return array_merge( - $this->get_registered_local_fonts(), - $this->get_registered_mock_fonts() - ); - } - - /** - * Returns an array of font-face styles that matches the font definitions - * in get_registered_local_fonts() and get_registered_mock_fonts(). - * - * @since X.X.X - * - * @return string[] - */ - protected function get_registered_fonts_css() { - return array( - 'merriweather-200-900-normal' => << << << 'font1-300-normal', - 'font1-300-italic' => 'font1-300-italic', - 'font1-900-normal' => 'font1-900-normal', - 'font2-200-900-normal' => 'font2-200-900-normal', - 'font2-200-900-italic' => 'font2-200-900-italic', - 'font3-bold-normal' => 'font3-bold-normal', - ); - } - - /** - * Gets font definitions for local provider. - * - * @since X.X.X - * - * @return string[][][] - */ - protected function get_registered_local_fonts() { - return array( - 'lato' => array(), - 'merriweather' => array( - 'merriweather-200-900-normal' => array( - 'provider' => 'local', - 'font-family' => 'Merriweather', - 'font-style' => 'normal', - 'font-weight' => '200 900', - 'font-display' => 'fallback', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', - ), - ), - 'Source Serif Pro' => array( - 'Source Serif Pro-300-normal' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'normal', - 'font-weight' => '300', - 'font-display' => 'fallback', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'font-display' => 'fallback', - ), - 'Source Serif Pro-900-italic' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'italic', - 'font-weight' => '900', - 'font-display' => 'fallback', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', - 'font-display' => 'fallback', - ), - ), - ); - } - - /** - * Gets font definitions for mock provider. - * - * @since X.X.X - * - * @return string[][][] - */ - protected function get_registered_mock_fonts() { - return array( - 'font1' => array( - 'font1-300-normal' => array( - 'provider' => 'mock', - 'font-family' => 'Font 1', - 'font-weight' => '300', - 'font-style' => 'normal', - 'font-display' => 'fallback', - ), - 'font1-300-italic' => array( - 'provider' => 'mock', - 'font-family' => 'Font 1', - 'font-weight' => '300', - 'font-style' => 'italic', - 'font-display' => 'fallback', - ), - 'font1-900-normal' => array( - 'provider' => 'mock', - 'font-family' => 'Font 1', - 'font-weight' => '900', - 'font-style' => 'normal', - 'font-display' => 'fallback', - ), - ), - 'font2' => array( - 'font2-200-900-normal' => array( - 'provider' => 'mock', - 'font-family' => 'Font 2', - 'font-weight' => '200 900', - 'font-style' => 'normal', - 'font-display' => 'fallback', - ), - 'font2-200-900-italic' => array( - 'provider' => 'mock', - 'font-family' => 'Font 2', - 'font-weight' => '200 900', - 'font-style' => 'italic', - 'font-display' => 'fallback', - ), - ), - 'font3' => array( - 'font3-bold-normal' => array( - 'provider' => 'mock', - 'font-family' => 'Font 3', - 'font-weight' => 'bold', - 'font-style' => 'normal', - 'font-display' => 'fallback', - 'font-stretch' => 'normal', - ), - ), - ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_print_user_selected_fonts() { - $global_styles = $this->get_mock_user_selected_fonts_global_styles(); - $font_faces = $this->get_registered_fonts_css(); - - return array( - 'print font1' => array( - 'global_styles' => $global_styles['font1'], - 'expected_done' => array( - 'font1-300-normal', - 'font1-300-italic', - 'font1-900-normal', - 'font1', - ), - 'expected_output' => sprintf( - '%s; %s; %s\n', - $font_faces['font1-300-normal'], - $font_faces['font1-300-italic'], - $font_faces['font1-900-normal'] - ), - ), - 'print font2' => array( - 'global_styles' => $global_styles['font2'], - 'expected_done' => array( 'font2-200-900-normal', 'font2-200-900-italic', 'font2' ), - 'expected_output' => sprintf( - '%s; %s\n', - $font_faces['font2-200-900-normal'], - $font_faces['font2-200-900-italic'] - ), - ), - 'print font3' => array( - 'global_styles' => $global_styles['font3'], - 'expected_done' => array( 'font3', 'font3-bold-normal' ), - 'expected_output' => sprintf( - '%s\n', - $font_faces['font3-bold-normal'] - ), - ), - 'print all fonts' => array( - 'global_styles' => $global_styles['all'], - 'expected_done' => array( - 'font1-300-normal', - 'font1-300-italic', - 'font1-900-normal', - 'font1', - 'font2-200-900-normal', - 'font2-200-900-italic', - 'font2', - 'font3-bold-normal', - 'font3', - ), - 'expected_output' => sprintf( - '%s; %s; %s; %s; %s; %s\n', - $font_faces['font1-300-normal'], - $font_faces['font1-300-italic'], - $font_faces['font1-900-normal'], - $font_faces['font2-200-900-normal'], - $font_faces['font2-200-900-italic'], - $font_faces['font3-bold-normal'] - ), - ), - 'print all valid fonts' => array( - 'global_styles' => $global_styles['all with invalid element'], - 'expected_done' => array( - 'font1-300-normal', - 'font1-300-italic', - 'font1-900-normal', - 'font1', - 'font2-200-900-normal', - 'font2-200-900-italic', - 'font2', - 'font3-bold-normal', - 'font3', - ), - 'expected_output' => sprintf( - '%s; %s; %s; %s; %s; %s\n', - $font_faces['font1-300-normal'], - $font_faces['font1-300-italic'], - $font_faces['font1-900-normal'], - $font_faces['font2-200-900-normal'], - $font_faces['font2-200-900-italic'], - $font_faces['font3-bold-normal'] - ), - ), - ); - } - - /** - * Gets user-selected fonts for global styles for the mock provider. - * - * @since X.X.X - * - * @return array - */ - protected function get_mock_user_selected_fonts_global_styles() { - return array( - 'font1' => array( - 'elements' => array( - 'heading' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'normal', - 'fontWeight' => '300', - ), - ), - 'caption' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'italic', - 'fontWeight' => '300', - ), - ), - ), - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'normal', - 'fontWeight' => '900', - ), - ), - 'font2' => array( - 'elements' => array( - 'heading' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font2', - 'fontStyle' => 'normal', - 'fontWeight' => '200-900', - ), - ), - 'button' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font2', - 'fontStyle' => 'italic', - 'fontWeight' => '200-900', - ), - ), - ), - ), - 'font3' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font3', - 'fontStyle' => 'normal', - 'fontWeight' => 'bold', - ), - ), - 'all' => array( - 'elements' => array( - 'link' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'italic', - 'fontWeight' => '300', - ), - ), - 'heading' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'normal', - 'fontWeight' => '900', - ), - ), - 'caption' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'italic', - 'fontWeight' => '300', - ), - ), - 'button' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font2', - 'fontStyle' => 'normal', - 'fontWeight' => '200-900', - ), - ), - ), - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font3', - 'fontStyle' => 'normal', - 'fontWeight' => 'bold', - ), - ), - 'all with invalid element' => array( - 'elements' => array( - 'link' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'italic', - 'fontWeight' => '300', - ), - ), - 'heading' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'normal', - 'fontWeight' => '900', - ), - ), - 'caption' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'italic', - 'fontWeight' => '300', - ), - ), - 'button' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font2', - 'fontStyle' => 'normal', - 'fontWeight' => '200-900', - ), - ), - 'invalid' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font2', - 'fontStyle' => 'italic', - 'fontWeight' => '200-900', - ), - ), - ), - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font3', - 'fontStyle' => 'normal', - 'fontWeight' => 'bold', - ), - ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpDeregisterFontFamily.php b/phpunit/tests/fonts-api/wpDeregisterFontFamily.php deleted file mode 100644 index 59db2e2abc826..0000000000000 --- a/phpunit/tests/fonts-api/wpDeregisterFontFamily.php +++ /dev/null @@ -1,74 +0,0 @@ -set_up_mock( 'remove_font_family' ); - $mock->expects( $this->once() ) - ->method( 'remove_font_family' ) - ->with( - $this->identicalTo( $font_family_handle ) - ); - - wp_deregister_font_family( $font_family_handle ); - } - - /** - * Integration test for enqueuing before registering a font family and all of its variations. - * - * @dataProvider data_font_family_handles - * - * @param string $font_family Font family to test. - */ - public function test_should_deregister_before_registration( $font_family ) { - wp_deregister_font_family( $font_family ); - - $this->assertIsArray( $this->get_registered(), 'Registration queue should be an array' ); - $this->assertEmpty( $this->get_registered(), 'Registration queue should be empty after deregistering' ); - } - - /** - * Integration test for deregistering a font family and all of its variations. - * - * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations - * - * @param string $font_family Font family to test. - * @param array $inputs Font family(ies) and variations to pre-register. - * @param array $registered_handles Expected handles after registering. - * @param array $expected Array of expected handles. - */ - public function test_deregister_after_registration( $font_family, array $inputs, array $registered_handles, array $expected ) { - foreach ( $inputs as $handle => $variations ) { - $this->setup_register( $handle, $variations ); - } - // Test the before state, just to make sure. - $this->assertSame( $registered_handles, $this->get_registered_handles(), 'Font family and variations should be registered before deregistering' ); - - wp_deregister_font_family( $font_family ); - - // Test after deregistering. - $this->assertIsArray( $this->get_registered_handles(), 'Registration queue should be an array' ); - $this->assertSame( $expected, $this->get_registered_handles(), 'Registration queue should match after deregistering' ); - } -} diff --git a/phpunit/tests/fonts-api/wpDeregisterFontVariation.php b/phpunit/tests/fonts-api/wpDeregisterFontVariation.php deleted file mode 100644 index 78b4bf51f758a..0000000000000 --- a/phpunit/tests/fonts-api/wpDeregisterFontVariation.php +++ /dev/null @@ -1,298 +0,0 @@ -wp_fonts = wp_fonts(); - $this->fonts_to_register = $this->get_registered_local_fonts(); - } - - /** - * Sets up the unit test by mocking the WP_Dependencies object using stdClass and - * registering each font family directly to the WP_Webfonts::$registered property - * and its variations to the mocked $deps property. - */ - private function setup_unit_test() { - $this->setup_registration_mocks( $this->fonts_to_register, $this->wp_fonts ); - } - - /** - * Sets up the integration test by properly registering each font family and its variations - * by using the WP_Webfonts::add() and WP_Webfonts::add_variation() methods. - */ - private function setup_integration_test() { - foreach ( $this->fonts_to_register as $font_family_handle => $variations ) { - $this->setup_register( $font_family_handle, $variations, $this->wp_fonts ); - } - } - - /** - * Testing the test setup to ensure it works. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - */ - public function test_mocked_setup( $font_family_handle, $variation_handle ) { - $this->setup_unit_test(); - - $this->assertArrayHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be in the registered queue before removal' ); - $this->assertContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should be in its font family deps before removal' ); - } - - /** - * Unit test for deregistering a font-family's variation using mock of WP_Webfonts. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family to test. - * @param string $variation_handle Variation's handle to test. - */ - public function test_should_deregister_when_mocked( $font_family_handle, $variation_handle ) { - $mock = $this->set_up_mock( 'remove_variation' ); - $mock->expects( $this->once() ) - ->method( 'remove_variation' ) - ->with( - $this->identicalTo( $font_family_handle, $variation_handle ) - ); - - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - } - - /** - * Unit test. - * - * @dataProvider data_should_do_nothing - * - * @param string $font_family Font family name. - * @param string $font_family_handle Font family handle. - * @param string $variation_handle Variation handle to remove. - */ - public function test_unit_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) { - // Set up the test. - unset( $this->fonts_to_register[ $font_family ] ); - $this->setup_unit_test(); - $registered_queue = $this->wp_fonts->registered; - - // Run the tests. - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - $this->assertArrayNotHasKey( $font_family_handle, $this->wp_fonts->registered, 'Font family should not be registered' ); - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' ); - $this->assertSame( $registered_queue, $this->wp_fonts->registered, 'Registered queue should not have changed' ); - } - - /** - * Integration test. - * - * @dataProvider data_should_do_nothing - * - * @param string $font_family Font family name. - * @param string $font_family_handle Font family handle. - * @param string $variation_handle Variation handle to remove. - */ - public function test_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) { - // Set up the test. - unset( $this->fonts_to_register[ $font_family ] ); - $this->setup_integration_test(); - $registered_queue = $this->wp_fonts->get_registered(); - - // Run the tests. - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - $this->assertArrayNotHasKey( $font_family_handle, $this->wp_fonts->registered, 'Font family should not be registered' ); - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' ); - $this->assertSameSets( $registered_queue, $this->wp_fonts->get_registered(), 'Registered queue should not have changed' ); - } - - /** - * Data provider for testing removal of variations. - * - * @return array - */ - public function data_should_do_nothing() { - return array( - 'Font with 1 variation' => array( - 'font_family' => 'merriweather', - 'font_family_handle' => 'merriweather', - 'variation_handle' => 'merriweather-200-900-normal', - ), - 'Font with multiple variations' => array( - 'font_family' => 'Source Serif Pro', - 'font_family_handle' => 'source-serif-pro', - 'variation_handle' => 'Source Serif Pro-300-normal', - ), - ); - } - - /** - * Unit test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_unit_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_unit_test(); - $this->setup_remove_variation_from_registered( $variation_handle ); - - // Run the tests. - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' ); - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Integration test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_integration_test(); - $this->setup_remove_variation_from_registered( $variation_handle ); - - // Run the tests. - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' ); - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Unit test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_unit_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_unit_test(); - $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ); - - $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' ); - - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Integration test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_integration_test(); - $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ); - - $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' ); - - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Unit test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_unit_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_unit_test(); - - $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' ); - - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be not be in registered queue' ); - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Integration test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_integration_test(); - - $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' ); - - wp_deregister_font_variation( $font_family_handle, $variation_handle ); - - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be not be in registered queue' ); - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Remove the variation handle from the font family's deps. - * - * @param string $font_family_handle Font family. - * @param string $variation_handle The variation handle to remove. - */ - private function setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ) { - foreach ( $this->wp_fonts->registered[ $font_family_handle ]->deps as $index => $vhandle ) { - if ( $variation_handle !== $vhandle ) { - continue; - } - unset( $this->wp_fonts->registered[ $font_family_handle ]->deps[ $index ] ); - break; - } - } - - /** - * Removes the variation from the WP_Webfonts::$registered queue. - * - * @param string $variation_handle The variation handle to remove. - */ - private function setup_remove_variation_from_registered( $variation_handle ) { - unset( $this->wp_fonts->registered[ $variation_handle ] ); - } -} diff --git a/phpunit/tests/fonts-api/wpEnqueueFontVariations.php b/phpunit/tests/fonts-api/wpEnqueueFontVariations.php deleted file mode 100644 index 6e026d99f5889..0000000000000 --- a/phpunit/tests/fonts-api/wpEnqueueFontVariations.php +++ /dev/null @@ -1,82 +0,0 @@ -set_up_mock( 'enqueue' ); - $mock->expects( $this->once() ) - ->method( 'enqueue' ) - ->with( - $this->identicalTo( $handles ) - ); - - wp_enqueue_font_variations( $handles ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_variation_handles() { - return array( - '1 variation handle' => array( 'merriweather-200-900-normal' ), - 'multiple same font family handles' => array( array( 'Source Serif Pro-300-normal', 'Source Serif Pro-900-italic' ) ), - 'handles from different font families' => array( array( 'merriweather-200-900-normal', 'Source Serif Pro-900-italic' ) ), - ); - } - - /** - * Integration test for enqueuing one or more specific variations. - * - * @dataProvider data_enqueue_variations - * - * @param string|string[] $handles Variation handles to test. - * @param array $expected Expected queue. - */ - public function test_should_enqueue_after_registration( $handles, array $expected ) { - foreach ( $this->get_data_registry() as $handle => $variations ) { - $this->setup_register( $handle, $variations ); - } - - wp_enqueue_font_variations( $handles ); - $this->assertEmpty( $this->get_queued_before_register(), '"queued_before_register" queue should be empty' ); - $this->assertSame( $expected, $this->get_enqueued_handles(), 'Queue should contain the given handles' ); - } - - /** - * Integration test for enqueuing before registering one or more specific variations. - * - * @dataProvider data_enqueue_variations - * - * @param string|string[] $handles Variation handles to test. - * @param array $not_used Not used. - * @param array $expected Expected "queued_before_register" queue. - */ - public function test_should_enqueue_before_registration( $handles, array $not_used, array $expected ) { - wp_enqueue_font_variations( $handles ); - - $this->assertSame( $expected, $this->get_queued_before_register(), '"queued_before_register" queue should contain the given handles' ); - $this->assertEmpty( $this->get_enqueued_handles(), 'Queue should be empty' ); - } -} diff --git a/phpunit/tests/fonts-api/wpEnqueueFonts.php b/phpunit/tests/fonts-api/wpEnqueueFonts.php deleted file mode 100644 index c00fb0f2dfdae..0000000000000 --- a/phpunit/tests/fonts-api/wpEnqueueFonts.php +++ /dev/null @@ -1,122 +0,0 @@ -set_up_mock( 'enqueue' ); - $mock->expects( $this->once() ) - ->method( 'enqueue' ) - ->with( - $this->identicalTo( $expected_handles ) - ); - - wp_enqueue_fonts( $font_families ); - } - - /** - * Integration test for enqueuing a font family and all of its variations. - * - * @dataProvider data_should_enqueue - * - * @param string[] $font_families Font families to test. - * @param string[] $expected_handles Expected handles passed to WP_Fonts::enqueue(). - */ - public function test_should_enqueue_after_registration( $font_families, $expected_handles ) { - // Register the font-families. - foreach ( $this->get_data_registry() as $handle => $variations ) { - $this->setup_register( $handle, $variations ); - } - - wp_enqueue_fonts( $font_families ); - - $this->assertEmpty( $this->get_queued_before_register(), '"queued_before_register" queue should be empty' ); - $this->assertSame( $expected_handles, $this->get_enqueued_handles(), 'Queue should contain the given font family(ies)' ); - } - - /** - * Integration test for enqueuing before registering a font family and all of its variations. - * - * @dataProvider data_should_enqueue - * - * @param string[] $font_families Font families to test. - * @param string[] $expected_handles Expected handles passed to WP_Fonts::enqueue(). - */ - public function test_should_enqueue_before_registration( $font_families, $expected_handles ) { - wp_enqueue_fonts( $font_families ); - - // Set up what "queued_before_register" queue should be. - $expected = array(); - foreach ( $expected_handles as $handle ) { - $expected[ $handle ] = null; - } - $this->assertSame( $expected, $this->get_queued_before_register(), '"queued_before_register" queue should contain the given font family(ies)' ); - $this->assertEmpty( $this->get_enqueued_handles(), 'Queue should be empty' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_should_enqueue() { - return array( - '1: single word handle' => array( - 'font_families' => array( 'lato' ), - 'expected_handles' => array( 'lato' ), - ), - '1: multiple word handle' => array( - 'font_families' => array( 'source-serif-pro' ), - 'expected_handles' => array( 'source-serif-pro' ), - ), - '1: single word name' => array( - 'font_families' => array( 'Merriweather' ), - 'expected_handles' => array( 'merriweather' ), - ), - '1: multiple word name' => array( - 'font_families' => array( 'My Font' ), - 'expected_handles' => array( 'my-font' ), - ), - '>1: single word handle' => array( - 'font_families' => array( 'lato', 'merriweather' ), - 'expected_handles' => array( 'lato', 'merriweather' ), - ), - '>1: multiple word handle' => array( - 'font_families' => array( 'source-serif-pro', 'my-font' ), - 'expected_handles' => array( 'source-serif-pro', 'my-font' ), - ), - '>1: single word name' => array( - 'font_families' => array( 'Lato', 'Merriweather' ), - 'expected_handles' => array( 'lato', 'merriweather' ), - ), - '>1: multiple word name' => array( - 'font_families' => array( 'My Font', 'Source Serif Pro' ), - 'expected_handles' => array( 'my-font', 'source-serif-pro' ), - ), - '>1: mixture of word handles and names' => array( - 'font_families' => array( 'Source Serif Pro', 'Merriweather', 'my-font', 'Lato' ), - 'expected_handles' => array( 'source-serif-pro', 'merriweather', 'my-font', 'lato' ), - ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts.php b/phpunit/tests/fonts-api/wpFonts.php deleted file mode 100644 index 3b33e58edf299..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts.php +++ /dev/null @@ -1,38 +0,0 @@ -assertInstanceOf( WP_Fonts::class, wp_fonts() ); - } - - public function test_global_set() { - global $wp_fonts; - $this->assertNull( $wp_fonts ); - $instance = wp_fonts(); - $this->assertInstanceOf( WP_Fonts::class, $wp_fonts ); - $this->assertSame( $instance, $wp_fonts ); - } - - public function test_local_provider_is_automatically_registered() { - $expected = array( - 'local' => array( - 'class' => 'WP_Fonts_Provider_Local', - 'fonts' => array(), - ), - ); - $this->assertSame( $expected, wp_fonts()->get_providers() ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/add.php b/phpunit/tests/fonts-api/wpFonts/add.php deleted file mode 100644 index 85d3d98c0f355..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/add.php +++ /dev/null @@ -1,44 +0,0 @@ -assertTrue( $wp_fonts->add( $handle, false ), 'Registering a handle should return true' ); - $this->assertCount( 1, $wp_fonts->registered ); - $this->assertArrayHasKey( $handle, $wp_fonts->registered, 'Font family handle should be in the registry after registration' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_handles() { - return array( - 'name: multiple' => array( 'Source Serif Pro' ), - 'handle: multiple' => array( 'source-serif-pro' ), - 'name: single' => array( 'Merriweather' ), - 'handle: single' => array( 'merriweather' ), - 'handle: variation' => array( 'my-custom-font-200-900-normal' ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/addFontFamily.php b/phpunit/tests/fonts-api/wpFonts/addFontFamily.php deleted file mode 100644 index 69b6bccf5ae35..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/addFontFamily.php +++ /dev/null @@ -1,65 +0,0 @@ -add_font_family( $font_family ); - - $this->assertSame( $expected, $font_family_handle, 'Registering a font-family should return its handle' ); - $this->assertCount( 1, $wp_fonts->registered ); - $this->assertArrayHasKey( $font_family_handle, $wp_fonts->registered, 'Font family handle should be in the registry after registration' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_handles() { - return array( - 'name: multiple' => array( - 'font_family' => 'Source Serif Pro', - 'expected' => 'source-serif-pro', - ), - 'handle: multiple' => array( - 'font_family' => 'source-serif-pro', - 'expected' => 'source-serif-pro', - ), - 'name: single' => array( - 'font_family' => 'Merriweather', - 'expected' => 'merriweather', - ), - 'handle: single' => array( - 'font_family' => 'merriweather', - 'expected' => 'merriweather', - ), - 'handle: variation' => array( - 'font_family' => 'my-custom-font-200-900-normal', - 'expected' => 'my-custom-font-200-900-normal', - ), - 'name: multiple font-families' => array( - 'font_family' => 'Source Serif Pro, Merriweather', - 'expected' => 'source-serif-pro', - ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/addVariation.php b/phpunit/tests/fonts-api/wpFonts/addVariation.php deleted file mode 100644 index 5b6d93bcc99fe..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/addVariation.php +++ /dev/null @@ -1,149 +0,0 @@ -add( $font_family_handle, false ); - - $variation_handle = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle ); - $this->assertSame( $expected, $variation_handle, 'Registering a variation should return its handle' ); - $this->assertArrayHasKey( $variation_handle, $wp_fonts->registered, 'Variation handle should be in the registry after registration' ); - $this->assertSame( array( $expected ), $this->get_variations( $font_family_handle, $wp_fonts ), 'Variation should be registered to font family' ); - } - - /** - * @dataProvider data_valid_variation - * - * @param string|bool $expected Expected results. - * @param string $font_family_handle The font family's handle for this variation. - * @param array $variation An array of variation properties to add. - * @param string $variation_handle Optional. The variation's handle. - */ - public function test_should_not_reregister_font_family( $expected, $font_family_handle, array $variation, $variation_handle = '' ) { - $wp_fonts = new WP_Fonts(); - $wp_fonts->add( $font_family_handle, false ); - - $variation_handle = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle ); - - // Font family should appear only once in the registered queue. - $expected = array( $font_family_handle, $variation_handle ); - $this->assertSame( $expected, array_keys( $wp_fonts->registered ), 'Font family should not be re-registered after registering a variation' ); - } - - /** - * @dataProvider data_valid_variation - * - * @param string|bool $expected Expected results. - * @param string $font_family_handle The font family's handle for this variation. - * @param array $variation An array of variation properties to add. - * @param string $variation_handle Optional. The variation's handle. - */ - public function test_should_not_reregister_variation( $expected, $font_family_handle, array $variation, $variation_handle = '' ) { - $wp_fonts = new WP_Fonts(); - $wp_fonts->add( $font_family_handle, false ); - - // Set up the test. - $variation_handle = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle ); - - // Run the test. - $variant_handle_on_reregister = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle ); - $this->assertSame( $expected, $variant_handle_on_reregister, 'Variation should be registered to font family' ); - $this->assertSame( $variation_handle, $variant_handle_on_reregister, 'Variation should return the previously registered variant handle' ); - $this->assertSame( array( $variation_handle ), $this->get_variations( $font_family_handle, $wp_fonts ), 'Variation should only be registered once' ); - - $this->assertCount( 2, $wp_fonts->registered ); - $this->assertArrayHasKey( $variation_handle, $wp_fonts->registered, 'Variation handle should be in the registry after registration' ); - } - - /** - * @dataProvider data_valid_variation - * - * @param string|bool $expected Expected results. - * @param string $font_family_handle The font family's handle for this variation. - * @param array $variation An array of variation properties to add. - * @param string $variation_handle Optional. The variation's handle. - */ - public function test_should_register_font_family_and_variation( $expected, $font_family_handle, array $variation, $variation_handle = '' ) { - $wp_fonts = new WP_Fonts(); - - $variation_handle = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle ); - $this->assertSame( $expected, $variation_handle, 'Variation should return its registered handle' ); - - // Extra checks to ensure both are registered. - $this->assertCount( 2, $wp_fonts->registered ); - $this->assertArrayHasKey( $font_family_handle, $wp_fonts->registered, 'Font family handle should be in the registry after registration' ); - $this->assertArrayHasKey( $variation_handle, $wp_fonts->registered, 'Variation handle should be in the registry after registration' ); - $this->assertSame( array( $variation_handle ), $this->get_variations( $font_family_handle, $wp_fonts ), 'Variation should be registered to the font family' ); - } - - /** - * @dataProvider data_font_family_handle_undefined - * - * @param string $font_family_handle The font family's handle for this variation. - * @param array $variation An array of variation properties to add. - */ - public function test_should_not_register_font_family_or_variant( $font_family_handle, array $variation ) { - $this->expectNotice(); - $this->expectNoticeMessage( 'Font family handle must be a non-empty string.' ); - - $wp_fonts = new WP_Fonts(); - $wp_fonts->add_variation( $font_family_handle, $variation ); - - $this->assertEmpty( $wp_fonts->registered, 'Registered queue should be empty' ); - $this->assertEmpty( $this->get_variations( $font_family_handle, $wp_fonts ), 'Variation should not be registered to the font family' ); - } - - /** - * @dataProvider data_font_family_undefined_in_variation - * @dataProviders data_unable_determine_variation_handle - * - * @param string $font_family_handle The font family's handle for this variation. - * @param array $variation An array of variation properties to add. - * @param string $expected_message Expected notice message. - */ - public function test_should_not_register_variation_when_font_family_not_defined( $font_family_handle, array $variation, $expected_message ) { - $this->expectNotice(); - $this->expectNoticeMessage( $expected_message ); - - $wp_fonts = new WP_Fonts(); - $this->assertNull( $wp_fonts->add_variation( $font_family_handle, $variation ) ); - } - - /** - * @dataProvider data_unable_determine_variation_handle - * - * @param string $font_family_handle The font family's handle for this variation. - * @param array $variation An array of variation properties to add. - */ - public function test_should_register_font_family_when_variant_fails_to_register( $font_family_handle, array $variation ) { - $this->expectNotice(); - $this->expectNoticeMessage( 'Variant handle could not be determined as font-weight and/or font-style are require' ); - - $wp_fonts = new WP_Fonts(); - $wp_fonts->add_variation( $font_family_handle, $variation ); - - $this->assertCount( 1, $wp_fonts->registered ); - $this->assertArrayHasKey( $font_family_handle, $wp_fonts->registered ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/dequeue.php b/phpunit/tests/fonts-api/wpFonts/dequeue.php deleted file mode 100644 index 6cd11dc55b937..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/dequeue.php +++ /dev/null @@ -1,72 +0,0 @@ -dequeue( $handles ); - $this->assertEmpty( $this->get_queued_before_register( $wp_fonts ), 'Prequeue should be empty' ); - $this->assertEmpty( $wp_fonts->queue, 'Queue should be empty' ); - } - - /** - * Integration test for dequeuing from queue. It first registers and then enqueues before dequeuing. - * - * @dataProvider data_enqueue - * @dataProvider data_enqueue_variations - * - * @param string|string[] $handles Handles to test. - */ - public function test_should_dequeue_from_queue( $handles ) { - $wp_fonts = new WP_Fonts(); - - // Register and enqueue. - foreach ( $this->get_data_registry() as $handle => $variations ) { - $this->setup_register( $handle, $variations, $wp_fonts ); - } - $wp_fonts->enqueue( $handles ); - - // To make sure the handles are in the queue before dequeuing. - $this->assertNotEmpty( $wp_fonts->queue, 'Queue not be empty before dequeueing' ); - - // Run the test. - $wp_fonts->dequeue( $handles ); - $this->assertEmpty( $wp_fonts->queue, 'Queue should be empty after dequeueing' ); - } - - /** - * Integration test for dequeuing from prequeue. It enqueues first. - * - * @dataProvider data_enqueue - * @dataProvider data_enqueue_variations - * - * @param string|string[] $handles Handles to test. - */ - public function test_should_dequeue_from_prequeue( $handles ) { - $wp_fonts = new WP_Fonts(); - $wp_fonts->enqueue( $handles ); - $this->assertNotEmpty( $this->get_queued_before_register( $wp_fonts ), 'Prequeue not be empty before dequeueing' ); - - $wp_fonts->dequeue( $handles ); - $this->assertEmpty( $this->get_queued_before_register( $wp_fonts ), 'Prequeue should be empty after dequeueing' ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/doItem.php b/phpunit/tests/fonts-api/wpFonts/doItem.php deleted file mode 100644 index a92bd244d1f2a..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/doItem.php +++ /dev/null @@ -1,336 +0,0 @@ -wp_fonts = new WP_Fonts(); - } - - public function test_should_return_false_when_provider_not_registered() { - $this->assertFalse( $this->wp_fonts->do_item( 'provider_not_registered' ) ); - } - - /** - * @dataProvider data_provider_definitions - * - * @param array $provider Provider to mock. - */ - public function test_should_return_false_when_no_fonts_enqueued_for_provider( array $provider ) { - $this->setup_provider_property_mock( $this->wp_fonts, $provider ); - $this->assertFalse( $this->wp_fonts->do_item( $provider['id'] ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_provider_definitions() { - $providers = $this->get_provider_definitions(); - - return array( - 'mock' => array( $providers['mock'] ), - 'local' => array( $providers['local'] ), - ); - } - - /** - * Test the test set up to ensure the `Tests_Fonts_WpFonts_DoItem_::setup_provider_property_mock()` - * method works as expected. - */ - public function test_mocking_providers_property() { - $font_handles = array( 'font1', 'font2', 'font3' ); - $expected = array( - 'mock' => array( - 'class' => Mock_Provider::class, - 'fonts' => $font_handles, - ), - ); - - $this->setup_provider_property_mock( $this->wp_fonts, $this->get_provider_definitions( 'mock' ), $font_handles ); - $actual = $this->property['WP_Fonts::$providers']->getValue( $this->wp_fonts ); - $this->assertSame( $expected, $actual ); - } - - /** - * Test the private method WP_Fonts::get_enqueued_fonts_for_provider(). - * - * Why? This test validates the right fonts are returned for use within - * WP_Fonts::do_item(). - * - * @dataProvider data_get_enqueued_fonts_for_provider - * - * @param array $font_handles Array of handles for the provider. - * @param array $to_do Handles to set for the WP_Fonts::$to_do property. - * @param array $expected Expected result. - */ - public function test_get_enqueued_fonts_for_provider( $font_handles, $to_do, $expected ) { - // Set up the `to_do` property. - $this->wp_fonts->to_do = $to_do; - - // Open the method's visibility for testing. - $get_enqueued_fonts_for_provider = $this->get_reflection_method( 'get_enqueued_fonts_for_provider' ); - - // Mock the WP_Fonts::$property to set up the test. - $this->setup_provider_property_mock( $this->wp_fonts, $this->get_provider_definitions( 'mock' ), $font_handles ); - - $actual = $get_enqueued_fonts_for_provider->invoke( $this->wp_fonts, 'mock' ); - $this->assertSameSets( $expected, $actual ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_get_enqueued_fonts_for_provider() { - return array( - 'to_do queue is empty' => array( - 'font_handles ' => array( 'font1', 'font2', 'font3' ), - 'to_do' => array(), - 'expected' => array(), - ), - 'fonts not in to_do queue' => array( - 'font_handles ' => array( 'font1', 'font2', 'font3' ), - 'to_do' => array( 'font12', 'font13' ), - 'expected' => array(), - ), - '2 of the provider fonts in to_do queue' => array( - 'font_handles ' => array( 'font11', 'font12', 'font13' ), - 'to_do' => array( 'font11', 'font13' ), - 'expected' => array( 'font11', 'font13' ), - ), - 'do all of the provider fonts' => array( - 'font_handles ' => array( 'font21', 'font22', 'font23' ), - 'to_do' => array( 'font21', 'font22', 'font23' ), - 'expected' => array( 'font21', 'font22', 'font23' ), - ), - ); - } - - /** - * Test the private method WP_Fonts::get_font_properties_for_provider(). - * - * Why? This test validates the right font properties are returned for use within - * WP_Fonts::do_item(). - * - * @dataProvider data_get_font_properties_for_provider - * - * @param array $font_handles Web fonts for testing. - * @param array $expected Expected result. - */ - public function test_get_font_properties_for_provider( $font_handles, $expected ) { - // Set up the fonts for WP_Dependencies:get_data(). - $fonts = $this->get_registered_fonts(); - // Set all variations to 'mock' provider. - - // Mock the WP_Fonts::$property to set up the test. - $this->setup_provider_property_mock( $this->wp_fonts, $this->get_provider_definitions( 'mock' ), $font_handles ); - $this->setup_registration_mocks( $fonts, $this->wp_fonts ); - - // Open the method's visibility for testing. - $method = $this->get_reflection_method( 'get_font_properties_for_provider' ); - - $actual = $method->invoke( $this->wp_fonts, $font_handles ); - $this->assertSame( $expected, $actual ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_get_font_properties_for_provider() { - $fonts = $this->get_registered_fonts(); - - return array( - 'handles not registered' => array( - 'font_handles' => array( 'font-not-registered1', 'font-not-registered2', 'font-not-registered3' ), - 'expected' => array(), - ), - 'registered and non-registered handles' => array( - 'font_handles' => array( 'Source Serif Pro-300-normal', 'not-registered-handle', 'Source Serif Pro-900-italic' ), - 'expected' => array( - 'Source Serif Pro-300-normal' => $fonts['Source Serif Pro']['Source Serif Pro-300-normal'], - 'Source Serif Pro-900-italic' => $fonts['Source Serif Pro']['Source Serif Pro-900-italic'], - ), - ), - 'font-family handles, ie no "font-properties" extra data' => array( - 'font_handles' => array( 'font1', 'font2', 'merriweather' ), - 'expected' => array(), - ), - ); - } - - /** - * @dataProvider data_print_enqueued_fonts - * - * @param array $provider Define provider. - * @param array $fonts Fonts to register and enqueue. - * @param array $expected Expected results. - */ - public function test_should_trigger_provider_when_mocked( array $provider, array $fonts, array $expected ) { - $this->setup_print_deps( $provider, $fonts ); - - $provider_mock = $this->setup_object_mock( array( 'set_fonts', 'print_styles' ), $provider['class'] ); - - // Test the provider's methods are invoked. - $provider_mock->expects( $this->once() )->method( 'set_fonts' )->with( $this->identicalTo( $expected['set_fonts'] ) ); - $provider_mock->expects( $this->once() )->method( 'print_styles' ); - - // Set up the WP_Fonts::$provider_instances property. - $provider_instances = $this->get_reflection_property( 'provider_instances' ); - $provider_instances->setValue( $this->wp_fonts, array( $provider['id'] => $provider_mock ) ); - - // Test the method successfully processes the provider. - $this->expectOutputString( '' ); - $this->assertTrue( $this->wp_fonts->do_item( $provider['id'] ), 'WP_Fonts::do_item() should return true' ); - } - - /** - * Integration test. - * - * @dataProvider data_print_enqueued_fonts - * - * @param array $provider Define provider. - * @param array $fonts Fonts to register and enqueue. - * @param array $expected Expected results. - */ - public function test_should_print( array $provider, array $fonts, array $expected ) { - $this->setup_print_deps( $provider, $fonts ); - - // Test the method successfully processes the provider. - $this->expectOutputString( $expected['printed_output'] ); - $this->assertTrue( $this->wp_fonts->do_item( $provider['id'] ), 'WP_Fonts::do_item() should return true' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_print_enqueued_fonts() { - $mock = $this->get_registered_mock_fonts(); - $local = $this->get_registered_local_fonts(); - $font_faces = $this->get_registered_fonts_css(); - - return array( - 'mock' => array( - 'provider' => $this->get_provider_definitions( 'mock' ), - 'fonts' => $mock, - 'expected' => array( - 'set_fonts' => array_merge( $mock['font1'], $mock['font2'], $mock['font3'] ), - 'printed_output' => sprintf( - '%s; %s; %s; %s; %s; %s\n', - $font_faces['font1-300-normal'], - $font_faces['font1-300-italic'], - $font_faces['font1-900-normal'], - $font_faces['font2-200-900-normal'], - $font_faces['font2-200-900-italic'], - $font_faces['font3-bold-normal'] - ), - ), - ), - 'local' => array( - 'provider' => $this->get_provider_definitions( 'local' ), - 'fonts' => $local, - 'expected' => array( - 'set_fonts' => array_merge( $local['merriweather'], $local['Source Serif Pro'] ), - 'printed_output' => sprintf( - "\n", - $font_faces['merriweather-200-900-normal'], - $font_faces['Source Serif Pro-300-normal'], - $font_faces['Source Serif Pro-900-italic'] - ), - ), - ), - ); - } - - /** - * Integration test. - * - * @dataProvider data_not_print_enqueued_fonts - * - * @param array $provider Define provider. - * @param array $fonts Fonts to register and enqueue. - * @param array $expected Not used. - * @param array $to_do_queue Value to set in the WP_Fonts::$to_do queue. - */ - public function test_should_not_print_when_to_do_queue_empty( array $provider, array $fonts, $expected, $to_do_queue ) { - $this->setup_print_deps( $provider, $fonts, $to_do_queue ); - - // Test the method successfully processes the provider. - $this->expectOutputString( '' ); - $this->assertFalse( $this->wp_fonts->do_item( $provider['id'] ), 'WP_Fonts::do_item() should return false' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_not_print_enqueued_fonts() { - $mock = $this->get_registered_mock_fonts(); - $local = $this->get_registered_local_fonts(); - - return array( - 'mock provider when to_do queue is empty' => array( - 'provider' => $this->get_provider_definitions( 'mock' ), - 'fonts' => $mock, - 'expected' => array(), - 'to_do_queue' => array(), - ), - 'local provider when to_do queue is empty' => array( - 'provider' => $this->get_provider_definitions( 'local' ), - 'fonts' => $local, - 'expected' => array(), - 'to_do_queue' => array(), - ), - 'fonts not in to_do queue' => array( - 'provider' => $this->get_provider_definitions( 'mock' ), - 'fonts' => $mock, - 'expected' => array(), - 'to_do_queue' => array(), - ), - ); - } - - /** - * Sets up the print dependencies. - * - * @param array $provider Provider id and class. - * @param array $fonts Fonts to register and enqueue. - * @param array|null $to_do_queue Set the WP_Fonts:$to_do queue. - */ - private function setup_print_deps( $provider, $fonts, $to_do_queue = null ) { - // Set up the fonts for WP_Dependencies:get_data(). - $mocks = $this->setup_registration_mocks( $fonts, $this->wp_fonts ); - $handles = array_keys( $mocks ); - $this->setup_provider_property_mock( $this->wp_fonts, $provider, $handles ); - - // Set up the `WP_Fonts::$to_do` and `WP_Fonts::$to_do_keyed_handles` properties. - if ( null === $to_do_queue ) { - $to_do_queue = $handles; - } - - $this->wp_fonts->to_do = $to_do_queue; - $to_do_keyed_handles = $this->get_reflection_property( 'to_do_keyed_handles' ); - $to_do_keyed_handles->setValue( $this->wp_fonts, array_flip( $to_do_queue ) ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/doItems.php b/phpunit/tests/fonts-api/wpFonts/doItems.php deleted file mode 100644 index 8a29e7463406a..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/doItems.php +++ /dev/null @@ -1,196 +0,0 @@ -wp_fonts = new WP_Fonts(); - } - - public function test_should_not_process_when_no_providers_registered() { - $this->setup_deps( array( 'enqueued' => 'font1' ) ); - - $done = $this->wp_fonts->do_items(); - - $this->assertSame( array(), $done, 'WP_Fonts::do_items() should return an empty array' ); - $this->assertSame( array(), $this->wp_fonts->to_do, 'WP_Fonts::$to_do should be an empty array' ); - } - - /** - * @dataProvider data_invalid_handles - * - * @param mixed $handles Handles to test. - */ - public function test_should_throw_notice_when_invalid_handles( $handles ) { - $this->expectNotice(); - $this->expectNoticeMessage( 'Handles must be a non-empty string or array of non-empty strings' ); - - $this->wp_fonts->do_items( $handles ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_invalid_handles() { - return array( - 'null' => array( null ), - 'empty array' => array( array() ), - 'empty string' => array( '' ), - 'array of empty strings' => array( array( '', '' ) ), - 'array of mixed falsey values' => array( array( '', false, null, array() ) ), - ); - } - - public function test_should_throw_notice_when_provider_class_not_found() { - $this->expectNotice(); - $this->expectNoticeMessage( 'Class "Provider_Does_Not_Exist" not found for "doesnotexist" font provider' ); - - $setup = array( - 'provider' => array( - 'doesnotexist' => array( - 'id' => 'doesnotexist', - 'class' => 'Provider_Does_Not_Exist', - ), - ), - 'provider_handles' => array( 'doesnotexist' => array( 'font1' ) ), - 'registered' => array( - 'doesnotexist' => array( - 'font1' => array( - 'font1-300-normal' => array( - 'provider' => 'doesnotexist', - 'font-weight' => '300', - 'font-style' => 'normal', - 'font-display' => 'fallback', - ), - ), - ), - ), - 'enqueued' => array( 'font1', 'font1-300-normal' ), - ); - $this->setup_deps( $setup ); - - $this->wp_fonts->do_items(); - } - - /** - * @dataProvider data_print_enqueued - * - * @param array $setup Test set up information for provider, fonts, and enqueued. - * @param array $expected_done Expected array of printed handles. - * @param string $expected_output Expected printed output. - */ - public function test_should_print_mocked_enqueued( $setup, $expected_done, $expected_output ) { - $this->setup_deps( $setup ); - - $this->expectOutputString( $expected_output ); - $actual_done = $this->wp_fonts->do_items(); - $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); - } - - /** - * Integration test that registers providers and fonts and then enqueues before - * testing the printing functionality. - * - * @dataProvider data_print_enqueued - * - * @param array $setup Test set up information for provider, fonts, and enqueued. - * @param array $expected_done Expected array of printed handles. - * @param string $expected_output Expected printed output. - */ - public function test_should_print_enqueued( $setup, $expected_done, $expected_output ) { - $this->setup_integrated_deps( $setup ); - - $this->expectOutputString( $expected_output, 'Printed @font-face styles should match' ); - $actual_done = $this->wp_fonts->do_items(); - $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); - } - - /** - * Integration test to validate printing given handles. Rather than mocking internal functionality, - * it registers providers and fonts but does not enqueue. - * - * @dataProvider data_print_enqueued - * - * @param array $setup Test set up information for provider, fonts, and enqueued. - * @param array $expected_done Expected array of printed handles. - * @param string $expected_output Expected printed output. - */ - public function test_should_print_handles_when_not_enqueued( $setup, $expected_done, $expected_output ) { - $this->setup_integrated_deps( $setup, false ); - // Do not enqueue. Instead, pass the handles to WP_Fonts::do_items(). - $handles = $setup['enqueued']; - $this->assertEmpty( $this->wp_fonts->queue, 'No fonts should be enqueued' ); - - $this->expectOutputString( $expected_output ); - $actual_done = $this->wp_fonts->do_items( $handles ); - $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); - } - - /** - * Sets up the dependencies for the mocked test. - * - * @param array $setup Dependencies to set up. - */ - private function setup_deps( array $setup ) { - $setup = array_merge( - array( - 'provider' => array(), - 'provider_handles' => array(), - 'registered' => array(), - 'enqueued' => array(), - ), - $setup - ); - - if ( ! empty( $setup['provider'] ) ) { - foreach ( $setup['provider'] as $provider_id => $provider ) { - $this->setup_provider_property_mock( $this->wp_fonts, $provider, $setup['provider_handles'][ $provider_id ] ); - } - } - - if ( ! empty( $setup['registered'] ) ) { - $this->setup_registration_mocks( $setup['registered'], $this->wp_fonts ); - } - - if ( ! empty( $setup['enqueued'] ) ) { - $queue = $this->get_reflection_property( 'queue' ); - $queue->setValue( $this->wp_fonts, $setup['enqueued'] ); - } - } - - /** - * Sets up the dependencies for integration test. - * - * @param array $setup Dependencies to set up. - * @param bool $enqueue Whether to enqueue. Default true. - */ - private function setup_integrated_deps( array $setup, $enqueue = true ) { - foreach ( $setup['provider'] as $provider ) { - $this->wp_fonts->register_provider( $provider['id'], $provider['class'] ); - } - foreach ( $setup['registered'] as $handle => $variations ) { - $this->setup_register( $handle, $variations, $this->wp_fonts ); - } - - if ( $enqueue ) { - $this->wp_fonts->enqueue( $setup['enqueued'] ); - } - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/enqueue.php b/phpunit/tests/fonts-api/wpFonts/enqueue.php deleted file mode 100644 index 09c14676c7ae0..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/enqueue.php +++ /dev/null @@ -1,53 +0,0 @@ -enqueue( $handles ); - - $this->assertSame( $expected, $this->get_queued_before_register( $wp_fonts ), 'Handles should be added to before registered queue' ); - $this->assertEmpty( $wp_fonts->queue, 'Handles should not be added to the enqueue queue when not registered' ); - } - - /** - * Integration test for enqueuing (a) a font family and all of its variations or (b) specific variations. - * - * @dataProvider data_enqueue - * @dataProviders data_enqueue_variations - * - * @param string|string[] $handles Handles to test. - * @param array $expected Expected queue. - */ - public function test_should_enqueue_when_registered( $handles, array $expected ) { - $wp_fonts = new WP_Fonts(); - foreach ( $this->get_data_registry() as $font_family => $variations ) { - $this->setup_register( $font_family, $variations, $wp_fonts ); - } - - $wp_fonts->enqueue( $handles ); - - $this->assertEmpty( $this->get_queued_before_register( $wp_fonts ), '"queued_before_register" queue should be empty' ); - $this->assertSame( $expected, $wp_fonts->queue, 'Queue should contain the given handles' ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/getEnqueued.php b/phpunit/tests/fonts-api/wpFonts/getEnqueued.php deleted file mode 100644 index bacb8a31aa650..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/getEnqueued.php +++ /dev/null @@ -1,57 +0,0 @@ -assertEmpty( $wp_fonts->get_enqueued() ); - } - - /** - * Unit test for when font families are enqueued. - * - * @dataProvider data_enqueue - * - * @param string|string[] $not_used Not used. - * @param array $expected Expected queue. - */ - public function test_should_return_queue_when_property_has_font_families( $not_used, array $expected ) { - $wp_fonts = new WP_Fonts(); - $wp_fonts->queue = $expected; - - $this->assertSame( $expected, $wp_fonts->get_enqueued() ); - } - - /** - * Full integration test that registers and enqueues the queue - * is properly wired for "get_enqueued()". - * - * @dataProvider data_enqueue - * - * @param string|string[] $font_family Font family to test. - * @param array $expected Expected queue. - */ - public function test_should_return_queue_when_font_families_registered_and_enqueued( $font_family, array $expected ) { - $wp_fonts = new WP_Fonts(); - - // Register and enqueue. - foreach ( $this->get_data_registry() as $handle => $variations ) { - $this->setup_register( $handle, $variations, $wp_fonts ); - } - $wp_fonts->enqueue( $font_family ); - - $this->assertSame( $expected, $wp_fonts->get_enqueued() ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/getProviders.php b/phpunit/tests/fonts-api/wpFonts/getProviders.php deleted file mode 100644 index 410adaace286a..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/getProviders.php +++ /dev/null @@ -1,62 +0,0 @@ -wp_fonts = new WP_Fonts(); - - $this->providers_property = new ReflectionProperty( WP_Fonts::class, 'providers' ); - $this->providers_property->setAccessible( true ); - } - - public function test_should_be_empty() { - $actual = $this->wp_fonts->get_providers(); - $this->assertIsArray( $actual, 'Should return an empty array' ); - $this->assertEmpty( $actual, 'Should return an empty array when no providers are registered' ); - } - - /** - * @dataProvider data_get_providers - * - * @param array $providers Array of providers to test. - * @param array $expected Expected results. - */ - public function test_get_providers( array $providers, array $expected ) { - $this->setup_providers( $providers ); - $this->assertSame( $expected, $this->wp_fonts->get_providers() ); - } - - /** - * Sets up the given providers and stores them in the `WP_Fonts::providers` property. - * - * @param array $providers Array of providers to set up. - */ - private function setup_providers( array $providers ) { - $data = array(); - - foreach ( $providers as $provider_id => $class ) { - $data[ $provider_id ] = array( - 'class' => $class, - 'fonts' => array(), - ); - } - - $this->providers_property->setValue( $this->wp_fonts, $data ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/getRegistered.php b/phpunit/tests/fonts-api/wpFonts/getRegistered.php deleted file mode 100644 index 7dc116f587539..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/getRegistered.php +++ /dev/null @@ -1,90 +0,0 @@ -assertEmpty( $wp_fonts->get_registered() ); - } - - /** - * Unit test for when font families are enqueued. - * - * @dataProvider data_get_registered - * - * @param array $inputs Font family(ies) and variations to register. - */ - public function test_should_return_queue_when_mocking_registered_property( array $inputs ) { - $wp_fonts = new WP_Fonts(); - $mocks = $this->setup_registration_mocks( $inputs, $wp_fonts ); - $expected = array_keys( $mocks ); - - $this->assertSame( $expected, $wp_fonts->get_registered() ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_get_registered() { - return array( - 'no variations' => array( - 'inputs' => array( - 'lato' => array(), - ), - ), - 'with 1 variation' => array( - 'inputs' => array( - 'Source Serif Pro' => array( 'variation-1' ), - ), - ), - 'with 2 variations' => array( - 'inputs' => array( - 'my-cool-font' => array( 'cool-1', 'cool-2' ), - ), - ), - 'when multiple font families registered' => array( - 'inputs' => array( - 'font-family-1' => array( 'variation-11', 'variation-12' ), - 'font-family-2' => array( 'variation-21', 'variation-22' ), - 'font-family-3' => array( 'variation-31', 'variation-32' ), - ), - ), - ); - } - - /** - * Full integration test that registers varying number of font families and variations - * to validate if "get_registered()" internals is property wired to the registered queue. - * - * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations - * - * @param string $font_family Not used. - * @param array $inputs Font family(ies) and variations to register. - * @param array $expected Expected results. - */ - public function test_should_return_queue_when_items_are_registered( $font_family, array $inputs, array $expected ) { - $wp_fonts = new WP_Fonts(); - - // Register before testing. - foreach ( $inputs as $handle => $variations ) { - $this->setup_register( $handle, $variations, $wp_fonts ); - } - - $this->assertSame( $expected, $wp_fonts->get_registered() ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/query.php b/phpunit/tests/fonts-api/wpFonts/query.php deleted file mode 100644 index 0e56b515e7ebf..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/query.php +++ /dev/null @@ -1,151 +0,0 @@ -wp_fonts = new WP_Fonts(); - } - - /** - * @dataProvider data_invalid_query - * @dataProvider data_valid_query - * - * @param string $query_handle Handle to test. - */ - public function test_should_fail_when_handles_not_registered( $query_handle ) { - $this->assertFalse( $this->wp_fonts->query( $query_handle, 'registered' ) ); - } - - /** - * @dataProvider data_invalid_query - * @dataProvider data_valid_query - * - * @param string $query_handle Handle to test. - */ - public function test_should_fail_when_handles_not_registered_or_enqueued( $query_handle ) { - $this->assertFalse( $this->wp_fonts->query( $query_handle, 'queue' ) ); - } - - /** - * @dataProvider data_valid_query - * - * @param string $query_handle Handle to test. - */ - public function test_registered_query_should_succeed_when_registered( $query_handle ) { - $this->setup_registry(); - - $actual = $this->wp_fonts->query( $query_handle, 'registered' ); - $this->assertInstanceOf( '_WP_Dependency', $actual, 'Query should return an instance of _WP_Dependency' ); - $this->assertSame( $query_handle, $actual->handle, 'Query object handle should match the given handle to query' ); - } - - /** - * @dataProvider data_valid_query - * - * @param string $query_handle Handle to test. - */ - public function test_enqueued_query_should_succeed_when_registered_and_enqueued( $query_handle ) { - $this->setup_registry(); - $this->wp_fonts->enqueue( $query_handle ); - - $this->assertTrue( $this->wp_fonts->query( $query_handle, 'enqueued' ) ); - } - - /** - * @dataProvider data_valid_query - * - * @param string $query_handle Handle to test. - */ - public function test_enqueued_query_should_fail_when_not_registered_but_enqueued( $query_handle ) { - $this->wp_fonts->enqueue( $query_handle ); - - $this->assertFalse( $this->wp_fonts->query( $query_handle, 'enqueued' ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_invalid_query() { - return array( - 'DM Sans' => array( 'DM Sans' ), - 'roboto' => array( 'roboto' ), - 'my-font' => array( 'my-font' ), - ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_valid_query() { - return array( - 'lato' => array( 'lato' ), - 'merriweather' => array( 'merriweather' ), - 'Source Serif Pro' => array( 'source-serif-pro' ), - ); - } - - public function test_done_query_should_fail_when_no_variations() { - $this->wp_fonts->register_provider( 'local', WP_Fonts_Provider_Local::class ); - $this->setup_registry(); - $this->wp_fonts->enqueue( 'lato' ); - - $this->wp_fonts->do_items( 'lato' ); - - $this->assertFalse( $this->wp_fonts->query( 'lato', 'done' ) ); - } - - /** - * @dataProvider data_done_query - * - * @param string $query_handle Handle to test. - */ - public function test_done_query_should_succeed_when_registered_and_enqueued( $query_handle ) { - $this->wp_fonts->register_provider( 'local', WP_Fonts_Provider_Local::class ); - $this->setup_registry(); - $this->wp_fonts->enqueue( $query_handle ); - - // Process the fonts while ignoring all the printed output. - $this->expectOutputRegex( '`.`' ); - $this->wp_fonts->do_items( $query_handle ); - $this->getActualOutput(); - - $this->assertTrue( $this->wp_fonts->query( $query_handle, 'done' ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_done_query() { - return array( - 'merriweather' => array( 'merriweather' ), - 'Source Serif Pro' => array( 'source-serif-pro' ), - ); - } - - private function setup_registry() { - foreach ( $this->get_registered_local_fonts() as $handle => $variations ) { - $this->setup_register( $handle, $variations, $this->wp_fonts ); - } - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/registerProvider.php b/phpunit/tests/fonts-api/wpFonts/registerProvider.php deleted file mode 100644 index c67976e0a6865..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/registerProvider.php +++ /dev/null @@ -1,116 +0,0 @@ -assertTrue( $wp_fonts->register_provider( $provider_id, $class_name ), 'WP_Fonts::register_provider() should return true' ); - $this->assertSame( $expected, $wp_fonts->get_providers(), 'Provider "' . $provider_id . '" should be registered in providers queue' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_register_providers() { - return array( - 'mock' => array( - 'provider_id' => 'mock', - 'class' => Mock_Provider::class, - 'expected' => array( - 'mock' => array( - 'class' => Mock_Provider::class, - 'fonts' => array(), - ), - ), - ), - 'local' => array( - 'provider_id' => 'local', - 'class' => WP_Fonts_Provider_Local::class, - 'expected' => array( - 'local' => array( - 'class' => WP_Fonts_Provider_Local::class, - 'fonts' => array(), - ), - ), - ), - ); - } - - public function test_should_register_multiple_providers() { - $wp_fonts = new WP_Fonts(); - $providers = $this->get_provider_definitions(); - foreach ( $providers as $provider ) { - $this->assertTrue( $wp_fonts->register_provider( $provider['id'], $provider['class'] ), 'WP_Fonts::register_provider() should return true for provider ' . $provider['id'] ); - } - - $expected = array( - 'mock' => array( - 'class' => $providers['mock']['class'], - 'fonts' => array(), - ), - 'local' => array( - 'class' => $providers['local']['class'], - 'fonts' => array(), - ), - ); - - $this->assertSame( $expected, $wp_fonts->get_providers(), 'Both local and mock providers should be registered' ); - } - - /** - * @dataProvider data_invalid_providers - * - * @param string $provider_id Provider ID. - * @param string $class_name Provider class name. - */ - public function test_should_not_register( $provider_id, $class_name ) { - $wp_fonts = new WP_Fonts(); - - $this->assertFalse( $wp_fonts->register_provider( $provider_id, $class_name ), 'WP_Fonts::register_provider() should return false' ); - $this->assertArrayNotHasKey( $provider_id, $wp_fonts->get_providers(), 'Both local and mock providers should be registered' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_invalid_providers() { - return array( - 'provider_id is empty' => array( - 'provider_id' => '', - 'class' => Mock_Provider::class, - ), - 'class is empty' => array( - 'provider_id' => 'local', - 'class' => '', - ), - 'class does not exist' => array( - 'provider_id' => 'doesnotexist', - 'class' => 'Provider_Does_Not_Exist', - ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/remove.php b/phpunit/tests/fonts-api/wpFonts/remove.php deleted file mode 100644 index 208bd58c3d2c3..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/remove.php +++ /dev/null @@ -1,118 +0,0 @@ -remove( array( 'handle-1', 'handle2' ) ); - - $this->assertEmpty( $wp_fonts->registered ); - } - - /** - * @dataProvider data_remove_when_registered - * - * @param array $handles Handles to remove. - * @param array $expected Expected handles are running test. - */ - public function test_should_remove_when_registered( array $handles, array $expected ) { - $wp_fonts = new WP_Fonts(); - $wp_fonts->registered = $this->generate_registered_queue(); - - $wp_fonts->remove( $handles ); - - $this->assertSameSets( $expected, array_keys( $wp_fonts->registered ), 'Registered queue should match after removing handles' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_remove_when_registered() { - $all = array( - 'handle-1', - 'handle-2', - 'handle-3', - 'handle-4', - 'handle-5', - 'handle-6', - 'handle-7', - 'handle-8', - 'handle-9', - 'handle-10', - ); - - return array( - 'remove none' => array( - 'handles' => array(), - 'expected' => $all, - ), - 'remove handle-5' => array( - 'handles' => array( 'handle-5' ), - 'expected' => array( - 'handle-1', - 'handle-2', - 'handle-3', - 'handle-4', - 'handle-6', - 'handle-7', - 'handle-8', - 'handle-9', - 'handle-10', - ), - ), - 'remove 2 from start and end' => array( - 'handles' => array( 'handle-1', 'handle-2', 'handle-9', 'handle-10' ), - 'expected' => array( - 'handle-3', - 'handle-4', - 'handle-5', - 'handle-6', - 'handle-7', - 'handle-8', - ), - ), - 'remove all' => array( - 'handles' => $all, - 'expected' => array(), - ), - 'remove only registered' => array( - 'handles' => array( 'handle-1', 'handle-10', 'handle-abc', 'handle-5' ), - 'expected' => array( - 'handle-2', - 'handle-3', - 'handle-4', - 'handle-6', - 'handle-7', - 'handle-8', - 'handle-9', - ), - ), - ); - } - - private function generate_registered_queue() { - $queue = array(); - for ( $num = 1; $num <= 10; $num++ ) { - $handle = "handle-{$num}"; - $queue[ $handle ] = $num; - } - - return $queue; - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/removeFontFamily.php b/phpunit/tests/fonts-api/wpFonts/removeFontFamily.php deleted file mode 100644 index 6e762307d0520..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/removeFontFamily.php +++ /dev/null @@ -1,89 +0,0 @@ -setup_registration_mocks( $inputs, $wp_fonts ); - // Test the before state, just to make sure. - $this->assertArrayHasKey( $font_family, $wp_fonts->registered, 'Registered queue should contain the font family before remove' ); - $this->assertSame( $registered_handles, array_keys( $wp_fonts->registered ), 'Font family and variations should be registered before remove' ); - - $wp_fonts->remove_font_family( $font_family ); - - $this->assertArrayNotHasKey( $font_family, $wp_fonts->registered, 'Registered queue should not contain the font family' ); - $this->assertSame( $expected, array_keys( $wp_fonts->registered ), 'Registered queue should match after removing font family' ); - } - - /** - * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations - * - * @param string $font_family Font family to test. - * @param array $inputs Font family(ies) and variations to pre-register. - * @param array $registered_handles Not used. - * @param array $expected Array of expected handles. - */ - public function test_should_bail_out_when_not_registered( $font_family, array $inputs, array $registered_handles, array $expected ) { - $wp_fonts = new WP_Fonts(); - unset( $inputs[ $font_family ] ); - $this->setup_registration_mocks( $inputs, $wp_fonts ); - - $wp_fonts->remove_font_family( $font_family ); - - $this->assertArrayNotHasKey( $font_family, $wp_fonts->registered, 'Registered queue should not contain the font family' ); - $this->assertSame( $expected, array_keys( $wp_fonts->registered ), 'Registered queue should match after removing font family' ); - } - - /** - * Integration test for removing a font family and all of its variation when font family is registered. - * - * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations - * - * @param string $font_family Font family to test. - * @param array $inputs Font family(ies) and variations to pre-register. - * @param array $registered_handles Expected handles after registering. - * @param array $expected Array of expected handles. - */ - public function test_should_deregister_when_registered( $font_family, array $inputs, array $registered_handles, array $expected ) { - $wp_fonts = new WP_Fonts(); - // Register all font families and their variations. - foreach ( $inputs as $input_font_family => $variations ) { - $handle = $wp_fonts->add_font_family( $input_font_family ); - foreach ( $variations as $variation_handle => $variation ) { - if ( ! is_string( $variation_handle ) ) { - $variation_handle = ''; - } - $wp_fonts->add_variation( $handle, $variation, $variation_handle ); - } - } - // Test the before state, just to make sure. - $this->assertArrayHasKey( $font_family, $wp_fonts->registered, 'Registered queue should contain the font family before remove' ); - $this->assertSame( $registered_handles, array_keys( $wp_fonts->registered ), 'Font family and variations should be registered before remove' ); - - $wp_fonts->remove_font_family( $font_family ); - - $this->assertArrayNotHasKey( $font_family, $wp_fonts->registered, 'Registered queue should not contain the font family' ); - $this->assertSame( $expected, array_keys( $wp_fonts->registered ), 'Registered queue should match after removing font family' ); - } -} diff --git a/phpunit/tests/fonts-api/wpFonts/removeVariation.php b/phpunit/tests/fonts-api/wpFonts/removeVariation.php deleted file mode 100644 index 508c8ce264d8f..0000000000000 --- a/phpunit/tests/fonts-api/wpFonts/removeVariation.php +++ /dev/null @@ -1,278 +0,0 @@ -wp_fonts = new WP_Fonts(); - $this->fonts_to_register = $this->get_registered_local_fonts(); - } - - /** - * Sets up the unit test by mocking the WP_Dependencies object using stdClass and - * registering each font family directly to the WP_Fonts::$registered property - * and its variations to the mocked $deps property. - */ - private function setup_unit_test() { - $this->setup_registration_mocks( $this->fonts_to_register, $this->wp_fonts ); - } - - /** - * Sets up the integration test by properly registering each font family and its variations - * by using the WP_Fonts::add() and WP_Fonts::add_variation() methods. - */ - private function setup_integration_test() { - foreach ( $this->fonts_to_register as $font_family_handle => $variations ) { - $this->setup_register( $font_family_handle, $variations, $this->wp_fonts ); - } - } - - /** - * Testing the test setup to ensure it works. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - */ - public function test_mocked_setup( $font_family_handle, $variation_handle ) { - $this->setup_unit_test(); - - $this->assertArrayHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be in the registered queue before removal' ); - $this->assertContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should be in its font family deps before removal' ); - } - - /** - * Unit test. - * - * @dataProvider data_should_do_nothing_when_variation_and_font_family_not_registered - * - * @param string $font_family Font family name. - * @param string $font_family_handle Font family handle. - * @param string $variation_handle Variation handle to remove. - */ - public function test_unit_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) { - // Set up the test. - unset( $this->fonts_to_register[ $font_family ] ); - $this->setup_unit_test(); - $registered_queue = $this->wp_fonts->registered; - - // Run the tests. - $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle ); - $this->assertArrayNotHasKey( $font_family_handle, $this->wp_fonts->registered, 'Font family should not be registered' ); - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' ); - $this->assertSame( $registered_queue, $this->wp_fonts->registered, 'Registered queue should not have changed' ); - } - - /** - * Integration test. - * - * @dataProvider data_should_do_nothing_when_variation_and_font_family_not_registered - * - * @param string $font_family Font family name. - * @param string $font_family_handle Font family handle. - * @param string $variation_handle Variation handle to remove. - */ - public function test_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) { - // Set up the test. - unset( $this->fonts_to_register[ $font_family ] ); - $this->setup_integration_test(); - $registered_queue = $this->wp_fonts->get_registered(); - - // Run the tests. - $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle ); - $this->assertArrayNotHasKey( $font_family_handle, $this->wp_fonts->registered, 'Font family should not be registered' ); - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' ); - $this->assertSameSets( $registered_queue, $this->wp_fonts->get_registered(), 'Registered queue should not have changed' ); - } - - /** - * Data provider for testing removal of variations. - * - * @return array - */ - public function data_should_do_nothing_when_variation_and_font_family_not_registered() { - return array( - 'Font with 1 variation' => array( - 'font_family' => 'merriweather', - 'font_family_handle' => 'merriweather', - 'variation_handle' => 'merriweather-200-900-normal', - ), - 'Font with multiple variations' => array( - 'font_family' => 'Source Serif Pro', - 'font_family_handle' => 'source-serif-pro', - 'variation_handle' => 'Source Serif Pro-300-normal', - ), - ); - } - - /** - * Unit test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_unit_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_unit_test(); - $this->setup_remove_variation_from_registered( $variation_handle ); - - // Run the tests. - $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle ); - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' ); - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Integration test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_integration_test(); - $this->setup_remove_variation_from_registered( $variation_handle ); - - // Run the tests. - $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle ); - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' ); - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Unit test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_unit_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_unit_test(); - $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ); - - $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' ); - - $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle ); - - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Integration test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_integration_test(); - $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ); - - $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' ); - - $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle ); - - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Unit test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_unit_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_unit_test(); - - $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' ); - - $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle ); - - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be not be in registered queue' ); - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Integration test. - * - * @dataProvider data_remove_variations - * - * @param string $font_family_handle Font family for the variation. - * @param string $variation_handle Variation handle to remove. - * @param array $expected Expected results. - */ - public function test_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) { - // Set up the test. - $this->setup_integration_test(); - - $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' ); - - $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle ); - - $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be not be in registered queue' ); - $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); - $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); - } - - /** - * Remove the variation handle from the font family's deps. - * - * @param string $font_family_handle Font family. - * @param string $variation_handle The variation handle to remove. - */ - private function setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ) { - foreach ( $this->wp_fonts->registered[ $font_family_handle ]->deps as $index => $vhandle ) { - if ( $variation_handle !== $vhandle ) { - continue; - } - unset( $this->wp_fonts->registered[ $font_family_handle ]->deps[ $index ] ); - break; - } - } - - /** - * Removes the variation from the WP_Fonts::$registered queue. - * - * @param string $variation_handle The variation handle to remove. - */ - private function setup_remove_variation_from_registered( $variation_handle ) { - unset( $this->wp_fonts->registered[ $variation_handle ] ); - } -} diff --git a/phpunit/tests/fonts-api/wpFontsProviderLocal.php b/phpunit/tests/fonts-api/wpFontsProviderLocal.php deleted file mode 100644 index f9319390c3b6b..0000000000000 --- a/phpunit/tests/fonts-api/wpFontsProviderLocal.php +++ /dev/null @@ -1,180 +0,0 @@ -provider = new WP_Fonts_Provider_Local(); - - $this->set_up_theme(); - } - - public function tear_down() { - // Restore the original theme directory setup. - $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - - parent::tear_down(); - } - - /** - * @covers WP_Fonts_Provider_Local::set_fonts - */ - public function test_set_fonts() { - $fonts = array( - 'source-serif-pro-200-900-normal-local' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'normal', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - ), - 'source-serif-pro-200-900-italic-local' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'italic', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', - ), - ); - - $this->provider->set_fonts( $fonts ); - - $property = $this->get_fonts_property(); - $this->assertSame( $fonts, $property->getValue( $this->provider ) ); - } - - /** - * @covers WP_Fonts_Provider_Local::get_css - * - * @dataProvider data_get_css_print_styles - * - * @param array $fonts Prepared fonts (to store in WP_Fonts_Provider_Local::$fonts property). - * @param string $expected Expected CSS. - */ - public function test_get_css( array $fonts, $expected ) { - $property = $this->get_fonts_property(); - $property->setValue( $this->provider, $fonts ); - - $this->assertSame( $expected['font-face-css'], $this->provider->get_css() ); - } - - /** - * @covers WP_Fonts_Provider_Local::print_styles - * - * @dataProvider data_get_css_print_styles - * - * @param array $fonts Prepared fonts (to store in WP_Fonts_Provider_Local::$fonts property). - * @param string $expected Expected CSS. - */ - public function test_print_styles( array $fonts, $expected ) { - $property = $this->get_fonts_property(); - $property->setValue( $this->provider, $fonts ); - - $expected_output = sprintf( $expected['style-element'], $expected['font-face-css'] ); - $this->expectOutputString( $expected_output ); - $this->provider->print_styles(); - } - - /** - * Data provider. - * - * @return array - */ - public function data_get_css_print_styles() { - return array( - 'truetype format' => array( - 'fonts' => array( - 'open-sans-bold-italic-local' => array( - 'provider' => 'local', - 'font-family' => 'Open Sans', - 'font-style' => 'italic', - 'font-weight' => 'bold', - 'src' => 'http://example.org/assets/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf', - ), - ), - 'expected' => array( - 'style-element' => "\n", - 'font-face-css' => << array( - 'fonts' => array( - 'source-serif-pro-200-900-normal-local' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'normal', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'http://example.org/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - ), - 'source-serif-pro-400-900-italic-local' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'italic', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'http://example.org/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', - ), - ), - 'expected' => array( - 'style-element' => "\n", - 'font-face-css' => <<theme_root = realpath( GUTENBERG_DIR_TESTDATA . '/themedir1' ); - $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; - $GLOBALS['wp_theme_directories'] = array( $this->theme_root ); - - $theme_root_callback = function () { - return $this->theme_root; - }; - add_filter( 'theme_root', $theme_root_callback ); - add_filter( 'stylesheet_root', $theme_root_callback ); - add_filter( 'template_root', $theme_root_callback ); - - // Clear caches. - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - } - - private function get_fonts_property() { - $property = new ReflectionProperty( $this->provider, 'fonts' ); - $property->setAccessible( true ); - - return $property; - } -} diff --git a/phpunit/tests/fonts-api/wpFontsResolver/addMissingFontsToThemeJson.php b/phpunit/tests/fonts-api/wpFontsResolver/addMissingFontsToThemeJson.php deleted file mode 100644 index d6e45561e5e33..0000000000000 --- a/phpunit/tests/fonts-api/wpFontsResolver/addMissingFontsToThemeJson.php +++ /dev/null @@ -1,252 +0,0 @@ -assertInstanceOf( WP_Theme_JSON_Gutenberg::class, $actual, 'Instance of WP_Theme_JSON_Gutenberg should be returned' ); - } - - /** - * @dataProvider data_themes - * - * @param string $theme Theme to use. - */ - public function test_should_bail_out_when_no_registered_fonts( $theme ) { - switch_theme( $theme ); - - $data = new WP_Theme_JSON_Gutenberg( self::$theme_json_data[ $theme ] ); - $actual = WP_Fonts_Resolver::add_missing_fonts_to_theme_json( $data ); - - $this->assertEmpty( wp_fonts()->get_registered_font_families(), 'No fonts should be registered in Fonts API' ); - $this->assertSame( $data, $actual, 'Same instance of WP_Theme_JSON_Gutenberg should be returned' ); - } - - /** - * Data Provider. - * - * @return array - */ - public function data_themes() { - return array( - 'no fonts defined' => array( 'block-theme' ), - 'no fonts registered' => array( static::FONTS_THEME ), - ); - } - - /** - * @dataProvider data_should_add_non_theme_json_fonts - * - * @param string $theme Theme to use. - * @param array $fonts Fonts to register. - * @param array $expected Expected fonts to be added. - */ - public function test_should_add_non_theme_json_fonts( $theme, $fonts, $expected ) { - switch_theme( static::FONTS_THEME ); - - // Register the fonts. - wp_register_fonts( $fonts ); - - $data = new WP_Theme_JSON_Gutenberg( self::$theme_json_data[ $theme ] ); - $actual = WP_Fonts_Resolver::add_missing_fonts_to_theme_json( $data ); - - $this->assertNotSame( $data, $actual, 'New instance of WP_Theme_JSON_Gutenberg should be returned' ); - $actual_raw_data = $actual->get_raw_data(); - - $this->assertArrayHasKey( 'typography', $actual_raw_data['settings'] ); - $this->assertArrayHasKey( 'fontFamilies', $actual_raw_data['settings']['typography'] ); - $this->assertArrayHasKey( 'theme', $actual_raw_data['settings']['typography']['fontFamilies'] ); - - $this->assertContains( - $expected, - $actual_raw_data['settings']['typography']['fontFamilies']['theme'], - 'Fonts should be added after running WP_Fonts_Resolver::add_missing_fonts_to_theme_json()' - ); - } - - /** - * Data Provider. - * - * @return array - */ - public function data_should_add_non_theme_json_fonts() { - $lato = array( - 'Lato' => array( - array( - 'font-family' => 'Lato', - 'font-style' => 'normal', - 'font-weight' => '400', - 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular.woff2', - ), - array( - 'font-family' => 'Lato', - 'font-style' => 'italic', - 'font-weight' => '400', - 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular-Italic.woff2', - ), - ), - ); - - $expected_lato = array( - 'fontFamily' => 'Lato', - 'name' => 'Lato', - 'slug' => 'lato', - 'fontFace' => array( - 'lato-400-normal' => array( - 'origin' => 'gutenberg_wp_fonts_api', - 'provider' => 'local', - 'fontFamily' => 'Lato', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'fontDisplay' => 'fallback', - 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular.woff2', - ), - 'lato-400-italic' => array( - 'origin' => 'gutenberg_wp_fonts_api', - 'provider' => 'local', - 'fontFamily' => 'Lato', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'fontDisplay' => 'fallback', - 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular-Italic.woff2', - ), - ), - ); - - return array( - 'theme with no fonts defined' => array( - 'theme' => 'block-theme', - 'fonts' => $lato, - 'expected' => $expected_lato, - ), - 'theme with fonts: new fonts not in theme' => array( - 'theme' => static::FONTS_THEME, - 'fonts' => $lato, - 'expected' => $expected_lato, - ), - - /* - * @TODO Add these tests fixing https://github.com/WordPress/gutenberg/issues/50047. - * - 'theme with fonts: new variations registered' => array( - 'theme' => static::FONTS_THEME, - 'fonts' => array( - 'DM Sans' => array( - 'dm-sans-500-normal' => array( - 'font-family' => 'DM Sans', - 'font-style' => 'normal', - 'font-weight' => '500', - 'src' => 'https://example.com/tests/assets/fonts/dm-sans/DMSans-Medium.woff2', - ), - 'dm-sans-500-italic' => array( - 'font-family' => 'DM Sans', - 'font-style' => 'italic', - 'font-weight' => '500', - 'src' => 'https://example.com/tests/assets/fonts/dm-sans/DMSans-Medium.woff2', - ), - ), - ), - 'expected' => array( - 'fontFace' => array( - array( - 'fontFamily' => 'DM Sans', - 'fontStretch' => 'normal', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => array( 'file:./assets/fonts/dm-sans/DMSans-Regular.woff2' ), - ), - array( - 'fontFamily' => 'DM Sans', - 'fontStretch' => 'normal', - 'fontStyle' => 'italic', - 'fontWeight' => '400', - 'src' => array( 'file:./assets/fonts/dm-sans/DMSans-Regular-Italic.woff2' ), - ), - 'dm-sans-500-normal' => array( - 'origin' => 'gutenberg_wp_fonts_api', - 'provider' => 'local', - 'fontFamily' => 'DM Sans', - 'fontStretch' => 'normal', - 'fontStyle' => 'normal', - 'fontWeight' => '500', - 'fontDisplay' => 'fallback', - 'src' => array( get_stylesheet_directory_uri() . 'assets/fonts/dm-sans/DMSans-Medium.woff2' ), - ), - 'dm-sans-500-italic' => array( - 'origin' => 'gutenberg_wp_fonts_api', - 'provider' => 'local', - 'fontFamily' => 'DM Sans', - 'fontStretch' => 'normal', - 'fontStyle' => 'italic', - 'fontWeight' => '500', - 'fontDisplay' => 'fallback', - 'src' => array( get_stylesheet_directory_uri() . 'assets/fonts/dm-sans/DMSans-Medium-Italic.woff2' ), - ), - array( - 'fontFamily' => 'DM Sans', - 'fontStretch' => 'normal', - 'fontStyle' => 'normal', - 'fontWeight' => '700', - 'src' => array( 'file:./assets/fonts/dm-sans/DMSans-Bold.woff2' ), - ), - array( - 'fontFamily' => 'DM Sans', - 'fontStretch' => 'normal', - 'fontStyle' => 'italic', - 'fontWeight' => '700', - 'src' => array( 'file:./assets/fonts/dm-sans/DMSans-Bold-Italic.woff2' ), - ), - ), - 'fontFamily' => '"DM Sans", sans-serif', - 'name' => 'DM Sans', - 'slug' => 'dm-sans', - ), - ), - */ - ); - } -} diff --git a/phpunit/tests/fonts-api/wpFontsResolver/enqueueUserSelectedFonts.php b/phpunit/tests/fonts-api/wpFontsResolver/enqueueUserSelectedFonts.php deleted file mode 100644 index 9ff01f1c3166c..0000000000000 --- a/phpunit/tests/fonts-api/wpFontsResolver/enqueueUserSelectedFonts.php +++ /dev/null @@ -1,131 +0,0 @@ -user->create( - array( - 'role' => 'administrator', - 'user_email' => 'administrator@example.com', - ) - ); - } - - /** - * @dataProvider data_should_not_enqueue_when_no_user_selected_fonts - * - * @param array $styles Optional. Test styles. Default empty array. - */ - public function test_should_not_enqueue_when_no_user_selected_fonts( $styles = array() ) { - $this->set_up_global_styles( $styles ); - - $mock = $this->set_up_mock( 'enqueue' ); - $mock->expects( $this->never() ) - ->method( 'enqueue' ); - - $expected = array(); - $this->assertSame( $expected, WP_Fonts_Resolver::enqueue_user_selected_fonts() ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_should_not_enqueue_when_no_user_selected_fonts() { - return array( - 'no user-selected styles' => array(), - 'invalid element' => array( - array( - 'elements' => array( - 'invalid' => array( - 'typography' => array( - 'fontFamily' => 'var:preset|font-family|font1', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - ), - ), - ), - ), - ), - ); - } - - /** - * @dataProvider data_should_enqueue_when_user_selected_fonts - * - * @param array $styles Test styles. - * @param array $expected Expected results. - */ - public function test_should_enqueue_when_user_selected_fonts( $styles, $expected ) { - $mock = $this->set_up_mock( 'enqueue' ); - $mock->expects( $this->once() ) - ->method( 'enqueue' ) - ->with( - $this->identicalTo( $expected ) - ); - - $this->set_up_global_styles( $styles ); - - $this->assertSameSets( $expected, WP_Fonts_Resolver::enqueue_user_selected_fonts() ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_should_enqueue_when_user_selected_fonts() { - $global_styles = $this->get_mock_user_selected_fonts_global_styles(); - - return array( - 'heading, caption, text' => array( - 'styles' => $global_styles['font1'], - 'expected' => array( 'font1' ), - ), - 'heading, button' => array( - 'styles' => $global_styles['font2'], - 'expected' => array( 'font2' ), - ), - 'text' => array( - 'styles' => $global_styles['font3'], - 'expected' => array( 'font3' ), - ), - 'all' => array( - 'styles' => $global_styles['all'], - 'expected' => array( - 0 => 'font1', - // font1 occurs 2 more times and gets removed as duplicates. - 3 => 'font2', - 4 => 'font3', - ), - ), - 'all with invalid element' => array( - 'styles' => $global_styles['all with invalid element'], - 'expected' => array( - 0 => 'font1', - // font1 occurs 2 more times and gets removed as duplicates. - 3 => 'font2', - // Skips font2 for the "invalid" element. - 4 => 'font3', - ), - ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php b/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php deleted file mode 100644 index 0186dbf34f3fd..0000000000000 --- a/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php +++ /dev/null @@ -1,297 +0,0 @@ - array( - // From theme.json. - 'dm-sans', - 'source-serif-pro', - // From style variation. - 'open-sans', - ), - ); - - public static function set_up_before_class() { - self::$requires_switch_theme_fixtures = true; - - parent::set_up_before_class(); - } - - public function test_should_bails_out_when_no_fonts_defined() { - switch_theme( 'block-theme' ); - - WP_Fonts_Resolver::register_fonts_from_theme_json(); - $wp_fonts = wp_fonts(); - - $this->assertEmpty( $wp_fonts->get_registered() ); - $this->assertEmpty( $wp_fonts->get_enqueued() ); - } - - public function test_should_register_and_enqueue_style_variation_fonts() { - switch_theme( static::FONTS_THEME ); - - WP_Fonts_Resolver::register_fonts_from_theme_json(); - $wp_fonts = wp_fonts(); - - $this->assertContains( 'open-sans', $wp_fonts->get_registered_font_families(), 'Font families should be registered' ); - $this->assertContains( 'open-sans', $wp_fonts->get_enqueued(), 'Font families should be enqueued' ); - } - - /** - * Tests all font families are registered and enqueued. "All" means all font families from - * the theme's theme.json and within the style variations. - */ - public function test_should_register_and_enqueue_all_defined_font_families() { - switch_theme( static::FONTS_THEME ); - - WP_Fonts_Resolver::register_fonts_from_theme_json(); - $wp_fonts = wp_fonts(); - - $expected = static::FONT_FAMILIES[ static::FONTS_THEME ]; - $this->assertSameSetsWithIndex( $expected, $wp_fonts->get_registered_font_families(), 'Font families should be registered' ); - $this->assertSameSetsWithIndex( $expected, $wp_fonts->get_enqueued(), 'Font families should be enqueued' ); - } - - /** - * Test ensures duplicate fonts and variations in the style variations - * are not re-registered. - * - * The Dm Sans fonts are duplicated in the theme's /styles/variations-duplicate-fonts.json. - */ - public function test_should_not_reregister_duplicate_fonts_from_style_variations() { - switch_theme( static::FONTS_THEME ); - - WP_Fonts_Resolver::register_fonts_from_theme_json(); - $wp_fonts = wp_fonts(); - - // Font families are not duplicated. - $this->assertSameSetsWithIndex( - static::FONT_FAMILIES[ static::FONTS_THEME ], - $wp_fonts->get_registered_font_families(), - 'Font families should not be duplicated' - ); - - // Font variations are not duplicated. - $this->assertSameSets( - array( - // From theme.json. - 'dm-sans', - 'dm-sans-400-normal', - 'dm-sans-400-italic', - 'dm-sans-700-normal', - 'dm-sans-700-italic', - 'source-serif-pro', - 'source-serif-pro-200-900-normal', - 'source-serif-pro-200-900-italic', - // From style variation. - 'open-sans', - 'open-sans-400-normal', - 'open-sans-400-italic', - 'dm-sans-500-normal', - 'dm-sans-500-italic', - ), - $wp_fonts->get_registered(), - 'Font families and their variations should not be duplicated' - ); - } - - /** - * @dataProvider data_should_replace_src_file_placeholder - * - * @param string $handle Variation's handle. - * @param string $expected Expected src. - */ - public function test_should_replace_src_file_placeholder( $handle, $expected ) { - switch_theme( static::FONTS_THEME ); - - WP_Fonts_Resolver::register_fonts_from_theme_json(); - - $variation = wp_fonts()->registered[ $handle ]; - $actual = array_pop( $variation->src ); - $expected = get_stylesheet_directory_uri() . $expected; - - $this->assertStringNotContainsString( 'file:./', $actual, 'Font src should not contain the "file:./" placeholder' ); - $this->assertSame( $expected, $actual, 'Font src should be an URL to its file' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_should_replace_src_file_placeholder() { - return array( - // Theme's theme.json. - 'DM Sans: 400 normal' => array( - 'handle' => 'dm-sans-400-normal', - 'expected' => '/assets/fonts/dm-sans/DMSans-Regular.woff2', - ), - 'DM Sans: 400 italic' => array( - 'handle' => 'dm-sans-400-italic', - 'expected' => '/assets/fonts/dm-sans/DMSans-Regular-Italic.woff2', - ), - 'DM Sans: 700 normal' => array( - 'handle' => 'dm-sans-700-normal', - 'expected' => '/assets/fonts/dm-sans/DMSans-Bold.woff2', - ), - 'DM Sans: 700 italic' => array( - 'handle' => 'dm-sans-700-italic', - 'expected' => '/assets/fonts/dm-sans/DMSans-Bold-Italic.woff2', - ), - 'Source Serif Pro: 200-900 normal' => array( - 'handle' => 'source-serif-pro-200-900-normal', - 'expected' => '/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - ), - 'Source Serif Pro: 200-900 italic' => array( - 'handle' => 'source-serif-pro-200-900-italic', - 'expected' => '/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', - ), - - // Style Variation: variation-with-new-font-family.json. - 'Style Variation: new font-family' => array( - 'handle' => 'open-sans-400-normal', - 'expected' => '/assets/fonts/open-sans/OpenSans-VariableFont_wdth,wght.tff', - ), - 'Style Variation: new font-family italic variation' => array( - 'handle' => 'open-sans-400-italic', - 'expected' => '/assets/fonts/open-sans/OpenSans-Italic-VariableFont_wdth,wght.tff', - ), - - // Style Variation: variation-with-new-variation.json. - 'Style Variation: new medium variation' => array( - 'handle' => 'dm-sans-500-normal', - 'expected' => '/assets/fonts/dm-sans/DMSans-Medium.woff2', - ), - 'Style Variation: new medium italic variation' => array( - 'handle' => 'dm-sans-500-italic', - 'expected' => '/assets/fonts/dm-sans/DMSans-Medium-Italic.woff2', - ), - ); - } - - public function test_should_convert_font_face_properties_into_kebab_case() { - switch_theme( static::FONTS_THEME ); - - WP_Fonts_Resolver::register_fonts_from_theme_json(); - - // Testing only one variation since this theme's fonts use the same properties. - $variation = wp_fonts()->registered['dm-sans-400-normal']; - $actual_properties = $variation->extra['font-properties']; - - $this->assertArrayHasKey( 'font-family', $actual_properties, 'fontFamily should have been converted into font-family' ); - $this->assertArrayNotHasKey( 'fontFamily', $actual_properties, 'fontFamily should not exist.' ); - $this->assertArrayHasKey( 'font-stretch', $actual_properties, 'fontStretch should have been converted into font-stretch' ); - $this->assertArrayNotHasKey( 'fontStretch', $actual_properties, 'fontStretch should not exist' ); - $this->assertArrayHasKey( 'font-style', $actual_properties, 'fontStyle should have been converted into font-style' ); - $this->assertArrayNotHasKey( 'fontStyle', $actual_properties, 'fontStyle should not exist.' ); - $this->assertArrayHasKey( 'font-weight', $actual_properties, 'fontWeight should have been converted into font-weight' ); - $this->assertArrayNotHasKey( 'fontWeight', $actual_properties, 'fontWeight should not exist' ); - } - - /** - * Tests that WP_Fonts_Resolver::register_fonts_from_theme_json() skips fonts that are already registered - * in the Fonts API. How does it do that? Using the 'origin' property when checking each variation. - * This property is added when WP_Theme_JSON_Resolver_Gutenberg::get_merged_data() runs. - * - * To simulate this scenario, a font is registered first, but not enqueued. Then after running, - * it checks if the WP_Fonts_Resolver::register_fonts_from_theme_json() enqueued the font. If no, then - * it was skipped as expected. - */ - public function test_should_skip_registered_fonts() { - switch_theme( static::FONTS_THEME ); - - // Register Lato font. - wp_register_fonts( - array( - 'Lato' => array( - array( - 'font-family' => 'Lato', - 'font-style' => 'normal', - 'font-weight' => '400', - 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular.woff2', - ), - array( - 'font-family' => 'Lato', - 'font-style' => 'italic', - 'font-weight' => '400', - 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular-Italic.woff2', - ), - ), - ) - ); - - // Pre-check to ensure no fonts are enqueued. - $this->assertEmpty( wp_fonts()->get_enqueued(), 'No fonts should be enqueued before running WP_Fonts_Resolver::register_fonts_from_theme_json()' ); - - /* - * When this function runs, it invokes WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(), - * which will include the Lato fonts with a 'origin' property set in each variation. - */ - WP_Fonts_Resolver::register_fonts_from_theme_json(); - - $actual_enqueued_fonts = wp_fonts()->get_enqueued(); - - $this->assertNotContains( 'lato', $actual_enqueued_fonts, 'Lato font-family should not be enqueued' ); - $this->assertSameSets( static::FONT_FAMILIES[ static::FONTS_THEME ], $actual_enqueued_fonts, 'Only the theme font families should be enqueued' ); - } - - public function test_should_skip_when_font_face_not_defined() { - switch_theme( static::FONTS_THEME ); - $expected_font_family = 'source-serif-pro'; - - /** - * Callback that removes the 'fontFace' of the expected font family from the theme's theme.json data. - * This callback is invoked at the start of WP_Fonts_Resolver::register_fonts_from_theme_json() before processing - * within that function. How? It's in the call stack of WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(). - * - * @param WP_Theme_JSON_Data_Gutenberg| WP_Theme_JSON_Data $theme_json_data Instance of the Data object. - * @return WP_Theme_JSON_Data_Gutenberg| WP_Theme_JSON_Data Modified instance. - * @throws ReflectionException - */ - $remove_expected_font_family = static function ( $theme_json_data ) use ( $expected_font_family ) { - // Need to get the underlying data array which is in WP_Theme_JSON_Gutenberg | WP_Theme_JSON object. - $property = new ReflectionProperty( $theme_json_data, 'theme_json' ); - $property->setAccessible( true ); - $theme_json_object = $property->getValue( $theme_json_data ); - - $property = new ReflectionProperty( $theme_json_object, 'theme_json' ); - $property->setAccessible( true ); - $data = $property->getValue( $theme_json_object ); - - // Loop through the fonts to find the expected font-family to modify. - foreach ( $data['settings']['typography']['fontFamilies']['theme'] as $index => $definitions ) { - if ( $expected_font_family !== $definitions['slug'] ) { - continue; - } - - // Remove the 'fontFace' element, which removes the font's variations. - unset( $data['settings']['typography']['fontFamilies']['theme'][ $index ]['fontFace'] ); - break; - } - - $theme_json_data->update_with( $data ); - - return $theme_json_data; - }; - add_filter( 'wp_theme_json_data_theme', $remove_expected_font_family ); - - WP_Fonts_Resolver::register_fonts_from_theme_json(); - - remove_filter( 'wp_theme_json_data_theme', $remove_expected_font_family ); - - $this->assertNotContains( $expected_font_family, wp_fonts()->get_registered_font_families() ); - } -} diff --git a/phpunit/tests/fonts-api/wpFontsUtils/convertFontFamilyIntoHandle.php b/phpunit/tests/fonts-api/wpFontsUtils/convertFontFamilyIntoHandle.php deleted file mode 100644 index 19b2f15c826ed..0000000000000 --- a/phpunit/tests/fonts-api/wpFontsUtils/convertFontFamilyIntoHandle.php +++ /dev/null @@ -1,84 +0,0 @@ -assertSame( $expected, WP_Fonts_Utils::convert_font_family_into_handle( $font_family ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_with_valid_input() { - return array( - 'font family single word name' => array( - 'font_family' => 'Merriweather', - 'expected' => 'merriweather', - ), - 'font family multiword name' => array( - 'font_family' => 'Source Sans Pro', - 'expected' => 'source-sans-pro', - ), - 'font family handle delimited by hyphens' => array( - 'font_family' => 'source-serif-pro', - 'expected' => 'source-serif-pro', - ), - 'font family handle delimited by underscore' => array( - 'font_family' => 'source_serif_pro', - 'expected' => 'source_serif_pro', - ), - 'font family handle delimited by hyphens and underscore' => array( - 'font_family' => 'my-custom_font_family', - 'expected' => 'my-custom_font_family', - ), - 'font family handle delimited mixture' => array( - 'font_family' => 'My custom_font-family', - 'expected' => 'my-custom_font-family', - ), - ); - } - - /** - * @dataProvider data_with_invalid_input - * - * @covers WP_Fonts_Utils::convert_font_family_into_handle - * - * @param mixed $invalid_input Invalid input. - */ - public function test_should_not_convert_with_invalid_input( $invalid_input ) { - $this->assertNull( WP_Fonts_Utils::convert_font_family_into_handle( $invalid_input ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_with_invalid_input() { - return array( - 'empty string' => array( '' ), - 'integer' => array( 10 ), - 'font family wrapped in an array' => array( array( 'source-serif-pro' ) ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpFontsUtils/convertVariationIntoHandle.php b/phpunit/tests/fonts-api/wpFontsUtils/convertVariationIntoHandle.php deleted file mode 100644 index 9268aac8ce372..0000000000000 --- a/phpunit/tests/fonts-api/wpFontsUtils/convertVariationIntoHandle.php +++ /dev/null @@ -1,122 +0,0 @@ -assertSame( $expected, WP_Fonts_Utils::convert_variation_into_handle( $font_family, $variation ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_with_valid_input() { - return array( - 'with only font-weight' => array( - 'font_family' => 'merriweather', - 'variation' => array( - 'font-weight' => '400', - ), - 'expected' => 'merriweather-400', - ), - 'with no font-style' => array( - 'font_family' => 'source-sans-pro', - 'variation' => array( - 'font-weight' => '200 900', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'provider' => 'local', - ), - 'expected' => 'source-sans-pro-200-900', - ), - 'with font family name and full variant' => array( - 'font_family' => 'source-sans-pro', - 'variation' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'normal', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'font-display' => 'fallback', - ), - 'expected' => 'source-sans-pro-200-900-normal', - ), - ); - } - - /** - * @dataProvider data_with_invalid_input - * - * @param string $font_family Font family to test. - * @param array $invalid_input Variation to test. - */ - public function tests_should_convert_with_invalid_input( $font_family, $invalid_input ) { - $this->expectNotice(); - $this->expectNoticeMessage( 'Variant handle could not be determined as font-weight and/or font-style are require' ); - - $this->assertNull( WP_Fonts_Utils::convert_variation_into_handle( $font_family, $invalid_input ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_with_invalid_input() { - return array( - 'with no font-weight or font-style' => array( - 'font_family' => 'merriweather', - 'variation' => array( - 'provider' => 'local', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'font-display' => 'fallback', - ), - ), - 'with non-string font-weight' => array( - 'font_family' => 'merriweather', - 'variation' => array( - 'font-weight' => 400, - ), - ), - 'with non-string font-style' => array( - 'font_family' => 'merriweather', - 'variation' => array( - 'font-style' => 0, - ), - ), - 'with empty string font-weight' => array( - 'font_family' => 'merriweather', - 'variation' => array( - 'font-weight' => '', - ), - ), - 'with empty string font-style' => array( - 'font_family' => 'merriweather', - 'variation' => array( - 'font-style' => '', - ), - ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpFontsUtils/getFontFamilyFromVariation.php b/phpunit/tests/fonts-api/wpFontsUtils/getFontFamilyFromVariation.php deleted file mode 100644 index 2b3e3689c4317..0000000000000 --- a/phpunit/tests/fonts-api/wpFontsUtils/getFontFamilyFromVariation.php +++ /dev/null @@ -1,134 +0,0 @@ -assertSame( $expected, WP_Fonts_Utils::get_font_family_from_variation( $variation ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_with_valid_variation() { - return array( - 'keyed by font-family' => array( - 'variation' => array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'normal', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'font-display' => 'fallback', - ), - 'expected' => 'Source Serif Pro', - ), - 'keyed by fontFamily and as a handle' => array( - 'variation' => array( - 'fontFamily' => 'source-sans-pro', - 'font-weight' => '200 900', - 'src' => 'https://example.com/assets/fonts/source-sans-pro/source-sans-pro.ttf.woff2', - 'provider' => 'local', - ), - 'expected' => 'source-sans-pro', - ), - 'with font family name and full variant' => array( - 'variation' => array( - 'provider' => 'local', - 'font-family' => 'Merriweather', - 'font-style' => 'normal', - 'font-weight' => '400 600', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', - 'font-display' => 'fallback', - ), - 'expected' => 'Merriweather', - ), - ); - } - - /** - * @dataProvider data_with_invalid_input - * - * @param array $invalid_variation Variation to test. - * @param string $expected_message Expected notice message. - */ - public function test_with_invalid_input( array $invalid_variation, $expected_message ) { - $this->expectNotice(); - $this->expectNoticeMessage( $expected_message ); - - $this->assertNull( WP_Fonts_Utils::get_font_family_from_variation( $invalid_variation ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_with_invalid_input() { - return array( - 'keyed with underscore' => array( - 'variation' => array( - 'provider' => 'local', - 'font_family' => 'Source Serif Pro', - 'font-style' => 'normal', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'font-display' => 'fallback', - ), - 'expected_message' => 'Font family not found.', - ), - 'keyed with space' => array( - 'variation' => array( - 'font family' => 'Source Sans Pro', - 'font-weight' => '200 900', - 'src' => 'https://example.com/assets/fonts/source-sans-pro/source-sans-pro.ttf.woff2', - 'provider' => 'local', - ), - 'expected_message' => 'Font family not found.', - ), - 'fontFamily => empty string' => array( - 'variation' => array( - 'fontFamily' => '', - 'font-weight' => '200 900', - 'src' => 'https://example.com/assets/fonts/source-sans-pro/source-sans-pro.ttf.woff2', - 'provider' => 'local', - ), - 'expected_message' => 'Font family not defined in the variation.', - ), - 'font-family => empty string' => array( - 'variation' => array( - 'provider' => 'local', - 'font-family' => '', - 'font-style' => 'normal', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'font-display' => 'fallback', - ), - 'expected_message' => 'Font family not defined in the variation.', - ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpFontsUtils/isDefined.php b/phpunit/tests/fonts-api/wpFontsUtils/isDefined.php deleted file mode 100644 index 3ae48ad52671a..0000000000000 --- a/phpunit/tests/fonts-api/wpFontsUtils/isDefined.php +++ /dev/null @@ -1,61 +0,0 @@ -assertTrue( WP_Fonts_Utils::is_defined( $input ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_when_defined() { - return array( - 'name: non empty string' => array( 'Some Font Family' ), - 'handle: non empty string' => array( 'some-font-family' ), - ); - } - - /** - * @dataProvider data_when_not_defined - * - * @param mixed $invalid_input Input to test. - */ - public function test_should_return_false_when_not_defined( $invalid_input ) { - $this->assertFalse( WP_Fonts_Utils::is_defined( $invalid_input ) ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_when_not_defined() { - return array( - 'empty string' => array( '' ), - 'string 0' => array( '0' ), - 'integer' => array( 10 ), - 'name wrapped in an array' => array( array( 'Some Font Family' ) ), - 'handle wrapped in an array' => array( array( 'some-font-family' ) ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpPrintFonts.php b/phpunit/tests/fonts-api/wpPrintFonts.php deleted file mode 100644 index 40b416aa56421..0000000000000 --- a/phpunit/tests/fonts-api/wpPrintFonts.php +++ /dev/null @@ -1,230 +0,0 @@ -assertSame( array(), wp_print_fonts() ); - } - - /** - * Unit test which mocks WP_Fonts methods. - * - * @dataProvider data_mocked_handles - * - * @param string|string[] $handles Handles to test. - */ - public function test_should_return_mocked_handles( $handles ) { - $mock = $this->set_up_mock( array( 'get_registered_font_families', 'do_items' ) ); - $mock->expects( $this->once() ) - ->method( 'get_registered_font_families' ) - ->will( $this->returnValue( $handles ) ); - - $mock->expects( $this->once() ) - ->method( 'do_items' ) - ->with( - $this->identicalTo( $handles ) - ) - ->will( $this->returnValue( $handles ) ); - - wp_print_fonts( $handles ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_mocked_handles() { - return array( - 'font family' => array( - array( 'my-custom-font' ), - ), - 'multiple font families' => array( - array( - 'font1', - 'font2', - ), - ), - ); - } - - /** - * Integration test that registers providers and fonts and then enqueues before - * testing the printing functionality. - * - * @dataProvider data_print_enqueued - * - * @param array $setup Test set up information for provider, fonts, and enqueued. - * @param array $expected_done Expected array of printed handles. - * @param string $expected_output Expected printed output. - */ - public function test_should_print_enqueued( $setup, $expected_done, $expected_output ) { - $wp_fonts = wp_fonts(); - - $this->setup_integrated_deps( $setup, $wp_fonts ); - - $this->expectOutputString( $expected_output ); - $actual_done = wp_print_fonts(); - $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); - } - - /** - * Integration test to validate printing given handles. Rather than mocking internal functionality, - * it registers providers and fonts but does not enqueue. - * - * @dataProvider data_print_enqueued - * - * @param array $setup Test set up information for provider, fonts, and enqueued. - * @param array $expected_done Expected array of printed handles. - * @param string $expected_output Expected printed output. - */ - public function test_should_print_handles_when_not_enqueued( $setup, $expected_done, $expected_output ) { - $wp_fonts = wp_fonts(); - - $this->setup_integrated_deps( $setup, $wp_fonts, false ); - // Do not enqueue. Instead, pass the handles to wp_print_fonts(). - $handles = $setup['enqueued']; - $this->assertEmpty( $wp_fonts->queue, 'No fonts should be enqueued' ); - - $this->expectOutputString( $expected_output ); - $actual_done = wp_print_fonts( $handles ); - $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); - } - - /** - * @dataProvider data_should_print_all_registered_fonts_for_iframed_editor - * - * @param string $fonts Fonts to register. - * @param array $expected Expected results. - */ - public function test_should_print_all_registered_fonts_for_iframed_editor( $fonts, $expected ) { - wp_register_fonts( $fonts ); - - $this->expectOutputString( $expected['output'] ); - $actual_done = wp_print_fonts( true ); - $this->assertSameSets( $expected['done'], $actual_done, 'All registered font-family handles should be returned' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_should_print_all_registered_fonts_for_iframed_editor() { - $local_fonts = $this->get_registered_local_fonts(); - $font_faces = $this->get_registered_fonts_css(); - - return array( - 'Merriweather with 1 variation' => array( - 'fonts' => array( 'merriweather' => $local_fonts['merriweather'] ), - 'expected' => array( - 'done' => array( 'merriweather', 'merriweather-200-900-normal' ), - 'output' => sprintf( - "\n", - $font_faces['merriweather-200-900-normal'] - ), - ), - ), - 'Source Serif Pro with 2 variations' => array( - 'fonts' => array( 'Source Serif Pro' => $local_fonts['Source Serif Pro'] ), - 'expected' => array( - 'done' => array( 'source-serif-pro', 'Source Serif Pro-300-normal', 'Source Serif Pro-900-italic' ), - 'output' => sprintf( - "\n", - $font_faces['Source Serif Pro-300-normal'], - $font_faces['Source Serif Pro-900-italic'] - ), - ), - ), - 'all fonts' => array( - 'fonts' => $local_fonts, - 'expected' => array( - 'done' => array( - 'merriweather', - 'merriweather-200-900-normal', - 'source-serif-pro', - 'Source Serif Pro-300-normal', - 'Source Serif Pro-900-italic', - ), - 'output' => sprintf( - "\n", - $font_faces['merriweather-200-900-normal'], - $font_faces['Source Serif Pro-300-normal'], - $font_faces['Source Serif Pro-900-italic'] - ), - ), - ), - ); - } - - /** - * Integration test for printing user-selected global fonts. - * This test registers providers and fonts and then enqueues before testing the printing functionality. - * - * @dataProvider data_print_user_selected_fonts - * - * @param array $global_styles Test set up information for provider, fonts, and enqueued. - * @param array $expected_done Expected array of printed handles. - * @param string $expected_output Expected printed output. - */ - public function test_should_print_user_selected_fonts( $global_styles, $expected_done, $expected_output ) { - $wp_fonts = wp_fonts(); - - $setup = array( - 'provider' => array( 'mock' => $this->get_provider_definitions( 'mock' ) ), - 'registered' => $this->get_registered_mock_fonts(), - 'global_styles' => $global_styles, - ); - $this->setup_integrated_deps( $setup, $wp_fonts, false ); - - $this->expectOutputString( $expected_output ); - $actual_printed_fonts = wp_print_fonts(); - $this->assertSameSets( $expected_done, $actual_printed_fonts, 'Should print font-faces for given user-selected fonts' ); - } - - - /** - * Sets up the dependencies for integration test. - * - * @param array $setup Dependencies to set up. - * @param WP_Fonts $wp_fonts Instance of WP_Fonts. - * @param bool $enqueue Whether to enqueue. Default true. - */ - private function setup_integrated_deps( array $setup, $wp_fonts, $enqueue = true ) { - foreach ( $setup['provider'] as $provider ) { - $wp_fonts->register_provider( $provider['id'], $provider['class'] ); - } - foreach ( $setup['registered'] as $handle => $variations ) { - $this->setup_register( $handle, $variations, $wp_fonts ); - } - - if ( $enqueue ) { - $wp_fonts->enqueue( $setup['enqueued'] ); - } - - if ( ! empty( $setup['global_styles'] ) ) { - $this->set_up_global_styles( $setup['global_styles'] ); - } - } -} diff --git a/phpunit/tests/fonts-api/wpRegisterFontProvider.php b/phpunit/tests/fonts-api/wpRegisterFontProvider.php deleted file mode 100644 index 907355bdc91c8..0000000000000 --- a/phpunit/tests/fonts-api/wpRegisterFontProvider.php +++ /dev/null @@ -1,95 +0,0 @@ -set_up_mock( 'register_provider' ); - $mock->expects( $this->once() ) - ->method( 'register_provider' ) - ->with( - $this->identicalTo( $provider_id ), - $this->identicalTo( $class_name ) - ) - ->will( $this->returnValue( true ) ); - - $this->assertTrue( wp_register_font_provider( $provider_id, $class_name ), 'wp_register_font_provider() should return true' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_register_providers() { - return array( - 'mock' => array( - 'provider_id' => 'mock', - 'class' => Mock_Provider::class, - ), - 'local' => array( - 'provider_id' => 'local', - 'class' => WP_Fonts_Provider_Local::class, - ), - ); - } - - /** - * @dataProvider data_invalid_providers - * - * @param string $provider_id Provider ID. - * @param string $class_name Provider class name. - */ - public function test_should_not_register( $provider_id, $class_name ) { - $mock = $this->set_up_mock( 'register_provider' ); - $mock->expects( $this->once() ) - ->method( 'register_provider' ) - ->with( - $this->identicalTo( $provider_id ), - $this->identicalTo( $class_name ) - ) - ->will( $this->returnValue( false ) ); - - $this->assertFalse( wp_register_font_provider( $provider_id, $class_name ), 'wp_register_font_provider() should return false' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_invalid_providers() { - return array( - 'provider_id is empty' => array( - 'provider_id' => '', - 'class' => Mock_Provider::class, - ), - 'class is empty' => array( - 'provider_id' => 'local', - 'class' => '', - ), - 'class does not exist' => array( - 'provider_id' => 'doesnotexist', - 'class' => 'Provider_Does_Not_Exist', - ), - ); - } -} diff --git a/phpunit/tests/fonts-api/wpRegisterFonts.php b/phpunit/tests/fonts-api/wpRegisterFonts.php deleted file mode 100644 index c57c1406a6cac..0000000000000 --- a/phpunit/tests/fonts-api/wpRegisterFonts.php +++ /dev/null @@ -1,104 +0,0 @@ -assertSame( $expected['wp_register_fonts'], $actual, 'Font family handle(s) should be returned' ); - $this->assertSame( $expected['get_registered'], $this->get_registered_handles(), 'Web fonts should match registered queue' ); - } - - /** - * @dataProvider data_fonts - * - * @param array $fonts Array of fonts to test. - */ - public function test_should_not_enqueue_on_registration( array $fonts ) { - wp_register_fonts( $fonts ); - $this->assertEmpty( $this->get_enqueued_handles() ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_fonts() { - return array( - 'font family keyed with slug' => array( - 'fonts' => array( - 'source-serif-pro' => array( - array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'normal', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'font-display' => 'fallback', - ), - ), - ), - 'expected' => array( - 'wp_register_fonts' => array( 'source-serif-pro' ), - 'get_registered' => array( - 'source-serif-pro', - 'source-serif-pro-200-900-normal', - ), - ), - ), - 'font family keyed with name' => array( - 'fonts' => array( - 'Source Serif Pro' => array( - array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'normal', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', - 'font-display' => 'fallback', - ), - array( - 'provider' => 'local', - 'font-family' => 'Source Serif Pro', - 'font-style' => 'italic', - 'font-weight' => '200 900', - 'font-stretch' => 'normal', - 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', - 'font-display' => 'fallback', - ), - ), - ), - 'expected' => array( - 'wp_register_fonts' => array( 'source-serif-pro' ), - 'get_registered' => array( - 'source-serif-pro', - 'source-serif-pro-200-900-normal', - 'source-serif-pro-200-900-italic', - ), - ), - ), - ); - } -} From cf3dbbcaf3b9839a77e281c82dc2f841f7342d08 Mon Sep 17 00:00:00 2001 From: Brian Coords Date: Mon, 22 Jan 2024 22:33:11 -0800 Subject: [PATCH 12/68] Docs: prefixes all php filters with wpdocs_ (#53914) --- docs/reference-guides/filters/block-filters.md | 16 ++++++++-------- docs/reference-guides/filters/editor-filters.md | 8 ++++---- .../filters/global-styles-filters.md | 4 ++-- docs/reference-guides/filters/parser-filters.md | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/reference-guides/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md index 4c7e3df7cec12..7e164a10e6c93 100644 --- a/docs/reference-guides/filters/block-filters.md +++ b/docs/reference-guides/filters/block-filters.md @@ -19,11 +19,11 @@ _Example_: ```php post ) ) { return array( 'core/paragraph', 'core/heading' ); } return $allowed_block_types; } -add_filter( 'allowed_block_types_all', 'filter_allowed_block_types_when_post_provided', 10, 2 ); +add_filter( 'allowed_block_types_all', 'wpdocs_filter_allowed_block_types_when_post_provided', 10, 2 ); ``` ## Managing block categories @@ -374,7 +374,7 @@ It is possible to filter the list of default block categories using the `block_c post ) ) { array_push( $block_categories, @@ -388,7 +388,7 @@ function filter_block_categories_when_post_provided( $block_categories, $editor_ return $block_categories; } -add_filter( 'block_categories_all', 'filter_block_categories_when_post_provided', 10, 2 ); +add_filter( 'block_categories_all', 'wpdocs_filter_block_categories_when_post_provided', 10, 2 ); ``` ### `wp.blocks.updateCategory` diff --git a/docs/reference-guides/filters/editor-filters.md b/docs/reference-guides/filters/editor-filters.md index 59f6d7ef8213f..943e161a1df49 100644 --- a/docs/reference-guides/filters/editor-filters.md +++ b/docs/reference-guides/filters/editor-filters.md @@ -84,14 +84,14 @@ _Example:_ post ) ) { $editor_settings['maxUploadFileSize'] = 12345; } return $editor_settings; } -add_filter( 'block_editor_settings_all', 'filter_block_editor_settings_when_post_provided', 10, 2 ); +add_filter( 'block_editor_settings_all', 'wpdocs_filter_block_editor_settings_when_post_provided', 10, 2 ); ``` #### `block_editor_rest_api_preload_paths` @@ -104,14 +104,14 @@ _Example:_ post ) ) { array_push( $preload_paths, array( '/wp/v2/blocks', 'OPTIONS' ) ); } return $preload_paths; } -add_filter( 'block_editor_rest_api_preload_paths', 'filter_block_editor_rest_api_preload_paths_when_post_provided', 10, 2 ); +add_filter( 'block_editor_rest_api_preload_paths', 'wpdocs_filter_block_editor_rest_api_preload_paths_when_post_provided', 10, 2 ); ``` ### Available default editor settings diff --git a/docs/reference-guides/filters/global-styles-filters.md b/docs/reference-guides/filters/global-styles-filters.md index 7d3b6be95768b..59bbbfcd5921d 100644 --- a/docs/reference-guides/filters/global-styles-filters.md +++ b/docs/reference-guides/filters/global-styles-filters.md @@ -14,7 +14,7 @@ _Example:_ This is how to pass a new color palette for the theme and disable the text color UI: ```php -function filter_theme_json_theme( $theme_json ){ +function wpdocs_filter_theme_json_theme( $theme_json ){ $new_data = array( 'version' => 2, 'settings' => array( @@ -38,5 +38,5 @@ function filter_theme_json_theme( $theme_json ){ return $theme_json->update_with( $new_data ); } -add_filter( 'wp_theme_json_data_theme', 'filter_theme_json_theme' ); +add_filter( 'wp_theme_json_data_theme', 'wpdocs_filter_theme_json_theme' ); ``` diff --git a/docs/reference-guides/filters/parser-filters.md b/docs/reference-guides/filters/parser-filters.md index 3acfb2489182d..7adc68cc0bf7e 100644 --- a/docs/reference-guides/filters/parser-filters.md +++ b/docs/reference-guides/filters/parser-filters.md @@ -26,11 +26,11 @@ class EmptyParser { } } -function my_plugin_select_empty_parser( $prev_parser_class ) { +function wpdocs_select_empty_parser( $prev_parser_class ) { return 'EmptyParser'; } -add_filter( 'block_parser_class', 'my_plugin_select_empty_parser', 10, 1 ); +add_filter( 'block_parser_class', 'wpdocs_select_empty_parser', 10, 1 ); ``` > **Note**: At the present time it's not possible to replace the client-side parser. From 38f8eaa3aa1472a73419edb3d192035b1759613f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:40:27 +0100 Subject: [PATCH 13/68] DataViews: revert list view as default for templates (#58079) * Revert "DataViews: use table layout for templates when experiment disabled (#57960)" This reverts commit e67f6fc22fecfd41b50b0d1fcab90e825bc493bd. * Revert list layout as default for templates * Make table layout the default, even with the experiment active * Revert changes to init edited entity --- .../edit-site/src/components/layout/router.js | 9 ++++---- .../src/components/page-templates/index.js | 15 +------------ .../edit-site/src/components/sidebar/index.js | 6 +---- .../use-init-edited-entity-from-url.js | 6 +---- .../edit-site/src/utils/get-is-list-page.js | 1 - .../site-editor/new-templates-list.spec.js | 22 +++---------------- 6 files changed, 10 insertions(+), 49 deletions(-) diff --git a/packages/edit-site/src/components/layout/router.js b/packages/edit-site/src/components/layout/router.js index 3f9a8be7f82de..f91de7767f5bd 100644 --- a/packages/edit-site/src/components/layout/router.js +++ b/packages/edit-site/src/components/layout/router.js @@ -55,12 +55,11 @@ export default function useLayoutAreas() { } // Templates - if ( - path === '/wp_template/all' || - ( path === '/wp_template' && window?.__experimentalAdminViews ) - ) { + if ( path === '/wp_template/all' ) { const isListLayout = - isCustom !== 'true' && ( ! layout || layout === 'list' ); + isCustom !== 'true' && + layout === 'list' && + window?.__experimentalAdminViews; return { areas: { content: , diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index 7deec674f9b41..7995ebe376b57 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -75,7 +75,7 @@ const defaultConfigPerViewType = { }; const DEFAULT_VIEW = { - type: window?.__experimentalAdminViews ? LAYOUT_LIST : LAYOUT_TABLE, + type: LAYOUT_TABLE, search: '', page: 1, perPage: 20, @@ -180,18 +180,6 @@ export default function DataviewsTemplates() { [ history, params, view?.type ] ); - const onDetailsChange = useCallback( - ( items ) => { - if ( items?.length === 1 ) { - history.push( { - postId: items[ 0 ].id, - postType: TEMPLATE_POST_TYPE, - } ); - } - }, - [ history ] - ); - const authors = useMemo( () => { if ( ! allTemplates ) { return EMPTY_ARRAY; @@ -390,7 +378,6 @@ export default function DataviewsTemplates() { view={ view } onChangeView={ onChangeView } onSelectionChange={ onSelectionChange } - onDetailsChange={ onDetailsChange } deferredRendering={ ! view.hiddenFields?.includes( 'preview' ) } /> diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 1d0be62982e87..73c6aea7e328c 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -76,11 +76,7 @@ function SidebarScreens() { - { window?.__experimentalAdminViews ? ( - - ) : ( - - ) } + diff --git a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js index 11c065cf862db..4c2214b76abd3 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js @@ -207,11 +207,7 @@ function useResolveEditedEntityAndContext( { path, postId, postType } ) { return {}; }, [ homepageId, postType, postId, path ] ); - if ( - ( path === '/wp_template/all' || - ( path === '/wp_template' && window?.__experimentalAdminViews ) ) && - postId - ) { + if ( path === '/wp_template/all' && postId ) { return { isReady: true, postType: 'wp_template', postId, context }; } diff --git a/packages/edit-site/src/utils/get-is-list-page.js b/packages/edit-site/src/utils/get-is-list-page.js index 3028b0e65d786..9530cd85bf04b 100644 --- a/packages/edit-site/src/utils/get-is-list-page.js +++ b/packages/edit-site/src/utils/get-is-list-page.js @@ -16,7 +16,6 @@ export default function getIsListPage( return ( [ '/wp_template/all', '/wp_template_part/all' ].includes( path ) || ( path === '/page' && window?.__experimentalAdminViews ) || - ( path === '/wp_template' && window?.__experimentalAdminViews ) || ( path === '/patterns' && // Don't treat "/patterns" without categoryType and categoryId as a // list page in mobile because the sidebar covers the whole page. diff --git a/test/e2e/specs/site-editor/new-templates-list.spec.js b/test/e2e/specs/site-editor/new-templates-list.spec.js index 272a3ffff80bf..13484abcb13ad 100644 --- a/test/e2e/specs/site-editor/new-templates-list.spec.js +++ b/test/e2e/specs/site-editor/new-templates-list.spec.js @@ -18,11 +18,7 @@ test.describe( 'Templates', () => { ] ); } ); test( 'Sorting', async ( { admin, page } ) => { - await admin.visitSiteEditor( { path: '/wp_template' } ); - // Switch to table layout. - await page.getByLabel( 'View options' ).click(); - await page.getByRole( 'menuitem', { name: 'Layout' } ).click(); - await page.getByRole( 'menuitemradio', { name: 'Table' } ).click(); + await admin.visitSiteEditor( { path: '/wp_template/all' } ); // Descending by title. await page .getByRole( 'button', { name: 'Template', exact: true } ) @@ -52,13 +48,7 @@ test.describe( 'Templates', () => { title: 'Date Archives', content: 'hi', } ); - await admin.visitSiteEditor( { path: '/wp_template' } ); - - // Switch to table layout. - await page.getByLabel( 'View options' ).click(); - await page.getByRole( 'menuitem', { name: 'Layout' } ).click(); - await page.getByRole( 'menuitemradio', { name: 'Table' } ).click(); - + await admin.visitSiteEditor( { path: '/wp_template/all' } ); // Global search. await page.getByRole( 'searchbox', { name: 'Filter list' } ).click(); await page.keyboard.type( 'tag' ); @@ -94,13 +84,7 @@ test.describe( 'Templates', () => { await expect( titles ).toHaveCount( 2 ); } ); test( 'Field visibility', async ( { admin, page } ) => { - await admin.visitSiteEditor( { path: '/wp_template' } ); - - // Switch to table layout. - await page.getByLabel( 'View options' ).click(); - await page.getByRole( 'menuitem', { name: 'Layout' } ).click(); - await page.getByRole( 'menuitemradio', { name: 'Table' } ).click(); - + await admin.visitSiteEditor( { path: '/wp_template/all' } ); await page.getByRole( 'button', { name: 'Description' } ).click(); await page.getByRole( 'menuitem', { name: 'Hide' } ).click(); await expect( From c6ac9c010551aed63a5e31f12b0796b08e941747 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 23 Jan 2024 16:49:32 +0800 Subject: [PATCH 14/68] Stabilize the pattern overrides block context (#58102) * Add pattern/overrides context to individual block.json files * Remove addition of block context usage via blocks supports * Add overrides attribute to pattern block and share it via providesContext * Revert "Add overrides attribute to pattern block and share it via providesContext" This reverts commit aca9b906e676a16bfa117ebfaa406e9141b72817. * Remove the experiment flag after reverting pattern block index.php changes * Register overrides attribute in block.json --- docs/reference-guides/core-blocks.md | 2 +- lib/block-supports/pattern.php | 46 ------------------- lib/load.php | 1 - packages/block-library/src/block/block.json | 3 ++ packages/block-library/src/block/index.php | 34 ++------------ packages/block-library/src/button/block.json | 1 + packages/block-library/src/heading/block.json | 1 + packages/block-library/src/image/block.json | 7 ++- .../block-library/src/paragraph/block.json | 2 +- 9 files changed, 16 insertions(+), 81 deletions(-) delete mode 100644 lib/block-supports/pattern.php diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index bee68530eeb23..ab6bb52b6b3b2 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -42,7 +42,7 @@ Create and save content to reuse across your site. Update the pattern, and the c - **Name:** core/block - **Category:** reusable - **Supports:** ~~customClassName~~, ~~html~~, ~~inserter~~, ~~renaming~~ -- **Attributes:** ref +- **Attributes:** overrides, ref ## Button diff --git a/lib/block-supports/pattern.php b/lib/block-supports/pattern.php deleted file mode 100644 index 0c5868c1fea0c..0000000000000 --- a/lib/block-supports/pattern.php +++ /dev/null @@ -1,46 +0,0 @@ - array( 'content' ), - 'core/heading' => array( 'content' ), - 'core/image' => array( 'url', 'title', 'alt' ), - 'core/button' => array( 'url', 'text' ), - ); - $pattern_support = array_key_exists( $block_type->name, $allowed_blocks ); - - if ( $pattern_support ) { - if ( ! $block_type->uses_context ) { - $block_type->uses_context = array(); - } - - if ( ! in_array( 'pattern/overrides', $block_type->uses_context, true ) ) { - $block_type->uses_context[] = 'pattern/overrides'; - } - } - } - - // Register the block support. - WP_Block_Supports::get_instance()->register( - 'pattern', - array( - 'register_attribute' => 'gutenberg_register_pattern_support', - ) - ); -} diff --git a/lib/load.php b/lib/load.php index 37e4d61c36832..8c9c8532d573c 100644 --- a/lib/load.php +++ b/lib/load.php @@ -213,7 +213,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-supports/duotone.php'; require __DIR__ . '/block-supports/shadow.php'; require __DIR__ . '/block-supports/background.php'; -require __DIR__ . '/block-supports/pattern.php'; // Data views. require_once __DIR__ . '/experimental/data-views.php'; diff --git a/packages/block-library/src/block/block.json b/packages/block-library/src/block/block.json index aeccdbfc1051d..b30c865e57a7f 100644 --- a/packages/block-library/src/block/block.json +++ b/packages/block-library/src/block/block.json @@ -10,6 +10,9 @@ "attributes": { "ref": { "type": "number" + }, + "overrides": { + "type": "object" } }, "supports": { diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 54b54fad139ff..444001fa49859 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -46,17 +46,14 @@ function render_block_core_block( $attributes ) { $content = $wp_embed->run_shortcode( $reusable_block->post_content ); $content = $wp_embed->autoembed( $content ); - $gutenberg_experiments = get_option( 'gutenberg-experiments' ); - $has_partial_synced_overrides = $gutenberg_experiments - && array_key_exists( 'gutenberg-pattern-partial-syncing', $gutenberg_experiments ) - && isset( $attributes['overrides'] ); + $has_pattern_overrides = isset( $attributes['overrides'] ); /** * We set the `pattern/overrides` context through the `render_block_context` * filter so that it is available when a pattern's inner blocks are * rendering via do_blocks given it only receives the inner content. */ - if ( $has_partial_synced_overrides ) { + if ( $has_pattern_overrides ) { $filter_block_context = static function ( $context ) use ( $attributes ) { $context['pattern/overrides'] = $attributes['overrides']; return $context; @@ -67,7 +64,7 @@ function render_block_core_block( $attributes ) { $content = do_blocks( $content ); unset( $seen_refs[ $attributes['ref'] ] ); - if ( $has_partial_synced_overrides ) { + if ( $has_pattern_overrides ) { remove_filter( 'render_block_context', $filter_block_context, 1 ); } @@ -86,28 +83,3 @@ function register_block_core_block() { ); } add_action( 'init', 'register_block_core_block' ); - -$gutenberg_experiments = get_option( 'gutenberg-experiments' ); -if ( $gutenberg_experiments && array_key_exists( 'gutenberg-pattern-partial-syncing', $gutenberg_experiments ) ) { - /** - * Registers the overrides attribute for core/block. - * - * @param array $args Array of arguments for registering a block type. - * @param string $block_name Block name including namespace. - * @return array $args - */ - function register_block_core_block_args( $args, $block_name ) { - if ( 'core/block' === $block_name ) { - $args['attributes'] = array_merge( - $args['attributes'], - array( - 'overrides' => array( - 'type' => 'object', - ), - ) - ); - } - return $args; - } - add_filter( 'register_block_type_args', 'register_block_core_block_args', 10, 2 ); -} diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index 3c232700a876e..f04d4642bb98e 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -8,6 +8,7 @@ "description": "Prompt visitors to take action with a button-style link.", "keywords": [ "link" ], "textdomain": "default", + "usesContext": [ "pattern/overrides" ], "attributes": { "tagName": { "type": "string", diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index 72cc67caddd9e..a1eb3fce32ef1 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -7,6 +7,7 @@ "description": "Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.", "keywords": [ "title", "subtitle" ], "textdomain": "default", + "usesContext": [ "pattern/overrides" ], "attributes": { "textAlign": { "type": "string" diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index c5191e3dd8654..a9357d28815b6 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -4,7 +4,12 @@ "name": "core/image", "title": "Image", "category": "media", - "usesContext": [ "allowResize", "imageCrop", "fixedHeight" ], + "usesContext": [ + "allowResize", + "imageCrop", + "fixedHeight", + "pattern/overrides" + ], "description": "Insert an image to make a visual statement.", "keywords": [ "img", "photo", "picture" ], "textdomain": "default", diff --git a/packages/block-library/src/paragraph/block.json b/packages/block-library/src/paragraph/block.json index a81d754d8ca1b..25a9a36fa8bf0 100644 --- a/packages/block-library/src/paragraph/block.json +++ b/packages/block-library/src/paragraph/block.json @@ -7,7 +7,7 @@ "description": "Start with the basic building block of all narrative.", "keywords": [ "text" ], "textdomain": "default", - "usesContext": [ "postId" ], + "usesContext": [ "postId", "pattern/overrides" ], "attributes": { "align": { "type": "string" From bf65a9fa3337018757285f50fcb692d2e3ba49e5 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 23 Jan 2024 10:53:44 +0100 Subject: [PATCH 15/68] Interactivity API: Add `wp-each` directive (#57859) * Copy the wp-each implementation * Scaffold files for wp-each e2e tests * Process template nodes inside `toVdom()` * Add tests for several list manipulations * Add a new test to implement * Add SSRed elements in PHP * Add SSRed elements to letters test * Add more test cases * Test support for siblings in template * Implement tests for wp-each on navigation * Update changelog * Add `wp-each` documentation * Extract localName * Add missing comment for `data-wp-each-child` elements in tests Co-authored-by: Luis Herranz * Clarify that `data-wp-each-child` is added automatically Co-authored-by: Luis Herranz * Add test for nested lists * Do nothing if element is not a template * Add SSRed elements to nested testing * Add tests for non-template elements * Add `wp-each` to the table of contents * Fix name of nested list tests * Add tests for derived state as keys * Fix broken tests * Add missing inner templates in SSR --------- Co-authored-by: Luis Herranz --- .../directive-each/block.json | 15 + .../directive-each/render.php | 226 ++++++++ .../interactive-blocks/directive-each/view.js | 189 +++++++ packages/interactivity/CHANGELOG.md | 1 + .../interactivity/docs/2-api-reference.md | 73 +++ packages/interactivity/src/directives.js | 47 +- packages/interactivity/src/hooks.tsx | 2 +- packages/interactivity/src/vdom.js | 26 +- .../interactivity/directive-each.spec.ts | 486 ++++++++++++++++++ 9 files changed, 1053 insertions(+), 12 deletions(-) create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-each/block.json create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-each/render.php create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-each/view.js create mode 100644 test/e2e/specs/interactivity/directive-each.spec.ts diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-each/block.json b/packages/e2e-tests/plugins/interactive-blocks/directive-each/block.json new file mode 100644 index 0000000000000..0d35e46105668 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-each/block.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/directive-each", + "title": "E2E Interactivity tests - directive each", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewScript": "directive-each-view", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-each/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-each/render.php new file mode 100644 index 0000000000000..b9deee18abe57 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-each/render.php @@ -0,0 +1,226 @@ + + +
+
+ + +

A

+

B

+

C

+
+ +
+ +
+ + + + + +

avocado

+

banana

+

cherimoya

+
+ +
+ +
+ + + + + + +

A Game of Thrones

+

A Clash of Kings

+

A Storm of Swords

+
+ +
+ +
+ + + +

1

+

2

+

3

+

4

+
+ +
+ +
+ + +

item X

+
+ +
+ + +

two

+

2

+

three

+

3

+

four

+

4

+
+ +
+ + + +
    + + +
  • + Dog +
      + +
    • Chihuahua
    • +
    • Rottweiler
    • +
    +
  • +
  • + Cat +
      + +
    • Sphynx
    • +
    • Siamese
    • +
    +
  • +
+
+ +
+
+

+
+
+ + +
+ + + +

avocado

+

banana

+

cherimoya

+
+
+ +
+ +
+ + +

beta

+

gamma

+

delta

+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-each/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-each/view.js new file mode 100644 index 0000000000000..55677c9629ad3 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-each/view.js @@ -0,0 +1,189 @@ +/** + * WordPress dependencies + */ +import { store, getContext, navigate } from '@wordpress/interactivity'; + +const { state } = store( 'directive-each' ); + +store( 'directive-each', { + state: { + letters: [ 'A', 'B', 'C' ], + }, +} ); + +store( 'directive-each', { + state: { + fruits: [ 'avocado', 'banana', 'cherimoya' ], + get fruitId() { + const { idPrefix, fruit } = getContext(); + return `${idPrefix}${fruit}`; + } + }, + actions: { + removeFruit() { + const { fruit } = getContext(); + state.fruits.splice( state.fruits.indexOf( fruit ), 1 ); + }, + rotateFruits() { + const fruit = state.fruits.pop(); + state.fruits.splice( 0, 0, fruit ); + }, + addFruit() { + state.fruits.splice( 0, 0, 'ananas' ); + }, + replaceFruit() { + state.fruits.splice( 0, 1, 'ananas' ); + }, + }, +} ); + +store( 'directive-each', { + state: { + books: [ + { + title: 'A Game of Thrones', + author: 'George R.R. Martin', + isbn: "9780553588484", + }, + { + title: 'A Clash of Kings', + author: 'George R.R. Martin', + isbn: "9780553381696", + }, + { + title: 'A Storm of Swords', + author: 'George R.R. Martin', + isbn: "9780553573428", + }, + ], + }, + actions: { + removeBook() { + const { book } = getContext(); + state.books.splice( state.books.indexOf( book ), 1 ); + }, + rotateBooks() { + const book = state.books.pop(); + state.books.splice( 0, 0, book ); + }, + addBook() { + const book = { + title: 'A Feast for Crows', + author: 'George R.R. Martin', + isbn: "9780553582024", + }; + state.books.splice( 0, 0, book ); + }, + replaceBook() { + const book = { + title: 'A Feast for Crows', + author: 'George R.R. Martin', + isbn: "9780553582024", + }; + state.books.splice( 0, 1, book ); + }, + modifyBook() { + const [ book ] = state.books; + book.title = book.title.toUpperCase(); + }, + }, +} ); + +store( 'directive-each', { + state: { + numbers: [ 1, 2, 3 ], + }, + actions: { + shiftNumber() { + state.numbers.shift(); + }, + unshiftNumber() { + if ( state.numbers.length > 0 ) { + state.numbers.unshift( state.numbers[ 0 ] - 1 ); + } + } + }, +} ); + +store( 'directive-each', { + state: { + emptyList: [] + }, + actions: { + addItem() { + state.emptyList.push( `item ${ state.emptyList.length }` ); + } + }, +} ); + +store( 'directive-each', { + state: { + numbersAndNames: [ + { name: "two", value: 2 }, + { name: "three", value: 3 }, + ], + }, + actions: { + unshiftNumberAndName() { + state.numbersAndNames.unshift( { name: "one", value: 1 } ); + } + }, +} ); + +store( 'directive-each', { + state: { + animalBreeds: [ + { name: "Dog", breeds: [ 'chihuahua', 'rottweiler' ] }, + { name: "Cat", breeds: [ 'sphynx', 'siamese' ] }, + ], + }, + actions: { + addAnimal() { + state.animalBreeds.unshift( { + name: "Rat", breeds: [ 'dumbo', 'rex' ] + } ); + }, + addBreeds() { + state + .animalBreeds + .forEach( ( { name, breeds } ) => { + if ( name === 'Dog') breeds.unshift( 'german shepherd' ); + if ( name === 'Cat') breeds.unshift( 'maine coon' ); + if ( name === 'Rat') breeds.unshift( 'satin' ); + } ); + } + } +} ); + +const html = ` +
+ + +

alpha

+

beta

+

gamma

+

delta

+
+`; + +store( 'directive-each', { + actions: { + navigate() { + return navigate( window.location, { + force: true, + html, + } ); + }, + } +} ); + diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index b37fff1bbb5c4..252b200f9d4d0 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -12,6 +12,7 @@ - Add the `data-wp-run` directive along with the `useInit` and `useWatch` hooks. ([#57805](https://github.com/WordPress/gutenberg/pull/57805)) - Add `wp-data-on-window` and `wp-data-on-document` directives. ([#57931](https://github.com/WordPress/gutenberg/pull/57931)) +- Add the `data-wp-each` directive to render lists of items using a template. ([57859](https://github.com/WordPress/gutenberg/pull/57859)) ### Breaking Changes diff --git a/packages/interactivity/docs/2-api-reference.md b/packages/interactivity/docs/2-api-reference.md index 922662c10a8e2..bae15e9a7fcf2 100644 --- a/packages/interactivity/docs/2-api-reference.md +++ b/packages/interactivity/docs/2-api-reference.md @@ -26,6 +26,7 @@ DOM elements are connected to data stored in the state and context through direc - [`wp-init`](#wp-init) ![](https://img.shields.io/badge/SIDE_EFFECTS-afd2e3.svg) - [`wp-run`](#wp-run) ![](https://img.shields.io/badge/SIDE_EFFECTS-afd2e3.svg) - [`wp-key`](#wp-key) ![](https://img.shields.io/badge/TEMPLATING-afd2e3.svg) + - [`wp-each`](#wp-each) ![](https://img.shields.io/badge/TEMPLATING-afd2e3.svg) - [Values of directives are references to store properties](#values-of-directives-are-references-to-store-properties) - [The store](#the-store) - [Elements of the store](#elements-of-the-store) @@ -620,6 +621,78 @@ But it can also be used on other elements: When the list is re-rendered, the Interactivity API will match elements by their keys to determine if an item was added/removed/reordered. Elements without keys might be recreated unnecessarily. + +#### `wp-each` + +The `wp-each` directive is intended to render a list of elements. The directive can be used in `