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) => {
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))
);

View File

@ -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(

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

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 {
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);

View File

@ -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;