diff --git a/client/state/posts/reducer.js b/client/state/posts/reducer.js index 35760038e099b..86686c657c5cb 100644 --- a/client/state/posts/reducer.js +++ b/client/state/posts/reducer.js @@ -23,6 +23,8 @@ import { getSerializedPostsQueryWithoutPage } from './utils'; import { DEFAULT_POST_QUERY } from './constants'; +import { itemsSchema } from './schema'; +import { isValidStateWithSchema } from 'state/utils'; /** * Tracks all known post objects, indexed by post global ID. @@ -36,8 +38,11 @@ export function items( state = {}, action ) { case POSTS_RECEIVE: return Object.assign( {}, state, keyBy( action.posts, 'global_ID' ) ); case SERIALIZE: - return {}; + return state; case DESERIALIZE: + if ( isValidStateWithSchema( state, itemsSchema ) ) { + return state; + } return {}; } return state; diff --git a/client/state/posts/schema.js b/client/state/posts/schema.js new file mode 100644 index 0000000000000..ab026525c3a09 --- /dev/null +++ b/client/state/posts/schema.js @@ -0,0 +1,53 @@ +export const itemsSchema = { + type: 'object', + patternProperties: { + //be careful to escape regexes properly + '^[0-9a-z]+$': { + type: 'object', + required: [ 'ID', 'site_ID', 'global_ID' ], + properties: { + ID: { type: 'integer' }, + site_ID: { type: 'integer' }, + global_ID: { type: 'string' }, + author: { type: 'object' }, + date: { type: 'string' }, + modified: { type: 'string' }, + title: { type: 'string' }, + URL: { type: 'string' }, + short_URL: { type: 'string' }, + content: { type: 'string' }, + excerpt: { type: 'string' }, + slug: { type: 'string' }, + guid: { type: 'string' }, + status: { type: 'string' }, + sticky: { type: 'boolean' }, + password: { type: 'string' }, + parent: { type: [ 'object', 'boolean' ] }, + type: { type: 'string' }, + discussion: { type: 'object' }, + likes_enabled: { type: 'boolean' }, + sharing_enabled: { type: 'boolean' }, + like_count: { type: 'integer' }, + i_like: { type: 'boolean' }, + is_reblogged: { type: 'boolean' }, + is_following: { type: 'boolean' }, + featured_image: { type: 'string' }, + post_thumbnail: { type: [ 'string', 'null' ] }, + format: { type: 'string' }, + geo: { type: 'boolean' }, + menu_order: { type: 'integer' }, + page_template: { type: 'string' }, + publicize_URLS: { type: 'array' }, + tags: { type: 'object' }, + categories: { type: 'object' }, + attachments: { type: 'object' }, + attachment_count: { type: 'integer' }, + metadata: { type: 'array' }, + meta: { type: 'object' }, + capabilities: { type: 'object' }, + other_URLs: { type: 'object' } + } + } + }, + additionalProperties: false +}; diff --git a/client/state/posts/test/reducer.js b/client/state/posts/test/reducer.js index a402ae628ff63..daee869a22b2c 100644 --- a/client/state/posts/test/reducer.js +++ b/client/state/posts/test/reducer.js @@ -3,6 +3,7 @@ */ import { expect } from 'chai'; import deepFreeze from 'deep-freeze'; +import sinon from 'sinon'; /** * Internal dependencies @@ -77,20 +78,52 @@ describe( 'reducer', () => { } ); } ); - it( 'never persists state because this is not implemented', () => { - const original = deepFreeze( { - '3d097cb7c5473c169bba0eb8e3c6cb64': { ID: 841, site_ID: 2916284, global_ID: '3d097cb7c5473c169bba0eb8e3c6cb64', title: 'Hello World' } + describe( 'persistence', () => { + before( () => { + sinon.stub( console, 'warn' ); + } ); + after( () => { + console.warn.restore(); + } ); + + it( 'persists state', () => { + const original = deepFreeze( { + '3d097cb7c5473c169bba0eb8e3c6cb64': { + ID: 841, + site_ID: 2916284, + global_ID: '3d097cb7c5473c169bba0eb8e3c6cb64', + title: 'Hello World' + } + } ); + const state = items( original, { type: SERIALIZE } ); + expect( state ).to.eql( original ); + } ); + + it( 'loads valid persisted state', () => { + const original = deepFreeze( { + '3d097cb7c5473c169bba0eb8e3c6cb64': { + ID: 841, + site_ID: 2916284, + global_ID: '3d097cb7c5473c169bba0eb8e3c6cb64', + title: 'Hello World' + } + } ); + const state = items( original, { type: DESERIALIZE } ); + expect( state ).to.eql( original ); + } ); + + it( 'loads default state when schema does not match', () => { + const original = deepFreeze( { + '3d097cb7c5473c169bba0eb8e3c6cb64': { + ID: 841, + site_ID: 'foo', + global_ID: '3d097cb7c5473c169bba0eb8e3c6cb64', + title: 'Hello World' + } + } ); + const state = items( original, { type: DESERIALIZE } ); + expect( state ).to.eql( {} ); } ); - const state = items( original, { type: SERIALIZE } ); - expect( state ).to.eql( {} ); - } ); - - it( 'never loads persisted state because this is not implemented', () => { - const original = deepFreeze( { - '3d097cb7c5473c169bba0eb8e3c6cb64': { ID: 841, site_ID: 2916284, global_ID: '3d097cb7c5473c169bba0eb8e3c6cb64', title: 'Hello World' } - } ); - const state = items( original, { type: DESERIALIZE } ); - expect( state ).to.eql( {} ); } ); } ); @@ -215,7 +248,7 @@ describe( 'reducer', () => { expect( state ).to.eql( {} ); } ); - it( 'never loads persisted state because this is not implemented', () => { + it( 'never persists state because this is not implemented', () => { const original = deepFreeze( { '2916284:{"search":"hello"}': { fetching: true @@ -314,7 +347,7 @@ describe( 'reducer', () => { } ); } ); - it( 'never persists state because this is not implemented', () => { + it( 'never persists state', () => { const original = deepFreeze( { '2916284:{"search":"hello"}': 1 } ); @@ -322,7 +355,7 @@ describe( 'reducer', () => { expect( state ).to.eql( {} ); } ); - it( 'never loads persisted state because this is not implemented', () => { + it( 'never loads persisted state', () => { const original = deepFreeze( { '2916284:{"search":"hello"}': 1 } ); @@ -407,7 +440,7 @@ describe( 'reducer', () => { } ); } ); - it( 'never persists state because this is not implemented', () => { + it( 'never persists state', () => { const state = siteRequests( deepFreeze( { 2916284: { 841: true @@ -419,7 +452,7 @@ describe( 'reducer', () => { expect( state ).to.eql( {} ); } ); - it( 'never loads persisted state because this is not implemented', () => { + it( 'never loads persisted state', () => { const state = siteRequests( deepFreeze( { 2916284: { 841: true