Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for defined IDs in TextControl component #52028

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/components/src/text-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ import BaseControl from '../base-control';
import type { WordPressComponentProps } from '../ui/context';
import type { TextControlProps } from './types';

function useUniqueId( idProp?: string ) {
const instanceId = useInstanceId( TextControl );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI we can pass the id prefix and the idProp directly into the useInstanceId() function to avoid this custom code ✨

/**
* Provides a unique instance ID.
*
* @param object Object reference to create an id for.
* @param [prefix] Prefix for the unique id.
* @param [preferredId] Default ID to use.
* @return The unique instance id.
*/
function useInstanceId(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this, and strongly considered using it. But the useUniqueId pattern seemed to be in quite a few places already, which made me second guess myself.

I'm quite happy to apply useInstanceId "properly" here though, if that's actually the more appropriate direction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm quite happy to apply useInstanceId "properly" here though, if that's actually the more appropriate direction.

Yes, I think it is. I have a feeling that the existing useUniqueId patterns were written before useInstanceId was expanded to take three arguments.

const id = `inspector-text-control-${ instanceId }`;

return idProp || id;
}

function UnforwardedTextControl(
props: WordPressComponentProps< TextControlProps, 'input', false >,
ref: ForwardedRef< HTMLInputElement >
Expand All @@ -26,13 +33,13 @@ function UnforwardedTextControl(
hideLabelFromVision,
value,
help,
id: idProp,
className,
onChange,
type = 'text',
...additionalProps
} = props;
const instanceId = useInstanceId( TextControl );
const id = `inspector-text-control-${ instanceId }`;
const id = useUniqueId( idProp );
const onChangeValue = ( event: ChangeEvent< HTMLInputElement > ) =>
onChange( event.target.value );

Expand Down
46 changes: 46 additions & 0 deletions packages/components/src/text-control/test/text-control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';

/**
* Internal dependencies
*/
import TextControl from '..';

const noop = () => {};

describe( 'TextControl', () => {
it( 'should generate an ID if not passed as a prop', () => {
render( <TextControl onChange={ noop } value={ '' } /> );

expect( screen.getByRole( 'textbox' ) ).toHaveAttribute(
'id',
expect.stringMatching( /^inspector-text-control-/ )
);
} );

it( 'should use the passed ID prop if provided', () => {
const id = 'test-id';
render( <TextControl onChange={ noop } id={ id } value={ '' } /> );

expect( screen.getByRole( 'textbox' ) ).toHaveAttribute( 'id', id );
} );

it( 'should map the label and input together when given an ID', () => {
const id = 'test-id';
const labelValue = 'Test Label';
render(
<TextControl
onChange={ noop }
id={ id }
label={ labelValue }
value={ '' }
/>
);

const textbox = screen.getByRole( 'textbox' );
const label = screen.getByText( labelValue );
expect( textbox ).toHaveAttribute( 'id', label.getAttribute( 'for' ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Not that this is inherently wrong, but I find it more idiomatic/robust within the testing-library context to simply assert that the textbox is accessibly labeled, rather than assert the implementation details.

Suggested change
const textbox = screen.getByRole( 'textbox' );
const label = screen.getByText( labelValue );
expect( textbox ).toHaveAttribute( 'id', label.getAttribute( 'for' ) );
expect( screen.getByRole( 'textbox', { name: labelValue } ) ).toBeVisible();

☝️ Something like that. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have much experience with testing-library, and I didn't know you could do this! That makes much more sense.

} );
} );
Loading