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

Disallow creation of empty links using Link UI directly #35060

Merged
merged 12 commits into from
Sep 29, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ function LinkControl( {
);
const isEndingEditWithFocus = useRef( false );

const currentInputIsEmpty = ! currentInputValue?.trim()?.length;

useEffect( () => {
if (
forceIsEditingLink !== undefined &&
Expand Down Expand Up @@ -234,14 +236,18 @@ function LinkControl( {
onClick={ () => handleSubmitButton() }
onKeyDown={ ( event ) => {
const { keyCode } = event;
if ( keyCode === ENTER ) {
if (
keyCode === ENTER &&
! currentInputIsEmpty // disallow submitting empty values.
) {
event.preventDefault();
handleSubmitButton();
}
} }
label={ __( 'Submit' ) }
icon={ keyboardReturn }
className="block-editor-link-control__search-submit"
disabled={ currentInputIsEmpty } // disallow submitting empty values.
/>
</div>
</LinkControlSearchInput>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,18 @@ const LinkControlSearchInput = forwardRef(
__experimentalShowInitialSuggestions={
showInitialSuggestions
}
onSubmit={ ( suggestion ) => {
onSuggestionSelected(
suggestion || focusedSuggestion || { url: value }
);
onSubmit={ ( suggestion, event ) => {
const hasSuggestion = suggestion || focusedSuggestion;

// If there is no suggestion and the value (ie: any manually entered URL) is empty
// then don't allow submission otherwise we get empty links.
if ( ! hasSuggestion && ! value?.trim()?.length ) {
event.preventDefault();
} else {
onSuggestionSelected(
hasSuggestion || { url: value }
);
}
} }
ref={ ref }
/>
Expand Down
98 changes: 98 additions & 0 deletions packages/block-editor/src/components/link-control/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,104 @@ describe( 'Manual link entry', () => {
}
);

describe( 'Handling of empty values', () => {
const testTable = [
[ 'containing only spaces', ' ' ],
[ 'containing only tabs', ' ' ],
[ 'from strings with no length', '' ],
];

it.each( testTable )(
'should not allow creation of links %s when using the keyboard',
async ( _desc, searchString ) => {
act( () => {
render( <LinkControl />, container );
} );

// Search Input UI
const searchInput = getURLInput();

let submitButton = queryByRole( container, 'button', {
name: 'Submit',
} );

expect( submitButton.disabled ).toBeTruthy();
expect( submitButton ).not.toBeNull();
expect( submitButton ).toBeInTheDocument();

// Simulate searching for a term
act( () => {
Simulate.change( searchInput, {
target: { value: searchString },
} );
} );

// fetchFauxEntitySuggestions resolves on next "tick" of event loop
await eventLoopTick();

// Attempt to submit the empty search value in the input.
act( () => {
Simulate.keyDown( searchInput, { keyCode: ENTER } );
} );

submitButton = queryByRole( container, 'button', {
name: 'Submit',
} );

// Verify the UI hasn't allowed submission.
expect( searchInput ).toBeInTheDocument();
expect( submitButton.disabled ).toBeTruthy();
expect( submitButton ).not.toBeNull();
expect( submitButton ).toBeInTheDocument();
}
);

it.each( testTable )(
'should not allow creation of links %s via the UI "submit" button',
async ( _desc, searchString ) => {
act( () => {
render( <LinkControl />, container );
} );

// Search Input UI
const searchInput = getURLInput();

let submitButton = queryByRole( container, 'button', {
name: 'Submit',
} );

expect( submitButton.disabled ).toBeTruthy();
expect( submitButton ).not.toBeNull();
expect( submitButton ).toBeInTheDocument();

// Simulate searching for a term
act( () => {
Simulate.change( searchInput, {
target: { value: searchString },
} );
} );

// fetchFauxEntitySuggestions resolves on next "tick" of event loop
await eventLoopTick();

// Attempt to submit the empty search value in the input.
act( () => {
Simulate.click( submitButton );
} );

submitButton = queryByRole( container, 'button', {
name: 'Submit',
} );

// Verify the UI hasn't allowed submission.
expect( searchInput ).toBeInTheDocument();
expect( submitButton.disabled ).toBeTruthy();
expect( submitButton ).not.toBeNull();
expect( submitButton ).toBeInTheDocument();
}
);
} );

describe( 'Alternative link protocols and formats', () => {
it.each( [
[ 'mailto:[email protected]', 'mailto' ],
Expand Down
25 changes: 19 additions & 6 deletions packages/block-editor/src/components/url-input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,20 @@ class URLInput extends Component {
return;
}

const isInitialSuggestions = ! ( value && value.length );
// Test for whitespace only:
// 1. must have a length - otherwise test from #1 will be truthy for string with no characters.
// 2. if all whitespace is stripped must be empty.
const containsOnlyWhitespace = value?.length && value.trim() === '';

// Initial suggestions are those shown when no search has been made.
// The criteria for "no search" are:
// 1. No value.
// 2. Value must not be entirely whitespace.
const isInitialSuggestions =
! value?.length && ! containsOnlyWhitespace;
adamziel marked this conversation as resolved.
Show resolved Hide resolved

adamziel marked this conversation as resolved.
Show resolved Hide resolved
// Trim only now we've determined it's not composed of purely whitespace.
value = value.trim();

// Allow a suggestions request if:
// - there are at least 2 characters in the search input (except manual searches where
Expand Down Expand Up @@ -219,7 +232,7 @@ class URLInput extends Component {

this.props.onChange( inputValue );
if ( ! this.props.disableSuggestions ) {
this.updateSuggestions( inputValue.trim() );
this.updateSuggestions( inputValue );
}
}

Expand All @@ -236,7 +249,7 @@ class URLInput extends Component {
! ( suggestions && suggestions.length )
) {
// Ensure the suggestions are updated with the current input value
this.updateSuggestions( value.trim() );
this.updateSuggestions( value );
}
}

Expand Down Expand Up @@ -288,7 +301,7 @@ class URLInput extends Component {
// Submitting while loading should trigger onSubmit
case ENTER: {
if ( this.props.onSubmit ) {
this.props.onSubmit();
this.props.onSubmit( null, event );
}

break;
Expand Down Expand Up @@ -338,10 +351,10 @@ class URLInput extends Component {
this.selectLink( suggestion );

if ( this.props.onSubmit ) {
this.props.onSubmit( suggestion );
this.props.onSubmit( suggestion, event );
}
} else if ( this.props.onSubmit ) {
this.props.onSubmit();
this.props.onSubmit( null, event );
}

break;
Expand Down