Moved persisting logic to entryDraft reducer + added tests.

This commit is contained in:
Andrey Okonetchnikov 2016-10-13 14:30:11 +02:00
parent ffe27acc10
commit e53262d92c
6 changed files with 177 additions and 90 deletions

View File

@ -211,13 +211,14 @@ export function createEmptyDraft(collection) {
}; };
} }
export function persistEntry(collection, entry) { export function persistEntry(collection, entryDraft) {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
const backend = currentBackend(state.config); 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)); 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)), () => dispatch(entryPersisted(collection, entry)),
error => dispatch(entryPersistFail(collection, entry, error)) error => dispatch(entryPersistFail(collection, entry, error))
); );

View File

@ -35,12 +35,12 @@ class EntryPage extends React.Component {
}; };
componentDidMount() { componentDidMount() {
const { entry, collection, slug } = this.props; const { entry, newEntry, collection, slug, createEmptyDraft, loadEntry } = this.props;
if (this.props.newEntry) { if (newEntry) {
this.props.createEmptyDraft(this.props.collection); createEmptyDraft(collection);
} else { } else {
this.props.loadEntry(entry, collection, slug); loadEntry(entry, collection, slug);
this.createDraft(entry); this.createDraft(entry);
} }
} }
@ -63,7 +63,8 @@ class EntryPage extends React.Component {
}; };
handlePersistEntry = () => { handlePersistEntry = () => {
this.props.persistEntry(this.props.collection, this.props.entryDraft); const { persistEntry, collection, entryDraft } = this.props;
persistEntry(collection, entryDraft);
}; };
render() { render() {
@ -105,7 +106,15 @@ function mapStateToProps(state, ownProps) {
const slug = ownProps.params.slug; const slug = ownProps.params.slug;
const entry = newEntry ? null : selectEntry(state, collection.get('name'), slug); const entry = newEntry ? null : selectEntry(state, collection.get('name'), slug);
const boundGetMedia = getMedia.bind(null, state); 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( export default connect(

View File

@ -1,16 +1,15 @@
import Immutable, { Map, OrderedMap, fromJS } from 'immutable'; import { Map, OrderedMap, fromJS } from 'immutable';
import * as actions from '../../actions/entries'; import * as actions from '../../actions/entries';
import reducer from '../entries'; import reducer from '../entries';
let initialState; const initialState = OrderedMap({
posts: Map({ name: 'posts' }),
});
describe('entries', () => { describe('entries', () => {
it('should mark entries as fetching', () => { it('should mark entries as fetching', () => {
const state = OrderedMap({
posts: Map({ name: 'posts' }),
});
expect( expect(
reducer(state, actions.entriesLoading(Map({ name: 'posts' }))) reducer(initialState, actions.entriesLoading(Map({ name: 'posts' })))
).toEqual( ).toEqual(
OrderedMap(fromJS({ OrderedMap(fromJS({
posts: { name: 'posts' }, posts: { name: 'posts' },
@ -22,12 +21,9 @@ describe('entries', () => {
}); });
it('should handle loaded entries', () => { it('should handle loaded entries', () => {
const state = OrderedMap({
posts: Map({ name: 'posts' }),
});
const entries = [{ slug: 'a', path: '' }, { slug: 'b', title: 'B' }]; const entries = [{ slug: 'a', path: '' }, { slug: 'b', title: 'B' }];
expect( expect(
reducer(state, actions.entriesLoaded(Map({ name: 'posts' }), entries, 0)) reducer(initialState, actions.entriesLoaded(Map({ name: 'posts' }), entries, 0))
).toEqual( ).toEqual(
OrderedMap(fromJS( 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();
});
});
}); });

View File

@ -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();
});
});
});

View File

@ -2,9 +2,6 @@ import { Map, List, fromJS } from 'immutable';
import { import {
ENTRY_REQUEST, ENTRY_REQUEST,
ENTRY_SUCCESS, ENTRY_SUCCESS,
ENTRY_PERSIST_REQUEST,
ENTRY_PERSIST_SUCCESS,
ENTRY_PERSIST_FAILURE,
ENTRIES_REQUEST, ENTRIES_REQUEST,
ENTRIES_SUCCESS, ENTRIES_SUCCESS,
SEARCH_ENTRIES_REQUEST, SEARCH_ENTRIES_REQUEST,
@ -16,10 +13,6 @@ let loadedEntries;
let page; let page;
let searchTerm; let searchTerm;
function getEntryPath(collectionName, entrySlug) {
return `${ collectionName }.${ entrySlug }`;
}
const entries = (state = Map({ entities: Map(), pages: Map() }), action) => { const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
switch (action.type) { switch (action.type) {
case ENTRY_REQUEST: case ENTRY_REQUEST:
@ -31,17 +24,6 @@ const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
fromJS(action.payload.entry) 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: case ENTRIES_REQUEST:
return state.setIn(['pages', action.payload.collection, 'isFetching'], true); return state.setIn(['pages', action.payload.collection, 'isFetching'], true);

View File

@ -1,10 +1,21 @@
import { Map, List, fromJS } from 'immutable'; import { Map, List, fromJS } from 'immutable';
import { DRAFT_CREATE_FROM_ENTRY, DRAFT_CREATE_EMPTY, DRAFT_DISCARD, DRAFT_CHANGE } from '../actions/entries'; import {
import { ADD_MEDIA, REMOVE_MEDIA } from '../actions/media'; 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 initialState = Map({ entry: Map(), mediaFiles: List() });
const entryDraft = (state = Map(), action) => { const entryDraftReducer = (state = Map(), action) => {
switch (action.type) { switch (action.type) {
case DRAFT_CREATE_FROM_ENTRY: case DRAFT_CREATE_FROM_ENTRY:
// Existing Entry // Existing Entry
@ -25,14 +36,23 @@ const entryDraft = (state = Map(), action) => {
case DRAFT_CHANGE: case DRAFT_CHANGE:
return state.set('entry', action.payload); 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: 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: 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: default:
return state; return state;
} }
}; };
export default entryDraft; export default entryDraftReducer;