Skip to content

Commit

Permalink
Post Editor: Refactor URL redirect as BrowserURL component (#7122)
Browse files Browse the repository at this point in the history
* State: Set post status to draft when autosave reset

* Post Editor: Refactor URL redirect as BrowserURL component

Behavior is specific to post editor, and can be expressed in terms of lifecycle as: Sync to current post ID so long as post is not auto-draft.
  • Loading branch information
aduth authored Jun 4, 2018
1 parent 7141ce6 commit 9a10190
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 34 deletions.
4 changes: 4 additions & 0 deletions edit-post/components/browser-url/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BrowserURL
==========

`<BrowserURL />` is a component used to keep the editor's saved post ID in sync with the browser's URL. Using the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API), it makes an in-place replacement (using `window.replaceState`) of the URL if the post ID changes and is not an auto-draft.
74 changes: 74 additions & 0 deletions edit-post/components/browser-url/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { withSelect } from '@wordpress/data';
import { addQueryArgs } from '@wordpress/url';

/**
* Returns the Post's Edit URL.
*
* @param {number} postId Post ID.
*
* @return {string} Post edit URL.
*/
export function getPostEditURL( postId ) {
return addQueryArgs( 'post.php', { post: postId, action: 'edit' } );
}

export class BrowserURL extends Component {
constructor() {
super( ...arguments );

this.state = {
historyId: null,
};
}

componentDidUpdate( prevProps ) {
const { postId, postStatus } = this.props;
const { historyId } = this.state;
if ( postId === prevProps.postId && postId === historyId ) {
return;
}

if ( postStatus !== 'auto-draft' ) {
this.setBrowserURL( postId );
}
}

/**
* Replaces the browser URL with a post editor link for the given post ID.
*
* Note it is important that, since this function may be called when the
* editor first loads, the result generated `getPostEditURL` matches that
* produced by the server. Otherwise, the URL will change unexpectedly.
*
* @param {number} postId Post ID for which to generate post editor URL.
*/
setBrowserURL( postId ) {
window.history.replaceState(
{ id: postId },
'Post ' + postId,
getPostEditURL( postId )
);

this.setState( () => ( {
historyId: postId,
} ) );
}

render() {
return null;
}
}

export default withSelect( ( select ) => {
const { getCurrentPost } = select( 'core/editor' );
const { id, status } = getCurrentPost();

return {
postId: id,
postStatus: status,
};
} )( BrowserURL );
98 changes: 98 additions & 0 deletions edit-post/components/browser-url/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* External dependencies
*/
import { shallow } from 'enzyme';

/**
* Internal dependencies
*/
import { getPostEditURL, BrowserURL } from '../';

describe( 'getPostEditURL', () => {
it( 'should generate relative path with post and action arguments', () => {
const url = getPostEditURL( 1 );

expect( url ).toBe( 'post.php?post=1&action=edit' );
} );
} );

describe( 'BrowserURL', () => {
let replaceStateSpy;

beforeAll( () => {
replaceStateSpy = jest.spyOn( window.history, 'replaceState' );
} );

beforeEach( () => {
replaceStateSpy.mockReset();
} );

afterAll( () => {
replaceStateSpy.mockRestore();
} );

it( 'not update URL if post is auto-draft', () => {
const wrapper = shallow( <BrowserURL /> );
wrapper.setProps( {
postId: 1,
postStatus: 'auto-draft',
} );

expect( replaceStateSpy ).not.toHaveBeenCalled();
} );

it( 'update URL if post is no longer auto-draft', () => {
const wrapper = shallow( <BrowserURL /> );
wrapper.setProps( {
postId: 1,
postStatus: 'auto-draft',
} );
wrapper.setProps( {
postStatus: 'draft',
} );

expect( replaceStateSpy ).toHaveBeenCalledWith(
{ id: 1 },
'Post 1',
'post.php?post=1&action=edit'
);
} );

it( 'not update URL if history is already set', () => {
const wrapper = shallow( <BrowserURL /> );
wrapper.setProps( {
postId: 1,
postStatus: 'draft',
} );
replaceStateSpy.mockReset();
wrapper.setProps( {
postId: 1,
} );

expect( replaceStateSpy ).not.toHaveBeenCalled();
} );

it( 'update URL if post ID changes', () => {
const wrapper = shallow( <BrowserURL /> );
wrapper.setProps( {
postId: 1,
postStatus: 'draft',
} );
replaceStateSpy.mockReset();
wrapper.setProps( {
postId: 2,
} );

expect( replaceStateSpy ).toHaveBeenCalledWith(
{ id: 2 },
'Post 2',
'post.php?post=2&action=edit'
);
} );

it( 'renders nothing', () => {
const wrapper = shallow( <BrowserURL /> );

expect( wrapper.type() ).toBeNull();
} );
} );
2 changes: 2 additions & 0 deletions edit-post/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { withViewportMatch } from '@wordpress/viewport';
* Internal dependencies
*/
import './style.scss';
import BrowserURL from '../browser-url';
import BlockSidebar from '../sidebar/block-sidebar';
import DocumentSidebar from '../sidebar/document-sidebar';
import Header from '../header';
Expand Down Expand Up @@ -60,6 +61,7 @@ function Layout( {
return (
<div className={ className }>
<DocumentTitle />
<BrowserURL />
<UnsavedChangesWarning forceIsDirty={ () => {
return some( metaBoxes, ( metaBox, location ) => {
return metaBox.isActive &&
Expand Down
10 changes: 1 addition & 9 deletions editor/store/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { speak } from '@wordpress/a11y';
/**
* Internal dependencies
*/
import { getPostEditUrl, getWPAdminURL } from '../utils/url';
import { getWPAdminURL } from '../utils/url';
import {
setupEditorState,
resetAutosave,
Expand Down Expand Up @@ -219,14 +219,6 @@ export default {
{ id: SAVE_POST_NOTICE_ID, spokenMessage: noticeMessage }
) );
}

if ( get( window.history.state, [ 'id' ] ) !== post.id ) {
window.history.replaceState(
{ id: post.id },
'Post ' + post.id,
getPostEditUrl( post.id )
);
}
},
REQUEST_POST_UPDATE_FAILURE( action, store ) {
const { post, edits } = action;
Expand Down
10 changes: 10 additions & 0 deletions editor/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,16 @@ export function currentPost( state = {}, action ) {
}

return mapValues( post, getPostRawValue );

case 'RESET_AUTOSAVE':
// A post is no longer auto-draft (now draft) when autosave occurs.
if ( state.status === 'auto-draft' ) {
return {
...state,
status: 'draft',
};
}
break;
}

return state;
Expand Down
14 changes: 0 additions & 14 deletions editor/store/test/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ describe( 'effects', () => {

describe( '.REQUEST_POST_UPDATE_SUCCESS', () => {
const handler = effects.REQUEST_POST_UPDATE_SUCCESS;
let replaceStateSpy;

const defaultPost = {
id: 1,
Expand All @@ -278,18 +277,6 @@ describe( 'effects', () => {
status: 'publish',
} );

beforeAll( () => {
replaceStateSpy = jest.spyOn( window.history, 'replaceState' );
} );

beforeEach( () => {
replaceStateSpy.mockReset();
} );

afterAll( () => {
replaceStateSpy.mockRestore();
} );

it( 'should dispatch notices when publishing or scheduling a post', () => {
const dispatch = jest.fn();
const store = { dispatch };
Expand Down Expand Up @@ -370,7 +357,6 @@ describe( 'effects', () => {
handler( { post, previousPost, isAutosave: true }, store );

expect( dispatch ).toHaveBeenCalledTimes( 0 );
expect( replaceStateSpy ).toHaveBeenCalledTimes( 0 );
} );
} );

Expand Down
18 changes: 18 additions & 0 deletions editor/store/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,24 @@ describe( 'state', () => {
status: 'publish',
} );
} );

it( 'should set status to draft if autosave reset while auto-draft', () => {
const original = deepFreeze( { title: '', status: 'auto-draft' } );

const state = currentPost( original, {
type: 'RESET_AUTOSAVE',
edits: {
title: 'Hello world',
content: '',
excerpt: '',
},
} );

expect( state ).toEqual( {
title: '',
status: 'draft',
} );
} );
} );

describe( 'isInsertionPointVisible', () => {
Expand Down
13 changes: 2 additions & 11 deletions editor/utils/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,11 @@
*/
import { addQueryArgs } from '@wordpress/url';

/**
* Returns the Post's Edit URL.
*
* @param {number} postId Post ID.
*
* @return {string} Post edit URL.
*/
export function getPostEditUrl( postId ) {
return getWPAdminURL( 'post.php', { post: postId, action: 'edit' } );
}

/**
* Returns the URL of a WPAdmin Page.
*
* TODO: This should be moved to a module less specific to the editor.
*
* @param {string} page Page to navigate to.
* @param {Object} query Query Args.
*
Expand Down

0 comments on commit 9a10190

Please sign in to comment.