diff --git a/src/Actions.ts b/src/Actions.ts index 9461fc6..fe4cab5 100644 --- a/src/Actions.ts +++ b/src/Actions.ts @@ -1,6 +1,6 @@ import { normalize } from 'normalizr'; import { Schemas } from './Schema'; -import { Content, IContent, ODataApi, ODataHelper, Repository, ContentTypes } from 'sn-client-js'; +import { Content, SavedContent, IContent, ODataApi, ContentTypes } from 'sn-client-js'; /** * Module that contains the action creators. @@ -364,25 +364,23 @@ export module Actions { }) /** * Action creator for deleting multiple Content from the Content Repository. - * @param path {string} Path of parent the Content. - * @param ids {string[]} Array of ids of the Content that should be deleted. + * @param ids {number[]} Array of ids of the Content that should be deleted. * @param permanently {boolean} Defines whether Content must be moved to the Trash or deleted permanently. * @returns {Object} Returns a redux action with the properties type, id and permanently. */ - export const DeleteBatch = (path: string, ids: string[], permanently: boolean = false) => ({ + export const DeleteBatch = (contentItems: Object, permanently: boolean = false) => ({ type: 'DELETE_BATCH_REQUEST', - path, - ids, + contentItems, permanently }) /** - * Action creator for the step when multiple Content deleted successfully. - * @param indexes {number[]} Array of indexes of the items in the state collection that should be removed. + * Action creator for the step when multiple Content was deleted successfully. + * @param response {ODataApi.ODataBatchResponse} response object contains the list of successes and/or errors. * @returns {Object} Returns a redux action with the properties type and index. */ - export const DeleteBatchSuccess = (ids: number[]) => ({ + export const DeleteBatchSuccess = (response: ODataApi.ODataBatchResponse) => ({ type: 'DELETE_BATCH_SUCCESS', - ids + response }) /** * Action creator for the step when deleting multiple Content is failed. @@ -393,6 +391,64 @@ export module Actions { type: 'DELETE_BATCH_FAILURE', message: error.message }) + /** + * Action creator for copying multiple Content in the Content Repository. + * @param ids {number[]} Array of ids of the Content that should be deleted. + * @param permanently {boolean} Defines whether Content must be moved to the Trash or deleted permanently. + * @returns {Object} Returns a redux action with the properties type, id and permanently. + */ + export const CopyBatch = (contentItems: Object, path: string) => ({ + type: 'COPY_BATCH_REQUEST', + contentItems, + path + }) + /** + * Action creator for the step when multiple Content was copied successfully. + * @param response {ODataApi.ODataBatchResponse} response object contains the list of successes and/or errors. + * @returns {Object} Returns a redux action with the properties type and index. + */ + export const CopyBatchSuccess = (response: ODataApi.ODataBatchResponse) => ({ + type: 'COPY_BATCH_SUCCESS', + response + }) + /** + * Action creator for the step when copying multiple Content is failed. + * @param error {any} The catched error object. + * @returns {Object} Returns a redux action with the properties type and the error message. + */ + export const CopyBatchFailure = (error: any) => ({ + type: 'COPY_BATCH_FAILURE', + message: error.message + }) + /** + * Action creator for moving multiple Content in the Content Repository. + * @param ids {number[]} Array of ids of the Content that should be deleted. + * @param permanently {boolean} Defines whether Content must be moved to the Trash or deleted permanently. + * @returns {Object} Returns a redux action with the properties type, id and permanently. + */ + export const MoveBatch = (contentItems = {}, path: string) => ({ + type: 'MOVE_BATCH_REQUEST', + contentItems, + path + }) + /** + * Action creator for the step when multiple Content was moved successfully. + * @param response {ODataApi.ODataBatchResponse} response object contains the list of successes and/or errors. + * @returns {Object} Returns a redux action with the properties type and index. + */ + export const MoveBatchSuccess = (response: ODataApi.ODataBatchResponse) => ({ + type: 'MOVE_BATCH_SUCCESS', + response + }) + /** + * Action creator for the step when moving multiple Content is failed. + * @param error {any} The catched error object. + * @returns {Object} Returns a redux action with the properties type and the error message. + */ + export const MoveBatchFailure = (error: any) => ({ + type: 'MOVE_BATCH_FAILURE', + message: error.message + }) /** * Action creator for checking out a Content in the Content Repository. * @param content {number} Content that should be checked out. @@ -666,7 +722,7 @@ export module Actions { * @param error {any} The catched error object. * @returns {Object} Returns a redux action with the properties type and the error message. */ - export const UserLoginFailure = (error: {status?: number, message: string}) => ({ + export const UserLoginFailure = (error: { status?: number, message: string }) => ({ type: 'USER_LOGIN_FAILURE', message: (error.status === 403) ? 'The username or the password is not valid!' : error.message }) @@ -706,20 +762,20 @@ export module Actions { /** * Action creator for selecting a Content * @param id {number} The id of the selected Content - * @returns {Object} Returns a redux action. + * @returns {Object} Returns a redux action. */ - export const SelectContent = (id) => ({ + export const SelectContent = (content) => ({ type: 'SELECT_CONTENT', - id + content }) /** * Action creator for deselecting a Content * @param id {number} The id of the deselected Content * @returns {Object} Returns a redux action. */ - export const DeSelectContent = (id) => ({ + export const DeSelectContent = (content) => ({ type: 'DESELECT_CONTENT', - id + content })/** * Action creator for clearing the array of selected content * @returns {Object} Returns a redux action. diff --git a/src/Epics.ts b/src/Epics.ts index e6571b8..15958e4 100644 --- a/src/Epics.ts +++ b/src/Epics.ts @@ -2,9 +2,10 @@ import { Actions } from './Actions'; import { Reducers } from './Reducers'; import { ActionsObservable, combineEpics } from 'redux-observable'; -import { Observable } from 'rxjs/Observable'; import { Repository, Content, ContentTypes, Collection, ODataApi, Authentication } from 'sn-client-js'; +import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/mergeMap'; +import 'rxjs/add/operator/catch' /** * Module for redux-observable Epics of the sensenet built-in OData actions. @@ -193,16 +194,50 @@ export module Epics { export const deleteBatchEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { return action$.ofType('DELETE_BATCH_REQUEST') .mergeMap(action => { - let collection = new Collection.Collection([], dependencies.repository, action.contentType); - return collection.Remove(action.ids, false) + let contentItems = Object.keys(action.contentItems).map(id => { + return dependencies.repository.HandleLoadedContent(action.contentItems[id], action.contentItems.__contentType); + }); + return dependencies.repository.DeleteBatch(contentItems, action.permanently) .map((response) => { - const state = store.getState(); - const ids = Reducers.getIds(state.collection); - return Actions.DeleteBatchSuccess(ids); + return Actions.DeleteBatchSuccess(response); }) .catch(error => Observable.of(Actions.DeleteBatchFailure(error))) }) } + /** + * Epic to copy multiple Content in the Content Repository. It is related to three redux actions, returns ```CopyBatch``` action and sends the response to the + * ```CopyBatchSuccess``` action if the ajax request ended successfully or catches the error if the request failed and sends the error message to the ```CopyBatchFailure``` action. + */ + export const copyBatchEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { + return action$.ofType('COPY_BATCH_REQUEST') + .mergeMap(action => { + let contentItems = Object.keys(action.contentItems).map(id => { + return dependencies.repository.HandleLoadedContent(action.contentItems[id], action.contentItems.__contentType); + }); + return dependencies.repository.CopyBatch(contentItems, action.path) + .map((response) => { + return Actions.CopyBatchSuccess(response); + }) + .catch(error => Observable.of(Actions.CopyBatchFailure(error))) + }) + } + /** + * Epic to move multiple Content in the Content Repository. It is related to three redux actions, returns ```MoveBatch``` action and sends the response to the + * ```MoveBatchSuccess``` action if the ajax request ended successfully or catches the error if the request failed and sends the error message to the ```MoveBatchFailure``` action. + */ + export const moveBatchEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { + return action$.ofType('MOVE_BATCH_REQUEST') + .mergeMap(action => { + let contentItems = Object.keys(action.contentItems).map(id => { + return dependencies.repository.HandleLoadedContent(action.contentItems[id], action.contentItems.__contentType); + }); + return dependencies.repository.MoveBatch(contentItems, action.path) + .map((response) => { + return Actions.MoveBatchSuccess(response); + }) + .catch(error => Observable.of(Actions.MoveBatchFailure(error))) + }) + } /** * Epic to checkout a Content in the Content Repository. It is related to three redux actions, returns ```CheckOut``` action and sends the response to the * ```CheckOutSuccess``` action if the ajax request ended successfully or catches the error if the request failed and sends the error message to the ```CheckOutFailure``` action. @@ -402,6 +437,8 @@ export module Epics { updateContentEpic, deleteContentEpic, deleteBatchEpic, + copyBatchEpic, + moveBatchEpic, checkoutContentEpic, checkinContentEpic, publishContentEpic, diff --git a/src/Reducers.ts b/src/Reducers.ts index 18d024e..903e216 100644 --- a/src/Reducers.ts +++ b/src/Reducers.ts @@ -1,4 +1,3 @@ -import { normalize } from 'normalizr'; import { combineReducers } from 'redux'; import { Authentication } from 'sn-client-js'; @@ -188,6 +187,18 @@ export module Reducers { return state case 'DELETE_CONTENT_SUCCESS': return [...state.slice(0, action.index), ...state.slice(action.index + 1)] + case 'DELETE_BATCH_SUCCESS': + case 'MOVE_BATCH_SUCCESS': + if (action.response.d.results.length > 0) { + let newIds = [] + let deletedIds = action.response.d.results.map(result => result.Id) + for (let i = 0; i < state.length; i++) { + if (deletedIds.indexOf(state[i]) === -1) { + newIds.push(state[i]) + } + } + return newIds + } default: return state; } @@ -205,7 +216,10 @@ export module Reducers { action.type !== 'LOAD_CONTENT_SUCCESS' && action.type !== 'REQUEST_CONTENT_ACTIONS_SUCCESS' && action.type !== 'UPDATE_CONTENT_SUCCESS' && - action.type !== 'UPLOAD_CONTENT_SUCCESS')) { + action.type !== 'UPLOAD_CONTENT_SUCCESS' && + action.type !== 'DELETE_BATCH_SUCCESS' && + action.type !== 'COPY_BATCH_SUCCESS' && + action.type !== 'MOVE_BATCH_SUCCESS')) { return (Object).assign({}, state, action.response.entities.entities); } switch (action.type) { @@ -213,6 +227,11 @@ export module Reducers { let res = Object.assign({}, state); delete res[action.id]; return res; + case 'DELETE_BATCH_SUCCESS': + case 'MOVE_BATCH_SUCCESS': + let resource = Object.assign({}, state); + action.response.d.results.map(result => delete resource[result.Id]) + return resource; case 'UPDATE_CONTENT_SUCCESS': state[action.response.Id] = action.response return state @@ -584,16 +603,16 @@ export module Reducers { }) /** * Reducer to handle Actions on the selected array. - * @param {Object} [state=[]] Represents the current state. + * @param {Array} [state=[]] Represents the current state. * @param {Object} action Represents an action that is called. * @returns {Object} state. Returns the next state based on the action. */ - export const selected = (state = [], action) => { + export const selectedIds = (state = [], action) => { switch (action.type) { case 'SELECT_CONTENT': - return [...state, action.id] + return [...state, action.content.Id] case 'DESELECT_CONTENT': - const index = state.indexOf(action.id) + const index = state.indexOf(action.content.Id) return [...state.slice(0, index), ...state.slice(index + 1)] case 'CLEAR_SELECTION': return [] @@ -601,6 +620,65 @@ export module Reducers { return state } } + export const selectedContentItems = (state = {}, action) => { + switch (action.type) { + case 'DESELECT_CONTENT': + let res = Object.assign({}, state); + delete res[action.content.Id]; + return res; + case 'SELECT_CONTENT': + let obj = {} + obj[action.content.Id] = action.content + return (Object).assign({}, state, obj); + case 'CLEAR_SELECTION': + return {} + default: + return state; + } + } + export const selected = combineReducers({ + ids: selectedIds, + entities: selectedContentItems + }) + /** + * Reducer to handle Actions on the OdataBatchResponse Object. + * @param {Array} state Represents the current state. + * @param {Object} action Represents an action that is called. + * @returns {Object} state. Returns the next state based on the action. + */ + export const OdataBatchResponse = (state = Object, action) => { + switch (action.type) { + case 'DELETE_BATCH_SUCCESS': + case 'COPY_BATCH_SUCCESS': + case 'MOVE_BATCH_SUCCESS': + return action.response + default: + return {} + } + } + /** + * Reducer to handle Actions on the batchResponseError Object. + * @param {string} state Represents the current state. + * @param {Object} action Represents an action that is called. + * @returns {Object} state. Returns the next state based on the action. + */ + export const batchResponseError = (state = '', action) => { + switch (action.type) { + case 'DELETE_BATCH_FAILURE': + case 'COPY_BATCH_FAILURE': + case 'MOVE_BATCH_FAILURE': + return action.message + default: + return '' + } + } + /** + * Reducer combining response and error into a single object, ```batchResponses```. + */ + export const batchResponses = combineReducers({ + response: OdataBatchResponse, + error: batchResponseError + }) /** * Reducer combining session, children, currentcontent and selected into a single object, ```sensenet``` which will be the top-level one. */ @@ -608,7 +686,8 @@ export module Reducers { session, children, currentcontent, - selected + selected, + batchResponses }) /** @@ -651,8 +730,12 @@ export module Reducers { return state.session.repository.RepositoryUrl; } - export const getSelectedContent = (state) => { - return state.selected + export const getSelectedContentIds = (state) => { + return state.selected.ids + } + + export const getSelectedContentItems = (state) => { + return state.selected.entities } export const getOpenedContent = (state) => { diff --git a/test/ActionsTests.ts b/test/ActionsTests.ts index a6a41b8..def0afc 100644 --- a/test/ActionsTests.ts +++ b/test/ActionsTests.ts @@ -1,7 +1,7 @@ /// import { Actions } from '../src/Actions' import * as Chai from 'chai'; -import { Mocks, ContentTypes, Repository } from 'sn-client-js'; +import { Mocks, ContentTypes, Repository, ODataApi } from 'sn-client-js'; const expect = Chai.expect; describe('Actions', () => { @@ -248,27 +248,36 @@ describe('Actions', () => { it('should create an action to a delete content request', () => { const expectedAction = { type: 'DELETE_BATCH_REQUEST', - path: path, - ids: ['1', '2', '3'], - permanently: false - } - expect(Actions.DeleteBatch(path, ['1', '2', '3'], false)).to.deep.equal(expectedAction) - }); - it('should create an action to a delete content request', () => { - const expectedAction = { - type: 'DELETE_BATCH_REQUEST', - path: path, - ids: ['1', '2', '3'], + contentItems: { + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + }, permanently: false } - expect(Actions.DeleteBatch(path, ['1', '2', '3'])).to.deep.equal(expectedAction) + expect(Actions.DeleteBatch({ + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + })).to.deep.equal(expectedAction) }); it('should create an action to delete content success', () => { + const response = new ODataApi.ODataBatchResponse() const expectedAction = { type: 'DELETE_BATCH_SUCCESS', - ids: [0, 1, 2] + response: response } - expect(Actions.DeleteBatchSuccess([0, 1, 2])).to.deep.equal(expectedAction) + expect(Actions.DeleteBatchSuccess(response)).to.deep.equal(expectedAction) }); it('should create an action to delete content failure', () => { const expectedAction = { @@ -278,6 +287,87 @@ describe('Actions', () => { expect(Actions.DeleteBatchFailure({ message: 'error' })).to.deep.equal(expectedAction) }); }); + describe('CopyBatchContent', () => { + it('should create an action to a copy multiple content request', () => { + const expectedAction = { + type: 'COPY_BATCH_REQUEST', + contentItems: + { + '1': { DisplaName: 'aaa', Id: 1 }, + '2': { DisplaName: 'bbb', Id: 2 } + }, + path: '/workspaces' + } + expect(Actions.CopyBatch({ + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + }, '/workspaces')).to.deep.equal(expectedAction) + }); + it('should create an action to copy multiple content success', () => { + const response = new ODataApi.ODataBatchResponse() + const expectedAction = { + type: 'COPY_BATCH_SUCCESS', + response: response + } + expect(Actions.CopyBatchSuccess(response)).to.deep.equal(expectedAction) + }); + it('should create an action to copy multiple content failure', () => { + const expectedAction = { + type: 'COPY_BATCH_FAILURE', + message: 'error' + } + expect(Actions.CopyBatchFailure({ message: 'error' })).to.deep.equal(expectedAction) + }); + }); + describe('MoveBatchContent', () => { + it('should create an action to a move multiple content request', () => { + const expectedAction = { + type: 'MOVE_BATCH_REQUEST', + contentItems: { + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + }, + path: '/workspaces' + } + expect(Actions.MoveBatch({ + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + }, '/workspaces')).to.deep.equal(expectedAction) + }); + it('should create an action to move multiple content success', () => { + const response = new ODataApi.ODataBatchResponse() + const expectedAction = { + type: 'MOVE_BATCH_SUCCESS', + response: response + } + expect(Actions.MoveBatchSuccess(response)).to.deep.equal(expectedAction) + }); + it('should create an action to move multiple content failure', () => { + const expectedAction = { + type: 'MOVE_BATCH_FAILURE', + message: 'error' + } + expect(Actions.MoveBatchFailure({ message: 'error' })).to.deep.equal(expectedAction) + }); + }); describe('CheckoutContent', () => { const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to a checkout content request', () => { @@ -555,21 +645,23 @@ describe('Actions', () => { }); }); describe('SelectContent', () => { + const content = repo.CreateContent({ DisplayName: 'My content', Id: 1 }, ContentTypes.Task); it('should return the select content action', () => { const expectedAction = { type: 'SELECT_CONTENT', - id: 1 + content: content } - expect(Actions.SelectContent(1)).to.deep.equal(expectedAction) + expect(Actions.SelectContent(content)).to.deep.equal(expectedAction) }) }) describe('DeSelectContent', () => { + const content = repo.CreateContent({ DisplayName: 'My content', Id: 1 }, ContentTypes.Task); it('should return the deselect content action', () => { const expectedAction = { type: 'DESELECT_CONTENT', - id: 1 + content: content } - expect(Actions.DeSelectContent(1)).to.deep.equal(expectedAction) + expect(Actions.DeSelectContent(content)).to.deep.equal(expectedAction) }) }) describe('ClearSelection', () => { diff --git a/test/EpicsTests.ts b/test/EpicsTests.ts index b235fe3..99021a4 100644 --- a/test/EpicsTests.ts +++ b/test/EpicsTests.ts @@ -277,34 +277,93 @@ describe('Epics', () => { { type: 'DELETE_CONTENT_FAILURE', error: 'error' }]); }) }); - // describe('deleteBatch Epic', () => { - // before(() => { - // initBefores(Epics.deleteBatchEpic) - // }); - - // after(() => { - // epicMiddleware.replaceEpic(Epics.deleteBatchEpic); - // }); - // it('handles the error', () => { - // store.dispatch({ type: 'DELETE_BATCH_REQUEST', ids: [1, 2], permanently: false }); - // expect(store.getActions()).to.be.deep.eq( - // [{ - // type: 'DELETE_BATCH_REQUEST', - // ids: [1, 2], - // permanently: false - // }]); - // }) - // it('handles the error', () => { - // store.dispatch({ type: 'DELETE_BATCH_FAILURE', error: 'error' }); - // expect(store.getActions()).to.be.deep.eq( - // [{ - // type: 'DELETE_BATCH_REQUEST', - // ids: [1, 2], - // permanently: false - // }, - // { type: 'DELETE_BATCH_FAILURE', error: 'error' }]); - // }) - // }); + describe('deleteBatch Epic', () => { + before(() => { + initBefores(Epics.deleteBatchEpic) + }); + + after(() => { + epicMiddleware.replaceEpic(Epics.deleteBatchEpic); + }); + it('handles the error', () => { + store.dispatch({ + type: 'DELETE_BATCH_REQUEST', contentItems: { + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + }, permanently: false + }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'DELETE_BATCH_REQUEST', + contentItems: { + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + }, + permanently: false + }]); + }) + it('handles the error', () => { + store.dispatch({ type: 'DELETE_BATCH_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'DELETE_BATCH_REQUEST', + contentItems: { + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + }, + permanently: false + }, + { type: 'DELETE_BATCH_FAILURE', error: 'error' }]); + }) + }); + describe('copyBatch Epic', () => { + before(() => { + initBefores(Epics.copyBatchEpic) + }); + + after(() => { + epicMiddleware.replaceEpic(Epics.copyBatchEpic); + }); + + it('handles the error', () => { + store.dispatch({ type: 'COPY_BATCH_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ type: 'COPY_BATCH_FAILURE', error: 'error' }]); + }) + }); + describe('moveBatch Epic', () => { + before(() => { + initBefores(Epics.moveBatchEpic) + }); + + after(() => { + epicMiddleware.replaceEpic(Epics.moveBatchEpic); + }); + + it('handles the error', () => { + store.dispatch({ type: 'MOVE_BATCH_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ type: 'MOVE_BATCH_FAILURE', error: 'error' }]); + }) + }); describe('checkoutContent Epic', () => { before(() => { initBefores(Epics.checkoutContentEpic) diff --git a/test/ReducersTests.ts b/test/ReducersTests.ts index 7da2a46..efd510a 100644 --- a/test/ReducersTests.ts +++ b/test/ReducersTests.ts @@ -183,6 +183,56 @@ describe('Reducers', () => { })) .to.be.deep.equal([1, 2, 3]); }); + it('should handle DELETE_BATCH_SUCCESS', () => { + expect(Reducers.ids([1, 2, 3], { + type: 'DELETE_BATCH_SUCCESS', + response: { + 'd': { + 'results': [ + { 'Id': 1 }, + { 'Id': 2 } + ], + 'errors': [] + } + } + })).to.be.deep.equal([3]); + }); + it('should handle DELETE_BATCH_SUCCESS', () => { + expect(Reducers.ids([1, 2, 3], { + type: 'DELETE_BATCH_SUCCESS', + response: { + 'd': { + 'results': [], + 'errors': [] + } + } + })).to.be.deep.equal([1, 2, 3]); + }); + it('should handle MOVE_BATCH_SUCCESS', () => { + expect(Reducers.ids([1, 2, 3], { + type: 'MOVE_BATCH_SUCCESS', + response: { + 'd': { + 'results': [ + { 'Id': 1 }, + { 'Id': 2 } + ], + 'errors': [] + } + } + })).to.be.deep.equal([3]); + }); + it('should handle MOVE_BATCH_SUCCESS', () => { + expect(Reducers.ids([1, 2, 3], { + type: 'MOVE_BATCH_SUCCESS', + response: { + 'd': { + 'results': [], + 'errors': [] + } + } + })).to.be.deep.equal([1, 2, 3]); + }); }); describe('entities reducer', () => { @@ -286,6 +336,37 @@ describe('Reducers', () => { } ); }); + it('should handle DELETE_BATCH_SUCCESS', () => { + const entities = { + 5122: { + Id: 5122, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + } + }; + expect(Reducers.entities(entities, { + type: 'DELETE_BATCH_SUCCESS', + response: { + 'd': { + 'results': [ + { 'Id': 5122 } + ], + 'errors': [] + } + } + })).to.be.deep.equal({ + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + } + }); + }); }); describe('isFetching reducer', () => { @@ -749,42 +830,253 @@ describe('Reducers', () => { }); }) describe('selected reducer', () => { + let repo: Mocks.MockRepository = new Mocks.MockRepository(); + it('should return the initial state', () => { - expect(Reducers.selected(undefined, {})).to.deep.equal([]); + expect(Reducers.selectedIds(undefined, {})).to.deep.equal([]); }); it('should return an array with one item with the id 1', () => { + let content = repo.CreateContent({ + Path: '/Root/Sites/Default_Site/tasks', + Status: Enums.Status.active, + Id: 1 + }, ContentTypes.Task) const action = { type: 'SELECT_CONTENT', - id: 1 + content: content } - expect(Reducers.selected(undefined, action)).to.deep.equal([1]); + expect(Reducers.selectedIds(undefined, action)).to.deep.equal([1]); }) it('should return an array with two items with the id 1 and 2', () => { + let content = repo.CreateContent({ + Path: '/Root/Sites/Default_Site/tasks', + Status: Enums.Status.active, + Id: 2 + }, ContentTypes.Task) const action = { type: 'SELECT_CONTENT', - id: 2 + content: content } - expect(Reducers.selected([1], action)).to.deep.equal([1, 2]); + expect(Reducers.selectedIds([1], action)).to.deep.equal([1, 2]); }) it('should return an array with one item with the id 1', () => { + let content = repo.CreateContent({ + Path: '/Root/Sites/Default_Site/tasks', + Status: Enums.Status.active, + Id: 2 + }, ContentTypes.Task) const action = { type: 'DESELECT_CONTENT', - id: 2 + content: content } - expect(Reducers.selected([1, 2], action)).to.deep.equal([1]); + expect(Reducers.selectedIds([1, 2], action)).to.deep.equal([1]); }) it('should return an empty array', () => { + let content = repo.CreateContent({ + Path: '/Root/Sites/Default_Site/tasks', + Status: Enums.Status.active, + Id: 1 + }, ContentTypes.Task) const action = { type: 'DESELECT_CONTENT', - id: 1 + content: content } - expect(Reducers.selected([1], action)).to.deep.equal([]); + expect(Reducers.selectedIds([1], action)).to.deep.equal([]); }) it('should return an empty array', () => { const action = { type: 'CLEAR_SELECTION' } - expect(Reducers.selected([1], action)).to.deep.equal([]); + expect(Reducers.selectedIds([1], action)).to.deep.equal([]); + }) + }) + describe('selectedContent reducer', () => { + let repo: Mocks.MockRepository = new Mocks.MockRepository(); + + it('should return the initial state', () => { + expect(Reducers.selectedContentItems(undefined, {})).to.deep.equal({}); + }); + it('should return an object with one children item with the id 1', () => { + let content = repo.CreateContent({ + Path: '/Root/Sites/Default_Site/tasks', + Status: Enums.Status.active, + Id: 1 + }, ContentTypes.Task) + const action = { + type: 'SELECT_CONTENT', + content: content + } + expect(Reducers.selectedContentItems(undefined, action)).to.deep.equal({ 1: content }); + }) + it('should return an object with two items with the id 1 and 2', () => { + const entities = { + 1: { + Id: 1, + DisplayName: 'Some Article', + Status: ['Active'] + } + }; + let content = repo.CreateContent({ + Path: '/Root/Sites/Default_Site/tasks', + Status: Enums.Status.active, + Id: 2 + }, ContentTypes.Task) + const action = { + type: 'SELECT_CONTENT', + content: content + } + expect(Reducers.selectedContentItems(entities, action)).to.deep.equal( + { + 1: { + Id: 1, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 2: content + } + ); + }) + it('should return an object with one item with the id 1', () => { + const entities = { + 1: { + Id: 1, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 2: { + Id: 2, + DisplayName: 'Some Article', + Status: ['Active'] + } + }; + let content = repo.CreateContent({ + Path: '/Root/Sites/Default_Site/tasks', + Status: Enums.Status.active, + Id: 2 + }, ContentTypes.Task) + const action = { + type: 'DESELECT_CONTENT', + content: content + } + expect(Reducers.selectedContentItems(entities, action)).to.deep.equal( + { + 1: { + Id: 1, + DisplayName: 'Some Article', + Status: ['Active'] + } + } + ); + }) + it('should return an empty object', () => { + const entities = { + 1: { + Id: 1, + DisplayName: 'Some Article', + Status: ['Active'] + } + }; + let content = repo.CreateContent({ + Path: '/Root/Sites/Default_Site/tasks', + Status: Enums.Status.active, + Id: 1 + }, ContentTypes.Task) + const action = { + type: 'DESELECT_CONTENT', + content: content + } + expect(Reducers.selectedContentItems(entities, action)).to.deep.equal({}); + }) + it('should return an empty object', () => { + const entities = { + 1: { + Id: 1, + DisplayName: 'Some Article', + Status: ['Active'] + } + }; + const action = { + type: 'CLEAR_SELECTION' + } + expect(Reducers.selectedContentItems(entities, action)).to.deep.equal({}); + }) + }) + describe('batchResponseError reducer', () => { + it('should return the initial state', () => { + expect(Reducers.batchResponseError(undefined, {})).to.deep.equal(''); + }); + it('should return an error message', () => { + const action = { + type: 'DELETE_BATCH_FAILURE', + message: 'error' + } + expect(Reducers.batchResponseError(undefined, action)).to.deep.equal('error'); + }) + it('should return an error message', () => { + const action = { + type: 'COPY_BATCH_FAILURE', + message: 'error' + } + expect(Reducers.batchResponseError(undefined, action)).to.deep.equal('error'); + }) + it('should return an error message', () => { + const action = { + type: 'MOVE_BATCH_FAILURE', + message: 'error' + } + expect(Reducers.batchResponseError(undefined, action)).to.deep.equal('error'); + }) + it('should return an empty string', () => { + const action = { + type: 'MOVE_BATCH_SUCCESS', + response: {} + } + expect(Reducers.batchResponseError(undefined, action)).to.deep.equal(''); + }) + }) + describe('OdataBatchResponse reducer', () => { + it('should return the initial state', () => { + expect(Reducers.OdataBatchResponse(undefined, {})).to.deep.equal({}); + }); + it('should return a response object', () => { + const action = { + type: 'DELETE_BATCH_SUCCESS', + response: { + vmi: '1' + } + } + expect(Reducers.OdataBatchResponse(undefined, action)).to.deep.equal({ + vmi: '1' + }); + }) + it('should return an error message', () => { + const action = { + type: 'COPY_BATCH_SUCCESS', + response: { + vmi: '1' + } + } + expect(Reducers.OdataBatchResponse(undefined, action)).to.deep.equal({ + vmi: '1' + }); + }) + it('should return an error message', () => { + const action = { + type: 'MOVE_BATCH_SUCCESS', + response: { + vmi: '1' + } + } + expect(Reducers.OdataBatchResponse(undefined, action)).to.deep.equal({ + vmi: '1' + }); + }) + it('should return an empty string', () => { + const action = { + type: 'MOVE_BATCH_FAILURE', + message: 'error' + } + expect(Reducers.OdataBatchResponse(undefined, action)).to.deep.equal({}); }) }) describe('getContent', () => { @@ -888,12 +1180,53 @@ describe('Reducers', () => { expect(Reducers.getRepositoryUrl(state)).to.be.eq('https://dmsservice.demo.sensenet.com'); }); }); - describe('getSelectedContent', () => { + describe('getSelectedContentIds', () => { const state = { - selected: [1, 2] + selected: { + ids: [1, 2], + entities: { + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + } + } } it('should return the value of the selected reducers current state, an array with two items', () => { - expect(Reducers.getSelectedContent(state)).to.be.deep.equal([1, 2]) + expect(Reducers.getSelectedContentIds(state)).to.be.deep.equal([1, 2]) + }) + }) + describe('getSelectedContentItems', () => { + const state = { + selected: { + ids: [1, 2], + entities: { + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + } + } + } + it('should return the value of the selected reducers current state, an array with two items', () => { + expect(Reducers.getSelectedContentItems(state)).to.be.deep.equal({ + 1: { + DisplaName: 'aaa', + Id: 1 + }, + 2: { + DisplaName: 'bbb', + Id: 2 + } + }) }) }) describe('getOpenedContentId', () => {