From de039bc6ceee7b139bdf6519cd72913323d93aa4 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 3 Oct 2016 11:05:38 +0300 Subject: [PATCH 1/9] State: Add actions, reducer, selectors and schema for post formats --- client/lib/wpcom-undocumented/lib/site.js | 2 +- client/state/action-types.js | 4 ++ client/state/post-formats/actions.js | 45 ++++++++++++++ client/state/post-formats/reducer.js | 74 +++++++++++++++++++++++ client/state/post-formats/schema.js | 19 ++++++ client/state/post-formats/selectors.js | 22 +++++++ 6 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 client/state/post-formats/actions.js create mode 100644 client/state/post-formats/reducer.js create mode 100644 client/state/post-formats/schema.js create mode 100644 client/state/post-formats/selectors.js diff --git a/client/lib/wpcom-undocumented/lib/site.js b/client/lib/wpcom-undocumented/lib/site.js index f63725b5974501..bcd57aa8523289 100644 --- a/client/lib/wpcom-undocumented/lib/site.js +++ b/client/lib/wpcom-undocumented/lib/site.js @@ -99,7 +99,7 @@ UndocumentedSite.prototype.domains = function( callback ) { }; UndocumentedSite.prototype.postFormatsList = function( callback ) { - this.wpcom.withLocale().req.get( '/sites/' + this._id + '/post-formats', {}, callback ); + return this.wpcom.withLocale().req.get( '/sites/' + this._id + '/post-formats', {}, callback ); }; UndocumentedSite.prototype.postAutosave = function( postId, attributes, callback ) { diff --git a/client/state/action-types.js b/client/state/action-types.js index ab631193291a46..e0aba758e3bd6e 100644 --- a/client/state/action-types.js +++ b/client/state/action-types.js @@ -187,6 +187,10 @@ export const POST_DELETE_FAILURE = 'POST_DELETE_FAILURE'; export const POST_DELETE_SUCCESS = 'POST_DELETE_SUCCESS'; export const POST_EDIT = 'POST_EDIT'; export const POST_EDITS_RESET = 'POST_EDITS_RESET'; +export const POST_FORMATS_RECEIVE = 'POST_FORMATS_RECEIVE'; +export const POST_FORMATS_REQUEST = 'POST_FORMATS_REQUEST'; +export const POST_FORMATS_REQUEST_FAILURE = 'POST_FORMATS_REQUEST_FAILURE'; +export const POST_FORMATS_REQUEST_SUCCESS = 'POST_FORMATS_REQUEST_SUCCESS'; export const POST_REQUEST = 'POST_REQUEST'; export const POST_REQUEST_FAILURE = 'POST_REQUEST_FAILURE'; export const POST_REQUEST_SUCCESS = 'POST_REQUEST_SUCCESS'; diff --git a/client/state/post-formats/actions.js b/client/state/post-formats/actions.js new file mode 100644 index 00000000000000..ab39278f359147 --- /dev/null +++ b/client/state/post-formats/actions.js @@ -0,0 +1,45 @@ +/** + * Internal dependencies + */ +import wpcom from 'lib/wp'; +import { + POST_FORMATS_RECEIVE, + POST_FORMATS_REQUEST, + POST_FORMATS_REQUEST_SUCCESS, + POST_FORMATS_REQUEST_FAILURE +} from 'state/action-types'; + +/** + * Returns an action thunk which, when invoked, triggers a network request to + * retrieve post formats for a site. + * + * @param {Number} siteId Site ID + * @return {Function} Action thunk + */ +export function requestPostFormats( siteId ) { + return ( dispatch ) => { + dispatch( { + type: POST_FORMATS_REQUEST, + siteId + } ); + + return wpcom.undocumented().site( siteId ).postFormatsList().then( ( { formats } ) => { + dispatch( { + type: POST_FORMATS_RECEIVE, + siteId, + formats + } ); + + dispatch( { + type: POST_FORMATS_REQUEST_SUCCESS, + siteId + } ); + } ).catch( ( error ) => { + dispatch( { + type: POST_FORMATS_REQUEST_FAILURE, + siteId, + error + } ); + } ); + }; +} diff --git a/client/state/post-formats/reducer.js b/client/state/post-formats/reducer.js new file mode 100644 index 00000000000000..49979241c3676b --- /dev/null +++ b/client/state/post-formats/reducer.js @@ -0,0 +1,74 @@ +/** + * External dependencies + */ +import { combineReducers } from 'redux'; + +/** + * Internal dependencies + */ +import { isValidStateWithSchema } from 'state/utils'; +import * as schema from './schema'; +import { + SERIALIZE, + DESERIALIZE, + POST_FORMATS_RECEIVE, + POST_FORMATS_REQUEST, + POST_FORMATS_REQUEST_SUCCESS, + POST_FORMATS_REQUEST_FAILURE +} from 'state/action-types'; + +/** + * Returns the updated requests state after an action has been dispatched. The + * state maps site ID keys to whether a request for post formats is in progress. + * + * @param {Object} state Current state + * @param {Object} action Action payload + * @return {Object} Updated state + */ +export function requesting( state = {}, action ) { + switch ( action.type ) { + case POST_FORMATS_REQUEST: + case POST_FORMATS_REQUEST_SUCCESS: + case POST_FORMATS_REQUEST_FAILURE: + return Object.assign( {}, state, { + [ action.siteId ]: POST_FORMATS_REQUEST === action.type + } ); + + case SERIALIZE: + case DESERIALIZE: + return {}; + } + + return state; +} + +/** + * Returns the updated items state after an action has been dispatched. The + * state maps site ID keys to an object that contains the site supported post formats. + * + * @param {Object} state Current state + * @param {Object} action Action payload + * @return {Object} Updated state + */ +export function items( state = {}, action ) { + switch ( action.type ) { + case POST_FORMATS_RECEIVE: + return Object.assign( {}, state, { + [ action.siteId ]: action.formats + } ); + + case DESERIALIZE: + if ( isValidStateWithSchema( state, schema.items ) ) { + return state; + } + + return {}; + } + + return state; +} + +export default combineReducers( { + requesting, + items +} ); diff --git a/client/state/post-formats/schema.js b/client/state/post-formats/schema.js new file mode 100644 index 00000000000000..883b88549bb837 --- /dev/null +++ b/client/state/post-formats/schema.js @@ -0,0 +1,19 @@ +export const items = { + type: 'object', + additionalProperties: false, + patternProperties: { + // Site ID + '^\\d+$': { + type: 'object', + description: 'List of supported post formats.', + additionalProperties: false, + patternProperties: { + // ID of the post format + '^[0-9a-z]+$': { + type: 'string', + description: 'Label of the post format', + } + } + } + } +}; diff --git a/client/state/post-formats/selectors.js b/client/state/post-formats/selectors.js new file mode 100644 index 00000000000000..4cec7d58f3515f --- /dev/null +++ b/client/state/post-formats/selectors.js @@ -0,0 +1,22 @@ +/** + * Returns true if currently requesting post formats for the specified site ID, or + * false otherwise. + * + * @param {Object} state Global state tree + * @param {Number} siteId Site ID + * @return {Boolean} Whether post formats are being requested + */ +export function isRequestingPostFormats( state, siteId ) { + return !! state.postFormats.requesting[ siteId ]; +} + +/** + * Returns the supported post formats for a site. + * + * @param {Object} state Global state tree + * @param {Number} siteId Site ID + * @return {?Object} Site post formats + */ +export function getPostFormats( state, siteId ) { + return state.postFormats.items[ siteId ] || null; +} From a7c0701ee40aefbdceb68b2541aa7b4acd79c5f5 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 3 Oct 2016 11:06:22 +0300 Subject: [PATCH 2/9] State: Add README for post formats --- client/state/post-formats/README.md | 62 +++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 client/state/post-formats/README.md diff --git a/client/state/post-formats/README.md b/client/state/post-formats/README.md new file mode 100644 index 00000000000000..b0db9265137ef8 --- /dev/null +++ b/client/state/post-formats/README.md @@ -0,0 +1,62 @@ +Post Formats +=============== + +A module for managing post formats. + +## Actions + +Used in combination with the Redux store instance `dispatch` function, actions can be used in manipulating the current global state. + +### `requestPostFormats( siteId: number )` + +Get a list of supported post formats for a given site. + +```js +import { requestPostFormats } from 'state/post-formats/actions'; + +requestPostFormats( 12345678 ); +``` + +## Reducer + +Data from the aforementioned actions is added to the global state tree, under `postFormats`, with the following structure: + +```js +state.postFormats = { + requesting: { + 12345678: false, + 87654321: true + }, + items: { + 12345678: { + image: 'Image', + video: 'Video' + }, + 87654321: { + status: 'Status' + } + } +} +``` + +## Selectors are intended to assist in extracting data from the global state tree for consumption by other modules. + +#### `isRequestingPostFormats` + +Returns true if post formats are currently fetching for the given site ID. + +```js +import { isRequestingPostFormats } from 'state/post-formats/selectors'; + +const isRequesting = isRequestingPostFormats( state, 12345678 ); +``` + +#### `getPostFormats` + +Returns an array of all supported site formats for the given site ID. + +```js +import { getPostFormats } from 'state/post-formats/selectors'; + +const postFormats = getPostFormats( state, 12345678 ); +``` From 7894ae67e81cc94e17849c1c4730b830b2ca5163 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 3 Oct 2016 11:09:09 +0300 Subject: [PATCH 3/9] State: Add tests for post formats actions, reducer and selectors --- client/state/post-formats/test/actions.js | 87 ++++++++ client/state/post-formats/test/reducer.js | 230 ++++++++++++++++++++ client/state/post-formats/test/selectors.js | 80 +++++++ 3 files changed, 397 insertions(+) create mode 100644 client/state/post-formats/test/actions.js create mode 100644 client/state/post-formats/test/reducer.js create mode 100644 client/state/post-formats/test/selectors.js diff --git a/client/state/post-formats/test/actions.js b/client/state/post-formats/test/actions.js new file mode 100644 index 00000000000000..fda8aec04ec093 --- /dev/null +++ b/client/state/post-formats/test/actions.js @@ -0,0 +1,87 @@ +/** + * External dependencies + */ +import sinon from 'sinon'; +import { expect } from 'chai'; +import { useSandbox } from 'test/helpers/use-sinon'; +import useNock from 'test/helpers/use-nock'; + +/** + * Internal dependencies + */ +import { + POST_FORMATS_RECEIVE, + POST_FORMATS_REQUEST, + POST_FORMATS_REQUEST_SUCCESS, + POST_FORMATS_REQUEST_FAILURE +} from 'state/action-types'; +import { requestPostFormats } from '../actions'; + +describe( 'actions', () => { + let spy; + useSandbox( ( sandbox ) => spy = sandbox.spy() ); + + describe( '#requestPostFormats()', () => { + useNock( ( nock ) => { + nock( 'https://public-api.wordpress.com:443' ) + .persist() + .get( '/rest/v1.1/sites/12345678/post-formats' ) + .reply( 200, { + formats: { + image: 'Image', + video: 'Video', + link: 'Link' + } + } ) + .get( '/rest/v1.1/sites/87654321/post-formats' ) + .reply( 403, { + error: 'authorization_required', + message: 'User cannot access this private blog.' + } ); + } ); + + it( 'should dispatch fetch action when thunk triggered', () => { + requestPostFormats( 12345678 )( spy ); + + expect( spy ).to.have.been.calledWith( { + type: POST_FORMATS_REQUEST, + siteId: 12345678 + } ); + } ); + + it( 'should dispatch receive action when request completes', () => { + return requestPostFormats( 12345678 )( spy ).then( () => { + expect( spy ).to.have.been.calledWith( { + type: POST_FORMATS_RECEIVE, + siteId: 12345678, + formats: { + image: 'Image', + video: 'Video', + link: 'Link' + } + } ); + } ); + } ); + + it( 'should dispatch request success action when request completes', () => { + return requestPostFormats( 12345678 )( spy ).then( () => { + expect( spy ).to.have.been.calledWith( { + type: POST_FORMATS_REQUEST_SUCCESS, + siteId: 12345678 + } ); + } ); + } ); + + it( 'should dispatch request failure action when request fails', () => { + return requestPostFormats( 87654321 )( spy ).then( () => { + expect( spy ).to.have.been.calledWith( { + type: POST_FORMATS_REQUEST_FAILURE, + siteId: 87654321, + error: sinon.match( { + message: 'User cannot access this private blog.' + } ) + } ); + } ); + } ); + } ); +} ); diff --git a/client/state/post-formats/test/reducer.js b/client/state/post-formats/test/reducer.js new file mode 100644 index 00000000000000..b8156543ce5c6f --- /dev/null +++ b/client/state/post-formats/test/reducer.js @@ -0,0 +1,230 @@ +/** + * External dependencies + */ +import { expect } from 'chai'; +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ +import { + POST_FORMATS_RECEIVE, + POST_FORMATS_REQUEST, + POST_FORMATS_REQUEST_FAILURE, + POST_FORMATS_REQUEST_SUCCESS, + SERIALIZE, + DESERIALIZE +} from 'state/action-types'; +import reducer, { requesting, items } from '../reducer'; +import { useSandbox } from 'test/helpers/use-sinon'; + +describe( 'reducer', () => { + useSandbox( ( sandbox ) => { + sandbox.stub( console, 'warn' ); + } ); + + it( 'should include expected keys in return value', () => { + expect( reducer( undefined, {} ) ).to.have.keys( [ + 'requesting', + 'items' + ] ); + } ); + + describe( '#requesting()', () => { + it( 'should default to an empty object', () => { + const state = requesting( undefined, {} ); + + expect( state ).to.eql( {} ); + } ); + + it( 'should set site ID to true value if a request is initiated', () => { + const state = requesting( undefined, { + type: POST_FORMATS_REQUEST, + siteId: 12345678 + } ); + + expect( state ).to.eql( { + 12345678: true + } ); + } ); + + it( 'should accumulate the requested site IDs', () => { + const state = requesting( deepFreeze( { + 12345678: true + } ), { + type: POST_FORMATS_REQUEST, + siteId: 87654321 + } ); + + expect( state ).to.eql( { + 12345678: true, + 87654321: true + } ); + } ); + + it( 'should set site ID to false if request finishes successfully', () => { + const state = requesting( deepFreeze( { + 12345678: true + } ), { + type: POST_FORMATS_REQUEST_SUCCESS, + siteId: 12345678 + } ); + + expect( state ).to.eql( { + 12345678: false + } ); + } ); + + it( 'should set site ID to false if request finishes unsuccessfully', () => { + const state = requesting( deepFreeze( { + 12345678: true + } ), { + type: POST_FORMATS_REQUEST_FAILURE, + siteId: 12345678 + } ); + + expect( state ).to.eql( { + 12345678: false + } ); + } ); + + it( 'should not persist state', () => { + const state = requesting( deepFreeze( { + 12345678: true + } ), { + type: SERIALIZE + } ); + + expect( state ).to.eql( {} ); + } ); + + it( 'should not load persisted state', () => { + const state = requesting( deepFreeze( { + 12345678: true + } ), { + type: DESERIALIZE + } ); + + expect( state ).to.eql( {} ); + } ); + } ); + + describe( '#items()', () => { + it( 'should default to an empty object', () => { + const state = items( undefined, {} ); + + expect( state ).to.eql( {} ); + } ); + + it( 'should index post formats by site ID', () => { + const state = items( null, { + type: POST_FORMATS_RECEIVE, + siteId: 12345678, + formats: { + image: 'Image', + video: 'Video', + link: 'Link' + } + } ); + + expect( state ).to.eql( { + 12345678: { + image: 'Image', + video: 'Video', + link: 'Link' + } + } ); + } ); + + it( 'should accumulate sites', () => { + const state = items( deepFreeze( { + 12345678: { + image: 'Image', + video: 'Video', + link: 'Link' + } + } ), { + type: POST_FORMATS_RECEIVE, + siteId: 87654321, + formats: { + status: 'Status' + } + } ); + + expect( state ).to.eql( { + 12345678: { + image: 'Image', + video: 'Video', + link: 'Link' + }, + 87654321: { + status: 'Status' + } + } ); + } ); + + it( 'should override previous post formats of same site ID', () => { + const state = items( deepFreeze( { + 12345678: { + image: 'Image', + video: 'Video', + link: 'Link' + } + } ), { + type: POST_FORMATS_RECEIVE, + siteId: 12345678, + formats: { + status: 'Status' + } + } ); + + expect( state ).to.eql( { + 12345678: { + status: 'Status' + } + } ); + } ); + + it( 'should persist state', () => { + const state = items( deepFreeze( { + 12345678: { + status: 'Status' + } + } ), { + type: SERIALIZE + } ); + + expect( state ).to.eql( { + 12345678: { + status: 'Status' + } + } ); + } ); + + it( 'should load valid persisted state', () => { + const state = items( deepFreeze( { + 12345678: { + status: 'Status' + } + } ), { + type: DESERIALIZE + } ); + + expect( state ).to.eql( { + 12345678: { + status: 'Status' + } + } ); + } ); + + it( 'should not load invalid persisted state', () => { + const state = items( deepFreeze( { + status: 'Status' + } ), { + type: DESERIALIZE + } ); + + expect( state ).to.eql( {} ); + } ); + } ); +} ); diff --git a/client/state/post-formats/test/selectors.js b/client/state/post-formats/test/selectors.js new file mode 100644 index 00000000000000..4c8f275b3f6cd9 --- /dev/null +++ b/client/state/post-formats/test/selectors.js @@ -0,0 +1,80 @@ +/** + * External dependencies + */ +import { expect } from 'chai'; + +/** + * Internal dependencies + */ +import { + isRequestingPostFormats, + getPostFormats +} from '../selectors'; + +describe( 'selectors', () => { + describe( '#isRequestingPostFormats()', () => { + it( 'should return false if the site has never been fetched', () => { + const isRequesting = isRequestingPostFormats( { + postFormats: { + requesting: {} + } + }, 12345678 ); + + expect( isRequesting ).to.be.false; + } ); + + it( 'should return false if the site is not fetching', () => { + const isRequesting = isRequestingPostFormats( { + postFormats: { + requesting: { + 12345678: false + } + } + }, 12345678 ); + + expect( isRequesting ).to.be.false; + } ); + + it( 'should return true if the site is fetching', () => { + const isRequesting = isRequestingPostFormats( { + postFormats: { + requesting: { + 12345678: true + } + } + }, 12345678 ); + + expect( isRequesting ).to.be.true; + } ); + } ); + + describe( '#getPostFormats()', () => { + it( 'should return null if the site has never been fetched', () => { + const postFormats = getPostFormats( { + postFormats: { + items: {} + } + }, 12345678 ); + + expect( postFormats ).to.be.null; + } ); + + it( 'should return the post formats for a site', () => { + const postFormats = getPostFormats( { + postFormats: { + items: { + 12345678: { + image: 'Image', + link: 'Link' + } + } + } + }, 12345678 ); + + expect( postFormats ).to.eql( { + image: 'Image', + link: 'Link' + } ); + } ); + } ); +} ); From d38b3a4826f63169e96d43b97891c9942f775187 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Mon, 3 Oct 2016 11:34:08 +0300 Subject: [PATCH 4/9] Components: Add QueryPostFormats query component --- .../data/query-post-formats/README.md | 38 ++++++++++++++ .../data/query-post-formats/index.jsx | 50 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 client/components/data/query-post-formats/README.md create mode 100644 client/components/data/query-post-formats/index.jsx diff --git a/client/components/data/query-post-formats/README.md b/client/components/data/query-post-formats/README.md new file mode 100644 index 00000000000000..13a2be9cebec52 --- /dev/null +++ b/client/components/data/query-post-formats/README.md @@ -0,0 +1,38 @@ +Query Post Formats +================ + +`` is a React component used in managing network requests for post formats. + +## Usage + +Render the component, passing `siteId`. It does not accept any children, nor does it render any elements to the page. You can use it adjacent to other sibling components which make use of the fetched data made available through the global application state. + +```jsx +import React from 'react'; +import QueryPostFormats from 'components/data/query-post-formats'; +import MyPostFormatsListItem from './list-item'; + +export default function MyPostFormatsList( { postFormats } ) { + return ( +
+ + { postFormats.map( ( label, id ) => { + return ( + + ); + } } +
+ ); +} +``` + +## Props + +### `siteId` + + + + +
TypeNumber
RequiredYes
+ +The site ID for which post formats should be requested. diff --git a/client/components/data/query-post-formats/index.jsx b/client/components/data/query-post-formats/index.jsx new file mode 100644 index 00000000000000..4a9fae85e2592e --- /dev/null +++ b/client/components/data/query-post-formats/index.jsx @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; + +/** + * Internal dependencies + */ +import { isRequestingPostFormats } from 'state/post-types/selectors'; +import { requestPostFormats } from 'state/post-types/actions'; + +class QueryPostFormats extends Component { + static propTypes = { + siteId: PropTypes.number.isRequired, + requestingPostFormats: PropTypes.bool, + requestPostFormats: PropTypes.func + }; + + componentWillMount() { + this.request( this.props ); + } + + componentWillReceiveProps( nextProps ) { + if ( this.props.siteId !== nextProps.siteId ) { + this.request( nextProps ); + } + } + + request( props ) { + if ( props.requestingPostFormats ) { + return; + } + + props.requestPostFormats( props.siteId ); + } + + render() { + return null; + } +} + +export default connect( + ( state, ownProps ) => { + return { + requestingPostFormats: isRequestingPostFormats( state, ownProps.siteId ) + }; + }, + { requestPostFormats } +)( QueryPostFormats ); From 25e67ee392948fe17941995fce386ae05c71f27a Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Tue, 4 Oct 2016 12:06:45 +0300 Subject: [PATCH 5/9] State: Rename post formats items schema --- client/state/post-formats/reducer.js | 4 ++-- client/state/post-formats/schema.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/state/post-formats/reducer.js b/client/state/post-formats/reducer.js index 49979241c3676b..160decc084f4dd 100644 --- a/client/state/post-formats/reducer.js +++ b/client/state/post-formats/reducer.js @@ -7,7 +7,7 @@ import { combineReducers } from 'redux'; * Internal dependencies */ import { isValidStateWithSchema } from 'state/utils'; -import * as schema from './schema'; +import { postFormatsItemsSchema } from './schema'; import { SERIALIZE, DESERIALIZE, @@ -58,7 +58,7 @@ export function items( state = {}, action ) { } ); case DESERIALIZE: - if ( isValidStateWithSchema( state, schema.items ) ) { + if ( isValidStateWithSchema( state, postFormatsItemsSchema ) ) { return state; } diff --git a/client/state/post-formats/schema.js b/client/state/post-formats/schema.js index 883b88549bb837..8ea01fe70ef758 100644 --- a/client/state/post-formats/schema.js +++ b/client/state/post-formats/schema.js @@ -1,4 +1,4 @@ -export const items = { +export const postFormatsItemsSchema = { type: 'object', additionalProperties: false, patternProperties: { From 772304b9aa05a6016e4d7954d91affb97a08a4f4 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Tue, 4 Oct 2016 12:09:07 +0300 Subject: [PATCH 6/9] State: Use createReducer to validate post formats reducer items schema --- client/state/post-formats/reducer.js | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/client/state/post-formats/reducer.js b/client/state/post-formats/reducer.js index 160decc084f4dd..92485cbfa753af 100644 --- a/client/state/post-formats/reducer.js +++ b/client/state/post-formats/reducer.js @@ -6,8 +6,8 @@ import { combineReducers } from 'redux'; /** * Internal dependencies */ -import { isValidStateWithSchema } from 'state/utils'; import { postFormatsItemsSchema } from './schema'; +import { createReducer } from 'state/utils'; import { SERIALIZE, DESERIALIZE, @@ -50,23 +50,11 @@ export function requesting( state = {}, action ) { * @param {Object} action Action payload * @return {Object} Updated state */ -export function items( state = {}, action ) { - switch ( action.type ) { - case POST_FORMATS_RECEIVE: - return Object.assign( {}, state, { - [ action.siteId ]: action.formats - } ); - - case DESERIALIZE: - if ( isValidStateWithSchema( state, postFormatsItemsSchema ) ) { - return state; - } - - return {}; +export const items = createReducer( {}, { + [ POST_FORMATS_RECEIVE ]: ( state, { siteId, formats } ) => { + return { ...state, [ siteId ]: formats }; } - - return state; -} +}, postFormatsItemsSchema ); export default combineReducers( { requesting, From 69318239669cb30095b542b4013b81fd159fb51a Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Tue, 4 Oct 2016 12:22:20 +0300 Subject: [PATCH 7/9] State: Support dashes and undescores in post formats items schema ID --- client/state/post-formats/schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/state/post-formats/schema.js b/client/state/post-formats/schema.js index 8ea01fe70ef758..a1412cae9aa037 100644 --- a/client/state/post-formats/schema.js +++ b/client/state/post-formats/schema.js @@ -9,7 +9,7 @@ export const postFormatsItemsSchema = { additionalProperties: false, patternProperties: { // ID of the post format - '^[0-9a-z]+$': { + '^[0-9a-z\-_]+$': { type: 'string', description: 'Label of the post format', } From 4f17df9e088ee894c861b0da429c1e8c08c9ebd0 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Thu, 6 Oct 2016 11:40:33 +0300 Subject: [PATCH 8/9] State: Use createReducer for the requesting post formats reducer --- client/state/post-formats/reducer.js | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/client/state/post-formats/reducer.js b/client/state/post-formats/reducer.js index 92485cbfa753af..65485ec01d5463 100644 --- a/client/state/post-formats/reducer.js +++ b/client/state/post-formats/reducer.js @@ -9,8 +9,6 @@ import { combineReducers } from 'redux'; import { postFormatsItemsSchema } from './schema'; import { createReducer } from 'state/utils'; import { - SERIALIZE, - DESERIALIZE, POST_FORMATS_RECEIVE, POST_FORMATS_REQUEST, POST_FORMATS_REQUEST_SUCCESS, @@ -25,22 +23,11 @@ import { * @param {Object} action Action payload * @return {Object} Updated state */ -export function requesting( state = {}, action ) { - switch ( action.type ) { - case POST_FORMATS_REQUEST: - case POST_FORMATS_REQUEST_SUCCESS: - case POST_FORMATS_REQUEST_FAILURE: - return Object.assign( {}, state, { - [ action.siteId ]: POST_FORMATS_REQUEST === action.type - } ); - - case SERIALIZE: - case DESERIALIZE: - return {}; - } - - return state; -} +export const requesting = createReducer( {}, { + [ POST_FORMATS_REQUEST ]: ( state, { siteId } ) => ( { ...state, [ siteId ]: true } ), + [ POST_FORMATS_REQUEST_SUCCESS ]: ( state, { siteId } ) => ( { ...state, [ siteId ]: false } ), + [ POST_FORMATS_REQUEST_FAILURE ]: ( state, { siteId } ) => ( { ...state, [ siteId ]: false } ) +} ); /** * Returns the updated items state after an action has been dispatched. The From df79fd8bd62fa1f02b06deca0427bc37eeec96d8 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Thu, 6 Oct 2016 11:49:50 +0300 Subject: [PATCH 9/9] State: Include the postFormats reducer in the main combined reducer --- client/state/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/state/index.js b/client/state/index.js index 08431e78ce0acf..a6072b3c0aa2b5 100644 --- a/client/state/index.js +++ b/client/state/index.js @@ -26,6 +26,7 @@ import notices from './notices/reducer'; import pageTemplates from './page-templates/reducer'; import plans from './plans/reducer'; import plugins from './plugins/reducer'; +import postFormats from './post-formats/reducer'; import posts from './posts/reducer'; import postTypes from './post-types/reducer'; import preferences from './preferences/reducer'; @@ -69,10 +70,11 @@ export const reducer = combineReducers( { pageTemplates, plugins, plans, - preferences, - preview, + postFormats, posts, postTypes, + preferences, + preview, productsList, purchases, pushNotifications,