Skip to content

Commit

Permalink
Merge pull request #1203 from WordPress/fix/1201-validate-empty
Browse files Browse the repository at this point in the history
Prevent save if post has no saveable content
  • Loading branch information
aduth authored Jun 16, 2017
2 parents a9b0950 + 986680b commit a09c4ca
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 2 deletions.
8 changes: 7 additions & 1 deletion editor/header/saved-state/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ import {
isEditedPostNew,
isEditedPostDirty,
isSavingPost,
isEditedPostSaveable,
getCurrentPost,
getEditedPostAttribute,
} from '../../selectors';

function SavedState( { isNew, isDirty, isSaving, status, onStatusChange, onSave } ) {
function SavedState( { isNew, isDirty, isSaving, isSaveable, status, onStatusChange, onSave } ) {
if ( ! isSaveable ) {
return null;
}

const className = 'editor-saved-state';

if ( isSaving ) {
Expand Down Expand Up @@ -60,6 +65,7 @@ export default connect(
isNew: isEditedPostNew( state ),
isDirty: isEditedPostDirty( state ),
isSaving: isSavingPost( state ),
isSaveable: isEditedPostSaveable( state ),
status: getEditedPostAttribute( state, 'status' ),
} ),
{
Expand Down
5 changes: 4 additions & 1 deletion editor/header/tools/publish-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
isEditedPostPublished,
isEditedPostBeingScheduled,
getEditedPostVisibility,
isEditedPostSaveable,
isEditedPostPublishable,
} from '../../selectors';

Expand All @@ -30,8 +31,9 @@ function PublishButton( {
isBeingScheduled,
visibility,
isPublishable,
isSaveable,
} ) {
const buttonEnabled = ! isSaving && isPublishable;
const buttonEnabled = ! isSaving && isPublishable && isSaveable;
let buttonText;
if ( isPublished ) {
buttonText = __( 'Update' );
Expand Down Expand Up @@ -76,6 +78,7 @@ export default connect(
isPublished: isEditedPostPublished( state ),
isBeingScheduled: isEditedPostBeingScheduled( state ),
visibility: getEditedPostVisibility( state ),
isSaveable: isEditedPostSaveable( state ),
isPublishable: isEditedPostPublishable( state ),
} ),
{
Expand Down
25 changes: 25 additions & 0 deletions editor/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ export function isEditedPostPublishable( state ) {
return isEditedPostDirty( state ) || [ 'publish', 'private', 'future' ].indexOf( post.status ) === -1;
}

/**
* Returns true if the post can be saved, or false otherwise. A post must
* contain a title, an excerpt, or non-empty content to be valid for save.
*
* @param {Object} state Global application state
* @return {Boolean} Whether the post can be saved
*/
export function isEditedPostSaveable( state ) {
return (
getBlockCount( state ) > 0 ||
!! getEditedPostTitle( state ) ||
!! getEditedPostExcerpt( state )
);
}

/**
* Return true if the post being edited is being scheduled. Preferring the
* unsaved status values.
Expand Down Expand Up @@ -251,6 +266,16 @@ export const getBlocks = createSelector(
]
);

/**
* Returns the number of blocks currently present in the post.
*
* @param {Object} state Global application state
* @return {Object} Number of blocks in the post
*/
export function getBlockCount( state ) {
return getBlockUids( state ).length;
}

/**
* Returns the currently selected block, or null if there is no selected block.
*
Expand Down
78 changes: 78 additions & 0 deletions editor/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import {
getEditedPostVisibility,
isEditedPostPublished,
isEditedPostPublishable,
isEditedPostSaveable,
isEditedPostBeingScheduled,
getEditedPostPreviewLink,
getBlock,
getBlocks,
getBlockCount,
getSelectedBlock,
getMultiSelectedBlockUids,
getMultiSelectedBlocksStartUid,
Expand Down Expand Up @@ -455,6 +457,66 @@ describe( 'selectors', () => {
} );
} );

describe( 'isEditedPostSaveable', () => {
it( 'should return false if the post has no title, excerpt, content', () => {
const state = {
editor: {
blocksByUid: {},
blockOrder: [],
edits: {},
},
currentPost: {},
};

expect( isEditedPostSaveable( state ) ).to.be.false();
} );

it( 'should return true if the post has a title', () => {
const state = {
editor: {
blocksByUid: {},
blockOrder: [],
edits: {},
},
currentPost: {
title: { raw: 'sassel' },
},
};

expect( isEditedPostSaveable( state ) ).to.be.true();
} );

it( 'should return true if the post has an excerpt', () => {
const state = {
editor: {
blocksByUid: {},
blockOrder: [],
edits: {},
},
currentPost: {
excerpt: { raw: 'sassel' },
},
};

expect( isEditedPostSaveable( state ) ).to.be.true();
} );

it( 'should return true if the post has content', () => {
const state = {
editor: {
blocksByUid: {
123: { uid: 123, name: 'core/text' },
},
blockOrder: [ 123 ],
edits: {},
},
currentPost: {},
};

expect( isEditedPostSaveable( state ) ).to.be.true();
} );
} );

describe( 'isEditedPostBeingScheduled', () => {
it( 'should return true for posts with a future date', () => {
const state = {
Expand Down Expand Up @@ -530,6 +592,22 @@ describe( 'selectors', () => {
} );
} );

describe( 'getBlockCount', () => {
it( 'should return the number of blocks in the post', () => {
const state = {
editor: {
blocksByUid: {
23: { uid: 23, name: 'core/heading' },
123: { uid: 123, name: 'core/text' },
},
blockOrder: [ 123, 23 ],
},
};

expect( getBlockCount( state ) ).to.equal( 2 );
} );
} );

describe( 'getSelectedBlock', () => {
it( 'should return null if no block is selected', () => {
const state = {
Expand Down

0 comments on commit a09c4ca

Please sign in to comment.