Skip to content

Commit

Permalink
Editor: Optimize the 'PostSlug' component (#60422)
Browse files Browse the repository at this point in the history
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
  • Loading branch information
3 people committed Apr 3, 2024
1 parent da9cd5c commit 7f868d4
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 74 deletions.
118 changes: 50 additions & 68 deletions packages/editor/src/components/post-slug/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/**
* WordPress dependencies
*/
import { withDispatch, withSelect } from '@wordpress/data';
import { Component } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { compose } from '@wordpress/compose';
import { safeDecodeURIComponent, cleanForSlug } from '@wordpress/url';
import { TextControl } from '@wordpress/components';

Expand All @@ -14,72 +13,55 @@ import { TextControl } from '@wordpress/components';
import PostSlugCheck from './check';
import { store as editorStore } from '../../store';

export class PostSlug extends Component {
constructor( { postSlug, postTitle, postID } ) {
super( ...arguments );

this.state = {
editedSlug:
safeDecodeURIComponent( postSlug ) ||
cleanForSlug( postTitle ) ||
postID,
};

this.setSlug = this.setSlug.bind( this );
}

setSlug( event ) {
const { postSlug, onUpdateSlug } = this.props;
const { value } = event.target;

const editedSlug = cleanForSlug( value );

if ( editedSlug === postSlug ) {
return;
}

onUpdateSlug( editedSlug );
}
function PostSlugControl() {
const postSlug = useSelect( ( select ) => {
return safeDecodeURIComponent(
select( editorStore ).getEditedPostSlug()
);
}, [] );
const { editPost } = useDispatch( editorStore );
const [ forceEmptyField, setForceEmptyField ] = useState( false );

render() {
const { editedSlug } = this.state;
return (
<PostSlugCheck>
<TextControl
__nextHasNoMarginBottom
label={ __( 'Slug' ) }
autoComplete="off"
spellCheck="false"
value={ editedSlug }
onChange={ ( slug ) =>
this.setState( { editedSlug: slug } )
return (
<TextControl
__nextHasNoMarginBottom
label={ __( 'Slug' ) }
autoComplete="off"
spellCheck="false"
value={ forceEmptyField ? '' : postSlug }
onChange={ ( newValue ) => {
editPost( { slug: newValue } );
// When we delete the field the permalink gets
// reverted to the original value.
// The forceEmptyField logic allows the user to have
// the field temporarily empty while typing.
if ( ! newValue ) {
if ( ! forceEmptyField ) {
setForceEmptyField( true );
}
onBlur={ this.setSlug }
className="editor-post-slug"
/>
</PostSlugCheck>
);
}
return;
}
if ( forceEmptyField ) {
setForceEmptyField( false );
}
} }
onBlur={ ( event ) => {
editPost( {
slug: cleanForSlug( event.target.value ),
} );
if ( forceEmptyField ) {
setForceEmptyField( false );
}
} }
className="editor-post-slug"
/>
);
}

export default compose( [
withSelect( ( select ) => {
const { getCurrentPost, getEditedPostAttribute } =
select( editorStore );

const { id } = getCurrentPost();
return {
postSlug: getEditedPostAttribute( 'slug' ),
postTitle: getEditedPostAttribute( 'title' ),
postID: id,
};
} ),
withDispatch( ( dispatch ) => {
const { editPost } = dispatch( editorStore );
return {
onUpdateSlug( slug ) {
editPost( { slug } );
},
};
} ),
] )( PostSlug );
export default function PostSlug() {
return (
<PostSlugCheck>
<PostSlugControl />
</PostSlugCheck>
);
}
35 changes: 29 additions & 6 deletions packages/editor/src/components/post-slug/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,46 @@
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import { PostSlug } from '../';
import PostSlug from '../';

jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() );
jest.mock( '@wordpress/data/src/components/use-dispatch/use-dispatch', () =>
jest.fn()
);

describe( 'PostSlug', () => {
it( 'should update slug with sanitized input', async () => {
const user = userEvent.setup();
const onUpdateSlug = jest.fn();
const editPost = jest.fn();

useSelect.mockImplementation( ( mapSelect ) =>
mapSelect( () => ( {
getPostType: () => null,
getEditedPostAttribute: () => 'post',
getEditedPostSlug: () => '1',
} ) )
);
useDispatch.mockImplementation( () => ( {
editPost,
} ) );

render( <PostSlug postSlug="index" onUpdateSlug={ onUpdateSlug } /> );
render( <PostSlug /> );

const input = screen.getByRole( 'textbox', { name: 'Slug' } );
await user.clear( input );
await user.type( input, 'Foo Bar-Baz 9!' );
await user.type( input, '2', {
initialSelectionStart: 0,
initialSelectionEnd: 1,
} );
act( () => input.blur() );

expect( onUpdateSlug ).toHaveBeenCalledWith( 'foo-bar-baz-9' );
expect( editPost ).toHaveBeenCalledWith( { slug: '2' } );
} );
} );

0 comments on commit 7f868d4

Please sign in to comment.