diff --git a/src/actions/entries.js b/src/actions/entries.js index 0269385b..0421278d 100644 --- a/src/actions/entries.js +++ b/src/actions/entries.js @@ -211,13 +211,14 @@ export function createEmptyDraft(collection) { }; } -export function persistEntry(collection, entry) { +export function persistEntry(collection, entryDraft) { return (dispatch, getState) => { const state = getState(); const backend = currentBackend(state.config); - const mediaProxies = entry.get('mediaFiles').map(path => getMedia(state, path)); + const mediaProxies = entryDraft.get('mediaFiles').map(path => getMedia(state, path)); + const entry = entryDraft.get('entry'); dispatch(entryPersisting(collection, entry)); - backend.persistEntry(state.config, collection, entry, mediaProxies.toJS()).then( + backend.persistEntry(state.config, collection, entryDraft, mediaProxies.toJS()).then( () => dispatch(entryPersisted(collection, entry)), error => dispatch(entryPersistFail(collection, entry, error)) ); diff --git a/src/containers/EntryPage.js b/src/containers/EntryPage.js index b2014112..05a583a7 100644 --- a/src/containers/EntryPage.js +++ b/src/containers/EntryPage.js @@ -35,12 +35,12 @@ class EntryPage extends React.Component { }; componentDidMount() { - const { entry, collection, slug } = this.props; + const { entry, newEntry, collection, slug, createEmptyDraft, loadEntry } = this.props; - if (this.props.newEntry) { - this.props.createEmptyDraft(this.props.collection); + if (newEntry) { + createEmptyDraft(collection); } else { - this.props.loadEntry(entry, collection, slug); + loadEntry(entry, collection, slug); this.createDraft(entry); } } @@ -63,7 +63,8 @@ class EntryPage extends React.Component { }; handlePersistEntry = () => { - this.props.persistEntry(this.props.collection, this.props.entryDraft); + const { persistEntry, collection, entryDraft } = this.props; + persistEntry(collection, entryDraft); }; render() { @@ -105,7 +106,15 @@ function mapStateToProps(state, ownProps) { const slug = ownProps.params.slug; const entry = newEntry ? null : selectEntry(state, collection.get('name'), slug); const boundGetMedia = getMedia.bind(null, state); - return { collection, collections, newEntry, entryDraft, boundGetMedia, slug, entry }; + return { + collection, + collections, + newEntry, + entryDraft, + boundGetMedia, + slug, + entry, + }; } export default connect( diff --git a/src/reducers/__tests__/entries.spec.js b/src/reducers/__tests__/entries.spec.js index 29978765..591e7709 100644 --- a/src/reducers/__tests__/entries.spec.js +++ b/src/reducers/__tests__/entries.spec.js @@ -1,16 +1,15 @@ -import Immutable, { Map, OrderedMap, fromJS } from 'immutable'; +import { Map, OrderedMap, fromJS } from 'immutable'; import * as actions from '../../actions/entries'; import reducer from '../entries'; -let initialState; +const initialState = OrderedMap({ + posts: Map({ name: 'posts' }), +}); describe('entries', () => { it('should mark entries as fetching', () => { - const state = OrderedMap({ - posts: Map({ name: 'posts' }), - }); expect( - reducer(state, actions.entriesLoading(Map({ name: 'posts' }))) + reducer(initialState, actions.entriesLoading(Map({ name: 'posts' }))) ).toEqual( OrderedMap(fromJS({ posts: { name: 'posts' }, @@ -22,12 +21,9 @@ describe('entries', () => { }); it('should handle loaded entries', () => { - const state = OrderedMap({ - posts: Map({ name: 'posts' }), - }); const entries = [{ slug: 'a', path: '' }, { slug: 'b', title: 'B' }]; expect( - reducer(state, actions.entriesLoaded(Map({ name: 'posts' }), entries, 0)) + reducer(initialState, actions.entriesLoaded(Map({ name: 'posts' }), entries, 0)) ).toEqual( OrderedMap(fromJS( { @@ -46,51 +42,4 @@ describe('entries', () => { )) ); }); - - describe('entry persisting', () => { - beforeEach(() => { - initialState = Immutable.fromJS({ - entities: { - 'posts.slug': { - collection: 'posts', - slug: 'slug', - path: 'content/blog/art-and-wine-festival.md', - partial: false, - raw: '', - data: {}, - metaData: null, - }, - }, - pages: {}, - }); - }); - - it('should handle persisting request', () => { - const newState = reducer( - initialState, - actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' })) - ); - expect(newState.getIn(['entities', 'posts.slug', 'isPersisting'])).toBe(true); - }); - - it('should handle persisting success', () => { - let newState = reducer(initialState, - actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' })) - ); - newState = reducer(newState, - actions.entryPersisted(Map({ name: 'posts' }), Map({ slug: 'slug' })) - ); - expect(newState.getIn(['entities', 'posts.slug', 'isPersisting'])).toBeUndefined(); - }); - - it('should handle persisting error', () => { - let newState = reducer(initialState, - actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' })) - ); - newState = reducer(newState, - actions.entryPersistFail(Map({ name: 'posts' }), Map({ slug: 'slug' }), 'Error message') - ); - expect(newState.getIn(['entities', 'posts.slug', 'isPersisting'])).toBeUndefined(); - }); - }); }); diff --git a/src/reducers/__tests__/entryDraft.spec.js b/src/reducers/__tests__/entryDraft.spec.js new file mode 100644 index 00000000..d9c76705 --- /dev/null +++ b/src/reducers/__tests__/entryDraft.spec.js @@ -0,0 +1,126 @@ +import { Map, List, fromJS } from 'immutable'; +import * as actions from '../../actions/entries'; +import reducer from '../entryDraft'; + +let initialState = Map({ entry: Map(), mediaFiles: List() }); + +const entry = { + collection: 'posts', + slug: 'slug', + path: 'content/blog/art-and-wine-festival.md', + partial: false, + raw: '', + data: {}, + metaData: null, +}; + +describe('entryDraft reducer', () => { + describe('DRAFT_CREATE_FROM_ENTRY', () => { + it('should create draft from the entry', () => { + expect( + reducer( + initialState, + actions.createDraftFromEntry(fromJS(entry)) + ) + ).toEqual( + fromJS({ + entry: { + ...entry, + newRecord: false, + }, + mediaFiles: [], + }) + ); + }); + }); + + describe('DRAFT_CREATE_EMPTY', () => { + it('should create a new draft ', () => { + expect( + reducer( + initialState, + actions.emmptyDraftCreated(fromJS(entry)) + ) + ).toEqual( + fromJS({ + entry: { + ...entry, + newRecord: true, + }, + mediaFiles: [], + }) + ); + }); + }); + + describe('DRAFT_DISCARD', () => { + it('should discard the draft and return initial state', () => { + expect(reducer(initialState, actions.discardDraft())) + .toEqual(initialState); + }); + }); + + describe('DRAFT_CHANGE', () => { + it.skip('should update the draft', () => { + const newEntry = { + ...entry, + raw: 'updated', + }; + expect(reducer(initialState, actions.changeDraft(newEntry))) + .toEqual(fromJS({ + entry: { + ...entry, + raw: 'updated', + }, + mediaFiles: [], + })); + }); + }); + + describe('persisting', () => { + beforeEach(() => { + initialState = fromJS({ + entities: { + 'posts.slug': { + collection: 'posts', + slug: 'slug', + path: 'content/blog/art-and-wine-festival.md', + partial: false, + raw: '', + data: {}, + metaData: null, + }, + }, + pages: {}, + }); + }); + + it('should handle persisting request', () => { + const newState = reducer( + initialState, + actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' })) + ); + expect(newState.getIn(['entry', 'isPersisting'])).toBe(true); + }); + + it('should handle persisting success', () => { + let newState = reducer(initialState, + actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' })) + ); + newState = reducer(newState, + actions.entryPersisted(Map({ name: 'posts' }), Map({ slug: 'slug' })) + ); + expect(newState.getIn(['entry', 'isPersisting'])).toBeUndefined(); + }); + + it('should handle persisting error', () => { + let newState = reducer(initialState, + actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' })) + ); + newState = reducer(newState, + actions.entryPersistFail(Map({ name: 'posts' }), Map({ slug: 'slug' }), 'Error message') + ); + expect(newState.getIn(['entry', 'isPersisting'])).toBeUndefined(); + }); + }); +}); diff --git a/src/reducers/entries.js b/src/reducers/entries.js index 4fccb962..c1971af4 100644 --- a/src/reducers/entries.js +++ b/src/reducers/entries.js @@ -2,9 +2,6 @@ import { Map, List, fromJS } from 'immutable'; import { ENTRY_REQUEST, ENTRY_SUCCESS, - ENTRY_PERSIST_REQUEST, - ENTRY_PERSIST_SUCCESS, - ENTRY_PERSIST_FAILURE, ENTRIES_REQUEST, ENTRIES_SUCCESS, SEARCH_ENTRIES_REQUEST, @@ -16,10 +13,6 @@ let loadedEntries; let page; let searchTerm; -function getEntryPath(collectionName, entrySlug) { - return `${ collectionName }.${ entrySlug }`; -} - const entries = (state = Map({ entities: Map(), pages: Map() }), action) => { switch (action.type) { case ENTRY_REQUEST: @@ -31,17 +24,6 @@ const entries = (state = Map({ entities: Map(), pages: Map() }), action) => { fromJS(action.payload.entry) ); - case ENTRY_PERSIST_REQUEST: { - const { collectionName, entrySlug } = action.payload; - return state.setIn(['entities', getEntryPath(collectionName, entrySlug), 'isPersisting'], true); - } - - case ENTRY_PERSIST_SUCCESS: - case ENTRY_PERSIST_FAILURE: { - const { collectionName, entrySlug } = action.payload; - return state.deleteIn(['entities', getEntryPath(collectionName, entrySlug), 'isPersisting']); - } - case ENTRIES_REQUEST: return state.setIn(['pages', action.payload.collection, 'isFetching'], true); diff --git a/src/reducers/entryDraft.js b/src/reducers/entryDraft.js index d9100ca0..a617932d 100644 --- a/src/reducers/entryDraft.js +++ b/src/reducers/entryDraft.js @@ -1,10 +1,21 @@ import { Map, List, fromJS } from 'immutable'; -import { DRAFT_CREATE_FROM_ENTRY, DRAFT_CREATE_EMPTY, DRAFT_DISCARD, DRAFT_CHANGE } from '../actions/entries'; -import { ADD_MEDIA, REMOVE_MEDIA } from '../actions/media'; +import { + DRAFT_CREATE_FROM_ENTRY, + DRAFT_CREATE_EMPTY, + DRAFT_DISCARD, + DRAFT_CHANGE, + ENTRY_PERSIST_REQUEST, + ENTRY_PERSIST_SUCCESS, + ENTRY_PERSIST_FAILURE, +} from '../actions/entries'; +import { + ADD_MEDIA, + REMOVE_MEDIA, +} from '../actions/media'; const initialState = Map({ entry: Map(), mediaFiles: List() }); -const entryDraft = (state = Map(), action) => { +const entryDraftReducer = (state = Map(), action) => { switch (action.type) { case DRAFT_CREATE_FROM_ENTRY: // Existing Entry @@ -25,14 +36,23 @@ const entryDraft = (state = Map(), action) => { case DRAFT_CHANGE: return state.set('entry', action.payload); + case ENTRY_PERSIST_REQUEST: { + return state.setIn(['entry', 'isPersisting'], true); + } + + case ENTRY_PERSIST_SUCCESS: + case ENTRY_PERSIST_FAILURE: { + return state.deleteIn(['entry', 'isPersisting']); + } + case ADD_MEDIA: - return state.update('mediaFiles', (list) => list.push(action.payload.public_path)); + return state.update('mediaFiles', list => list.push(action.payload.public_path)); case REMOVE_MEDIA: - return state.update('mediaFiles', (list) => list.filterNot((path) => path === action.payload)); + return state.update('mediaFiles', list => list.filterNot(path => path === action.payload)); default: return state; } }; -export default entryDraft; +export default entryDraftReducer;