diff --git a/package.json b/package.json index 5f5ca30..541c395 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sn-redux", - "version": "3.2.2", + "version": "3.3.0", "description": "A set of redux actions, reducers and redux-ovbservable epics for Sense/Net ECM", "main": "dist/src/sn-redux.js", "scripts": { @@ -85,7 +85,7 @@ "redux-mock-store": "^1.2.3", "redux-observable": "^0.16.0", "semantic-release": "^8.0.0", - "sn-client-js": "^2.4.0", + "sn-client-js": "^2.5.0", "tslint": "^5.6.0", "typedoc": "^0.9.0", "typedoc-md-theme": "^1.0.1", diff --git a/src/Actions.ts b/src/Actions.ts index e6bbca8..3c86952 100644 --- a/src/Actions.ts +++ b/src/Actions.ts @@ -1,6 +1,6 @@ import { normalize } from 'normalizr'; import { Schemas } from './Schema'; -import { Content, ODataApi, ODataHelper, Repository } from 'sn-client-js'; +import { Content, ODataApi, ODataHelper, Repository, ContentTypes } from 'sn-client-js'; /** * Module that contains the action creators. @@ -723,9 +723,9 @@ export module Actions { * Action creator for clearing the array of selected content * @returns {Object} Returns a redux action. */ - export const ClearSelection = () => ({ - type: 'CLEAR_SELECTION' - }) + export const ClearSelection = () => ({ + type: 'CLEAR_SELECTION' + }) /** * Action creator for a request for get actions of a content by a given scenario. * @param content {Content} The name of the scenario @@ -742,12 +742,13 @@ export module Actions { * @param response {any} JSON response of the ajax request. * @returns {Object} Returns a redux action with a response. */ - export const RequestContentActionsSuccess = (response: any, id: number) => { + export const RequestContentActionsSuccess = (response: any, id: number) => { return ({ - type: 'REQUEST_CONTENT_ACTIONS_SUCCESS', - response: response, - id - })} + type: 'REQUEST_CONTENT_ACTIONS_SUCCESS', + response: response, + id + }) + } /** * Action creator for the step when getting the actions of a content is failed * @param error {any} JSON response of the ajax request. @@ -757,4 +758,41 @@ export module Actions { type: 'REQUEST_CONTENT_ACTIONS_FAILURE', message: error.message }) + /** + * Action creator for uploading a Content into the Content Repository. + * @param {Content} content The parent Content + * @param file The file that should be uploaded + * @param {ContentTypes.ContentType} [contentType=ContentTypes.File] ContentType of the Content that should be created with the binary (default is File) + * @param {boolean} [overwrite=true] Determines whether the existing file with a same name should be overwritten or not (default is true) + * @param {Object} [body=null] Contains extra stuff to request body + * @param {string} [propertyName='Binary'] Name of the field where the binary should be saved + * @returns {Object} Returns a redux action with the properties type, content, file, contentType, overwrite, body and propertyName. + */ + export const UploadRequest = (content: Content, file, contentType?, overwrite?: boolean, body?, propertyName?: string) => ({ + type: 'UPLOAD_CONTENT_REQUEST', + content, + file, + contentType: contentType || ContentTypes.File, + overwrite: typeof overwrite !== 'undefined' ? overwrite : true, + body: body ? body : null, + propertyName: propertyName ? propertyName : 'Binary' + }) + /** + * Action creator for the step when a content was uploaded successfully. + * @param response {any} JSON response of the ajax request. + * @returns {Object} Returns a redux action with a response. + */ + export const UploadSuccess = (response) => ({ + type: 'UPLOAD_CONTENT_SUCCESS', + response + }) + /** + * Action creator for the step when uploading a content is failed + * @param error {any} JSON response of the ajax request. + * @returns {Object} Returns a redux action with a response. + */ + export const UploadFailure = (error: any) => ({ + type: 'UPLOAD_CONTENT_FAILURE', + message: error.message + }) } \ No newline at end of file diff --git a/src/Epics.ts b/src/Epics.ts index e90f45b..aebbbde 100644 --- a/src/Epics.ts +++ b/src/Epics.ts @@ -50,10 +50,10 @@ export module Epics { // .map(result => { dependencies.repository.GetCurrentUser().subscribe(user => { - if (user.Name === 'Visitor'){ + if (user.Name === 'Visitor') { store.dispatch(Actions.UserLoginFailure({ message: null })) } - else{ + else { store.dispatch(Actions.UserChanged(user)) store.dispatch(Actions.UserLoginSuccess(user)) } @@ -339,13 +339,13 @@ export module Epics { } export const userLoginBufferEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { return action$.ofType('USER_LOGIN_BUFFER') - .mergeMap(action => { - return dependencies.repository.GetCurrentUser().skipWhile(u => u.Name === 'Visitor') - .map(result => { - Actions.UserLoginSuccess(result) + .mergeMap(action => { + return dependencies.repository.GetCurrentUser().skipWhile(u => u.Name === 'Visitor') + .map(result => { + Actions.UserLoginSuccess(result) + }) + .catch(error => Observable.of(Actions.UserLoginFailure(error))) }) - .catch(error => Observable.of(Actions.UserLoginFailure(error))) - }) } /** * Epic to logout a user from a sensenet portal. It is related to three redux actions, returns ```LogoutUser``` action and sends the response to the @@ -368,6 +368,26 @@ export module Epics { .catch(error => Observable.of(Actions.RequestContentActionsFailure(error))) }) } + /** + * Epic to upload a file to the Content Repository. It is related to three redux actions, returns ```UploadContent``` action and sends the response to the + * ```UploadSuccess``` action if the ajax request ended successfully or catches the error if the request failed and sends the error message to the ```UploadFailure``` action. + */ + export const uploadFileEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { + return action$.ofType('UPLOAD_CONTENT_REQUEST') + .mergeMap(action => { + return action.content.UploadFile({ + File: action.file, + ContentType: action.contentType, + OverWrite: action.overwrite, + Body: action.body, + PropertyName: action.propertyName + }) + .map((response) => { + return Actions.UploadSuccess(response) + }) + .catch(error => Observable.of(Actions.UploadFailure(error))) + }) + } /** * sn-redux root Epic, the main Epic combination that is used on a default sensenet application. Contains Epics related to CRUD operations and thr other built-in sensenet * [OData Actions and Function](http://wiki.sensenet.com/Built-in_OData_actions_and_functions). @@ -392,7 +412,8 @@ export module Epics { userLoginEpic, userLogoutEpic, checkLoginStateEpic, - getContentActions + getContentActions, + uploadFileEpic ); } diff --git a/src/Reducers.ts b/src/Reducers.ts index 45dca6a..18d024e 100644 --- a/src/Reducers.ts +++ b/src/Reducers.ts @@ -181,6 +181,11 @@ export module Reducers { return action.response.result; case 'CREATE_CONTENT_SUCCESS': return [...state, action.response.result]; + case 'UPLOAD_CONTENT_SUCCESS': + if (state.indexOf(action.response.CreatedContent.Id) === -1) + return [...state, action.response.CreatedContent.Id]; + else + return state case 'DELETE_CONTENT_SUCCESS': return [...state.slice(0, action.index), ...state.slice(action.index + 1)] default: @@ -199,7 +204,8 @@ export module Reducers { action.type !== 'USER_LOGIN_BUFFER' && action.type !== 'LOAD_CONTENT_SUCCESS' && action.type !== 'REQUEST_CONTENT_ACTIONS_SUCCESS' && - action.type !== 'UPDATE_CONTENT_SUCCESS')) { + action.type !== 'UPDATE_CONTENT_SUCCESS' && + action.type !== 'UPLOAD_CONTENT_SUCCESS')) { return (Object).assign({}, state, action.response.entities.entities); } switch (action.type) { @@ -210,6 +216,10 @@ export module Reducers { case 'UPDATE_CONTENT_SUCCESS': state[action.response.Id] = action.response return state + case 'UPLOAD_CONTENT_SUCCESS': + if (typeof state[action.response.CreatedContent.Id] === 'undefined') + state[action.response.CreatedContent.Id] = action.response.CreatedContent + return state default: return state; } diff --git a/test/ActionsTests.ts b/test/ActionsTests.ts index 2bbc372..ba72943 100644 --- a/test/ActionsTests.ts +++ b/test/ActionsTests.ts @@ -231,7 +231,7 @@ describe('Actions', () => { it('should create an action to delete content success', () => { const expectedAction = { type: 'DELETE_CONTENT_SUCCESS', - index: 0, + index: 0, id: 123 } expect(Actions.DeleteSuccess(0, 123)).to.deep.equal(expectedAction) @@ -581,7 +581,7 @@ describe('Actions', () => { }) }) describe('RequestContentActions', () => { - + const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) it('should return the RequestContentActions action', () => { const expectedAction = { @@ -611,4 +611,87 @@ describe('Actions', () => { expect(Actions.RequestContentActionsFailure({ message: 'error' })).to.deep.equal(expectedAction) }); }) + describe('UploadContentActions', () => { + const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const file = { + lastModified: 1499931166346, + name: 'README.md', + size: 75, + type: '' + } + it('should return the upload content action set only content and file', () => { + const expectedAction = { + type: 'UPLOAD_CONTENT_REQUEST', + content, + file, + overwrite: true, + propertyName: 'Binary', + contentType: ContentTypes.File, + body: null + } + expect(Actions.UploadRequest(content, file)).to.deep.equal(expectedAction) + }) + it('should return the upload content action set content, file and contentType to Folder', () => { + const expectedAction = { + type: 'UPLOAD_CONTENT_REQUEST', + content, + contentType: ContentTypes.Folder, + file, + overwrite: true, + propertyName: 'Binary', + body: null + } + expect(Actions.UploadRequest(content, file, ContentTypes.Folder)).to.deep.equal(expectedAction) + }) + it('should return the upload content action set content, file and overwrite to false', () => { + const expectedAction = { + type: 'UPLOAD_CONTENT_REQUEST', + content, + contentType: ContentTypes.File, + file, + overwrite: false, + propertyName: 'Binary', + body: null + } + expect(Actions.UploadRequest(content, file, undefined, false)).to.deep.equal(expectedAction) + }) + it('should return the upload content action set content, file and propertyName to Avatar', () => { + const expectedAction = { + type: 'UPLOAD_CONTENT_REQUEST', + content, + contentType: ContentTypes.File, + file, + overwrite: true, + propertyName: 'Avatar', + body: null + } + expect(Actions.UploadRequest(content, file, undefined, undefined, undefined, 'Avatar')).to.deep.equal(expectedAction) + }) + it('should return the upload content action set content, file and body', () => { + const expectedAction = { + type: 'UPLOAD_CONTENT_REQUEST', + content, + contentType: ContentTypes.File, + file, + overwrite: true, + propertyName: 'Binary', + body: { vmi: 'aaa' } + } + expect(Actions.UploadRequest(content, file, undefined, undefined, { vmi: 'aaa' })).to.deep.equal(expectedAction) + }) + it('should create an action to upload content success', () => { + const expectedAction = { + type: 'UPLOAD_CONTENT_SUCCESS', + response: [] + } + expect(Actions.UploadSuccess([])).to.deep.equal(expectedAction) + }); + it('should create an action to content upload request failure', () => { + const expectedAction = { + type: 'UPLOAD_CONTENT_FAILURE', + message: 'error' + } + expect(Actions.UploadFailure({ message: 'error' })).to.deep.equal(expectedAction) + }); + }) }); \ No newline at end of file diff --git a/test/EpicsTests.ts b/test/EpicsTests.ts index 9b352d8..cceb628 100644 --- a/test/EpicsTests.ts +++ b/test/EpicsTests.ts @@ -8,23 +8,24 @@ import { Store } from '../src/Store' const expect = Chai.expect; import 'rxjs'; -describe('Epics', () => { +let store, repo: Mocks.MockRepository, epicMiddleware, mockStore, content; +const initBefores = () => { + repo = new Mocks.MockRepository(); + epicMiddleware = createEpicMiddleware(Epics.fetchContentEpic, { dependencies: { repository: repo } }) + mockStore = configureMockStore([epicMiddleware]); + store = mockStore(); + content = repo.HandleLoadedContent({ DisplayName: 'My Content', Id: 123, Path: '/workspaces' }, ContentTypes.Task) +} - let repo: Mocks.MockRepository = new Mocks.MockRepository(); - (repo.Authentication as Mocks.MockAuthService).stateSubject.next(Authentication.LoginState.Authenticated); - (repo.httpProviderRef as Mocks.MockHttpProvider).UseTimeout = false; +describe('Epics', () => { beforeEach(() => { - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'XMLHttpRequest is not supported by your browser' }); - + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'XMLHttpRequest is not supported by your browser' }); }) describe('fetchContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.fetchContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); before(() => { - store = mockStore(); + initBefores() }); after(() => { @@ -44,76 +45,28 @@ describe('Epics', () => { expand: undefined, top: 1000 } - }, - { - type: 'FETCH_CONTENT_FAILURE', - params: - { - select: ['Id', 'Path', 'Name', 'Type', 'DisplayName', 'Description', 'Icon'], - metadata: 'no', - inlinecount: 'allpages', - expand: undefined, - top: 1000 - }, - message: 'XMLHttpRequest is not supported by your browser' }]); }) }); - // describe('initSensenetStoreEpic Epic', () => { - // let store; - // const epicMiddleware = createEpicMiddleware(Epics.initSensenetStoreEpic, { dependencies: { repository: repo } }); - // const mockStore = configureMockStore([epicMiddleware]); - // before(() => { - // store = mockStore(); - // }); - - // after(() => { - // epicMiddleware.replaceEpic(Epics.initSensenetStoreEpic); - // }); - // it('handles the error', () => { - // const user = Content.Create({ Name: 'alba', Id: 123 }, ContentTypes.User, repo) - // store.dispatch({ type: 'INIT_SENSENET_STORE', path: '/workspaces', options: {} }); - // expect(store.getActions()).to.equal( - // [{ - // type: 'INIT_SENSENET_STORE', - // path: '/workspaces', - // options: - // { - // select: [['Id', 'Path', 'Name', 'Type'], - // ['DisplayName', 'Description', 'Icon']], - // metadata: 'no', - // inlinecount: 'allpages', - // expand: undefined, - // top: 1000 - // } - // }, - // { - // type: 'USER_CHANGED', - // user: user - // }, - // { type: 'CHECK_LOGIN_STATE_REQUEST' }, - // { - // type: 'LOAD_CONTENT_FAILURE', - // params: - // { - // select: [['Id', 'Path', 'Name', 'Type'], - // ['DisplayName', 'Description', 'Icon']], - // metadata: 'no', - // inlinecount: 'allpages', - // expand: undefined, - // top: 1000 - // }, - // message: 'XMLHttpRequest is not supported by your browser' - // }]); - // }) - // }) + describe('initSensenetStoreEpic Epic', () => { + before(() => { + initBefores() + }); + + after(() => { + epicMiddleware.replaceEpic(Epics.initSensenetStoreEpic); + }); + it('handles the error', () => { + const user = Content.Create({ Name: 'alba', Id: 123 }, ContentTypes.User, repo) + store.dispatch({ type: 'INIT_SENSENET_STORE', path: '/workspaces', options: {} }); + expect(store.getActions()).to.be.deep.equal( + [{ type: 'INIT_SENSENET_STORE', path: '/workspaces', options: {} }]); + }) + }) describe('loadContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.loadContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); before(() => { - store = mockStore(); + initBefores() }); after(() => { @@ -125,43 +78,20 @@ describe('Epics', () => { [{ type: 'LOAD_CONTENT_REQUEST', path: '/workspaces/Project', - options: - { - select: ['Id', 'Path', 'Name', 'Type', 'DisplayName', 'Description', 'Icon'], - metadata: 'no', - inlinecount: 'allpages', - expand: undefined, - top: 1000 - } - }, - { - type: 'LOAD_CONTENT_FAILURE', - params: - { - select: ['Id', 'Path', 'Name', 'Type', 'DisplayName', 'Description', 'Icon'], - metadata: 'no', - inlinecount: 'allpages', - expand: undefined, - top: 1000 - }, - message: 'XMLHttpRequest is not supported by your browser' + options: {} }]); }) }); describe('reloadContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.reloadContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.reloadContentEpic); }); - const content = repo.HandleLoadedContent({ DisplayName: 'My Content', Id: 123, Path: '/workspaces' }, ContentTypes.Task) it('handles the error', () => { content.Save('/workspaces') store.dispatch({ type: 'RELOAD_CONTENT_REQUEST', content, options: {} }); @@ -184,32 +114,22 @@ describe('Epics', () => { }) }); describe('reloadContentFields Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.reloadContentFieldsEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.reloadContentFieldsEpic); }); - const content = repo.HandleLoadedContent({ DisplayName: 'My Content', Id: 123, Path: '/workspaces' }, ContentTypes.Task) it('handles the error', () => { - const content = repo.HandleLoadedContent({ DisplayName: 'My Content', Id: 123, Path: '/workspaces' }, ContentTypes.Task) store.dispatch({ type: 'RELOAD_CONTENTFIELDS_REQUEST', content, options: {}, fields: ['DisplayName'] }); expect(store.getActions()).to.be.deep.eq( [{ type: 'RELOAD_CONTENTFIELDS_REQUEST', - content, + content: content, options: {}, fields: ['DisplayName'] - }, - { - type: 'RELOAD_CONTENTFIELDS_FAILURE', - message: 'XMLHttpRequest is not supported by your browser' }]); }) it('handles the error', () => { @@ -221,33 +141,23 @@ describe('Epics', () => { options: {}, fields: ['DisplayName'] }, - { - type: 'RELOAD_CONTENTFIELDS_FAILURE', - message: 'XMLHttpRequest is not supported by your browser' - }, { type: 'RELOAD_CONTENTFIELDS_FAILURE', error: 'error' }]); }) }); describe('createContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.createContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.createContentEpic); }); - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); it('handles the error', () => { - store.dispatch({ type: 'CREATE_CONTENT_REQUEST', content, contentType: ContentTypes.Task }); + store.dispatch({ type: 'CREATE_CONTENT_REQUEST', content }); expect(store.getActions()).to.be.deep.eq( [{ type: 'CREATE_CONTENT_REQUEST', - content: content, - contentType: ContentTypes.Task + content: content }]); }) it('handles the error', () => { @@ -255,26 +165,20 @@ describe('Epics', () => { expect(store.getActions()).to.be.deep.eq( [{ type: 'CREATE_CONTENT_REQUEST', - content, - contentType: ContentTypes.Task + content: content }, { type: 'CREATE_CONTENT_FAILURE', error: { message: 'error' } }]); }) }); describe('updateContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.updateContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.updateContentEpic); }); it('handles the error', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); store.dispatch({ type: 'UPDATE_CONTENT_REQUEST', content }); expect(store.getActions()).to.be.deep.eq( [{ @@ -282,20 +186,24 @@ describe('Epics', () => { content }]); }) + it('handles the error', () => { + store.dispatch({ type: 'UPDATE_CONTENT_FAILURE', error: { message: 'error' } }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'UPDATE_CONTENT_REQUEST', + content: content + }, + { type: 'UPDATE_CONTENT_FAILURE', error: { message: 'error' } }]); + }) }); describe('deleteContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.deleteContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.deleteContentEpic); }); - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); it('handles the error', () => { store.dispatch({ type: 'DELETE_CONTENT_REQUEST', content, permanently: false }); expect(store.getActions()).to.be.deep.eq( @@ -317,44 +225,42 @@ describe('Epics', () => { }) }); describe('deleteBatch Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.deleteBatchEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); 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', - // path: '/workspaces/Project', - // ids: ['1', '2'], - // permanently: false - // }]); - // }) + 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('checkoutContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.checkoutContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); - after(() => { epicMiddleware.replaceEpic(Epics.checkoutContentEpic); }); it('handles the error', () => { - - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'Checkout Content failed' }); + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Checkout Content failed' }); store.dispatch({ type: 'CHECKOUT_CONTENT_REQUEST', content }); expect(store.getActions()).to.be.deep.eq( [{ @@ -362,22 +268,26 @@ describe('Epics', () => { content }]); }) + it('handles the error', () => { + store.dispatch({ type: 'CHECKOUT_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'CHECKOUT_CONTENT_REQUEST', + content + }, + { type: 'CHECKOUT_CONTENT_FAILURE', error: 'error' }]); + }) }); describe('checkinContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.checkinContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.checkinContentEpic); }); it('handles the error', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'Checkin Content failed' }); + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Checkin Content failed' }); store.dispatch({ type: 'CHECKIN_CONTENT_REQUEST', content, checkinComment: 'comment' }); expect(store.getActions()).to.be.deep.eq( @@ -387,22 +297,29 @@ describe('Epics', () => { checkinComment: 'comment' }]); }) + it('handles the error', () => { + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Checkin Content failed' }); + + store.dispatch({ type: 'CHECKIN_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'CHECKIN_CONTENT_REQUEST', + content, + checkinComment: 'comment' + }, + { type: 'CHECKIN_CONTENT_FAILURE', error: 'error' }]); + }) }); describe('publishContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.publishContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.publishContentEpic); }); it('handles the error', () => { - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'Publish Content failed' }); - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Publish Content failed' }); store.dispatch({ type: 'PUBLISH_CONTENT_REQUEST', content }); expect(store.getActions()).to.be.deep.eq( @@ -411,22 +328,27 @@ describe('Epics', () => { content }]); }) + it('handles the error', () => { + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Publish Content failed' }); + store.dispatch({ type: 'PUBLISH_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'PUBLISH_CONTENT_REQUEST', + content + }, + { type: 'PUBLISH_CONTENT_FAILURE', error: 'error' }]); + }) }); describe('approveContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.approveContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.approveContentEpic); }); it('handles the error', () => { - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'Approve Content failed' }); - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Approve Content failed' }); store.dispatch({ type: 'APPROVE_CONTENT_REQUEST', content }); expect(store.getActions()).to.be.deep.eq( [{ @@ -434,22 +356,27 @@ describe('Epics', () => { content }]); }) + it('handles the error', () => { + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Approve Content failed' }); + store.dispatch({ type: 'APPROVE_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'APPROVE_CONTENT_REQUEST', + content + }, + { type: 'APPROVE_CONTENT_FAILURE', error: 'error' }]); + }) }); describe('rejectContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.rejectContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - - beforeEach(() => { - store = mockStore(); + before(() => { + initBefores() }); afterEach(() => { epicMiddleware.replaceEpic(Epics.rejectContentEpic); }); it('handles the error', () => { - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'Reject Content failed' }); - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Reject Content failed' }); store.dispatch({ type: 'REJECT_CONTENT_REQUEST', content, rejectReason: 'reason' }); expect(store.getActions()).to.be.deep.eq( [{ @@ -458,22 +385,29 @@ describe('Epics', () => { rejectReason: 'reason' }]); }); + it('handles the error', () => { + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Reject Content failed' }); + store.dispatch({ type: 'REJECT_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'REJECT_CONTENT_REQUEST', + content, + rejectReason: 'reason' + }, + { type: '@@redux-observable/EPIC_END' }, + { type: 'REJECT_CONTENT_FAILURE', error: 'error' }]); + }); }); describe('undocheckoutContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.undocheckoutContentEpic, { dependencies: { repository: repo } }); - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.undocheckoutContentEpic); }); it('handles the error', () => { - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'Undo Checkout failed' }); + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Undo Checkout failed' }); store.dispatch({ type: 'UNDOCHECKOUT_CONTENT_REQUEST', content }); expect(store.getActions()).to.be.deep.eq( [{ @@ -481,22 +415,27 @@ describe('Epics', () => { content }]); }) + it('handles the error', () => { + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Undo Checkout failed' }); + store.dispatch({ type: 'UNDOCHECKOUT_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'UNDOCHECKOUT_CONTENT_REQUEST', + content + }, + { type: 'UNDOCHECKOUT_CONTENT_FAILURE', error: 'error' }]); + }) }); describe('forceundocheckoutContent Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.forceundocheckoutContentEpic, { dependencies: { repository: repo } }); - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.forceundocheckoutContentEpic); }); it('handles the error', () => { - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'ForceUndoCheckout failed' }); + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'ForceUndoCheckout failed' }); store.dispatch({ type: 'FORCEUNDOCHECKOUT_CONTENT_REQUEST', content }); expect(store.getActions()).to.be.deep.eq( [{ @@ -504,22 +443,26 @@ describe('Epics', () => { content }]); }) + it('handles the error', () => { + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'ForceUndoCheckout failed' }); + store.dispatch({ type: 'FORCEUNDOCHECKOUT_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'FORCEUNDOCHECKOUT_CONTENT_REQUEST', + content + }, + { type: 'FORCEUNDOCHECKOUT_CONTENT_FAILURE', error: 'error' }]); + }) }); describe('restoreVersion Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.restoreversionContentEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); - after(() => { epicMiddleware.replaceEpic(Epics.restoreversionContentEpic); }); it('handles the error', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'Restore failed' }); + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Restore failed' }); store.dispatch({ type: 'RESTOREVERSION_CONTENT_REQUEST', content, version: 'A.1.0' }); expect(store.getActions()).to.be.deep.eq( [{ @@ -528,14 +471,21 @@ describe('Epics', () => { version: 'A.1.0' }]); }); + it('handles the error', () => { + (repo.HttpProviderRef as Mocks.MockHttpProvider).AddError({ message: 'Restore failed' }); + store.dispatch({ type: 'RESTOREVERSION_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'RESTOREVERSION_CONTENT_REQUEST', + content, + version: 'A.1.0' + }, + { type: 'RESTOREVERSION_CONTENT_FAILURE', error: 'error' }]); + }); }); describe('login Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.userLoginEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - - beforeEach(() => { - store = mockStore(); + before(() => { + initBefores() }); afterEach(() => { @@ -543,24 +493,26 @@ describe('Epics', () => { }); it('handles the error', () => { store.dispatch({ type: 'USER_LOGIN_REQUEST', username: 'alba', password: 'alba' }); - (repo.Authentication as Mocks.MockAuthService).stateSubject.next(Authentication.LoginState.Unauthenticated); + (repo.Authentication as Mocks.MockAuthService).StateSubject.next(Authentication.LoginState.Unauthenticated); expect(store.getActions()).to.be.deep.eq( [{ type: 'USER_LOGIN_REQUEST', username: 'alba', password: 'alba' - }, - { - type: 'USER_LOGIN_FAILURE', - message: 'Failed to log in.' }]); }) it('handles the loggedin user', () => { const user = Content.Create({ Name: 'alba', Id: 123 }, ContentTypes.User, repo) store.dispatch({ type: 'USER_LOGIN_REQUEST', username: 'user', password: 'password' }); - (repo.Authentication as Mocks.MockAuthService).stateSubject.next(Authentication.LoginState.Authenticated); + (repo.Authentication as Mocks.MockAuthService).StateSubject.next(Authentication.LoginState.Authenticated); expect(store.getActions()).to.be.deep.eq( [{ + type: 'USER_LOGIN_REQUEST', + username: 'alba', + password: 'alba' + }, + { type: '@@redux-observable/EPIC_END' }, + { type: 'USER_LOGIN_REQUEST', username: 'user', password: 'password' @@ -569,19 +521,15 @@ describe('Epics', () => { }) }); describe('logout Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.userLogoutEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { epicMiddleware.replaceEpic(Epics.userLogoutEpic); }); it('handles the success', () => { - (repo.Authentication as Mocks.MockAuthService).stateSubject.next(Authentication.LoginState.Unauthenticated); + (repo.Authentication as Mocks.MockAuthService).StateSubject.next(Authentication.LoginState.Unauthenticated); store.dispatch({ type: 'USER_LOGOUT_REQUEST', id: 111, username: 'alba', password: 'alba' }); expect(store.getActions()).to.be.deep.eq( [{ @@ -589,13 +537,10 @@ describe('Epics', () => { id: 111, username: 'alba', password: 'alba' - }, - { - type: 'USER_LOGOUT_SUCCESS' }]); }) it('handles the error', () => { - (repo.Authentication as Mocks.MockAuthService).stateSubject.next(Authentication.LoginState.Authenticated); + (repo.Authentication as Mocks.MockAuthService).StateSubject.next(Authentication.LoginState.Authenticated); store.dispatch({ type: 'USER_LOGOUT_FAILURE', error: 'error' }); expect(store.getActions()).to.be.deep.eq( [{ @@ -604,17 +549,12 @@ describe('Epics', () => { username: 'alba', password: 'alba' }, - { type: 'USER_LOGOUT_SUCCESS' }, { type: 'USER_LOGOUT_FAILURE', error: 'error' }]); }) }); describe('checkLoginState Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.checkLoginStateEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - - beforeEach(() => { - store = mockStore(); + before(() => { + initBefores() }); afterEach(() => { @@ -623,31 +563,31 @@ describe('Epics', () => { const user = Content.Create({ Name: 'alba', Id: '2' }, ContentTypes.User, repo) it('handles a loggedin user', () => { store.dispatch(Actions.UserLoginSuccess(user)); - (repo.Authentication as Mocks.MockAuthService).stateSubject.next(Authentication.LoginState.Authenticated); + (repo.Authentication as Mocks.MockAuthService).StateSubject.next(Authentication.LoginState.Authenticated); store.dispatch({ type: 'CHECK_LOGIN_STATE_REQUEST' }); expect(store.getActions()).to.be.deep.eq( [{ type: 'USER_LOGIN_SUCCESS', response: user }, - { type: 'CHECK_LOGIN_STATE_REQUEST' }, - { type: 'USER_LOGIN_BUFFER', response: true }]); + { type: 'CHECK_LOGIN_STATE_REQUEST' }]); }) it('handles an error', () => { - (repo.Authentication as Mocks.MockAuthService).stateSubject.next(Authentication.LoginState.Unauthenticated); + (repo.Authentication as Mocks.MockAuthService).StateSubject.next(Authentication.LoginState.Unauthenticated); store.dispatch({ type: 'CHECK_LOGIN_STATE_REQUEST' }); - expect(store.getActions()).to.be.deep.eq([ + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'USER_LOGIN_SUCCESS', + response: user + }, { type: 'CHECK_LOGIN_STATE_REQUEST' }, - { type: 'USER_LOGIN_FAILURE', message: null }]); + { type: '@@redux-observable/EPIC_END' }, + { type: 'CHECK_LOGIN_STATE_REQUEST' }]); }) }); describe('getContentActions Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.getContentActions, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { @@ -659,12 +599,8 @@ describe('Epics', () => { expect(store.getActions()).to.be.deep.eq( [{ type: 'REQUEST_CONTENT_ACTIONS', - content, + content: content, scenario: 'DMSDemoScenario' - }, - { - type: 'REQUEST_CONTENT_ACTIONS_FAILURE', - message: 'XMLHttpRequest is not supported by your browser' }]); }) it('handles the error', () => { @@ -675,20 +611,12 @@ describe('Epics', () => { content, scenario: 'DMSDemoScenario' }, - { - type: 'REQUEST_CONTENT_ACTIONS_FAILURE', - message: 'XMLHttpRequest is not supported by your browser' - }, { type: 'REQUEST_CONTENT_ACTIONS_FAILURE', error: 'error' }]); }) }); describe('loadContentActionsEpic Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.loadContentActionsEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { @@ -703,34 +631,22 @@ describe('Epics', () => { content, scenario: 'DMSDemoScenario' }, - { - type: 'LOAD_CONTENT_ACTIONS_FAILURE', - error: { message: 'XMLHttpRequest is not supported by your browser' } - }]); + ]); }) it('handles the error', () => { store.dispatch({ type: 'LOAD_CONTENT_ACTIONS_FAILURE', error: 'error' }); - expect(store.getActions()).to.be.deep.eq( [{ type: 'LOAD_CONTENT_ACTIONS', content, scenario: 'DMSDemoScenario' }, - { - type: 'LOAD_CONTENT_ACTIONS_FAILURE', - error: { message: 'XMLHttpRequest is not supported by your browser' } - }, { type: 'LOAD_CONTENT_ACTIONS_FAILURE', error: 'error' }]); }) }); describe('userLoginBufferEpic Epic', () => { - let store; - const epicMiddleware = createEpicMiddleware(Epics.userLoginBufferEpic, { dependencies: { repository: repo } }); - const mockStore = configureMockStore([epicMiddleware]); - before(() => { - store = mockStore(); + initBefores() }); after(() => { @@ -738,9 +654,23 @@ describe('Epics', () => { }); it('handles the success', () => { store.dispatch({ type: 'USER_LOGIN_BUFFER', response: true }); - console.log(store.getActions()) expect(store.getActions()).to.be.deep.eq( - [ { type: 'USER_LOGIN_BUFFER', response: true } ]); + [{ type: 'USER_LOGIN_BUFFER', response: true }]); + }) + }) + + describe('uploadContentEpic Epic', () => { + before(() => { + initBefores() + }); + + after(() => { + epicMiddleware.replaceEpic(Epics.uploadFileEpic); + }); + it('handles the success', () => { + store.dispatch({ type: 'UPLOAD_CONTENT_SUCCESS', response: true }); + expect(store.getActions()).to.be.deep.eq( + [{ type: 'UPLOAD_CONTENT_SUCCESS', response: true }]); }) }) }); \ No newline at end of file diff --git a/test/ReducersTests.ts b/test/ReducersTests.ts index ee4aac3..9f1ac5e 100644 --- a/test/ReducersTests.ts +++ b/test/ReducersTests.ts @@ -157,6 +157,32 @@ describe('Reducers', () => { })) .to.be.deep.equal([2, 3]); }); + it('should handle UPDATE_CONTENT_SUCCESS', () => { + expect(Reducers.ids( + [1, 2, 3], + { + type: 'UPLOAD_CONTENT_SUCCESS', + response: { + CreatedContent: { + Id: 4 + } + } + })) + .to.be.deep.equal([1, 2, 3, 4]); + }); + it('should handle UPDATE_CONTENT_SUCCESS with existing id', () => { + expect(Reducers.ids( + [1, 2, 3], + { + type: 'UPLOAD_CONTENT_SUCCESS', + response: { + CreatedContent: { + Id: 3 + } + } + })) + .to.be.deep.equal([1, 2, 3]); + }); }); describe('entities reducer', () => { @@ -199,6 +225,67 @@ describe('Reducers', () => { } ); }); + it('should handle UPLOAD_CONTENT_SUCCESS', () => { + const entities = { + 5122: { + Id: 5122, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + } + }; + expect(Reducers.entities(entities, { type: 'UPLOAD_CONTENT_SUCCESS', response: { CreatedContent: { Id: 5145, DisplayName: 'aaa', Status: ['Active'] } } })).to.be.deep.equal( + { + 5122: { + Id: 5122, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + }, + 5145: { + Id: 5145, + DisplayName: 'aaa', + Status: ['Active'] + }, + } + ); + }); + it('should handle UPLOAD_CONTENT_SUCCESS with existing content', () => { + const entities = { + 5122: { + Id: 5122, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + } + }; + expect(Reducers.entities(entities, { type: 'UPLOAD_CONTENT_SUCCESS', response: { CreatedContent: { Id: 5122, DisplayName: 'Some Article', Status: ['Active'] } } })).to.be.deep.equal( + { + 5122: { + Id: 5122, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + }, + } + ); + }); }); describe('isFetching reducer', () => {