@wordpress/editor
Version:
Enhanced block editor for WordPress posts.
555 lines (536 loc) • 14.8 kB
JavaScript
/**
* WordPress dependencies
*/
import { apiFetch } from '@wordpress/data-controls';
import { controls } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
/**
* Internal dependencies
*/
import * as actions from '../actions';
import { STORE_NAME, TRASH_POST_NOTICE_ID } from '../constants';
const postType = {
rest_base: 'posts',
labels: {
item_updated: 'Updated Post',
item_published: 'Post published',
},
};
const postId = 44;
const postTypeSlug = 'post';
describe( 'Post generator actions', () => {
describe( 'savePost()', () => {
let fulfillment, currentPost, currentPostStatus, isAutosave;
beforeEach( () => {
currentPost = () => ( {
id: postId,
type: postTypeSlug,
title: 'bar',
content: 'bar',
excerpt: 'crackers',
status: currentPostStatus,
} );
} );
const reset = ( isAutosaving ) =>
( fulfillment = actions.savePost( { isAutosave: isAutosaving } ) );
const testConditions = [
[
'yields an action for checking if the post is saveable',
() => true,
() => {
reset( isAutosave );
const { value } = fulfillment.next();
expect( value ).toEqual(
controls.select( STORE_NAME, 'isEditedPostSaveable' )
);
},
],
[
'yields an action for selecting the current edited post content',
() => true,
() => {
const { value } = fulfillment.next( true );
expect( value ).toEqual(
controls.select( STORE_NAME, 'getEditedPostContent' )
);
},
],
[
"yields an action for editing the post entity's content if not an autosave",
() => true,
() => {
if ( ! isAutosave ) {
const edits = { content: currentPost().content };
const { value } = fulfillment.next( edits.content );
expect( value ).toEqual(
controls.dispatch( STORE_NAME, 'editPost', edits, {
undoIgnore: true,
} )
);
}
},
],
[
'yields an action for signalling that an update to the post started',
() => true,
() => {
const { value } = fulfillment.next();
expect( value ).toEqual( {
type: 'REQUEST_POST_UPDATE_START',
options: { isAutosave },
} );
},
],
[
'yields an action for selecting the current post',
() => true,
() => {
const { value } = fulfillment.next();
expect( value ).toEqual(
controls.select( STORE_NAME, 'getCurrentPost' )
);
},
],
[
"yields an action for selecting the post entity's non transient edits",
() => true,
() => {
const post = currentPost();
const { value } = fulfillment.next( post );
expect( value ).toEqual(
controls.select(
'core',
'getEntityRecordNonTransientEdits',
'postType',
post.type,
post.id
)
);
},
],
[
'yields an action for dispatching an update to the post entity',
() => true,
() => {
const post = currentPost();
const { value } = fulfillment.next( post );
expect( value ).toEqual(
controls.dispatch(
'core',
'saveEntityRecord',
'postType',
post.type,
isAutosave ? { ...post, content: undefined } : post,
{
isAutosave,
}
)
);
},
],
[
'yields an action for signalling that an update to the post finished',
() => true,
() => {
const { value } = fulfillment.next();
expect( value ).toEqual( {
type: 'REQUEST_POST_UPDATE_FINISH',
options: { isAutosave },
} );
},
],
[
"yields an action for selecting the entity's save error",
() => true,
() => {
const post = currentPost();
const { value } = fulfillment.next();
expect( value ).toEqual(
controls.select(
'core',
'getLastEntitySaveError',
'postType',
post.type,
post.id
)
);
},
],
[
'yields an action for selecting the current post',
() => true,
() => {
const { value } = fulfillment.next();
expect( value ).toEqual(
controls.select( STORE_NAME, 'getCurrentPost' )
);
},
],
[
'yields an action for selecting the current post type config',
() => true,
() => {
const post = currentPost();
const { value } = fulfillment.next( post );
expect( value ).toEqual(
controls.resolveSelect(
'core',
'getPostType',
post.type
)
);
},
],
[
'yields an action for dispatching a success notice',
() => true,
() => {
if ( ! isAutosave && currentPostStatus === 'publish' ) {
const { value } = fulfillment.next( postType );
expect( value ).toEqual(
controls.dispatch(
noticesStore,
'createSuccessNotice',
'Updated Post',
{
actions: [],
id: 'SAVE_POST_NOTICE_ID',
type: 'snackbar',
}
)
);
}
},
],
[
'yields an action for marking the last change as persistent',
() => true,
() => {
if ( ! isAutosave ) {
const { value } = fulfillment.next();
expect( value ).toEqual(
controls.dispatch(
'core/block-editor',
'__unstableMarkLastChangeAsPersistent'
)
);
}
},
],
[
'implicitly returns undefined',
() => true,
() => {
expect( fulfillment.next() ).toEqual( {
done: true,
value: undefined,
} );
},
],
];
const conditionalRunTestRoutine = ( isAutosaving ) => ( [
testDescription,
shouldRun,
testRoutine,
] ) => {
if ( shouldRun( isAutosaving ) ) {
// eslint-disable-next-line jest/valid-title
it( testDescription, () => {
testRoutine();
} );
}
};
describe( 'yields with expected responses for when not autosaving and edited post is new', () => {
beforeEach( () => {
isAutosave = false;
currentPostStatus = 'draft';
} );
testConditions.forEach( conditionalRunTestRoutine( false ) );
} );
describe( 'yields with expected responses for when not autosaving and edited post is not new', () => {
beforeEach( () => {
isAutosave = false;
currentPostStatus = 'publish';
} );
testConditions.forEach( conditionalRunTestRoutine( false ) );
} );
describe( 'yields with expected responses for when autosaving is true and edited post is not new', () => {
beforeEach( () => {
isAutosave = true;
currentPostStatus = 'autosave';
} );
testConditions.forEach( conditionalRunTestRoutine( true ) );
} );
} );
describe( 'autosave()', () => {
it( 'dispatches savePost with the correct arguments', () => {
const fulfillment = actions.autosave();
const { value } = fulfillment.next();
expect( value.actionName ).toBe( 'savePost' );
expect( value.args ).toEqual( [ { isAutosave: true } ] );
} );
} );
describe( 'trashPost()', () => {
let fulfillment;
const currentPost = { id: 10, content: 'foo', status: 'publish' };
const reset = () => ( fulfillment = actions.trashPost() );
const rewind = () => {
reset();
fulfillment.next();
fulfillment.next( postTypeSlug );
fulfillment.next( postType );
fulfillment.next();
fulfillment.next( currentPost );
};
it( 'yields expected action for selecting the current post type slug', () => {
reset();
const { value } = fulfillment.next();
expect( value ).toEqual(
controls.select( STORE_NAME, 'getCurrentPostType' )
);
} );
it( 'yields expected action for selecting the post type object', () => {
const { value } = fulfillment.next( postTypeSlug );
expect( value ).toEqual(
controls.resolveSelect( 'core', 'getPostType', postTypeSlug )
);
} );
it(
'yields expected action for dispatching removing the trash notice ' +
'for the post',
() => {
const { value } = fulfillment.next( postType );
expect( value ).toEqual(
controls.dispatch(
noticesStore,
'removeNotice',
TRASH_POST_NOTICE_ID
)
);
}
);
it( 'yields expected action for selecting the currentPost', () => {
const { value } = fulfillment.next();
expect( value ).toEqual(
controls.select( STORE_NAME, 'getCurrentPost' )
);
} );
it( 'yields expected action object for the api fetch', () => {
const { value } = fulfillment.next( currentPost );
expect( value ).toEqual(
apiFetch( {
path: `/wp/v2/${ postType.rest_base }/${ currentPost.id }`,
method: 'DELETE',
} )
);
} );
describe( 'expected yields when fetch throws an error', () => {
it( 'yields expected action for dispatching an error notice', () => {
const error = { foo: 'bar', code: 'fail' };
const { value } = fulfillment.throw( error );
expect( value ).toEqual(
controls.dispatch(
noticesStore,
'createErrorNotice',
'Trashing failed',
{
id: TRASH_POST_NOTICE_ID,
}
)
);
} );
} );
describe( 'expected yields when fetch does not throw an error', () => {
it( 'yields expected dispatch action for saving the post', () => {
rewind();
const { value } = fulfillment.next();
expect( value ).toEqual(
controls.dispatch( STORE_NAME, 'savePost' )
);
} );
} );
} );
describe( 'refreshPost()', () => {
let fulfillment;
const currentPost = { id: 10, content: 'foo' };
const reset = () => ( fulfillment = actions.refreshPost() );
it( 'yields expected action for selecting the currentPost', () => {
reset();
const { value } = fulfillment.next();
expect( value ).toEqual(
controls.select( STORE_NAME, 'getCurrentPost' )
);
} );
it( 'yields expected action for selecting the current post type', () => {
const { value } = fulfillment.next( currentPost );
expect( value ).toEqual(
controls.select( STORE_NAME, 'getCurrentPostType' )
);
} );
it( 'yields expected action for selecting the post type object', () => {
const { value } = fulfillment.next( postTypeSlug );
expect( value ).toEqual(
controls.resolveSelect( 'core', 'getPostType', postTypeSlug )
);
} );
it( 'yields expected action for the api fetch call', () => {
const { value } = fulfillment.next( postType );
// since the timestamp is a computed value we can't do a direct comparison.
// so we'll just see if the path has most of the value.
expect( value.request.path ).toEqual(
expect.stringContaining(
`/wp/v2/${ postType.rest_base }/${ currentPost.id }?context=edit&_timestamp=`
)
);
} );
it( 'yields expected action for dispatching the reset of the post', () => {
const { value } = fulfillment.next( currentPost );
expect( value ).toEqual(
controls.dispatch( STORE_NAME, 'resetPost', currentPost )
);
} );
} );
} );
describe( 'Editor actions', () => {
describe( 'setupEditor()', () => {
const post = { content: { raw: '' }, status: 'publish' };
let fulfillment;
const reset = ( edits, template ) =>
( fulfillment = actions.setupEditor( post, edits, template ) );
beforeAll( () => {
reset();
} );
it( 'should yield action object for resetPost', () => {
const { value } = fulfillment.next();
expect( value ).toEqual( actions.resetPost( post ) );
} );
it( 'should yield the SETUP_EDITOR action', () => {
const { value } = fulfillment.next();
expect( value ).toEqual( {
type: 'SETUP_EDITOR',
post: { content: { raw: '' }, status: 'publish' },
} );
} );
it( 'should yield action object for resetEditorBlocks', () => {
const { value } = fulfillment.next();
expect( Object.keys( value ) ).toEqual( [] );
} );
it( 'should yield action object for setupEditorState', () => {
const { value } = fulfillment.next();
expect( value ).toEqual(
actions.setupEditorState( {
content: { raw: '' },
status: 'publish',
} )
);
} );
} );
describe( 'resetPost', () => {
it( 'should return the RESET_POST action', () => {
const post = {};
const result = actions.resetPost( post );
expect( result ).toEqual( {
type: 'RESET_POST',
post,
} );
} );
} );
describe( 'requestPostUpdateStart', () => {
it( 'should return the REQUEST_POST_UPDATE_START action', () => {
const result = actions.__experimentalRequestPostUpdateStart();
expect( result ).toEqual( {
type: 'REQUEST_POST_UPDATE_START',
options: {},
} );
} );
} );
describe( 'editPost', () => {
it( 'should edit the relevant entity record', () => {
const edits = { format: 'sample' };
const fulfillment = actions.editPost( edits );
expect( fulfillment.next() ).toEqual( {
done: false,
value: controls.select( STORE_NAME, 'getCurrentPost' ),
} );
const post = { id: 1, type: 'post' };
expect( fulfillment.next( post ) ).toEqual( {
done: false,
value: controls.dispatch(
'core',
'editEntityRecord',
'postType',
post.type,
post.id,
edits,
undefined
),
} );
expect( fulfillment.next() ).toEqual( {
done: true,
value: undefined,
} );
} );
} );
describe( 'redo', () => {
it( 'should yield the REDO action', () => {
const fulfillment = actions.redo();
expect( fulfillment.next() ).toEqual( {
done: false,
value: controls.dispatch( 'core', 'redo' ),
} );
expect( fulfillment.next() ).toEqual( {
done: true,
value: undefined,
} );
} );
} );
describe( 'undo', () => {
it( 'should yield the UNDO action', () => {
const fulfillment = actions.undo();
expect( fulfillment.next() ).toEqual( {
done: false,
value: controls.dispatch( 'core', 'undo' ),
} );
expect( fulfillment.next() ).toEqual( {
done: true,
value: undefined,
} );
} );
} );
describe( 'lockPostSaving', () => {
it( 'should return the LOCK_POST_SAVING action', () => {
const result = actions.lockPostSaving( 'test' );
expect( result ).toEqual( {
type: 'LOCK_POST_SAVING',
lockName: 'test',
} );
} );
} );
describe( 'unlockPostSaving', () => {
it( 'should return the UNLOCK_POST_SAVING action', () => {
const result = actions.unlockPostSaving( 'test' );
expect( result ).toEqual( {
type: 'UNLOCK_POST_SAVING',
lockName: 'test',
} );
} );
} );
describe( 'lockPostAutosaving', () => {
it( 'should return the LOCK_POST_AUTOSAVING action', () => {
const result = actions.lockPostAutosaving( 'test' );
expect( result ).toEqual( {
type: 'LOCK_POST_AUTOSAVING',
lockName: 'test',
} );
} );
} );
describe( 'unlockPostAutosaving', () => {
it( 'should return the UNLOCK_POST_AUTOSAVING action', () => {
const result = actions.unlockPostAutosaving( 'test' );
expect( result ).toEqual( {
type: 'UNLOCK_POST_AUTOSAVING',
lockName: 'test',
} );
} );
} );
} );