Saga helpers for editing a collection (#4209)
This commit is contained in:
Родитель
4a86a7defe
Коммит
a1891d5480
|
@ -28,6 +28,9 @@ export const ADDON_ADDED_TO_COLLECTION: 'ADDON_ADDED_TO_COLLECTION'
|
|||
= 'ADDON_ADDED_TO_COLLECTION';
|
||||
export const LOAD_COLLECTION_ADDONS: 'LOAD_COLLECTION_ADDONS'
|
||||
= 'LOAD_COLLECTION_ADDONS';
|
||||
export const FINISH_UPDATE_COLLECTION: 'FINISH_UPDATE_COLLECTION'
|
||||
= 'FINISH_UPDATE_COLLECTION';
|
||||
export const UPDATE_COLLECTION: 'UPDATE_COLLECTION' = 'UPDATE_COLLECTION';
|
||||
|
||||
export type CollectionType = {
|
||||
addons: Array<AddonType> | null,
|
||||
|
@ -73,6 +76,9 @@ export type CollectionsState = {
|
|||
|};
|
||||
},
|
||||
},
|
||||
collectionUpdates: {
|
||||
[collectionSlug: string]: {| updating: boolean, successful?: boolean |},
|
||||
},
|
||||
};
|
||||
|
||||
export const initialState: CollectionsState = {
|
||||
|
@ -81,6 +87,7 @@ export const initialState: CollectionsState = {
|
|||
current: { id: null, loading: false },
|
||||
userCollections: {},
|
||||
addonInCollections: {},
|
||||
collectionUpdates: {},
|
||||
};
|
||||
|
||||
type FetchCurrentCollectionParams = {|
|
||||
|
@ -440,6 +447,80 @@ export const addAddonToCollection = ({
|
|||
};
|
||||
};
|
||||
|
||||
type UpdateCollectionParams = {|
|
||||
errorHandlerId: string,
|
||||
collectionSlug: string,
|
||||
defaultLocale?: string,
|
||||
description?: string,
|
||||
isPublic?: boolean,
|
||||
name?: string,
|
||||
user: number | string,
|
||||
|};
|
||||
|
||||
type UpdateCollectionAction = {|
|
||||
type: typeof UPDATE_COLLECTION,
|
||||
payload: UpdateCollectionParams,
|
||||
|};
|
||||
|
||||
export const updateCollection = ({
|
||||
errorHandlerId,
|
||||
collectionSlug,
|
||||
defaultLocale,
|
||||
description,
|
||||
isPublic,
|
||||
name,
|
||||
user,
|
||||
}: UpdateCollectionParams = {}): UpdateCollectionAction => {
|
||||
if (!errorHandlerId) {
|
||||
throw new Error('errorHandlerId is required');
|
||||
}
|
||||
if (!collectionSlug) {
|
||||
throw new Error('collectionSlug is required');
|
||||
}
|
||||
if (!user) {
|
||||
throw new Error('user is required');
|
||||
}
|
||||
|
||||
return {
|
||||
type: UPDATE_COLLECTION,
|
||||
payload: {
|
||||
errorHandlerId,
|
||||
collectionSlug,
|
||||
defaultLocale,
|
||||
description,
|
||||
isPublic,
|
||||
name,
|
||||
user,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type FinishUpdateCollectionParams = {|
|
||||
collectionSlug: string,
|
||||
successful: boolean,
|
||||
|};
|
||||
|
||||
type FinishUpdateCollectionAction = {|
|
||||
type: typeof FINISH_UPDATE_COLLECTION,
|
||||
payload: FinishUpdateCollectionParams,
|
||||
|};
|
||||
|
||||
export const finishUpdateCollection = (
|
||||
{ collectionSlug, successful }: FinishUpdateCollectionParams = {}
|
||||
): FinishUpdateCollectionAction => {
|
||||
if (!collectionSlug) {
|
||||
throw new Error('The collectionSlug parameter is required');
|
||||
}
|
||||
if (typeof successful === 'undefined') {
|
||||
throw new Error('The successful parameter is required');
|
||||
}
|
||||
|
||||
return {
|
||||
type: FINISH_UPDATE_COLLECTION,
|
||||
payload: { collectionSlug, successful },
|
||||
};
|
||||
};
|
||||
|
||||
export const createInternalAddons = (
|
||||
items: ExternalCollectionAddons
|
||||
): Array<AddonType> => {
|
||||
|
@ -570,10 +651,12 @@ type Action =
|
|||
| FetchCurrentCollectionAction
|
||||
| FetchCurrentCollectionPageAction
|
||||
| FetchUserCollectionsAction
|
||||
| FinishUpdateCollectionAction
|
||||
| LoadCollectionAddonsAction
|
||||
| LoadCurrentCollectionAction
|
||||
| LoadCurrentCollectionPageAction
|
||||
| LoadUserCollectionsAction
|
||||
| UpdateCollectionAction
|
||||
;
|
||||
|
||||
const reducer = (
|
||||
|
@ -790,6 +873,35 @@ const reducer = (
|
|||
});
|
||||
}
|
||||
|
||||
case UPDATE_COLLECTION: {
|
||||
const { collectionSlug } = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
collectionUpdates: {
|
||||
[collectionSlug]: {
|
||||
...state.collectionUpdates[collectionSlug],
|
||||
updating: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case FINISH_UPDATE_COLLECTION: {
|
||||
const { collectionSlug, successful } = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
collectionUpdates: {
|
||||
[collectionSlug]: {
|
||||
...state.collectionUpdates[collectionSlug],
|
||||
updating: false,
|
||||
successful,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ import {
|
|||
FETCH_CURRENT_COLLECTION,
|
||||
FETCH_CURRENT_COLLECTION_PAGE,
|
||||
FETCH_USER_COLLECTIONS,
|
||||
UPDATE_COLLECTION,
|
||||
abortAddAddonToCollection,
|
||||
abortFetchCurrentCollection,
|
||||
abortFetchUserCollections,
|
||||
addonAddedToCollection,
|
||||
finishUpdateCollection,
|
||||
loadCurrentCollection,
|
||||
loadCurrentCollectionPage,
|
||||
loadUserCollections,
|
||||
|
@ -132,6 +134,42 @@ export function* addAddonToCollection({
|
|||
}
|
||||
}
|
||||
|
||||
export function* updateCollection({
|
||||
payload: {
|
||||
errorHandlerId,
|
||||
collectionSlug,
|
||||
defaultLocale,
|
||||
description,
|
||||
isPublic,
|
||||
name,
|
||||
slug,
|
||||
user,
|
||||
},
|
||||
}) {
|
||||
const errorHandler = createErrorHandler(errorHandlerId);
|
||||
yield put(errorHandler.createClearingAction());
|
||||
|
||||
try {
|
||||
const state = yield select(getState);
|
||||
yield call(api.updateCollection, {
|
||||
api: state.api,
|
||||
collectionSlug,
|
||||
defaultLocale,
|
||||
description,
|
||||
isPublic,
|
||||
name,
|
||||
slug,
|
||||
user,
|
||||
});
|
||||
|
||||
yield put(finishUpdateCollection({ collectionSlug, successful: true }));
|
||||
} catch (error) {
|
||||
log.warn(`Failed to update collection: ${error}`);
|
||||
yield put(errorHandler.createErrorAction(error));
|
||||
yield put(finishUpdateCollection({ collectionSlug, successful: false }));
|
||||
}
|
||||
}
|
||||
|
||||
export default function* collectionsSaga() {
|
||||
yield takeLatest(FETCH_CURRENT_COLLECTION, fetchCurrentCollection);
|
||||
yield takeLatest(
|
||||
|
@ -139,4 +177,5 @@ export default function* collectionsSaga() {
|
|||
);
|
||||
yield takeLatest(FETCH_USER_COLLECTIONS, fetchUserCollections);
|
||||
yield takeLatest(ADD_ADDON_TO_COLLECTION, addAddonToCollection);
|
||||
yield takeLatest(UPDATE_COLLECTION, updateCollection);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import reducer, {
|
|||
fetchCurrentCollection,
|
||||
fetchCurrentCollectionPage,
|
||||
fetchUserCollections,
|
||||
finishUpdateCollection,
|
||||
getCollectionById,
|
||||
getCurrentCollection,
|
||||
initialState,
|
||||
|
@ -17,6 +18,7 @@ import reducer, {
|
|||
loadCurrentCollection,
|
||||
loadCurrentCollectionPage,
|
||||
loadUserCollections,
|
||||
updateCollection,
|
||||
} from 'amo/reducers/collections';
|
||||
import { parsePage } from 'core/utils';
|
||||
import { createStubErrorHandler } from 'tests/unit/helpers';
|
||||
|
@ -825,4 +827,105 @@ describe(__filename, () => {
|
|||
.toThrow(/collectionSlug parameter is required/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateCollection', () => {
|
||||
const getParams = (params = {}) => {
|
||||
return {
|
||||
errorHandlerId: 'error-handler-id',
|
||||
collectionSlug: 'some-collection',
|
||||
user: 'some-user-name',
|
||||
...params,
|
||||
};
|
||||
};
|
||||
|
||||
it('requires errorHandlerId parameter', () => {
|
||||
const params = getParams();
|
||||
delete params.errorHandlerId;
|
||||
|
||||
expect(() => updateCollection(params))
|
||||
.toThrow(/errorHandlerId is required/);
|
||||
});
|
||||
|
||||
it('requires collectionSlug parameter', () => {
|
||||
const params = getParams();
|
||||
delete params.collectionSlug;
|
||||
|
||||
expect(() => updateCollection(params))
|
||||
.toThrow(/collectionSlug is required/);
|
||||
});
|
||||
|
||||
it('requires user parameter', () => {
|
||||
const params = getParams();
|
||||
delete params.user;
|
||||
|
||||
expect(() => updateCollection(params))
|
||||
.toThrow(/user is required/);
|
||||
});
|
||||
|
||||
it('changes update state', () => {
|
||||
const collectionSlug = 'some-collection';
|
||||
|
||||
const state = reducer(initialState, updateCollection(getParams({
|
||||
collectionSlug,
|
||||
})));
|
||||
|
||||
expect(state.collectionUpdates[collectionSlug].updating)
|
||||
.toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('finishUpdateCollection', () => {
|
||||
const getParams = (params = {}) => {
|
||||
return {
|
||||
collectionSlug: 'some-collection', successful: true, ...params,
|
||||
};
|
||||
};
|
||||
|
||||
it('requires collectionSlug parameter', () => {
|
||||
const params = getParams();
|
||||
delete params.collectionSlug;
|
||||
|
||||
expect(() => finishUpdateCollection(params))
|
||||
.toThrow(/collectionSlug parameter is required/);
|
||||
});
|
||||
|
||||
it('requires successful parameter', () => {
|
||||
const params = getParams();
|
||||
delete params.successful;
|
||||
|
||||
expect(() => finishUpdateCollection(params))
|
||||
.toThrow(/successful parameter is required/);
|
||||
});
|
||||
|
||||
it('handles a falsy successful parameter', () => {
|
||||
const params = getParams({ successful: false });
|
||||
|
||||
// Make sure this doesn't throw.
|
||||
finishUpdateCollection(params);
|
||||
});
|
||||
|
||||
it('finishes a successful update', () => {
|
||||
const collectionSlug = 'some-collection';
|
||||
|
||||
const params = getParams({ collectionSlug, successful: true });
|
||||
const state = reducer(initialState, finishUpdateCollection(params));
|
||||
|
||||
expect(state.collectionUpdates[collectionSlug].updating)
|
||||
.toEqual(false);
|
||||
expect(state.collectionUpdates[collectionSlug].successful)
|
||||
.toEqual(true);
|
||||
});
|
||||
|
||||
it('finishes an unsuccessful update', () => {
|
||||
const collectionSlug = 'some-collection';
|
||||
|
||||
const params = getParams({ collectionSlug, successful: false });
|
||||
const state = reducer(initialState, finishUpdateCollection(params));
|
||||
|
||||
expect(state.collectionUpdates[collectionSlug].updating)
|
||||
.toEqual(false);
|
||||
expect(state.collectionUpdates[collectionSlug].successful)
|
||||
.toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,9 +10,11 @@ import collectionsReducer, {
|
|||
fetchCurrentCollection,
|
||||
fetchCurrentCollectionPage,
|
||||
fetchUserCollections,
|
||||
finishUpdateCollection,
|
||||
loadCurrentCollection,
|
||||
loadCurrentCollectionPage,
|
||||
loadUserCollections,
|
||||
updateCollection,
|
||||
} from 'amo/reducers/collections';
|
||||
import collectionsSaga from 'amo/sagas/collections';
|
||||
import apiReducer from 'core/reducers/api';
|
||||
|
@ -334,4 +336,84 @@ describe(__filename, () => {
|
|||
.toEqual(abortAddAddonToCollection({ addonId, userId }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateCollection', () => {
|
||||
const _updateCollection = (params = {}) => {
|
||||
sagaTester.dispatch(updateCollection({
|
||||
errorHandlerId: errorHandler.id,
|
||||
collectionSlug: 'some-collection',
|
||||
user: 321,
|
||||
...params,
|
||||
}));
|
||||
};
|
||||
|
||||
it('sends a patch to the collection API', async () => {
|
||||
const collectionSlug = 'a-collection';
|
||||
const params = {
|
||||
collectionSlug,
|
||||
name: 'New collection name',
|
||||
user: 543,
|
||||
};
|
||||
const state = sagaTester.getState();
|
||||
|
||||
mockApi
|
||||
.expects('updateCollection')
|
||||
.withArgs({
|
||||
api: state.api,
|
||||
collectionSlug,
|
||||
defaultLocale: undefined,
|
||||
description: undefined,
|
||||
isPublic: undefined,
|
||||
name: params.name,
|
||||
slug: undefined,
|
||||
user: params.user,
|
||||
})
|
||||
.once()
|
||||
.returns(Promise.resolve());
|
||||
|
||||
_updateCollection(params);
|
||||
|
||||
const expectedLoadAction = finishUpdateCollection({
|
||||
collectionSlug: params.collectionSlug,
|
||||
successful: true,
|
||||
});
|
||||
|
||||
await sagaTester.waitFor(expectedLoadAction.type);
|
||||
mockApi.verify();
|
||||
|
||||
const calledActions = sagaTester.getCalledActions();
|
||||
const loadAction = calledActions[2];
|
||||
expect(loadAction).toEqual(expectedLoadAction);
|
||||
});
|
||||
|
||||
it('clears the error handler', async () => {
|
||||
_updateCollection();
|
||||
|
||||
const expectedAction = errorHandler.createClearingAction();
|
||||
|
||||
await sagaTester.waitFor(expectedAction.type);
|
||||
expect(sagaTester.getCalledActions()[1])
|
||||
.toEqual(errorHandler.createClearingAction());
|
||||
});
|
||||
|
||||
it('handles errors', async () => {
|
||||
const collectionSlug = 'a-collection';
|
||||
const error = new Error('some API error maybe');
|
||||
|
||||
mockApi
|
||||
.expects('updateCollection')
|
||||
.returns(Promise.reject(error));
|
||||
|
||||
_updateCollection({ collectionSlug });
|
||||
|
||||
const errorAction = errorHandler.createErrorAction(error);
|
||||
await sagaTester.waitFor(errorAction.type);
|
||||
|
||||
expect(sagaTester.getCalledActions()[2]).toEqual(errorAction);
|
||||
expect(sagaTester.getCalledActions()[3])
|
||||
.toEqual(finishUpdateCollection({
|
||||
collectionSlug, successful: false,
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче