';
$tags = new WP_HTML_Tag_Processor( $markup );
$tags->next_tag();
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, $ns );
$this->assertSame(
array(
@@ -38,39 +39,39 @@ public function test_directive_merges_context_correctly_upon_wp_context_attribut
public function test_directive_resets_context_correctly_upon_closing_tag() {
$context = new WP_Directive_Context(
- array( 'my-key' => 'original-value' )
+ array( 'myblock' => array( 'my-key' => 'original-value' ) )
);
$context->set_context(
- array( 'my-key' => 'new-value' )
+ array( 'myblock' => array( 'my-key' => 'new-value' ) )
);
$markup = '
';
$tags = new WP_HTML_Tag_Processor( $markup );
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
$this->assertSame(
array( 'my-key' => 'original-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
}
public function test_directive_doesnt_throw_on_malformed_context_objects() {
$context = new WP_Directive_Context(
- array( 'my-key' => 'some-value' )
+ array( 'myblock' => array( 'my-key' => 'some-value' ) )
);
$markup = '
';
$tags = new WP_HTML_Tag_Processor( $markup );
$tags->next_tag();
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
}
@@ -87,36 +88,36 @@ public function test_directive_keeps_working_after_malformed_context_objects() {
// Parent div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
// Children div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
// Still the same context.
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
// Closing children div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
// Still the same context.
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
// Closing parent div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
// Now the context is empty.
$this->assertSame(
@@ -138,36 +139,36 @@ public function test_directive_keeps_working_with_a_directive_without_value() {
// Parent div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
// Children div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
// Still the same context.
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
// Closing children div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
// Still the same context.
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
// Closing parent div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
// Now the context is empty.
$this->assertSame(
@@ -189,36 +190,36 @@ public function test_directive_keeps_working_with_an_empty_directive() {
// Parent div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
// Children div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
// Still the same context.
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
// Closing children div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
// Still the same context.
$this->assertSame(
array( 'my-key' => 'some-value' ),
- $context->get_context()
+ $context->get_context()['myblock']
);
// Closing parent div.
$tags->next_tag( array( 'tag_closers' => 'visit' ) );
- gutenberg_interactivity_process_wp_context( $tags, $context );
+ gutenberg_interactivity_process_wp_context( $tags, $context, 'myblock' );
// Now the context is empty.
$this->assertSame(
diff --git a/phpunit/experimental/interactivity-api/directives/wp-style-test.php b/phpunit/experimental/interactivity-api/directives/wp-style-test.php
index 51468bd8a28141..9625803ebca78f 100644
--- a/phpunit/experimental/interactivity-api/directives/wp-style-test.php
+++ b/phpunit/experimental/interactivity-api/directives/wp-style-test.php
@@ -18,9 +18,9 @@ public function test_directive_adds_style() {
$tags = new WP_HTML_Tag_Processor( $markup );
$tags->next_tag();
- $context_before = new WP_Directive_Context( array( 'color' => 'green' ) );
+ $context_before = new WP_Directive_Context( array( 'myblock' => array( 'color' => 'green' ) ) );
$context = $context_before;
- gutenberg_interactivity_process_wp_style( $tags, $context );
+ gutenberg_interactivity_process_wp_style( $tags, $context, 'myblock' );
$this->assertSame(
'
Test
',
@@ -35,9 +35,9 @@ public function test_directive_ignores_empty_style() {
$tags = new WP_HTML_Tag_Processor( $markup );
$tags->next_tag();
- $context_before = new WP_Directive_Context( array( 'color' => 'green' ) );
+ $context_before = new WP_Directive_Context( array( 'myblock' => array( 'color' => 'green' ) ) );
$context = $context_before;
- gutenberg_interactivity_process_wp_style( $tags, $context );
+ gutenberg_interactivity_process_wp_style( $tags, $context, 'myblock' );
$this->assertSame( $markup, $tags->get_updated_html() );
$this->assertStringNotContainsString( 'color: green;', $tags->get_attribute( 'style' ) );
@@ -49,9 +49,9 @@ public function test_directive_works_without_style_attribute() {
$tags = new WP_HTML_Tag_Processor( $markup );
$tags->next_tag();
- $context_before = new WP_Directive_Context( array( 'color' => 'green' ) );
+ $context_before = new WP_Directive_Context( array( 'myblock' => array( 'color' => 'green' ) ) );
$context = $context_before;
- gutenberg_interactivity_process_wp_style( $tags, $context );
+ gutenberg_interactivity_process_wp_style( $tags, $context, 'myblock' );
$this->assertSame(
'
Test
',
diff --git a/phpunit/experimental/interactivity-api/directives/wp-text-test.php b/phpunit/experimental/interactivity-api/directives/wp-text-test.php
index 81d2d0f370a64b..9c889a3f0eb68f 100644
--- a/phpunit/experimental/interactivity-api/directives/wp-text-test.php
+++ b/phpunit/experimental/interactivity-api/directives/wp-text-test.php
@@ -14,31 +14,31 @@
*/
class Tests_Directives_WpText extends WP_UnitTestCase {
public function test_directive_sets_inner_html_based_on_attribute_value_and_escapes_html() {
- $markup = '
';
+ $markup = '
';
$tags = new WP_Directive_Processor( $markup );
$tags->next_tag();
$context_before = new WP_Directive_Context( array( 'myblock' => array( 'someText' => 'The HTML tag
produces a line break.' ) ) );
$context = clone $context_before;
- gutenberg_interactivity_process_wp_text( $tags, $context );
+ gutenberg_interactivity_process_wp_text( $tags, $context, 'myblock' );
- $expected_markup = '
The HTML tag <br> produces a line break.
';
+ $expected_markup = '
The HTML tag <br> produces a line break.
';
$this->assertSame( $expected_markup, $tags->get_updated_html() );
$this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-text directive changed context' );
}
public function test_directive_overwrites_inner_html_based_on_attribute_value() {
- $markup = '
Lorem ipsum dolor sit.
';
+ $markup = '
Lorem ipsum dolor sit.
';
$tags = new WP_Directive_Processor( $markup );
$tags->next_tag();
$context_before = new WP_Directive_Context( array( 'myblock' => array( 'someText' => 'Honi soit qui mal y pense.' ) ) );
$context = clone $context_before;
- gutenberg_interactivity_process_wp_text( $tags, $context );
+ gutenberg_interactivity_process_wp_text( $tags, $context, 'myblock' );
- $expected_markup = '
Honi soit qui mal y pense.
';
+ $expected_markup = '
Honi soit qui mal y pense.
';
$this->assertSame( $expected_markup, $tags->get_updated_html() );
$this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-text directive changed context' );
}
From e079d899a584b104370dc8f0347200b4e9d6b330 Mon Sep 17 00:00:00 2001
From: Nick Diego
Date: Tue, 9 Jan 2024 12:46:06 -0600
Subject: [PATCH 30/51] Fix formatting issue due to incorrect link parsing
(#57693)
---
docs/getting-started/quick-start-guide.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/getting-started/quick-start-guide.md b/docs/getting-started/quick-start-guide.md
index c6f22ce219136c..736a56c006c9e1 100644
--- a/docs/getting-started/quick-start-guide.md
+++ b/docs/getting-started/quick-start-guide.md
@@ -41,7 +41,7 @@ When you are finished making changes, run the `npm run build` command. This opti
You can use any local WordPress development environment to test your new block, but the scaffolded plugin includes configuration for `wp-env`. You must have [Docker](https://www.docker.com/products/docker-desktop) already installed and running on your machine, but if you do, run the `npx wp-env start` command.
-Once the script finishes running, you can access the local environment at: `http://localhost:8888`. Log into the WordPress dashboard using username `admin` and password `password`. The plugin will already be installed and activated. Open the Editor or Site Editor, and insert the Copyright Date Block as you would any other block.
+Once the script finishes running, you can access the local environment at: http://localhost:8888
. Log into the WordPress dashboard using username `admin` and password `password`. The plugin will already be installed and activated. Open the Editor or Site Editor, and insert the Copyright Date Block as you would any other block.
Visit the [Getting started](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/) guide to learn more about `wp-env`.
From 16c12f3d66d2ab54601adbf8560255ac68b7197b Mon Sep 17 00:00:00 2001
From: Andrew Serong <14988353+andrewserong@users.noreply.github.com>
Date: Wed, 10 Jan 2024 10:57:54 +1100
Subject: [PATCH 31/51] Table: Remove unnecessary margin override (#57699)
---
packages/block-library/src/table/editor.scss | 3 ---
1 file changed, 3 deletions(-)
diff --git a/packages/block-library/src/table/editor.scss b/packages/block-library/src/table/editor.scss
index 0367ed0a9c5d95..55652742a5ae9d 100644
--- a/packages/block-library/src/table/editor.scss
+++ b/packages/block-library/src/table/editor.scss
@@ -1,7 +1,4 @@
.wp-block-table {
- // Remove default style.
- margin: 0;
-
.wp-block[data-align="left"] > &,
.wp-block[data-align="right"] > &,
.wp-block[data-align="center"] > & {
From 8562a2831a986c0218759577afb26fde35b8d7d9 Mon Sep 17 00:00:00 2001
From: tellthemachines
Date: Wed, 10 Jan 2024 11:13:12 +1100
Subject: [PATCH 32/51] Try reducing the specificity of block style variation
selectors. (#57659)
---
lib/class-wp-theme-json-gutenberg.php | 3 +--
packages/block-library/src/button/style.scss | 12 ++++++------
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index af750fa0599795..aa8de83df9597b 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -1021,8 +1021,7 @@ protected static function get_blocks_metadata() {
if ( ! empty( $block_type->styles ) ) {
$style_selectors = array();
foreach ( $block_type->styles as $style ) {
- // The style variation classname is duplicated in the selector to ensure that it overrides core block styles.
- $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'] . '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] );
+ $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] );
}
static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors;
}
diff --git a/packages/block-library/src/button/style.scss b/packages/block-library/src/button/style.scss
index d9efc928c5b1c7..f441152107973f 100644
--- a/packages/block-library/src/button/style.scss
+++ b/packages/block-library/src/button/style.scss
@@ -98,19 +98,19 @@ $blocks-block__margin: 0.5em;
border-radius: 0 !important;
}
-.wp-block-button.is-style-outline > .wp-block-button__link,
-.wp-block-button .wp-block-button__link.is-style-outline {
+.wp-block-button:where(.is-style-outline) > .wp-block-button__link,
+.wp-block-button .wp-block-button__link:where(.is-style-outline) {
border: 2px solid currentColor;
padding: 0.667em 1.333em;
}
-.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-text-color),
-.wp-block-button .wp-block-button__link.is-style-outline:not(.has-text-color) {
+.wp-block-button:where(.is-style-outline) > .wp-block-button__link:not(.has-text-color),
+.wp-block-button .wp-block-button__link:where(.is-style-outline):not(.has-text-color) {
color: currentColor;
}
-.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-background),
-.wp-block-button .wp-block-button__link.is-style-outline:not(.has-background) {
+.wp-block-button:where(.is-style-outline) > .wp-block-button__link:not(.has-background),
+.wp-block-button .wp-block-button__link:where(.is-style-outline):not(.has-background) {
background-color: transparent;
// background-image is required to overwrite a gradient background
background-image: none;
From 5380b6c549668b94e38ae5619aa725f57ef7eb0f Mon Sep 17 00:00:00 2001
From: Andrea Fercia
Date: Wed, 10 Jan 2024 02:17:32 +0100
Subject: [PATCH 33/51] Label the HeightControl. (#57683)
---
packages/block-editor/src/components/height-control/index.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/packages/block-editor/src/components/height-control/index.js b/packages/block-editor/src/components/height-control/index.js
index e1797522497447..23738378b69983 100644
--- a/packages/block-editor/src/components/height-control/index.js
+++ b/packages/block-editor/src/components/height-control/index.js
@@ -157,6 +157,8 @@ export default function HeightControl( {
onUnitChange={ handleUnitChange }
min={ 0 }
size={ '__unstable-large' }
+ label={ label }
+ hideLabelFromVision
/>
@@ -175,6 +177,8 @@ export default function HeightControl( {
withInputField={ false }
onChange={ handleSliderChange }
__nextHasNoMarginBottom
+ label={ label }
+ hideLabelFromVision
/>
From d3f2c77d64a3ae2138e3788c38fecbe47f2f4d0d Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Wed, 10 Jan 2024 09:08:19 +0400
Subject: [PATCH 34/51] Migrate 'dropdown menu' e2e tests to Playwright
(#57663)
* Migrate 'dropdown menu' e2e tests to Playwright
* Use test steps
* Remove old test file
* Use original selectors
---
.../editor/various/dropdown-menu.test.js | 143 ------------------
.../editor/various/dropdown-menu.spec.js | 62 ++++++++
2 files changed, 62 insertions(+), 143 deletions(-)
delete mode 100644 packages/e2e-tests/specs/editor/various/dropdown-menu.test.js
create mode 100644 test/e2e/specs/editor/various/dropdown-menu.spec.js
diff --git a/packages/e2e-tests/specs/editor/various/dropdown-menu.test.js b/packages/e2e-tests/specs/editor/various/dropdown-menu.test.js
deleted file mode 100644
index 1ab5730ab66777..00000000000000
--- a/packages/e2e-tests/specs/editor/various/dropdown-menu.test.js
+++ /dev/null
@@ -1,143 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { createNewPost, pressKeyTimes } from '@wordpress/e2e-test-utils';
-
-const moreMenuButtonSelector = '.components-button[aria-label="Options"]';
-const moreMenuDropdownSelector =
- '.components-dropdown-menu__menu[aria-label="Options"]';
-const menuItemsSelector = [ 'menuitem', 'menuitemcheckbox', 'menuitemradio' ]
- .map( ( role ) => `${ moreMenuDropdownSelector } [role="${ role }"]` )
- .join( ',' );
-
-describe( 'Dropdown Menu', () => {
- beforeEach( async () => {
- await createNewPost();
- } );
-
- it( 'allows navigation through each item using arrow keys', async () => {
- await page.click( moreMenuButtonSelector );
- const menuItems = await page.$$( menuItemsSelector );
-
- // Catch any issues with the selector, which could cause a false positive test result.
- expect( menuItems.length ).toBeGreaterThan( 0 );
-
- let activeElementText = await page.evaluate(
- () => document.activeElement.textContent
- );
- const [ firstMenuItem ] = menuItems;
- const firstMenuItemText = await firstMenuItem.evaluate(
- ( element ) => element.textContent
- );
-
- // Expect the first menu item to be focused.
- expect( activeElementText ).toBeDefined();
- expect( activeElementText ).toBe( firstMenuItemText );
-
- // Arrow down to the last item.
- await pressKeyTimes( 'ArrowDown', menuItems.length - 1 );
-
- activeElementText = await page.evaluate(
- () => document.activeElement.textContent
- );
-
- const [ lastMenuItem ] = menuItems.slice( -1 );
- const lastMenuItemText = await lastMenuItem.evaluate(
- ( element ) => element.textContent
- );
-
- // Expect the last menu item to be focused.
- expect( activeElementText ).toBeDefined();
- expect( activeElementText ).toBe( lastMenuItemText );
-
- // Arrow back up to the first item.
- await pressKeyTimes( 'ArrowUp', menuItems.length - 1 );
-
- activeElementText = await page.evaluate(
- () => document.activeElement.textContent
- );
-
- // Expect the first menu item to be focused again.
- expect( activeElementText ).toBeDefined();
- expect( activeElementText ).toBe( firstMenuItemText );
- } );
-
- it( 'loops to the beginning and end when navigating past the boundaries of the menu', async () => {
- await page.click( moreMenuButtonSelector );
- const menuItems = await page.$$( menuItemsSelector );
-
- // Catch any issues with the selector, which could cause a false positive test result.
- expect( menuItems.length ).toBeGreaterThan( 0 );
-
- let activeElementText = await page.evaluate(
- () => document.activeElement.textContent
- );
- const [ firstMenuItem ] = menuItems;
- const firstMenuItemText = await firstMenuItem.evaluate(
- ( element ) => element.textContent
- );
-
- // Expect the first menu item to be focused.
- expect( activeElementText ).toBeDefined();
- expect( activeElementText ).toBe( firstMenuItemText );
-
- // Arrow up to the last item.
- await page.keyboard.press( 'ArrowUp' );
-
- activeElementText = await page.evaluate(
- () => document.activeElement.textContent
- );
-
- const [ lastMenuItem ] = menuItems.slice( -1 );
- const lastMenuItemText = await lastMenuItem.evaluate(
- ( element ) => element.textContent
- );
-
- // Expect the last menu item to be focused.
- expect( activeElementText ).toBeDefined();
- expect( activeElementText ).toBe( lastMenuItemText );
-
- // Arrow back down to the first item.
- await page.keyboard.press( 'ArrowDown' );
-
- activeElementText = await page.evaluate(
- () => document.activeElement.textContent
- );
-
- // Expect the first menu item to be focused again.
- expect( activeElementText ).toBeDefined();
- expect( activeElementText ).toBe( firstMenuItemText );
- } );
-
- it( 'ignores arrow key navigation that is orthogonal to the orientation of the menu, but stays open', async () => {
- await page.click( moreMenuButtonSelector );
- const menuItems = await page.$$( menuItemsSelector );
-
- // Catch any issues with the selector, which could cause a false positive test result.
- expect( menuItems.length ).toBeGreaterThan( 0 );
-
- let activeElementText = await page.evaluate(
- () => document.activeElement.textContent
- );
- const [ firstMenuItem ] = menuItems;
- const firstMenuItemText = await firstMenuItem.evaluate(
- ( element ) => element.textContent
- );
-
- // Expect the first menu item to be focused.
- expect( activeElementText ).toBeDefined();
- expect( activeElementText ).toBe( firstMenuItemText );
-
- // Press left and right keys an arbitrary (but > 1) number of times.
- await pressKeyTimes( 'ArrowLeft', 5 );
- await pressKeyTimes( 'ArrowRight', 5 );
-
- activeElementText = await page.evaluate(
- () => document.activeElement.textContent
- );
-
- // Expect the first menu item to still be focused.
- expect( activeElementText ).toBeDefined();
- expect( activeElementText ).toBe( firstMenuItemText );
- } );
-} );
diff --git a/test/e2e/specs/editor/various/dropdown-menu.spec.js b/test/e2e/specs/editor/various/dropdown-menu.spec.js
new file mode 100644
index 00000000000000..916ef3447d80a4
--- /dev/null
+++ b/test/e2e/specs/editor/various/dropdown-menu.spec.js
@@ -0,0 +1,62 @@
+/**
+ * WordPress dependencies
+ */
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+
+test.describe( 'Dropdown Menu', () => {
+ test.beforeEach( async ( { admin } ) => {
+ await admin.createNewPost();
+ } );
+
+ test( 'keyboard navigiation', async ( { page, pageUtils } ) => {
+ await page
+ .getByRole( 'region', { name: 'Editor top bar' } )
+ .getByRole( 'button', { name: 'Options' } )
+ .click();
+ const menuItems = page.locator(
+ '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]'
+ );
+ const totalItems = await menuItems.count();
+
+ // Catch any issues with the selector, which could cause a false positive test result.
+ expect( totalItems ).toBeGreaterThan( 0 );
+
+ await test.step( 'allows navigation through each item using arrow keys', async () => {
+ // Expect the first menu item to be focused.
+ await expect( menuItems.first() ).toBeFocused();
+
+ // Arrow down to the last item.
+ await pageUtils.pressKeys( 'ArrowDown', { times: totalItems - 1 } );
+ await expect( menuItems.last() ).toBeFocused();
+
+ // Arrow back up to the first item.
+ await pageUtils.pressKeys( 'ArrowUp', { times: totalItems - 1 } );
+ await expect( menuItems.first() ).toBeFocused();
+ } );
+
+ await test.step( 'loops to the beginning and end when navigating past the boundaries of the menu', async () => {
+ // Expect the first menu item to be focused.
+ await expect( menuItems.first() ).toBeFocused();
+
+ // Arrow up to the last item.
+ await page.keyboard.press( 'ArrowUp' );
+ await expect( menuItems.last() ).toBeFocused();
+
+ // Arrow back down to the first item.
+ await page.keyboard.press( 'ArrowDown' );
+ await expect( menuItems.first() ).toBeFocused();
+ } );
+
+ await test.step( 'ignores arrow key navigation that is orthogonal to the orientation of the menu, but stays open', async () => {
+ // Expect the first menu item to be focused.
+ await expect( menuItems.first() ).toBeFocused();
+
+ // Press left and right keys an arbitrary (but > 1) number of times.
+ await pageUtils.pressKeys( 'ArrowLeft', { times: 5 } );
+ await pageUtils.pressKeys( 'ArrowRight', { times: 5 } );
+
+ // Expect the first menu item to still be focused.
+ await expect( menuItems.first() ).toBeFocused();
+ } );
+ } );
+} );
From 7c2110073c26fbedb74cf527e3ef3163ddc5039e Mon Sep 17 00:00:00 2001
From: Faisal Ahmed
Date: Wed, 10 Jan 2024 11:56:03 +0600
Subject: [PATCH 35/51] Migrate 'core settings' e2e tests to Playwright
(#57581)
* Migrate 'core-settings' e2e tests to Playwright
* renamed test file
* Added role based selectors
---
.../editor/various/core-settings.spec.js | 36 ++++++++++++-------
1 file changed, 23 insertions(+), 13 deletions(-)
rename packages/e2e-tests/specs/editor/various/core-settings.test.js => test/e2e/specs/editor/various/core-settings.spec.js (58%)
diff --git a/packages/e2e-tests/specs/editor/various/core-settings.test.js b/test/e2e/specs/editor/various/core-settings.spec.js
similarity index 58%
rename from packages/e2e-tests/specs/editor/various/core-settings.test.js
rename to test/e2e/specs/editor/various/core-settings.spec.js
index 0eb98a2de050b4..9dddc273e6b16e 100644
--- a/packages/e2e-tests/specs/editor/various/core-settings.test.js
+++ b/test/e2e/specs/editor/various/core-settings.spec.js
@@ -1,10 +1,10 @@
/**
* WordPress dependencies
*/
-import { visitAdminPage } from '@wordpress/e2e-test-utils';
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
-async function getOptionsValues( selector ) {
- await visitAdminPage( 'options.php' );
+async function getOptionsValues( selector, admin, page ) {
+ await admin.visitAdminPage( 'options.php' );
return page.evaluate( ( theSelector ) => {
const inputs = Array.from( document.querySelectorAll( theSelector ) );
return inputs.reduce( ( memo, input ) => {
@@ -16,22 +16,32 @@ async function getOptionsValues( selector ) {
// It might make sense to include a similar test in WP core (or move this one over).
// See discussion here: https://github.com/WordPress/gutenberg/pull/32797#issuecomment-864192088.
-describe( 'Settings', () => {
- test( 'Regression: updating a specific option will only change its value and will not corrupt others', async () => {
+test.describe( 'Settings', () => {
+ test( 'Regression: updating a specific option will only change its value and will not corrupt others', async ( {
+ page,
+ admin,
+ } ) => {
// We won't select the option that we updated and will also remove some
// _transient options that seem to change at every update.
const optionsInputsSelector =
'form#all-options table.form-table input:not([id*="_transient"]):not([id="blogdescription"])';
- const optionsBefore = await getOptionsValues( optionsInputsSelector );
-
- await visitAdminPage( 'options-general.php' );
- await page.type(
- 'input#blogdescription',
- 'Just another Gutenberg site'
+ const optionsBefore = await getOptionsValues(
+ optionsInputsSelector,
+ admin,
+ page
);
- await page.click( 'input#submit' );
- const optionsAfter = await getOptionsValues( optionsInputsSelector );
+ await admin.visitAdminPage( 'options-general.php' );
+ await page
+ .getByRole( 'textbox', { name: 'Tagline' } )
+ .fill( 'Just another Gutenberg site' );
+ await page.getByRole( 'button', { name: 'Save Changes' } ).click();
+
+ const optionsAfter = await getOptionsValues(
+ optionsInputsSelector,
+ admin,
+ page
+ );
Object.entries( optionsBefore ).forEach( ( optionBefore ) => {
const [ id ] = optionBefore;
From 5d063e3fbfbaaef0688e060ad16a1e5ed47fee26 Mon Sep 17 00:00:00 2001
From: Glen Davies
Date: Wed, 10 Jan 2024 19:40:31 +1300
Subject: [PATCH 36/51] Patterns: Refactor the findOrCreate term method
(#57655)
* Refactor the findOrCreate term method to not call API if user category already exist, and fix bug that allowed different cased labels that could then not be selected
* Add missing lower casing
* Remove type as we can just use id to identify the user patterns in this context
* Add the duplicate term error handling back in as had a duplicate term once in testing, but haven't been able to replicate, but no disadvantage in having this code just in case
---
.../src/components/create-pattern-modal.js | 47 +++++++++++--------
1 file changed, 27 insertions(+), 20 deletions(-)
diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js
index e69390b9997521..137c14222ced34 100644
--- a/packages/patterns/src/components/create-pattern-modal.js
+++ b/packages/patterns/src/components/create-pattern-modal.js
@@ -77,24 +77,27 @@ export function CreatePatternModalContents( {
const categoryMap = useMemo( () => {
// Merge the user and core pattern categories and remove any duplicates.
const uniqueCategories = new Map();
- [ ...userPatternCategories, ...corePatternCategories ].forEach(
- ( category ) => {
- if (
- ! uniqueCategories.has( category.label ) &&
- // There are two core categories with `Post` label so explicitly remove the one with
- // the `query` slug to avoid any confusion.
- category.name !== 'query'
- ) {
- // We need to store the name separately as this is used as the slug in the
- // taxonomy and may vary from the label.
- uniqueCategories.set( category.label, {
- label: category.label,
- value: category.label,
- name: category.name,
- } );
- }
+ userPatternCategories.forEach( ( category ) => {
+ uniqueCategories.set( category.label.toLowerCase(), {
+ label: category.label,
+ name: category.name,
+ id: category.id,
+ } );
+ } );
+
+ corePatternCategories.forEach( ( category ) => {
+ if (
+ ! uniqueCategories.has( category.label.toLowerCase() ) &&
+ // There are two core categories with `Post` label so explicitly remove the one with
+ // the `query` slug to avoid any confusion.
+ category.name !== 'query'
+ ) {
+ uniqueCategories.set( category.label.toLowerCase(), {
+ label: category.label,
+ name: category.name,
+ } );
}
- );
+ } );
return uniqueCategories;
}, [ userPatternCategories, corePatternCategories ] );
@@ -140,9 +143,13 @@ export function CreatePatternModalContents( {
*/
async function findOrCreateTerm( term ) {
try {
- // We need to match any existing term to the correct slug to prevent duplicates, eg.
- // the core `Headers` category uses the singular `header` as the slug.
- const existingTerm = categoryMap.get( term );
+ const existingTerm = categoryMap.get( term.toLowerCase() );
+ if ( existingTerm && existingTerm.id ) {
+ return existingTerm.id;
+ }
+ // If we have an existing core category we need to match the new user category to the
+ // correct slug rather than autogenerating it to prevent duplicates, eg. the core `Headers`
+ // category uses the singular `header` as the slug.
const termData = existingTerm
? { name: existingTerm.label, slug: existingTerm.name }
: { name: term };
From 5dcd9d1cf5e27f10b99d55c0d3d63f096a3030ad Mon Sep 17 00:00:00 2001
From: Carlos Garcia
Date: Wed, 10 Jan 2024 09:39:18 +0100
Subject: [PATCH 37/51] [RNMobile] Fix losing undo/redo history when using
non-breaking space HTML entity (#57652)
* Stop removing non-breaking space HTML entity
The non-breaking space HTML entity is part of the RichText content, so we shouldn't remove it to preserve the original value, especially when pasting HTML. Otherwise, the removal leads to breaking the undo/redo history.
* Use `render` from test helpers in RichText unit tests
* Rename describe section in RichText unit tests
* Use `RichTextData.empty` to create empty value in RichText unit tests
* Add test case to cover non-breaking space HTML entity
* Update `describe` description in RichText unit tests
* Update snapshots of `RichText` unit tests
---
.../rich-text/native/index.native.js | 10 +---
.../test/__snapshots__/index.native.js.snap | 6 +-
.../rich-text/native/test/index.native.js | 58 ++++++++++++++++---
3 files changed, 56 insertions(+), 18 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 f83d03ece47983..1f536011b35b6f 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
@@ -52,10 +52,6 @@ import { getFormatColors } from './get-format-colors';
import styles from './style.scss';
import ToolbarButtonWithOptions from './toolbar-button-with-options';
-const unescapeSpaces = ( text ) => {
- return text.replace( / | /gi, ' ' );
-};
-
// The flattened color palettes array is memoized to ensure that the same array instance is
// returned for the colors palettes. This value might be used as a prop, so having the same
// instance will prevent unnecessary re-renders of the RichText component.
@@ -318,7 +314,7 @@ export class RichText extends Component {
}
const contentWithoutRootTag = this.removeRootTagsProducedByAztec(
- unescapeSpaces( event.nativeEvent.text )
+ event.nativeEvent.text
);
// On iOS, onChange can be triggered after selection changes, even though there are no content changes.
if ( contentWithoutRootTag === this.value.toString() ) {
@@ -333,7 +329,7 @@ export class RichText extends Component {
onTextUpdate( event ) {
const contentWithoutRootTag = this.removeRootTagsProducedByAztec(
- unescapeSpaces( event.nativeEvent.text )
+ event.nativeEvent.text
);
this.debounceCreateUndoLevel();
@@ -660,7 +656,7 @@ export class RichText extends Component {
// Check and dicsard stray event, where the text and selection is equal to the ones already cached.
const contentWithoutRootTag = this.removeRootTagsProducedByAztec(
- unescapeSpaces( event.nativeEvent.text )
+ event.nativeEvent.text
);
if (
contentWithoutRootTag === this.value.toString() &&
diff --git a/packages/block-editor/src/components/rich-text/native/test/__snapshots__/index.native.js.snap b/packages/block-editor/src/components/rich-text/native/test/__snapshots__/index.native.js.snap
index c9d3d62e40ce9d..84e9b467132714 100644
--- a/packages/block-editor/src/components/rich-text/native/test/__snapshots__/index.native.js.snap
+++ b/packages/block-editor/src/components/rich-text/native/test/__snapshots__/index.native.js.snap
@@ -1,12 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[` Font Size renders component with style and font size 1`] = `
+exports[` when applying the font size renders component with style and font size 1`] = `
"
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed imperdiet ut nibh vitae ornare. Sed auctor nec augue at blandit.
"
`;
-exports[` Font Size should update the font size when style prop with font size property is provided 1`] = `
+exports[` when applying the font size should update the font size when style prop with font size property is provided 1`] = `
Font Size should update the font size when style prop with
`;
-exports[` Font Size should update the font size with decimals when style prop with font size property is provided 1`] = `
+exports[` when applying the font size should update the font size with decimals when style prop with font size property is provided 1`] = `
', () => {
} );
} );
- describe( 'when changes arrive from Aztec', () => {
+ describe( 'when the value changes', () => {
it( 'should avoid updating attributes when values are equal', async () => {
const handleChange = jest.fn();
- const defaultEmptyValue = new RichTextData();
- const screen = render(
+ const defaultEmptyValue = RichTextData.empty();
+ render(
', () => {
expect( handleChange ).not.toHaveBeenCalled();
} );
+
+ it( 'should preserve non-breaking space HTML entity', () => {
+ const onChange = jest.fn();
+ const onSelectionChange = jest.fn();
+ // The initial value is created using an HTML element to preserve
+ // the HTML entity.
+ const initialValue = RichTextData.fromHTMLElement(
+ __unstableCreateElement( document, ' ' )
+ );
+ render(
+
+ );
+
+ // Trigger selection event with same text value as initial.
+ fireEvent(
+ screen.getByLabelText( /Text input/ ),
+ 'onSelectionChange',
+ 0,
+ 0,
+ initialValue.toString(),
+ {
+ nativeEvent: {
+ eventCount: 0,
+ target: undefined,
+ text: initialValue.toString(),
+ },
+ }
+ );
+
+ expect( onChange ).not.toHaveBeenCalled();
+ expect( onSelectionChange ).toHaveBeenCalled();
+ } );
} );
- describe( 'Font Size', () => {
+ describe( 'when applying the font size', () => {
it( 'should display rich text at the DEFAULT font size.', () => {
// Arrange.
const expectedFontSize = 16;
@@ -259,7 +301,7 @@ describe( '', () => {
const fontSize = '10';
const style = { fontSize: '12' };
// Act.
- const screen = render( );
+ render( );
screen.update( );
// Assert.
expect( screen.toJSON() ).toMatchSnapshot();
@@ -281,7 +323,7 @@ describe( '', () => {
const fontSize = '10';
const style = { fontSize: '12.56px' };
// Act.
- const screen = render( );
+ render( );
screen.update( );
// Assert.
expect( screen.toJSON() ).toMatchSnapshot();
From 66879a5a15dd558bf0d8fdf9cff7f8fb05ee7788 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com>
Date: Wed, 10 Jan 2024 09:42:24 +0100
Subject: [PATCH 38/51] DataViews: add new page button in Pages page (#57685)
---
.../src/components/page-pages/index.js | 38 +++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js
index 9bf23100e5db53..45368c11833f39 100644
--- a/packages/edit-site/src/components/page-pages/index.js
+++ b/packages/edit-site/src/components/page-pages/index.js
@@ -4,6 +4,7 @@
import {
__experimentalView as View,
__experimentalVStack as VStack,
+ Button,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useEntityRecords, store as coreStore } from '@wordpress/core-data';
@@ -42,6 +43,7 @@ import {
useEditPostAction,
} from '../actions';
import PostPreview from '../post-preview';
+import AddNewPageModal from '../add-new-page';
import Media from '../media';
import { unlock } from '../../lock-unlock';
const { useLocation, useHistory } = unlock( routerPrivateApis );
@@ -323,6 +325,29 @@ export default function PagePages() {
[ view.type, setView ]
);
+ const [ showAddPageModal, setShowAddPageModal ] = useState( false );
+ const openModal = useCallback( () => {
+ if ( ! showAddPageModal ) {
+ setShowAddPageModal( true );
+ }
+ }, [ showAddPageModal ] );
+ const closeModal = useCallback( () => {
+ if ( showAddPageModal ) {
+ setShowAddPageModal( false );
+ }
+ }, [ showAddPageModal ] );
+ const handleNewPage = useCallback(
+ ( { type, id } ) => {
+ history.push( {
+ postId: id,
+ postType: type,
+ canvas: 'edit',
+ } );
+ closeModal();
+ },
+ [ history ]
+ );
+
// TODO: we need to handle properly `data={ data || EMPTY_ARRAY }` for when `isLoading`.
return (
<>
@@ -333,6 +358,19 @@ export default function PagePages() {
: null
}
title={ __( 'Pages' ) }
+ actions={
+ <>
+
+ { showAddPageModal && (
+
+ ) }
+ >
+ }
>
Date: Wed, 10 Jan 2024 17:57:07 +0900
Subject: [PATCH 39/51] Image Block: Change upload icon label (#57704)
---
packages/block-library/src/image/image.js | 2 +-
test/e2e/specs/editor/blocks/image.spec.js | 6 ++++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js
index ea12457c2585db..d8788fde4844f6 100644
--- a/packages/block-library/src/image/image.js
+++ b/packages/block-library/src/image/image.js
@@ -459,7 +459,7 @@ export default function Image( {
diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js
index adeabc860c8342..9080a6dc194021 100644
--- a/test/e2e/specs/editor/blocks/image.spec.js
+++ b/test/e2e/specs/editor/blocks/image.spec.js
@@ -696,7 +696,9 @@ test.describe( 'Image', () => {
await expect( linkDom ).toHaveAttribute( 'href', url );
} );
- test( 'should upload external image', async ( { editor } ) => {
+ test( 'should upload external image to media library', async ( {
+ editor,
+ } ) => {
await editor.insertBlock( {
name: 'core/image',
attributes: {
@@ -704,7 +706,7 @@ test.describe( 'Image', () => {
},
} );
- await editor.clickBlockToolbarButton( 'Upload external image' );
+ await editor.clickBlockToolbarButton( 'Upload image to media library' );
const imageBlock = editor.canvas.locator(
'role=document[name="Block: Image"i]'
From 3eb3eca08a90c36603ade6a754c1ab2fc4e6d92a Mon Sep 17 00:00:00 2001
From: Andrea Fercia
Date: Wed, 10 Jan 2024 10:18:54 +0100
Subject: [PATCH 40/51] Use full text instead of abbreviation for min height
setting. (#57680)
---
.../src/components/global-styles/dimensions-panel.js | 4 ++--
packages/block-editor/src/components/height-control/README.md | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/block-editor/src/components/global-styles/dimensions-panel.js b/packages/block-editor/src/components/global-styles/dimensions-panel.js
index 47b5bd329725a7..47e50aa515e3c6 100644
--- a/packages/block-editor/src/components/global-styles/dimensions-panel.js
+++ b/packages/block-editor/src/components/global-styles/dimensions-panel.js
@@ -603,7 +603,7 @@ export default function DimensionsPanel( {
{ showMinHeightControl && (
diff --git a/packages/block-editor/src/components/height-control/README.md b/packages/block-editor/src/components/height-control/README.md
index 67b52f1d56f9b2..9be1741e8cdd8e 100644
--- a/packages/block-editor/src/components/height-control/README.md
+++ b/packages/block-editor/src/components/height-control/README.md
@@ -43,7 +43,7 @@ A callback function that handles the application of the height value.
- **Type:** `String`
- **Default:** `'Height'`
-A label for the height control. This is useful when using the height control for a feature that is controlled in the same way as height, but requires a different label. For example, "Min. height".
+A label for the height control. This is useful when using the height control for a feature that is controlled in the same way as height, but requires a different label. For example, "Minimum height".
## Related components
From 4c9b8cc09fc57f5d62b740b5eef3c6672f0b116a Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Wed, 10 Jan 2024 13:22:33 +0400
Subject: [PATCH 41/51] Editor: Use hooks instead of HoCs in
'PostVisibilityCheck' (#57705)
---
.../src/components/post-visibility/check.js | 25 +++++--------
.../components/post-visibility/test/check.js | 37 ++++++++++++-------
2 files changed, 34 insertions(+), 28 deletions(-)
diff --git a/packages/editor/src/components/post-visibility/check.js b/packages/editor/src/components/post-visibility/check.js
index 4bf9bd03772da6..116db0f546de2b 100644
--- a/packages/editor/src/components/post-visibility/check.js
+++ b/packages/editor/src/components/post-visibility/check.js
@@ -1,26 +1,21 @@
/**
* WordPress dependencies
*/
-import { compose } from '@wordpress/compose';
-import { withSelect } from '@wordpress/data';
+import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import { store as editorStore } from '../../store';
-export function PostVisibilityCheck( { hasPublishAction, render } ) {
- const canEdit = hasPublishAction;
+export default function PostVisibilityCheck( { render } ) {
+ const canEdit = useSelect( ( select ) => {
+ return (
+ select( editorStore ).getCurrentPost()._links?.[
+ 'wp:action-publish'
+ ] ?? false
+ );
+ } );
+
return render( { canEdit } );
}
-
-export default compose( [
- withSelect( ( select ) => {
- const { getCurrentPost, getCurrentPostType } = select( editorStore );
- return {
- hasPublishAction:
- getCurrentPost()._links?.[ 'wp:action-publish' ] ?? false,
- postType: getCurrentPostType(),
- };
- } ),
-] )( PostVisibilityCheck );
diff --git a/packages/editor/src/components/post-visibility/test/check.js b/packages/editor/src/components/post-visibility/test/check.js
index 8ec0c2df04ec90..828e876cceb102 100644
--- a/packages/editor/src/components/post-visibility/test/check.js
+++ b/packages/editor/src/components/post-visibility/test/check.js
@@ -3,32 +3,43 @@
*/
import { render, screen } from '@testing-library/react';
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+
+jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() );
+
/**
* Internal dependencies
*/
-import { PostVisibilityCheck } from '../check';
+import PostVisibilityCheck from '../check';
+
+function setupMockSelect( hasPublishAction ) {
+ useSelect.mockImplementation( ( mapSelect ) => {
+ return mapSelect( () => ( {
+ getCurrentPost: () => ( {
+ _links: {
+ 'wp:action-publish': hasPublishAction,
+ },
+ } ),
+ } ) );
+ } );
+}
describe( 'PostVisibilityCheck', () => {
const renderProp = ( { canEdit } ) => ( canEdit ? 'yes' : 'no' );
it( "should not render the edit link if the user doesn't have the right capability", () => {
- render(
-
- );
+ setupMockSelect( false );
+ render( );
expect( screen.queryByText( 'yes' ) ).not.toBeInTheDocument();
expect( screen.getByText( 'no' ) ).toBeVisible();
} );
it( 'should render if the user has the correct capability', () => {
- render(
-
- );
+ setupMockSelect( true );
+ render( );
expect( screen.queryByText( 'no' ) ).not.toBeInTheDocument();
expect( screen.getByText( 'yes' ) ).toBeVisible();
} );
From 6ef4629f77ab3da6502508b4c9539a3912300f0e Mon Sep 17 00:00:00 2001
From: Andrea Fercia
Date: Wed, 10 Jan 2024 10:35:55 +0100
Subject: [PATCH 42/51] Clean up code editor CSS. (#57519)
---
packages/edit-post/src/components/text-editor/style.scss | 6 ------
packages/edit-site/src/components/code-editor/style.scss | 4 ----
2 files changed, 10 deletions(-)
diff --git a/packages/edit-post/src/components/text-editor/style.scss b/packages/edit-post/src/components/text-editor/style.scss
index c02e983057e6ef..ab248317de1dbf 100644
--- a/packages/edit-post/src/components/text-editor/style.scss
+++ b/packages/edit-post/src/components/text-editor/style.scss
@@ -39,10 +39,8 @@
margin-right: auto;
@include break-large() {
- padding: $grid-unit-20 $grid-unit-30 #{ $grid-unit-60 * 2 } $grid-unit-30;
padding: 0 $grid-unit-30 $grid-unit-30 $grid-unit-30;
}
-
}
// Exit code editor toolbar.
@@ -70,8 +68,4 @@
font-size: $default-font-size;
color: $gray-900;
}
-
- .components-button svg {
- order: 1;
- }
}
diff --git a/packages/edit-site/src/components/code-editor/style.scss b/packages/edit-site/src/components/code-editor/style.scss
index 0e79575c49f678..17431de27b896c 100644
--- a/packages/edit-site/src/components/code-editor/style.scss
+++ b/packages/edit-site/src/components/code-editor/style.scss
@@ -41,10 +41,6 @@
font-size: $default-font-size;
color: $gray-900;
}
-
- .components-button svg {
- order: 1;
- }
}
}
From 73a4716f429b5dce0190638049f5bd30f0b242f6 Mon Sep 17 00:00:00 2001
From: Jon Surrell
Date: Wed, 10 Jan 2024 12:47:03 +0100
Subject: [PATCH 43/51] Scripts: Fix webpack not setting environment.module
true (#57714)
Webpack (via wp-scripts) may refuse to output external modules
unless we set `output.environment.modules = true`:
> The target environment doesn't support EcmaScriptModule syntax so it's not possible to use external type 'module'
> while analyzing module external module "@wordpress/interactivity" for concatenation
---
packages/scripts/config/webpack.config.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js
index 3919558c2f05ca..57bd258d325393 100644
--- a/packages/scripts/config/webpack.config.js
+++ b/packages/scripts/config/webpack.config.js
@@ -403,6 +403,10 @@ if ( hasExperimentalModulesFlag ) {
...baseConfig.output,
module: true,
chunkFormat: 'module',
+ environment: {
+ ...baseConfig.output.environment,
+ module: true,
+ },
library: {
...baseConfig.output.library,
type: 'module',
From b3e534e636cc286ca79a796d3a47164ff362302b Mon Sep 17 00:00:00 2001
From: Carlos Garcia
Date: Wed, 10 Jan 2024 13:35:36 +0100
Subject: [PATCH 44/51] Add tests to cover enabling autoplay and loop setting
in Audio block (#57715)
---
.../test/__snapshots__/edit.native.js.snap | 12 ++++++++
.../src/audio/test/edit.native.js | 29 +++++++++++++++++++
2 files changed, 41 insertions(+)
diff --git a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap
index dca3f782efc676..4cf28f7063ad31 100644
--- a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap
+++ b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap
@@ -532,3 +532,15 @@ exports[`Audio block renders placeholder without crashing 1`] = `
`;
+
+exports[`Audio block should enable autoplay setting 1`] = `
+"
+
+"
+`;
+
+exports[`Audio block should enable loop setting 1`] = `
+"
+
+"
+`;
diff --git a/packages/block-library/src/audio/test/edit.native.js b/packages/block-library/src/audio/test/edit.native.js
index c191fd2fff7989..7296d595d7aaab 100644
--- a/packages/block-library/src/audio/test/edit.native.js
+++ b/packages/block-library/src/audio/test/edit.native.js
@@ -5,7 +5,10 @@ import {
addBlock,
dismissModal,
fireEvent,
+ getBlock,
+ getEditorHtml,
initializeEditor,
+ openBlockSettings,
render,
screen,
setupCoreBlocks,
@@ -31,6 +34,10 @@ jest.unmock( '@wordpress/react-native-aztec' );
const MEDIA_UPLOAD_STATE_FAILED = 3;
+const AUDIO_BLOCK = `
+
+`;
+
let uploadCallBack;
subscribeMediaUpload.mockImplementation( ( callback ) => {
uploadCallBack = callback;
@@ -100,4 +107,26 @@ describe( 'Audio block', () => {
screen.getByText( 'Invalid URL. Audio file not found.' )
).toBeVisible();
} );
+
+ it( 'should enable autoplay setting', async () => {
+ await initializeEditor( { initialHtml: AUDIO_BLOCK } );
+
+ const audioBlock = getBlock( screen, 'Audio' );
+ fireEvent.press( audioBlock );
+ await openBlockSettings( screen );
+
+ fireEvent.press( screen.getByText( 'Autoplay' ) );
+ expect( getEditorHtml() ).toMatchSnapshot();
+ } );
+
+ it( 'should enable loop setting', async () => {
+ await initializeEditor( { initialHtml: AUDIO_BLOCK } );
+
+ const audioBlock = getBlock( screen, 'Audio' );
+ fireEvent.press( audioBlock );
+ await openBlockSettings( screen );
+
+ fireEvent.press( screen.getByText( 'Loop' ) );
+ expect( getEditorHtml() ).toMatchSnapshot();
+ } );
} );
From db067e16df101128fe7588e483112c7030dfa83e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com>
Date: Wed, 10 Jan 2024 13:51:21 +0100
Subject: [PATCH 45/51] DataViews: add footer to Pages sidebar (#57690)
---
.../index.js | 77 +++++++++++++++++++
.../edit-site/src/components/sidebar/index.js | 9 +--
2 files changed, 79 insertions(+), 7 deletions(-)
create mode 100644 packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js
new file mode 100644
index 00000000000000..171d59c108e9b8
--- /dev/null
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js
@@ -0,0 +1,77 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ __experimentalTruncate as Truncate,
+ __experimentalVStack as VStack,
+} from '@wordpress/components';
+import { layout } from '@wordpress/icons';
+import { useMemo } from '@wordpress/element';
+import { useEntityRecords } from '@wordpress/core-data';
+import { decodeEntities } from '@wordpress/html-entities';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { useLink } from '../routes/link';
+import { TEMPLATE_POST_TYPE } from '../../utils/constants';
+import SidebarNavigationItem from '../sidebar-navigation-item';
+import SidebarNavigationScreen from '../sidebar-navigation-screen';
+import DataViewsSidebarContent from '../sidebar-dataviews';
+
+const PageItem = ( { postType = 'page', postId, ...props } ) => {
+ const linkInfo = useLink(
+ {
+ postType,
+ postId,
+ },
+ {
+ backPath: '/page',
+ }
+ );
+ return ;
+};
+
+export default function SidebarNavigationScreenPagesDataViews() {
+ const { records: templateRecords } = useEntityRecords(
+ 'postType',
+ TEMPLATE_POST_TYPE,
+ {
+ per_page: -1,
+ }
+ );
+ const templates = useMemo(
+ () =>
+ templateRecords?.filter( ( { slug } ) =>
+ [ '404', 'search' ].includes( slug )
+ ),
+ [ templateRecords ]
+ );
+
+ return (
+ }
+ footer={
+
+ { templates?.map( ( item ) => (
+
+
+ { decodeEntities(
+ item.title?.rendered || __( '(no title)' )
+ ) }
+
+
+ ) ) }
+
+ }
+ />
+ );
+}
diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js
index 0f986d486fbb81..73c6aea7e328c5 100644
--- a/packages/edit-site/src/components/sidebar/index.js
+++ b/packages/edit-site/src/components/sidebar/index.js
@@ -7,7 +7,6 @@ import classNames from 'classnames';
* WordPress dependencies
*/
import { memo, useRef } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
import {
__experimentalNavigatorProvider as NavigatorProvider,
__experimentalNavigatorScreen as NavigatorScreen,
@@ -32,9 +31,8 @@ import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen
import SaveHub from '../save-hub';
import { unlock } from '../../lock-unlock';
import SidebarNavigationScreenPages from '../sidebar-navigation-screen-pages';
+import SidebarNavigationScreenPagesDataViews from '../sidebar-navigation-screen-pages-dataviews';
import SidebarNavigationScreenPage from '../sidebar-navigation-screen-page';
-import SidebarNavigationScreen from '../sidebar-navigation-screen';
-import DataViewsSidebarContent from '../sidebar-dataviews';
const { useLocation } = unlock( routerPrivateApis );
@@ -69,10 +67,7 @@ function SidebarScreens() {
{ window?.__experimentalAdminViews ? (
- }
- />
+
) : (
) }
From 08f236d0319f503c1c784dc8050df058cbccdea9 Mon Sep 17 00:00:00 2001
From: Jon Surrell
Date: Wed, 10 Jan 2024 15:13:22 +0100
Subject: [PATCH 46/51] Interactive template: Use viewModule (#57712)
Use viewModule in the create-block interactivity template.
- Add `viewModule` support to `@wordpress/create-block`.
- Set the `--experimental-modules` option when building the templated block.
- The plugin php template is updated to register with block metadata.
---------
Co-authored-by: Luis Herranz
---
.../CHANGELOG.md | 14 +++++++++-----
.../create-block-interactive-template/README.md | 2 ++
.../block-templates/render.php.mustache | 5 -----
.../create-block-interactive-template/index.js | 6 ++++++
.../plugin-templates/$slug.php.mustache | 11 +----------
packages/create-block/CHANGELOG.md | 4 ++++
packages/create-block/lib/init-block.js | 2 ++
packages/create-block/lib/scaffold.js | 2 ++
packages/interactivity/docs/1-getting-started.md | 16 ++--------------
9 files changed, 28 insertions(+), 34 deletions(-)
diff --git a/packages/create-block-interactive-template/CHANGELOG.md b/packages/create-block-interactive-template/CHANGELOG.md
index 47a8aec6c92a31..159c65e9ada19c 100644
--- a/packages/create-block-interactive-template/CHANGELOG.md
+++ b/packages/create-block-interactive-template/CHANGELOG.md
@@ -2,20 +2,24 @@
## Unreleased
+### Enhancement
+
+- Update the template to use `viewModule` in block.json ([#57712](https://github.com/WordPress/gutenberg/pull/57712)).
+
## 1.11.0 (2023-12-13)
-- Add all files to the generated plugin zip. [#56943](https://github.com/WordPress/gutenberg/pull/56943)
-- Prevent crash when Gutenberg plugin is not installed. [#56941](https://github.com/WordPress/gutenberg/pull/56941)
+- Add all files to the generated plugin zip ([#56943](https://github.com/WordPress/gutenberg/pull/56943)).
+- Prevent crash when Gutenberg plugin is not installed ([#56941](https://github.com/WordPress/gutenberg/pull/56941)).
## 1.10.1 (2023-12-07)
-- Update template to use modules instead of scripts. [#56694](https://github.com/WordPress/gutenberg/pull/56694)
+- Update template to use modules instead of scripts ([#56694](https://github.com/WordPress/gutenberg/pull/56694)).
## 1.10.0 (2023-11-29)
### Enhancement
-- Update `view.js` and `render.php` templates to the new `store()` API. [#56613](https://github.com/WordPress/gutenberg/pull/56613)
+- Update `view.js` and `render.php` templates to the new `store()` API ([#56613](https://github.com/WordPress/gutenberg/pull/56613)).
## 1.9.0 (2023-11-16)
@@ -35,4 +39,4 @@
### Enhancement
-- Moves the `example` property into block.json by leveraging changes to create-block to now support `example`. [#52801](https://github.com/WordPress/gutenberg/pull/52801)
+- Moves the `example` property into block.json by leveraging changes to create-block to now support `example` ([#52801](https://github.com/WordPress/gutenberg/pull/52801)).
diff --git a/packages/create-block-interactive-template/README.md b/packages/create-block-interactive-template/README.md
index cc0530c0630549..adf3cab6594cc9 100644
--- a/packages/create-block-interactive-template/README.md
+++ b/packages/create-block-interactive-template/README.md
@@ -10,6 +10,8 @@ This block template can be used by running the following command:
npx @wordpress/create-block --template @wordpress/create-block-interactive-template
```
+It requires Gutenberg 17.5 or higher.
+
## Contributing to this package
This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects.
diff --git a/packages/create-block-interactive-template/block-templates/render.php.mustache b/packages/create-block-interactive-template/block-templates/render.php.mustache
index 0f6883a9362407..960da619f790a4 100644
--- a/packages/create-block-interactive-template/block-templates/render.php.mustache
+++ b/packages/create-block-interactive-template/block-templates/render.php.mustache
@@ -13,11 +13,6 @@
// Generate unique id for aria-controls.
$unique_id = wp_unique_id( 'p-' );
-
-// Enqueue the view file.
-if (function_exists('gutenberg_enqueue_module')) {
- gutenberg_enqueue_module( '{{namespace}}-view' );
-}
?>
!! value )
diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js
index 49d3cbf794777a..bd9ba0396b75e3 100644
--- a/packages/create-block/lib/scaffold.js
+++ b/packages/create-block/lib/scaffold.js
@@ -44,6 +44,7 @@ module.exports = async (
editorStyle,
style,
render,
+ viewModule,
viewScript,
variantVars,
customPackageJSON,
@@ -84,6 +85,7 @@ module.exports = async (
editorStyle,
style,
render,
+ viewModule,
viewScript,
variantVars,
customPackageJSON,
diff --git a/packages/interactivity/docs/1-getting-started.md b/packages/interactivity/docs/1-getting-started.md
index 85af2021807351..660671a8b10cd8 100644
--- a/packages/interactivity/docs/1-getting-started.md
+++ b/packages/interactivity/docs/1-getting-started.md
@@ -26,18 +26,6 @@ We can scaffold a WordPress plugin that registers an interactive block (using th
npx @wordpress/create-block@latest my-first-interactive-block --template @wordpress/create-block-interactive-template
```
-> **Note**
-> The Interactivity API recently switched from [using modules instead of scripts in the frontend](https://github.com/WordPress/gutenberg/pull/56143). Therefore, in order to test this scaffolded block, you will need to add the following line to the `package.json` file of the generated plugin:
-
-```json
-"files": [
- "src/view.js"
-]
-```
-> This should be updated in the [scripts package](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) soon.
-
-
-
#### 2. Generate the build
When the plugin folder is generated, we should launch the build process to get the final version of the interactive block that can be used from WordPress.
@@ -61,7 +49,7 @@ At this point you should be able to insert the "My First Interactive Block" bloc
## Requirements of the Interactivity API
-To start working with the Interactivity API you'll need to have a [proper WordPress development environment for blocks](https://developer.wordpress.org/block-editor/getting-started/devenv/) and some specific code in your block, which should include:
+To start working with the Interactivity API you'll need to have a [proper WordPress development environment for blocks](https://developer.wordpress.org/block-editor/getting-started/devenv/) and some specific code in your block, which should include:
#### A local WordPress installation
@@ -71,7 +59,7 @@ To get quickly started, [`wp-now`](https://www.npmjs.com/package/@wp-now/wp-now)
#### Latest vesion of Gutenberg
-The Interactivity API is currently only available as an experimental feature from Gutenberg 17.2, so you'll need to have Gutenberg 17.2 or higher version installed and activated in your WordPress installation.
+The Interactivity API is currently only available as an experimental feature from Gutenberg, so you'll need to have Gutenberg 17.5 or higher version installed and activated in your WordPress installation.
#### Node.js
From ba367dcc86de157f74eebf419f167381b5d797b2 Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Wed, 10 Jan 2024 15:28:52 +0100
Subject: [PATCH 47/51] Update @ariakit/react to v0.3.12 and @ariakit/test to
v0.3.7 (#57547)
* Update @ariakit/react to v0.3.12 and @ariakit/test to v0.3.7
* CHANGELOG
* Use @ariakit/test for AlignmentMatrixControl unit tests
* Use @ariakit/text for ToggleGroupControl unit tests
* Refactor 'hoverOutside' method
* Improve tooltip-related tests in `ToggleGroupControl`
---
package-lock.json | 152 ++++++++----------
package.json | 2 +-
packages/components/CHANGELOG.md | 1 +
packages/components/package.json | 2 +-
.../alignment-matrix-control/test/index.tsx | 26 ++-
.../src/toggle-group-control/test/index.tsx | 103 ++++++------
6 files changed, 140 insertions(+), 146 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index d2c4a65342a353..02de0a60ff4c93 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -86,7 +86,7 @@
"devDependencies": {
"@actions/core": "1.9.1",
"@actions/github": "5.0.0",
- "@ariakit/test": "^0.3.5",
+ "@ariakit/test": "^0.3.7",
"@babel/core": "7.16.0",
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
"@babel/plugin-syntax-jsx": "7.16.0",
@@ -1628,13 +1628,48 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@ariakit/core": {
+ "version": "0.3.10",
+ "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.10.tgz",
+ "integrity": "sha512-AcN+GSoVXuUOzKx5d3xPL3YsEHevh4PIO6QIt/mg/nRX1XQ6cvxQEiAjO/BJQm+/MVl7/VbuGBoTFjr0tPU6NQ=="
+ },
+ "node_modules/@ariakit/react": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.12.tgz",
+ "integrity": "sha512-HxKMZZhWSkwwS/Sh9OdWyuNKQ2tjDAIQIy2KVI7IRa8ZQ6ze/4g3YLUHbfCxO7oDupXHfXaeZ4hWx8lP7l1U/g==",
+ "dependencies": {
+ "@ariakit/react-core": "0.3.12"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ariakit"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@ariakit/react-core": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.12.tgz",
+ "integrity": "sha512-w6P1A7TYb1fKUe9QbwaoTOWofl13g7TEuXdV4JyefJCQL1e9HQdEw9UL67I8aXRo8/cFHH94/z0N37t8hw5Ogg==",
+ "dependencies": {
+ "@ariakit/core": "0.3.10",
+ "@floating-ui/dom": "^1.0.0",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/@ariakit/test": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.5.tgz",
- "integrity": "sha512-7UCQBnJZ88JptkEnAXT7iSgtxEZiFwqdkKtxLCXDssTOJNatbFsnq0Jow324y41jGfAE2n4Lf5qY2FsZUPf9XQ==",
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.7.tgz",
+ "integrity": "sha512-rOa9pJA0ZfPPSI4SkDX41CsBcvxs6BmxgzFEElZWZo/uBBqtnr8ZL4oe5HySeZKEAHRH86XDqfxFISkhV76m5g==",
"dev": true,
"dependencies": {
- "@ariakit/core": "0.3.8",
+ "@ariakit/core": "0.3.10",
"@testing-library/dom": "^8.0.0 || ^9.0.0"
},
"peerDependencies": {
@@ -1650,12 +1685,6 @@
}
}
},
- "node_modules/@ariakit/test/node_modules/@ariakit/core": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz",
- "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==",
- "dev": true
- },
"node_modules/@aw-web-design/x-default-browser": {
"version": "1.4.126",
"resolved": "https://registry.npmjs.org/@aw-web-design/x-default-browser/-/x-default-browser-1.4.126.tgz",
@@ -54244,7 +54273,7 @@
"version": "25.14.0",
"license": "GPL-2.0-or-later",
"dependencies": {
- "@ariakit/react": "^0.3.10",
+ "@ariakit/react": "^0.3.12",
"@babel/runtime": "^7.16.0",
"@emotion/cache": "^11.7.1",
"@emotion/css": "^11.7.1",
@@ -54303,41 +54332,6 @@
"react-dom": "^18.0.0"
}
},
- "packages/components/node_modules/@ariakit/core": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz",
- "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q=="
- },
- "packages/components/node_modules/@ariakit/react": {
- "version": "0.3.10",
- "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.10.tgz",
- "integrity": "sha512-XRY69IOm8Oy+HSPoaspcVLAhLo3ToLhhJKSLK1voTAZtSzu5kUeUf4nUPxTzYFsvirKORZgOLAeNwuo1gPr61g==",
- "dependencies": {
- "@ariakit/react-core": "0.3.10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/ariakit"
- },
- "peerDependencies": {
- "react": "^17.0.0 || ^18.0.0",
- "react-dom": "^17.0.0 || ^18.0.0"
- }
- },
- "packages/components/node_modules/@ariakit/react-core": {
- "version": "0.3.10",
- "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.10.tgz",
- "integrity": "sha512-CzSffcNlOyS2xuy21UB6fgJXi5LriJ9JrTSJzcgJmE+P9/WfQlplJC3L75d8O2yKgaGPeFnQ0hhDA6ItsI98eQ==",
- "dependencies": {
- "@ariakit/core": "0.3.8",
- "@floating-ui/dom": "^1.0.0",
- "use-sync-external-store": "^1.2.0"
- },
- "peerDependencies": {
- "react": "^17.0.0 || ^18.0.0",
- "react-dom": "^17.0.0 || ^18.0.0"
- }
- },
"packages/components/node_modules/@floating-ui/react-dom": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz",
@@ -57392,22 +57386,37 @@
}
}
},
+ "@ariakit/core": {
+ "version": "0.3.10",
+ "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.10.tgz",
+ "integrity": "sha512-AcN+GSoVXuUOzKx5d3xPL3YsEHevh4PIO6QIt/mg/nRX1XQ6cvxQEiAjO/BJQm+/MVl7/VbuGBoTFjr0tPU6NQ=="
+ },
+ "@ariakit/react": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.12.tgz",
+ "integrity": "sha512-HxKMZZhWSkwwS/Sh9OdWyuNKQ2tjDAIQIy2KVI7IRa8ZQ6ze/4g3YLUHbfCxO7oDupXHfXaeZ4hWx8lP7l1U/g==",
+ "requires": {
+ "@ariakit/react-core": "0.3.12"
+ }
+ },
+ "@ariakit/react-core": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.12.tgz",
+ "integrity": "sha512-w6P1A7TYb1fKUe9QbwaoTOWofl13g7TEuXdV4JyefJCQL1e9HQdEw9UL67I8aXRo8/cFHH94/z0N37t8hw5Ogg==",
+ "requires": {
+ "@ariakit/core": "0.3.10",
+ "@floating-ui/dom": "^1.0.0",
+ "use-sync-external-store": "^1.2.0"
+ }
+ },
"@ariakit/test": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.5.tgz",
- "integrity": "sha512-7UCQBnJZ88JptkEnAXT7iSgtxEZiFwqdkKtxLCXDssTOJNatbFsnq0Jow324y41jGfAE2n4Lf5qY2FsZUPf9XQ==",
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.7.tgz",
+ "integrity": "sha512-rOa9pJA0ZfPPSI4SkDX41CsBcvxs6BmxgzFEElZWZo/uBBqtnr8ZL4oe5HySeZKEAHRH86XDqfxFISkhV76m5g==",
"dev": true,
"requires": {
- "@ariakit/core": "0.3.8",
+ "@ariakit/core": "0.3.10",
"@testing-library/dom": "^8.0.0 || ^9.0.0"
- },
- "dependencies": {
- "@ariakit/core": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz",
- "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==",
- "dev": true
- }
}
},
"@aw-web-design/x-default-browser": {
@@ -69363,7 +69372,7 @@
"@wordpress/components": {
"version": "file:packages/components",
"requires": {
- "@ariakit/react": "^0.3.10",
+ "@ariakit/react": "^0.3.12",
"@babel/runtime": "^7.16.0",
"@emotion/cache": "^11.7.1",
"@emotion/css": "^11.7.1",
@@ -69415,29 +69424,6 @@
"valtio": "1.7.0"
},
"dependencies": {
- "@ariakit/core": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz",
- "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q=="
- },
- "@ariakit/react": {
- "version": "0.3.10",
- "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.10.tgz",
- "integrity": "sha512-XRY69IOm8Oy+HSPoaspcVLAhLo3ToLhhJKSLK1voTAZtSzu5kUeUf4nUPxTzYFsvirKORZgOLAeNwuo1gPr61g==",
- "requires": {
- "@ariakit/react-core": "0.3.10"
- }
- },
- "@ariakit/react-core": {
- "version": "0.3.10",
- "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.10.tgz",
- "integrity": "sha512-CzSffcNlOyS2xuy21UB6fgJXi5LriJ9JrTSJzcgJmE+P9/WfQlplJC3L75d8O2yKgaGPeFnQ0hhDA6ItsI98eQ==",
- "requires": {
- "@ariakit/core": "0.3.8",
- "@floating-ui/dom": "^1.0.0",
- "use-sync-external-store": "^1.2.0"
- }
- },
"@floating-ui/react-dom": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz",
diff --git a/package.json b/package.json
index e98ad7cb1587f5..f2f239762de976 100644
--- a/package.json
+++ b/package.json
@@ -98,7 +98,7 @@
"devDependencies": {
"@actions/core": "1.9.1",
"@actions/github": "5.0.0",
- "@ariakit/test": "^0.3.5",
+ "@ariakit/test": "^0.3.7",
"@babel/core": "7.16.0",
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
"@babel/plugin-syntax-jsx": "7.16.0",
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 8805736c2e4409..c7c1a515a64cec 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -24,6 +24,7 @@
### Enhancements
- Update `ariakit` to version `0.3.10` ([#57325](https://github.com/WordPress/gutenberg/pull/57325)).
+- Update `@ariakit/react` to version `0.3.12` and @ariakit/test to version `0.3.7` ([#57547](https://github.com/WordPress/gutenberg/pull/57547)).
- `DropdownMenuV2`: do not collapse suffix width ([#57238](https://github.com/WordPress/gutenberg/pull/57238)).
- `DateTimePicker`: Adjustment of the dot position on DayButton and expansion of the button area. ([#55502](https://github.com/WordPress/gutenberg/pull/55502)).
- `Modal`: Improve application of body class names ([#55430](https://github.com/WordPress/gutenberg/pull/55430)).
diff --git a/packages/components/package.json b/packages/components/package.json
index 885c1e455fea40..cd440998b93230 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -30,7 +30,7 @@
],
"types": "build-types",
"dependencies": {
- "@ariakit/react": "^0.3.10",
+ "@ariakit/react": "^0.3.12",
"@babel/runtime": "^7.16.0",
"@emotion/cache": "^11.7.1",
"@emotion/css": "^11.7.1",
diff --git a/packages/components/src/alignment-matrix-control/test/index.tsx b/packages/components/src/alignment-matrix-control/test/index.tsx
index 6836bc7e45f95c..a820b69b26c8ff 100644
--- a/packages/components/src/alignment-matrix-control/test/index.tsx
+++ b/packages/components/src/alignment-matrix-control/test/index.tsx
@@ -2,7 +2,7 @@
* External dependencies
*/
import { render, screen, waitFor, within } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
+import { press, click } from '@ariakit/test';
/**
* Internal dependencies
@@ -37,11 +37,9 @@ describe( 'AlignmentMatrixControl', () => {
} );
it( 'should be centered by default', async () => {
- const user = userEvent.setup();
-
await renderAndInitCompositeStore( );
- await user.tab();
+ await press.Tab();
expect( getCell( 'center center' ) ).toHaveFocus();
} );
@@ -60,7 +58,6 @@ describe( 'AlignmentMatrixControl', () => {
'bottom center',
'bottom right',
] )( '%s', async ( alignment ) => {
- const user = userEvent.setup();
const spy = jest.fn();
await renderAndInitCompositeStore(
@@ -72,14 +69,13 @@ describe( 'AlignmentMatrixControl', () => {
const cell = getCell( alignment );
- await user.click( cell );
+ await click( cell );
expect( cell ).toHaveFocus();
expect( spy ).toHaveBeenCalledWith( alignment );
} );
it( 'unless already focused', async () => {
- const user = userEvent.setup();
const spy = jest.fn();
await renderAndInitCompositeStore(
@@ -91,7 +87,7 @@ describe( 'AlignmentMatrixControl', () => {
const cell = getCell( 'center center' );
- await user.click( cell );
+ await click( cell );
expect( cell ).toHaveFocus();
expect( spy ).not.toHaveBeenCalled();
@@ -106,16 +102,15 @@ describe( 'AlignmentMatrixControl', () => {
[ 'ArrowLeft', 'center left' ],
[ 'ArrowDown', 'bottom center' ],
[ 'ArrowRight', 'center right' ],
- ] )( '%s', async ( keyRef, cellRef ) => {
- const user = userEvent.setup();
+ ] as const )( '%s', async ( keyRef, cellRef ) => {
const spy = jest.fn();
await renderAndInitCompositeStore(
);
- await user.tab();
- await user.keyboard( `[${ keyRef }]` );
+ await press.Tab();
+ await press[ keyRef ]();
expect( getCell( cellRef ) ).toHaveFocus();
expect( spy ).toHaveBeenCalledWith( cellRef );
@@ -128,8 +123,7 @@ describe( 'AlignmentMatrixControl', () => {
[ 'ArrowLeft', 'top left' ],
[ 'ArrowDown', 'bottom right' ],
[ 'ArrowRight', 'bottom right' ],
- ] )( '%s', async ( keyRef, cellRef ) => {
- const user = userEvent.setup();
+ ] as const )( '%s', async ( keyRef, cellRef ) => {
const spy = jest.fn();
await renderAndInitCompositeStore(
@@ -137,8 +131,8 @@ describe( 'AlignmentMatrixControl', () => {
);
const cell = getCell( cellRef );
- await user.click( cell );
- await user.keyboard( `[${ keyRef }]` );
+ await click( cell );
+ await press[ keyRef ]();
expect( cell ).toHaveFocus();
expect( spy ).toHaveBeenCalledWith( cellRef );
diff --git a/packages/components/src/toggle-group-control/test/index.tsx b/packages/components/src/toggle-group-control/test/index.tsx
index b54b5764d4e0ff..99a2dd8a00421c 100644
--- a/packages/components/src/toggle-group-control/test/index.tsx
+++ b/packages/components/src/toggle-group-control/test/index.tsx
@@ -2,7 +2,7 @@
* External dependencies
*/
import { render, screen, waitFor } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
+import { press, click, hover, sleep } from '@ariakit/test';
/**
* WordPress dependencies
@@ -19,8 +19,13 @@ import {
ToggleGroupControlOption,
ToggleGroupControlOptionIcon,
} from '../index';
+import { TOOLTIP_DELAY } from '../../tooltip';
import type { ToggleGroupControlProps } from '../types';
-import cleanupTooltip from '../../tooltip/test/utils';
+
+const hoverOutside = async () => {
+ await hover( document.body );
+ await hover( document.body, { clientX: 10, clientY: 10 } );
+};
const ControlledToggleGroupControl = ( {
value: valueProp,
@@ -113,7 +118,6 @@ describe.each( [
} );
} );
it( 'should call onChange with proper value', async () => {
- const user = userEvent.setup();
const mockOnChange = jest.fn();
render(
@@ -126,13 +130,12 @@ describe.each( [
);
- await user.click( screen.getByRole( 'radio', { name: 'R' } ) );
+ await click( screen.getByRole( 'radio', { name: 'R' } ) );
expect( mockOnChange ).toHaveBeenCalledWith( 'rigas' );
} );
it( 'should render tooltip where `showTooltip` === `true`', async () => {
- const user = userEvent.setup();
render(
{ optionsWithTooltip }
@@ -143,19 +146,26 @@ describe.each( [
'Click for Delicious Gnocchi'
);
- await user.hover( firstRadio );
+ await hover( firstRadio );
- const tooltip = await screen.findByText(
- 'Click for Delicious Gnocchi'
- );
+ const tooltip = await screen.findByRole( 'tooltip', {
+ name: 'Click for Delicious Gnocchi',
+ } );
await waitFor( () => expect( tooltip ).toBeVisible() );
- await cleanupTooltip( user );
+ // hover outside of radio
+ await hoverOutside();
+
+ // Tooltip should hide
+ expect(
+ screen.queryByRole( 'tooltip', {
+ name: 'Click for Delicious Gnocchi',
+ } )
+ ).not.toBeInTheDocument();
} );
it( 'should not render tooltip', async () => {
- const user = userEvent.setup();
render(
{ optionsWithTooltip }
@@ -166,19 +176,24 @@ describe.each( [
'Click for Sumptuous Caponata'
);
- await user.hover( secondRadio );
+ await hover( secondRadio );
- await waitFor( () =>
- expect(
- screen.queryByText( 'Click for Sumptuous Caponata' )
- ).not.toBeInTheDocument()
- );
+ // Tooltip shouldn't show
+ expect(
+ screen.queryByText( 'Click for Sumptuous Caponata' )
+ ).not.toBeInTheDocument();
+
+ // Advance time by default delay
+ await sleep( TOOLTIP_DELAY );
+
+ // Tooltip shouldn't show.
+ expect(
+ screen.queryByText( 'Click for Sumptuous Caponata' )
+ ).not.toBeInTheDocument();
} );
if ( mode === 'controlled' ) {
it( 'should reset values correctly', async () => {
- const user = userEvent.setup();
-
render(
{ options }
@@ -188,25 +203,23 @@ describe.each( [
const rigasOption = screen.getByRole( 'radio', { name: 'R' } );
const jackOption = screen.getByRole( 'radio', { name: 'J' } );
- await user.click( rigasOption );
+ await click( rigasOption );
expect( jackOption ).not.toBeChecked();
expect( rigasOption ).toBeChecked();
- await user.keyboard( '[ArrowRight]' );
+ await press.ArrowRight();
expect( rigasOption ).not.toBeChecked();
expect( jackOption ).toBeChecked();
- await user.click( screen.getByRole( 'button', { name: 'Reset' } ) );
+ await click( screen.getByRole( 'button', { name: 'Reset' } ) );
expect( rigasOption ).not.toBeChecked();
expect( jackOption ).not.toBeChecked();
} );
it( 'should update correctly when triggered by external updates', async () => {
- const user = userEvent.setup();
-
render(
{
it( 'should not be deselectable', async () => {
const mockOnChange = jest.fn();
- const user = userEvent.setup();
render(
{
- const user = userEvent.setup();
-
render(
-
- { options }
-
+ <>
+
+ { options }
+
+
+ >
);
const rigas = screen.getByRole( 'radio', {
name: 'R',
} );
- await user.tab();
+ await press.Tab();
expect( rigas ).toHaveFocus();
- await user.tab();
+ await press.Tab();
+ // When in controlled mode, there is an additional "Reset" button.
const expectedFocusTarget =
mode === 'uncontrolled'
- ? rigas.ownerDocument.body
+ ? screen.getByRole( 'button', {
+ name: 'After ToggleGroupControl',
+ } )
: screen.getByRole( 'button', { name: 'Reset' } );
expect( expectedFocusTarget ).toHaveFocus();
@@ -301,7 +317,6 @@ describe.each( [
describe( 'isDeselectable = true', () => {
it( 'should be deselectable', async () => {
const mockOnChange = jest.fn();
- const user = userEvent.setup();
render(
);
- await user.click(
+ await click(
screen.getByRole( 'button', {
name: 'R',
pressed: true,
@@ -323,7 +338,7 @@ describe.each( [
expect( mockOnChange ).toHaveBeenCalledTimes( 1 );
expect( mockOnChange ).toHaveBeenLastCalledWith( undefined );
- await user.click(
+ await click(
screen.getByRole( 'button', {
name: 'R',
pressed: false,
@@ -334,15 +349,13 @@ describe.each( [
} );
it( 'should tab to the next option button', async () => {
- const user = userEvent.setup();
-
render(
{ options }
);
- await user.tab();
+ await press.Tab();
expect(
screen.getByRole( 'button', {
name: 'R',
@@ -350,7 +363,7 @@ describe.each( [
} )
).toHaveFocus();
- await user.tab();
+ await press.Tab();
expect(
screen.getByRole( 'button', {
name: 'J',
@@ -359,7 +372,7 @@ describe.each( [
).toHaveFocus();
// Focus should not move with arrow keys
- await user.keyboard( '{ArrowLeft}' );
+ await press.ArrowLeft();
expect(
screen.getByRole( 'button', {
name: 'J',
From 6693377d080d8c581f279bfd7521f5b2bcf02149 Mon Sep 17 00:00:00 2001
From: Gutenberg Repository Automation
Date: Wed, 10 Jan 2024 14:34:24 +0000
Subject: [PATCH 48/51] Bump plugin version to 17.5.0-rc.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 35e416006bea50..9559f838608da9 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.4.1
+ * Version: 17.5.0-rc.1
* Author: Gutenberg Team
* Text Domain: gutenberg
*
diff --git a/package-lock.json b/package-lock.json
index 02de0a60ff4c93..96a14ec8eeb50e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "gutenberg",
- "version": "17.4.1",
+ "version": "17.5.0-rc.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "gutenberg",
- "version": "17.4.1",
+ "version": "17.5.0-rc.1",
"hasInstallScript": true,
"license": "GPL-2.0-or-later",
"dependencies": {
diff --git a/package.json b/package.json
index f2f239762de976..684f35d408d3c2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "17.4.1",
+ "version": "17.5.0-rc.1",
"private": true,
"description": "A new WordPress editor experience.",
"author": "The WordPress Contributors",
From e93f250746b14acaed14190cdb552960a6c490ce Mon Sep 17 00:00:00 2001
From: Gerardo Pacheco
Date: Wed, 10 Jan 2024 15:49:08 +0100
Subject: [PATCH 49/51] [Mobile] - Fix missing custom gradient indicator in the
color palette (#57605)
* Mobile - Fix missing Custom indicator for custom gradients
* Pass enableCustomColor in Cover block as true
* Fix typo
* Update Changelog
* Fix condition
* Update test to use the openBlockSettings helper
* Simplify condition by adding optional chaining
* Use flatMap
---
.../test/__snapshots__/edit.native.js.snap | 6 +++
.../src/buttons/test/edit.native.js | 49 +++++++++++++++++++
.../block-library/src/cover/edit.native.js | 1 +
.../src/color-palette/index.native.js | 25 +++++++---
.../color-settings/palette.screen.native.js | 12 +++--
packages/react-native-editor/CHANGELOG.md | 1 +
6 files changed, 82 insertions(+), 12 deletions(-)
diff --git a/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap
index 1a55c807225d9d..f04eacee4b91c1 100644
--- a/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap
+++ b/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap
@@ -6,6 +6,12 @@ exports[`Buttons block color customization sets a background color 1`] = `
"
`;
+exports[`Buttons block color customization sets a custom gradient background color 1`] = `
+"
+
+"
+`;
+
exports[`Buttons block color customization sets a gradient background color 1`] = `
"
diff --git a/packages/block-library/src/buttons/test/edit.native.js b/packages/block-library/src/buttons/test/edit.native.js
index f393a31c7330ad..af2ffe762e6a36 100644
--- a/packages/block-library/src/buttons/test/edit.native.js
+++ b/packages/block-library/src/buttons/test/edit.native.js
@@ -10,6 +10,7 @@ import {
initializeEditor,
triggerBlockListLayout,
typeInRichText,
+ openBlockSettings,
waitFor,
} from 'test/helpers';
@@ -391,5 +392,53 @@ describe( 'Buttons block', () => {
// Assert
expect( getEditorHtml() ).toMatchSnapshot();
} );
+
+ it( 'sets a custom gradient background color', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Buttons' );
+
+ // Act
+ const buttonsBlock = getBlock( screen, 'Buttons' );
+ fireEvent.press( buttonsBlock );
+
+ // Trigger onLayout for the list
+ await triggerBlockListLayout( buttonsBlock );
+
+ const buttonBlock = await getBlock( screen, 'Button' );
+ fireEvent.press( buttonBlock );
+
+ // Open Block Settings.
+ await openBlockSettings( screen );
+
+ // Open Text color settings
+ fireEvent.press( screen.getByLabelText( 'Background, Default' ) );
+
+ // Tap on the gradient segment
+ fireEvent.press( screen.getByLabelText( 'Gradient' ) );
+
+ // Tap one gradient color
+ fireEvent.press(
+ screen.getByLabelText( 'Light green cyan to vivid green cyan' )
+ );
+
+ // Tap on Customize Gradient
+ fireEvent.press( screen.getByLabelText( /Customize Gradient/ ) );
+
+ // Change the current angle
+ fireEvent.press( screen.getByText( '135', { hidden: true } ) );
+ const angleTextInput = screen.getByDisplayValue( '135', {
+ hidden: true,
+ } );
+ fireEvent.changeText( angleTextInput, '200' );
+
+ // Go back to the settings list.
+ fireEvent.press( await screen.findByLabelText( 'Go back' ) );
+
+ // Assert
+ const customButton = await screen.findByText( 'CUSTOM' );
+ expect( customButton ).toBeVisible();
+ expect( getEditorHtml() ).toMatchSnapshot();
+ } );
} );
} );
diff --git a/packages/block-library/src/cover/edit.native.js b/packages/block-library/src/cover/edit.native.js
index 81ff43128b1a35..989c5ec3a0d332 100644
--- a/packages/block-library/src/cover/edit.native.js
+++ b/packages/block-library/src/cover/edit.native.js
@@ -538,6 +538,7 @@ const Cover = ( {
{ ( { shouldEnableBottomSheetScroll } ) => (
color )
),
];
- const mergedColors = [
+ const mergedGradients = [
+ ...new Set(
+ ( defaultSettings.gradients ?? [] ).map(
+ ( { gradient } ) => gradient
+ )
+ ),
+ ];
+ const allAvailableColors = [
...new Set(
( defaultSettings.allColors ?? [] ).map( ( { color } ) => color )
),
];
- const defaultGradientColors = [
+ const allAvailableGradients = [
...new Set(
- ( defaultSettings.gradients ?? [] ).map(
+ ( defaultSettings.allGradients ?? [] ).map(
( { gradient } ) => gradient
)
),
];
- const colors = isGradientSegment ? defaultGradientColors : defaultColors;
+
+ const colors = isGradientSegment ? mergedGradients : mergedColors;
+ const allColors = isGradientSegment
+ ? allAvailableGradients
+ : allAvailableColors;
const customIndicatorColor = isGradientSegment
? activeColor
@@ -110,7 +121,7 @@ function ColorPalette( {
function isSelectedCustom() {
const isWithinColors =
- activeColor && mergedColors && mergedColors.includes( activeColor );
+ activeColor && allColors?.includes( activeColor );
if ( enableCustomColor && activeColor ) {
if ( isGradientSegment ) {
return isGradientColor && ! isWithinColors;
diff --git a/packages/components/src/mobile/color-settings/palette.screen.native.js b/packages/components/src/mobile/color-settings/palette.screen.native.js
index bc7187fd092b8c..fcf03f9ecd4483 100644
--- a/packages/components/src/mobile/color-settings/palette.screen.native.js
+++ b/packages/components/src/mobile/color-settings/palette.screen.native.js
@@ -29,7 +29,6 @@ import { colorsUtils } from './utils';
import styles from './style.scss';
const HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 };
-const THEME_PALETTE_NAME = 'Theme';
const PaletteScreen = () => {
const route = useRoute();
@@ -48,7 +47,6 @@ const PaletteScreen = () => {
const [ currentValue, setCurrentValue ] = useState( colorValue );
const isGradientColor = isGradient( currentValue );
const selectedSegmentIndex = isGradientColor ? 1 : 0;
- const allAvailableColors = useMobileGlobalStylesColors();
const [ currentSegment, setCurrentSegment ] = useState(
segments[ selectedSegmentIndex ]
@@ -57,6 +55,10 @@ const PaletteScreen = () => {
const currentSegmentColors = ! isGradientSegment
? defaultSettings.colors
: defaultSettings.gradients;
+ const allAvailableColors = useMobileGlobalStylesColors();
+ const allAvailableGradients = currentSegmentColors
+ .flatMap( ( { gradients } ) => gradients )
+ .filter( Boolean );
const horizontalSeparatorStyle = usePreferredColorSchemeStyle(
styles.horizontalSeparator,
@@ -184,10 +186,10 @@ const PaletteScreen = () => {
colors: palette.colors,
gradients: palette.gradients,
allColors: allAvailableColors,
+ allGradients: allAvailableGradients,
};
- const enableCustomColor =
- ! isGradientSegment &&
- palette.name === THEME_PALETTE_NAME;
+ // Limit to show the custom indicator to the first available palette
+ const enableCustomColor = paletteKey === 0;
return (
Date: Wed, 10 Jan 2024 09:58:32 -0500
Subject: [PATCH 50/51] Font Library: filter fonts upload directory (#57697)
* add global configuration variables for font directory
* add multi-site based directory path for fonts
* add docblock for get_multi_site_font_sub_dir
* Rename function for accuracy.
* Use filter instead of constant to determine where fonts are uploaded.
* Format php.
* simplify the filters code and output the same array as upload_filter does
* remove tests no longer used
* add a test case for the filter
* rename function. Replaces the misleading 'subdir' term by 'dir'.
---------
Co-authored-by: madhusudhand
Co-authored-by: Matias Benedetto
---
.../font-library/class-wp-font-family.php | 4 +-
.../font-library/class-wp-font-library.php | 76 +++++++++++++------
.../font-library/wpFontLibrary/fontsDir.php | 70 +++++++++++++++++
.../wpFontLibrary/getFontsDir.php | 18 -----
.../wpFontLibrary/setUploadDir.php | 32 --------
5 files changed, 126 insertions(+), 74 deletions(-)
create mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php
delete mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php
delete mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php
diff --git a/lib/experimental/fonts/font-library/class-wp-font-family.php b/lib/experimental/fonts/font-library/class-wp-font-family.php
index a4204dfe1fa2c7..e47cf0afdac1de 100644
--- a/lib/experimental/fonts/font-library/class-wp-font-family.php
+++ b/lib/experimental/fonts/font-library/class-wp-font-family.php
@@ -599,9 +599,9 @@ private function create_or_update_font_post() {
*/
public function install( $files = null ) {
add_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) );
- add_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) );
+ add_filter( 'upload_dir', array( 'WP_Font_Library', 'fonts_dir' ) );
$were_assets_written = $this->download_or_move_font_faces( $files );
- remove_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) );
+ remove_filter( 'upload_dir', array( 'WP_Font_Library', 'fonts_dir' ) );
remove_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) );
if ( ! $were_assets_written ) {
diff --git a/lib/experimental/fonts/font-library/class-wp-font-library.php b/lib/experimental/fonts/font-library/class-wp-font-library.php
index 59ec5e93fa787e..99de81e0bd74a3 100644
--- a/lib/experimental/fonts/font-library/class-wp-font-library.php
+++ b/lib/experimental/fonts/font-library/class-wp-font-library.php
@@ -141,40 +141,72 @@ public static function get_font_collection( $id ) {
}
/**
- * Gets the upload directory for fonts.
+ * Returns an array containing the current fonts upload directory's path and URL.
*
* @since 6.5.0
*
- * @return string Path of the upload directory for fonts.
+ * @param array $defaults {
+ * Array of information about the upload directory.
+ *
+ * @type string $path Base directory and subdirectory or full path to the fonts upload directory.
+ * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory.
+ * @type string $subdir Subdirectory
+ * @type string $basedir Path without subdir.
+ * @type string $baseurl URL path without subdir.
+ * @type string|false $error False or error message.
+ * }
+ *
+ * @return array $defaults {
+ * Array of information about the upload directory.
+ *
+ * @type string $path Base directory and subdirectory or full path to the fonts upload directory.
+ * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory.
+ * @type string $subdir Subdirectory
+ * @type string $basedir Path without subdir.
+ * @type string $baseurl URL path without subdir.
+ * @type string|false $error False or error message.
+ * }
*/
- public static function get_fonts_dir() {
- return path_join( WP_CONTENT_DIR, 'fonts' );
+ public static function fonts_dir( $defaults = array() ) {
+ $site_path = self::get_multi_site_dir();
+
+ // Sets the defaults.
+ $defaults['path'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path;
+ $defaults['url'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path;
+ $defaults['subdir'] = '';
+ $defaults['basedir'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path;
+ $defaults['baseurl'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path;
+ $defaults['error'] = false;
+
+ // Filters the fonts directory data.
+ return apply_filters( 'fonts_dir', $defaults );
}
/**
- * Sets the upload directory for fonts.
+ * Gets the Site dir for fonts, using the blog ID if multi-site, empty otherwise.
*
* @since 6.5.0
*
- * @param array $defaults {
- * Default upload directory.
+ * @return string Site dir path.
+ */
+ private static function get_multi_site_dir() {
+ $font_sub_dir = '';
+ if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) {
+ $font_sub_dir = '/sites/' . get_current_blog_id();
+ }
+ return $font_sub_dir;
+ }
+
+ /**
+ * Gets the upload directory for fonts.
*
- * @type string $path Path to the directory.
- * @type string $url URL for the directory.
- * @type string $subdir Sub-directory of the directory.
- * @type string $basedir Base directory.
- * @type string $baseurl Base URL.
- * }
- * @return array Modified upload directory.
+ * @since 6.5.0
+ *
+ * @return string Path of the upload directory for fonts.
*/
- public static function set_upload_dir( $defaults ) {
- $defaults['basedir'] = WP_CONTENT_DIR;
- $defaults['baseurl'] = content_url();
- $defaults['subdir'] = '/fonts';
- $defaults['path'] = self::get_fonts_dir();
- $defaults['url'] = $defaults['baseurl'] . '/fonts';
-
- return $defaults;
+ public static function get_fonts_dir() {
+ $fonts_dir_settings = self::fonts_dir();
+ return $fonts_dir_settings['path'];
}
/**
diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php
new file mode 100644
index 00000000000000..9926bb74090888
--- /dev/null
+++ b/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php
@@ -0,0 +1,70 @@
+dir_defaults = array(
+ 'path' => path_join( WP_CONTENT_DIR, 'fonts' ),
+ 'url' => content_url( 'fonts' ),
+ 'subdir' => '',
+ 'basedir' => path_join( WP_CONTENT_DIR, 'fonts' ),
+ 'baseurl' => content_url( 'fonts' ),
+ 'error' => false,
+ );
+ }
+
+ public function test_fonts_dir() {
+ $fonts_dir = WP_Font_Library::fonts_dir();
+ $this->assertEquals( $fonts_dir, $this->dir_defaults );
+ }
+
+ public function test_fonts_dir_with_filter() {
+ // Define a callback function to pass to the filter.
+ function set_new_values( $defaults ) {
+ $defaults['path'] = '/custom-path/fonts/my-custom-subdir';
+ $defaults['url'] = 'http://example.com/custom-path/fonts/my-custom-subdir';
+ $defaults['subdir'] = 'my-custom-subdir';
+ $defaults['basedir'] = '/custom-path/fonts';
+ $defaults['baseurl'] = 'http://example.com/custom-path/fonts';
+ $defaults['error'] = false;
+ return $defaults;
+ }
+
+ // Add the filter.
+ add_filter( 'fonts_dir', 'set_new_values' );
+
+ // Gets the fonts dir.
+ $fonts_dir = WP_Font_Library::fonts_dir();
+
+ $expected = array(
+ 'path' => '/custom-path/fonts/my-custom-subdir',
+ 'url' => 'http://example.com/custom-path/fonts/my-custom-subdir',
+ 'subdir' => 'my-custom-subdir',
+ 'basedir' => '/custom-path/fonts',
+ 'baseurl' => 'http://example.com/custom-path/fonts',
+ 'error' => false,
+ );
+
+ $this->assertEquals( $fonts_dir, $expected, 'The fonts_dir() method should return the expected values.' );
+
+ // Remove the filter.
+ remove_filter( 'fonts_dir', 'set_new_values' );
+
+ // Gets the fonts dir.
+ $fonts_dir = WP_Font_Library::fonts_dir();
+
+ $this->assertEquals( $fonts_dir, $this->dir_defaults, 'The fonts_dir() method should return the default values.' );
+ }
+}
diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php
deleted file mode 100644
index 1200200d7160b2..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php
+++ /dev/null
@@ -1,18 +0,0 @@
-assertStringEndsWith( '/wp-content/fonts', WP_Font_Library::get_fonts_dir() );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php
deleted file mode 100644
index 29d481d8afd6bc..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php
+++ /dev/null
@@ -1,32 +0,0 @@
- '/abc',
- 'basedir' => '/any/path',
- 'baseurl' => 'http://example.com/an/arbitrary/url',
- 'path' => '/any/path/abc',
- 'url' => 'http://example.com/an/arbitrary/url/abc',
- );
- $expected = array(
- 'subdir' => '/fonts',
- 'basedir' => WP_CONTENT_DIR,
- 'baseurl' => content_url(),
- 'path' => path_join( WP_CONTENT_DIR, 'fonts' ),
- 'url' => content_url() . '/fonts',
- );
- $this->assertSame( $expected, WP_Font_Library::set_upload_dir( $defaults ) );
- }
-}
From 714e6b107670e5ee4d92b5126f99c54305fee03b Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Wed, 10 Jan 2024 16:08:05 +0100
Subject: [PATCH 51/51] Tooltip: no-op when nested inside another Tooltip
component (#57202)
* Tooltip: no-op when nested inside another Tooltip component
* Fix Storybook control type
* Use internal components context system
* Add Storybook example
* Snapshots
* Add nested unit test
* CHANGELOG
* Avoid ESLint disable
* Keep nested tooltip storybook example
* isNestedInParentTooltip => isNestedInTooltip
* toBeInTheDocument => toBeVisible
* "inner" => "nested"
* Forward rest props to the tooltip instead of the ancor, update snapshots
* Format warning message
* Add paragraph in docs
* Complete comment
---
packages/components/CHANGELOG.md | 1 +
packages/components/src/tooltip/README.md | 4 ++
packages/components/src/tooltip/index.tsx | 56 ++++++++++++++++---
.../src/tooltip/stories/index.story.tsx | 19 ++++++-
.../components/src/tooltip/test/index.tsx | 46 +++++++++++++++
packages/components/src/tooltip/types.ts | 4 ++
6 files changed, 120 insertions(+), 10 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index c7c1a515a64cec..a8dd57900cfae1 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -32,6 +32,7 @@
- `InputControl`, `NumberControl`, `UnitControl`, `SelectControl`, `TreeSelect`: Add `compact` size variant ([#57398](https://github.com/WordPress/gutenberg/pull/57398)).
- `ToggleGroupControl`: Update button size in large variant to be 32px ([#57338](https://github.com/WordPress/gutenberg/pull/57338)).
- `Tooltip`: improve unit tests ([#57345](https://github.com/WordPress/gutenberg/pull/57345)).
+- `Tooltip`: no-op when nested inside other `Tooltip` components ([#57202](https://github.com/WordPress/gutenberg/pull/57202)).
### Experimental
diff --git a/packages/components/src/tooltip/README.md b/packages/components/src/tooltip/README.md
index 9b214e8fc6b00e..ef2cd35d25543e 100644
--- a/packages/components/src/tooltip/README.md
+++ b/packages/components/src/tooltip/README.md
@@ -16,6 +16,10 @@ const MyTooltip = () => (
);
```
+### Nested tooltips
+
+In case one or more `Tooltip` components are rendered inside another `Tooltip` component, only the tooltip associated to the outermost `Tooltip` component will be rendered in the browser and shown to the user appropriately. The rest of the nested `Tooltip` components will simply no-op and pass-through their anchor.
+
## Props
The component accepts the following props:
diff --git a/packages/components/src/tooltip/index.tsx b/packages/components/src/tooltip/index.tsx
index 817d6d18812ee4..1e652d9a42dbb4 100644
--- a/packages/components/src/tooltip/index.tsx
+++ b/packages/components/src/tooltip/index.tsx
@@ -8,22 +8,37 @@ import * as Ariakit from '@ariakit/react';
* WordPress dependencies
*/
import { useInstanceId } from '@wordpress/compose';
-import { Children } from '@wordpress/element';
+import { Children, cloneElement } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
-import type { TooltipProps } from './types';
+import type { TooltipProps, TooltipInternalContext } from './types';
import Shortcut from '../shortcut';
import { positionToPlacement } from '../popover/utils';
+import {
+ contextConnect,
+ useContextSystem,
+ ContextSystemProvider,
+} from '../context';
+import type { WordPressComponentProps } from '../context';
/**
* Time over anchor to wait before showing tooltip
*/
export const TOOLTIP_DELAY = 700;
-function Tooltip( props: TooltipProps ) {
+const CONTEXT_VALUE = {
+ Tooltip: {
+ isNestedInTooltip: true,
+ },
+};
+
+function UnconnectedTooltip(
+ props: WordPressComponentProps< TooltipProps, 'div', false >,
+ ref: React.ForwardedRef< any >
+) {
const {
children,
delay = TOOLTIP_DELAY,
@@ -32,7 +47,15 @@ function Tooltip( props: TooltipProps ) {
position,
shortcut,
text,
- } = props;
+
+ // From Internal Context system
+ isNestedInTooltip,
+
+ ...restProps
+ } = useContextSystem< typeof props & TooltipInternalContext >(
+ props,
+ 'Tooltip'
+ );
const baseId = useInstanceId( Tooltip, 'tooltip' );
const describedById = text || shortcut ? baseId : undefined;
@@ -43,7 +66,7 @@ function Tooltip( props: TooltipProps ) {
if ( 'development' === process.env.NODE_ENV ) {
// eslint-disable-next-line no-console
console.error(
- 'Tooltip should be called with only a single child element.'
+ 'wp-components.Tooltip should be called with only a single child element.'
);
}
}
@@ -64,24 +87,37 @@ function Tooltip( props: TooltipProps ) {
}
computedPlacement = computedPlacement || 'bottom';
- const tooltipStore = Ariakit.useTooltipStore( {
+ // Removing the `Ariakit` namespace from the hook name allows ESLint to
+ // properly identify the hook, and apply the correct linting rules.
+ const useAriakitTooltipStore = Ariakit.useTooltipStore;
+ const tooltipStore = useAriakitTooltipStore( {
placement: computedPlacement,
showTimeout: delay,
} );
+ if ( isNestedInTooltip ) {
+ return isOnlyChild
+ ? cloneElement( children, {
+ ...restProps,
+ ref,
+ } )
+ : children;
+ }
+
return (
- <>
+
{ isOnlyChild ? undefined : children }
{ isOnlyChild && ( text || shortcut ) && (
) }
- >
+
);
}
+export const Tooltip = contextConnect( UnconnectedTooltip, 'Tooltip' );
+
export default Tooltip;
diff --git a/packages/components/src/tooltip/stories/index.story.tsx b/packages/components/src/tooltip/stories/index.story.tsx
index 760f3dcc23e2fd..b006bc03aced96 100644
--- a/packages/components/src/tooltip/stories/index.story.tsx
+++ b/packages/components/src/tooltip/stories/index.story.tsx
@@ -30,7 +30,7 @@ const meta: Meta< typeof Tooltip > = {
'bottom right',
],
},
- shortcut: { control: { type: 'text' } },
+ shortcut: { control: { type: 'object' } },
},
parameters: {
controls: { expanded: true },
@@ -57,3 +57,20 @@ KeyboardShortcut.args = {
ariaLabel: shortcutAriaLabel.primaryShift( ',' ),
},
};
+
+/**
+ * In case one or more `Tooltip` components are rendered inside another
+ * `Tooltip` component, only the tooltip associated to the outermost `Tooltip`
+ * component will be rendered in the browser and shown to the user
+ * appropriately. The rest of the nested `Tooltip` components will simply no-op
+ * and pass-through their anchor.
+ */
+export const Nested: StoryFn< typeof Tooltip > = Template.bind( {} );
+Nested.args = {
+ children: (
+
+
+
+ ),
+ text: 'Outer tooltip text',
+};
diff --git a/packages/components/src/tooltip/test/index.tsx b/packages/components/src/tooltip/test/index.tsx
index cbe144cfa53d4d..ed6f7b5f7b4a14 100644
--- a/packages/components/src/tooltip/test/index.tsx
+++ b/packages/components/src/tooltip/test/index.tsx
@@ -436,4 +436,50 @@ describe( 'Tooltip', () => {
await waitExpectTooltipToHide();
} );
} );
+
+ describe( 'nested', () => {
+ it( 'should render the outer tooltip and ignore nested tooltips', async () => {
+ render(
+
+
+
+
+
+
+
+ );
+
+ // Hover the anchor. Only the outer tooltip should show.
+ await hover(
+ screen.getByRole( 'button', {
+ name: 'Tooltip anchor',
+ } )
+ );
+
+ await waitFor( () =>
+ expect(
+ screen.getByRole( 'tooltip', { name: 'Outer tooltip' } )
+ ).toBeVisible()
+ );
+ expect(
+ screen.queryByRole( 'tooltip', { name: 'Middle tooltip' } )
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole( 'tooltip', { name: 'Inner tooltip' } )
+ ).not.toBeInTheDocument();
+ expect(
+ screen.getByRole( 'button', {
+ description: 'Outer tooltip',
+ } )
+ ).toBeVisible();
+
+ // Hover outside of the anchor, tooltip should hide
+ await hoverOutside();
+ await waitFor( () =>
+ expect(
+ screen.queryByRole( 'tooltip', { name: 'Outer tooltip' } )
+ ).not.toBeInTheDocument()
+ );
+ } );
+ } );
} );
diff --git a/packages/components/src/tooltip/types.ts b/packages/components/src/tooltip/types.ts
index 8708ae7005f5b3..3d28a1a0e96c67 100644
--- a/packages/components/src/tooltip/types.ts
+++ b/packages/components/src/tooltip/types.ts
@@ -59,3 +59,7 @@ export type TooltipProps = {
*/
text?: string;
};
+
+export type TooltipInternalContext = {
+ isNestedInTooltip?: boolean;
+};