diff --git a/packages/block-library/src/cover/test/edit.js b/packages/block-library/src/cover/test/edit.js index 74db754ff1696..8a391baa5f01d 100644 --- a/packages/block-library/src/cover/test/edit.js +++ b/packages/block-library/src/cover/test/edit.js @@ -64,9 +64,9 @@ describe( 'Cover block', () => { await setup(); expect( - screen.getByRole( 'group', { - name: 'To edit this block, you need permission to upload media.', - } ) + within( screen.getByLabelText( 'Block: Cover' ) ).getByText( + 'To edit this block, you need permission to upload media.' + ) ).toBeInTheDocument(); } ); diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index bfa095a9d32bb..b0102ecc3ca17 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,8 +2,9 @@ ## Unreleased -### Bug fix +### Bug Fix +- `Placeholder`: Improved DOM structure and screen reader announcements ([#45801](https://github.com/WordPress/gutenberg/pull/45801)). - `DateTimePicker`: fix onChange callback check so that it also works inside iframes ([#54669](https://github.com/WordPress/gutenberg/pull/54669)). ## 25.8.0 (2023-09-20) diff --git a/packages/components/src/placeholder/index.tsx b/packages/components/src/placeholder/index.tsx index 13634f6710d94..cdb845710251d 100644 --- a/packages/components/src/placeholder/index.tsx +++ b/packages/components/src/placeholder/index.tsx @@ -8,6 +8,8 @@ import classnames from 'classnames'; */ import { useResizeObserver } from '@wordpress/compose'; import { SVG, Path } from '@wordpress/primitives'; +import { useEffect } from '@wordpress/element'; +import { speak } from '@wordpress/a11y'; /** * Internal dependencies @@ -72,10 +74,17 @@ export function Placeholder( modifierClassNames, withIllustration ? 'has-illustration' : null ); + const fieldsetClasses = classnames( 'components-placeholder__fieldset', { 'is-column-layout': isColumnLayout, } ); + useEffect( () => { + if ( instructions ) { + speak( instructions ); + } + }, [ instructions ] ); + return (
{ withIllustration ? PlaceholderIllustration : null } @@ -90,14 +99,12 @@ export function Placeholder( { label }
-
- { !! instructions && ( - - { instructions } - - ) } - { children } -
+ { !! instructions && ( +
+ { instructions } +
+ ) } +
{ children }
); } diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss index 1cb3edfcfdbba..ce98350b76735 100644 --- a/packages/components/src/placeholder/style.scss +++ b/packages/components/src/placeholder/style.scss @@ -72,18 +72,6 @@ } } -// Overrides for browser and editor fieldset styles. -.components-placeholder__fieldset.components-placeholder__fieldset { - border: none; - padding: 0; - - .components-placeholder__instructions { - padding: 0; - font-weight: normal; - font-size: 1em; - } -} - .components-placeholder__fieldset.is-column-layout, .components-placeholder__fieldset.is-column-layout form { flex-direction: column; diff --git a/packages/components/src/placeholder/test/index.tsx b/packages/components/src/placeholder/test/index.tsx index c01de24eb2b05..dbb88a4882634 100644 --- a/packages/components/src/placeholder/test/index.tsx +++ b/packages/components/src/placeholder/test/index.tsx @@ -8,6 +8,7 @@ import { render, screen, within } from '@testing-library/react'; */ import { useResizeObserver } from '@wordpress/compose'; import { SVG, Path } from '@wordpress/primitives'; +import { speak } from '@wordpress/a11y'; /** * Internal dependencies @@ -41,6 +42,9 @@ const Placeholder = ( const getPlaceholder = () => screen.getByTestId( 'placeholder' ); +jest.mock( '@wordpress/a11y', () => ( { speak: jest.fn() } ) ); +const mockedSpeak = jest.mocked( speak ); + describe( 'Placeholder', () => { beforeEach( () => { // @ts-ignore @@ -48,10 +52,11 @@ describe( 'Placeholder', () => {
, { width: 320 }, ] ); + mockedSpeak.mockReset(); } ); describe( 'basic rendering', () => { - it( 'should by default render label section and fieldset.', () => { + it( 'should by default render label section and content section.', () => { render( ); const placeholder = getPlaceholder(); @@ -74,9 +79,12 @@ describe( 'Placeholder', () => { ); expect( placeholderInstructions ).not.toBeInTheDocument(); - // Test for empty fieldset. - const placeholderFieldset = - within( placeholder ).getByRole( 'group' ); + // Test for empty content. When the content is empty, + // the only way to query the div is with `querySelector` + // eslint-disable-next-line testing-library/no-node-access + const placeholderFieldset = placeholder.querySelector( + '.components-placeholder__fieldset' + ); expect( placeholderFieldset ).toBeInTheDocument(); expect( placeholderFieldset ).toBeEmptyDOMElement(); } ); @@ -104,27 +112,38 @@ describe( 'Placeholder', () => { expect( placeholderLabel ).toBeInTheDocument(); } ); - it( 'should display a fieldset from the children property', () => { - const content = 'Fieldset'; + it( 'should display content from the children property', () => { + const content = 'Placeholder content'; render( { content } ); - const placeholderFieldset = screen.getByRole( 'group' ); + const placeholder = screen.getByText( content ); - expect( placeholderFieldset ).toBeInTheDocument(); - expect( placeholderFieldset ).toHaveTextContent( content ); + expect( placeholder ).toBeInTheDocument(); + expect( placeholder ).toHaveTextContent( content ); } ); - it( 'should display a legend if instructions are passed', () => { + it( 'should display instructions when provided', () => { const instructions = 'Choose an option.'; render( -
Fieldset
+
Placeholder content
+
+ ); + const placeholder = getPlaceholder(); + const instructionsContainer = + within( placeholder ).getByText( instructions ); + + expect( instructionsContainer ).toBeInTheDocument(); + } ); + + it( 'should announce instructions to screen readers', () => { + const instructions = 'Awesome block placeholder instructions.'; + render( + +
Placeholder content
); - const captionedFieldset = screen.getByRole( 'group', { - name: instructions, - } ); - expect( captionedFieldset ).toBeInTheDocument(); + expect( speak ).toHaveBeenCalledWith( instructions ); } ); it( 'should add an additional className to the top container', () => {