From c1e678df2622d9b2b29e02317b7291d512f7bf2c Mon Sep 17 00:00:00 2001 From: Aniko Litvanyi Date: Mon, 28 Aug 2017 10:10:05 +0200 Subject: [PATCH 01/13] Merge release/3.0.1 branch (#35) (#36) * fix(Epics): Fix checkoutLoginState at failure checkoutLoginState epic is now return null instead of error message when user is not authenticated * chore: Update version number --- package.json | 2 +- src/Epics.ts | 2 +- test/EpicsTests.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0758cd9..6c6edb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sn-redux", - "version": "3.0.0", + "version": "3.0.1", "description": "A set of redux actions, reducers and redux-ovbservable epics for Sense/Net ECM", "main": "dist/src/sn-redux.js", "scripts": { diff --git a/src/Epics.ts b/src/Epics.ts index 5ba2d34..72a7c94 100644 --- a/src/Epics.ts +++ b/src/Epics.ts @@ -300,7 +300,7 @@ export module Epics { return result === Authentication.LoginState.Authenticated ? Actions.UserLoginSuccess(result) : - Actions.UserLoginFailure({ message: 'Failed to log in.' }); + Actions.UserLoginFailure({ message: null }); }) }) } diff --git a/test/EpicsTests.ts b/test/EpicsTests.ts index 1171d7a..5f3dcad 100644 --- a/test/EpicsTests.ts +++ b/test/EpicsTests.ts @@ -600,7 +600,7 @@ describe('Epics', () => { store.dispatch({ type: 'CHECK_LOGIN_STATE_REQUEST' }); expect(store.getActions()).to.be.deep.eq([ { type: 'CHECK_LOGIN_STATE_REQUEST' }, - { type: 'USER_LOGIN_FAILURE', message: 'Failed to log in.' }]); + { type: 'USER_LOGIN_FAILURE', message: null }]); }) }); }); \ No newline at end of file From 454519acb2c0aa768d222547a7c2e8d0515631f7 Mon Sep 17 00:00:00 2001 From: Aniko Litvanyi Date: Thu, 31 Aug 2017 13:34:22 +0200 Subject: [PATCH 02/13] [KFI]chore: Update version number --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 84fcf33..107f52a 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,6 @@ { "name": "sn-redux", -<<<<<<< HEAD "version": "3.1.1", -======= - "version": "3.0.1", ->>>>>>> c1e678df2622d9b2b29e02317b7291d512f7bf2c "description": "A set of redux actions, reducers and redux-ovbservable epics for Sense/Net ECM", "main": "dist/src/sn-redux.js", "scripts": { From 8107c1361484d504d97a543579773699e6a285a6 Mon Sep 17 00:00:00 2001 From: Lajos Date: Mon, 6 Nov 2017 13:42:20 +0100 Subject: [PATCH 03/13] sn-client-js update (#49) * chore(package): update @types/redux-mock-store to version 0.0.11 (#37) * chore(package): update semantic-release to version 8.0.0 (#38) * Greenkeeper/sn client js 2.4.0 (#40) * chore(package): update sn-client-js to version 2.4.0 * [KFI]chore(launch.json): fixed mocha debug profile for vs code * [KFI]test(EpicsTest): Flatterned Select assertions, added RELOAD_CONTENTFIELDS_REQUEST field list (r * Release/3.2.0 (#41) * [KFI]fix(Epics): Remove requestContent call from the initSensenetStoreEpic * [KFI]fix(Reducers): Fix userAvatarPath For now userAvatarPath contains the Avatar field's value so it doesn't matter if the avatar is a binary or a reference * [KFI]test(ReducerTests): Fix userAvatarPath test * [KFI]fix(Reducers): Fix fetch getError and order reducers * [KFI]feat(Actions): Add two new Actions for selection and deselecting a Content * [KFI]feat(Reducers): Change selected reducer to handle select and deselect actions * [KFI]test(Actions): Add tests for select and deselect actions * [KFI]test(Reducers): Add test for testing the selected Reducer handling select and deselect Actions * [KFI]feat(Actions): Add actions for getting sn Actions of a content * [KFI]test(Actions): Add tests for testing the new sn action getter Actions * [KFI]feat(Reducers): Complete the Reducer of the content items * [KFI]test(Reducers): Add test for testing the childrenactions reducer * [KFI]feat(Reducers): Add a isOpened reducer This reducer holds the id of the content where the actionmenu was opened last * [KFI]fix(Reducers): Change action in isOpened reducer to REQUEST_CONTENT_ACTION * [KFI]test(Reducers): Add test for testing isOpened reducer * [KFI]feat(Reducers): Add a function to return the currently opened items id * [KFI]refactor(Reducer): Rename getOpenedContentId to getOpenedContent * [KFI]test(Reducers): Add a test for testing getOpenedContent function * [KFI]feat(Actions): Add id as input attr to RequestContentActions Action * [KFI]feat(Reducers): Add getChildrenActions function to return actions from the state tree * [KFI]feat(Actions): Change RequestContentActions first input param to content * [KFI]feat(Epics): Add getContentActions Epic * [KFI]fix(GetActions): Fix GetActions action * [KFI]feat(Reducers): Add getCurrentContent to get the path of the current content * [KFI]fix(Epics): Fix loadContentActions epic * [KFI]test: Improve epic tests * [KFI]chore: Update version number * [KFI]fix(Login): Fix action order and subscribing in the login process * [KFI]test(Login): Add tests for testing the new login buffer action and epic * [KFI]chore: Update version number * chore(package): update mocha to version 4.0.0 (#42) * chore(package): update typedoc to version 0.9.0 (#43) * Merge release/3.2.2 (#44) * [KFI]fix(Reducers): Fix entity list after update success action * [KFI]test(Reducers): Add test for checking the entity list after update success * [KFI]chore: Update version number * [KFI]chore: Update version number * [KFI]feat(Actions): Add an action to clear the selected reducer * [KFI]test(Actions): Add test for testing the new clear selection action * [KFI]chore: Update version number to 3.2.2 * Fileupload (#45) * [KFI]feat(Actions): Add upload request, success and failure actions * [KFI]test(Actions): Add tests for testing the new upload related actions * [KFI]feat(Epics): Add new epic for upload a file * [KFI]test(Epics): Add test for testing the upload epic * [KFI]feat(Reducers): Change ids and entities reducer to handle UPLOAD_CONTENT_SUCCESS * [KFI]test(Reducers): Add upload related reducer tests * [KFI]chore: Update sn-client-js to 2.5.0 * [KFI]test(Epics): Fix epic tests related to sn-client-js upgrade * [KFI]chore: Update version number to 3.3.0 * [KFI]fix(Epics): Add rxjs mergeMap import * merge fix * [KFI]refactor(project): replaced '@reactivex/rxjs' package with 'rxjs', updated imports --- .vscode/launch.json | 54 +++-- package.json | 15 +- src/Actions.ts | 153 ++++++++++-- src/Epics.ts | 80 +++++- src/Reducers.ts | 110 ++++++--- test/ActionsTests.ts | 220 ++++++++++++++--- test/EpicsTests.ts | 547 ++++++++++++++++++++++++++---------------- test/ReducersTests.ts | 246 +++++++++++++++++-- 8 files changed, 1056 insertions(+), 369 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d6a3d68..fe89608 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,27 +1,31 @@ { - // Use IntelliSense to learn about possible Node.js debug attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Debug tests", - "runtimeExecutable": "mocha", - "windows": { - "runtimeExecutable": "mocha.cmd" + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Mocha Tests", + "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "-p", + "${workspaceRoot}\\tsconfig.json", + "${workspaceRoot}/dist/test/index.js" + ], + "internalConsoleOptions": "openOnSessionStart" }, - "preLaunchTask": "build", - "runtimeArgs": [ - "--debug-brk", - "./dist/test/index.js" - ], - "program": "${workspaceRoot}\\test\\index.ts", - "outFiles": [ - "${workspaceRoot}\\dist\\**\\*.js" - ], - "port": 5858 - } - ] -} \ No newline at end of file + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceRoot}\\dist\\src\\sn-redux.js", + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ] + } + ] + } \ No newline at end of file diff --git a/package.json b/package.json index 107f52a..56a35a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sn-redux", - "version": "3.1.1", + "version": "3.3.1", "description": "A set of redux actions, reducers and redux-ovbservable epics for Sense/Net ECM", "main": "dist/src/sn-redux.js", "scripts": { @@ -54,14 +54,13 @@ }, "homepage": "https://sensenet.com", "dependencies": { - "@reactivex/rxjs": "^5.4.3", "normalizr": "^3.2.3", "nyc": "^11.1.0", "redux": "^3.7.2", "redux-logger": "^3.0.6", "redux-observable": "^0.16.0", "rimraf": "^2.6.1", - "rxjs": "^5.4.3", + "rxjs": "^5.5.2", "sensenet-kfi-cz-conventional-changelog": "^1.0.0" }, "devDependencies": { @@ -69,7 +68,7 @@ "@types/mocha": "^2.2.42", "@types/nock": "^8.2.1", "@types/orchestrator": "^0.3.0", - "@types/redux-mock-store": "^0.0.10", + "@types/redux-mock-store": "^0.0.11", "chai": "^4.1.1", "codecov.io": "^0.1.6", "commitizen": "^2.9.6", @@ -78,16 +77,16 @@ "gulp": "^3.9.1", "gulp-rename": "^1.2.2", "gulp-typedoc": "^2.0.3", - "mocha": "^3.5.0", + "mocha": "^4.0.0", "nock": "^9.0.14", "normalizr": "^3.2.3", "redux": "^3.7.2", "redux-mock-store": "^1.2.3", "redux-observable": "^0.16.0", - "semantic-release": "^7.0.2", - "sn-client-js": "^2.3.0", + "semantic-release": "^8.0.0", + "sn-client-js": "^3.0.0-development.2", "tslint": "^5.6.0", - "typedoc": "^0.8.0", + "typedoc": "^0.9.0", "typedoc-md-theme": "^1.0.1", "typedoc-plugin-external-module-name": "^1.0.9", "typescript": "^2.4.2" diff --git a/src/Actions.ts b/src/Actions.ts index 49aca5d..9461fc6 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, IContent, ODataApi, ODataHelper, Repository, ContentTypes } from 'sn-client-js'; /** * Module that contains the action creators. @@ -116,7 +116,7 @@ export module Actions { * @param path {string} Path of the root Content * @param options {OData.IODataParams} Represents an ODataOptions object based on the IODataOptions interface. Holds the possible url parameters as properties. */ - export const InitSensenetStore = (path?: string, options: ODataApi.IODataParams = {}) => ({ + export const InitSensenetStore = (path?: string, options: ODataApi.IODataParams = {}) => ({ type: 'INIT_SENSENET_STORE', path: path ? path : '/Root', options: options @@ -128,7 +128,7 @@ export module Actions { * @param contentType {ContentType} Content Type of the requested content. * @returns {Object} Returns a redux action with the properties type, path, options and contentType. */ - export const RequestContent = (path: string, options: ODataApi.IODataParams = {}, contentType?: { new(...args): T }) => ({ + export const RequestContent = (path: string, options: ODataApi.IODataParams = {}, contentType?: { new(...args): T }) => ({ type: 'FETCH_CONTENT_REQUEST', path, options, @@ -140,7 +140,7 @@ export module Actions { * @param params {string} String with the url params. * @returns {Object} Returns a redux action with the properties type, normalized response and params. */ - export const ReceiveContent = (response: Content[], params: any) => + export const ReceiveContent = (response: IContent[], params: any) => ({ type: 'FETCH_CONTENT_SUCCESS', response: normalize(response, Schemas.arrayOfContent), @@ -164,7 +164,7 @@ export module Actions { * @param contentType {ContentType} Content Type of the requested content. * @returns {Object} Returns a redux action with the properties id, options and contentType. */ - export const LoadContent = (id: number, options: ODataApi.IODataParams = {}, contentType?: { new(...args): T }) => ({ + export const LoadContent = (id: number, options: ODataApi.IODataParams = {}, contentType?: { new(...args): T }) => ({ type: 'LOAD_CONTENT_REQUEST', id, options: options, @@ -176,7 +176,7 @@ export module Actions { * @param params {string} String with the url params. * @returns {Object} Returns a redux action with the properties type, normalized response and params. */ - export const ReceiveLoadedContent = (response: Content, params: any) => + export const ReceiveLoadedContent = (response: Content, params: any) => ({ type: 'LOAD_CONTENT_SUCCESS', response, @@ -198,7 +198,7 @@ export module Actions { * @param content {Content} The requested Content. * @param scenario {string} The Actions should be in the given Scenario */ - export const LoadContentActions = (content: Content, scenario?: string) => ({ + export const LoadContentActions = (content: IContent, scenario?: string) => ({ type: 'LOAD_CONTENT_ACTIONS', content, scenario @@ -225,7 +225,7 @@ export module Actions { * @param actionName {string} Name of the action witch which we want to reload the content (edit, new, etc). * @returns {Object} Returns a redux action with the properties type and actionName. */ - export const ReloadContent = (content: Content, actionName: 'edit' | 'view') => ({ + export const ReloadContent = (content: Content, actionName: 'edit' | 'view') => ({ type: 'RELOAD_CONTENT_REQUEST', content, actionName @@ -255,7 +255,7 @@ export module Actions { * @param fields {any[]} List of the fields to be loaded * @returns {Object} Returns a redux action with the properties type and fields. */ - export const ReloadContentFields = (content: Content, fields: any[]) => ({ + export const ReloadContentFields = (content: Content, fields: any[]) => ({ type: 'RELOAD_CONTENTFIELDS_REQUEST', content, fields @@ -284,7 +284,7 @@ export module Actions { * @param content {Content} Content that have to be created in the Content Respository. * @returns {Object} Returns a redux action with the properties type, path of the parent and content. */ - export const CreateContent = (content: T) => ({ + export const CreateContent = (content: T) => ({ type: 'CREATE_CONTENT_REQUEST', content }); @@ -312,7 +312,7 @@ export module Actions { * @param content {Object} Content object with the field value pairs that have to be modified. * @returns {Object} Returns a redux action with the properties type, id and fields. */ - export const UpdateContent = (content: Partial) => ({ + export const UpdateContent = (content: Partial) => ({ type: 'UPDATE_CONTENT_REQUEST', content }); @@ -341,7 +341,7 @@ export module Actions { * @param permanently {boolean} Defines whether the a 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 Delete = (content: T, permanently: boolean = false) => ({ type: 'DELETE_CONTENT_REQUEST', content, permanently }); + export const Delete = (content: T, permanently: boolean = false) => ({ type: 'DELETE_CONTENT_REQUEST', content, permanently }); /** * Action creator for the step when Content deleted successfully. * @param index {number} Index of the item in the state collection. @@ -398,7 +398,7 @@ export module Actions { * @param content {number} Content that should be checked out. * @returns {Object} Returns a redux action with the properties type and id . */ - export const CheckOut = (content: T) => ({ + export const CheckOut = (content: T) => ({ type: 'CHECKOUT_CONTENT_REQUEST', content }) @@ -425,7 +425,7 @@ export module Actions { * @param content {Content} Content that should be checked in. * @returns {Object} Returns a redux action with the properties type, id and checkinComment. */ - export const CheckIn = (content: T, checkInComment: string = '') => ({ + export const CheckIn = (content: T, checkInComment: string = '') => ({ type: 'CHECKIN_CONTENT_REQUEST', content, checkInComment @@ -453,7 +453,7 @@ export module Actions { * @param content {Content} Content that should be published. * @returns {Object} Returns a redux action with the properties type and id. */ - export const Publish = (content: T) => ({ + export const Publish = (content: T) => ({ type: 'PUBLISH_CONTENT_REQUEST', content }) @@ -480,7 +480,7 @@ export module Actions { * @param content {Content} Content that should be approved. * @returns {Object} Returns a redux action with the properties type and id. */ - export const Approve = (content: T) => ({ + export const Approve = (content: T) => ({ type: 'APPROVE_CONTENT_REQUEST', content }) @@ -508,7 +508,7 @@ export module Actions { * @param rejectReason {string} Reason of rejecting. * @returns {Object} Returns a redux action with the properties type, rejectReason and id. */ - export const Reject = (content: T, rejectReason: string = '') => ({ + export const Reject = (content: T, rejectReason: string = '') => ({ type: 'REJECT_CONTENT_REQUEST', content, rejectReason @@ -536,7 +536,7 @@ export module Actions { * @param content {Content} Content that should be checked in. * @returns {Object} Returns a redux action with the properties type and id. */ - export const UndoCheckout = (content: T) => ({ + export const UndoCheckout = (content: T) => ({ type: 'UNDOCHECKOUT_CONTENT_REQUEST', content }) @@ -563,7 +563,7 @@ export module Actions { * @param content {Content} Content that should be checked in. * @returns {Object} Returns a redux action with the properties type and id. */ - export const ForceUndoCheckout = (content: T) => ({ + export const ForceUndoCheckout = (content: T) => ({ type: 'FORCEUNDOCHECKOUT_CONTENT_REQUEST', content }) @@ -591,7 +591,7 @@ export module Actions { * @param version {string} Specify which old version to restore * @returns {Object} Returns a redux action with the properties type and id. */ - export const RestoreVersion = (content: T, version: string) => ({ + export const RestoreVersion = (content: T, version: string) => ({ type: 'RESTOREVERSION_CONTENT_REQUEST', content, version @@ -633,8 +633,8 @@ export module Actions { /** * Action creator for login a user to a sensenet portal. - * @param userName {string} Login name of the user. - * @param password {string} Password of the user. + * @param {string} userName Login name of the user. + * @param {string} password Password of the user. * @returns {Object} Returns a redux action with the properties userName and password. */ export const UserLogin = (userName: string, password: string) => ({ @@ -642,21 +642,31 @@ export module Actions { userName, password }) + /** + * Action creator for handling a user login success response without a loggedin user. + * @param {boolean} response Response of the login request + * @returns {Object} Returns a redux action with the properties userName and password. + */ + export const UserLoginBuffer = (response: boolean) => ({ + type: 'USER_LOGIN_BUFFER', + response + }) /** * Action creator for the step when a User is logged in successfully. * @param response {any} JSON response of the ajax request. * @returns {Object} Returns a redux action with the user as a response. */ - export const UserLoginSuccess = (response: any) => ({ + + export const UserLoginSuccess = (content: Content) => ({ type: 'USER_LOGIN_SUCCESS', - response: response + response: content }) /** * Action creator for the step when login of a user 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 UserLoginFailure = (error: any) => ({ + 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 }) @@ -693,4 +703,97 @@ export module Actions { type: 'LOAD_REPOSITORY', repository: repositoryConfig }) + /** + * Action creator for selecting a Content + * @param id {number} The id of the selected Content + * @returns {Object} Returns a redux action. + */ + export const SelectContent = (id) => ({ + type: 'SELECT_CONTENT', + id + }) + /** + * 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) => ({ + type: 'DESELECT_CONTENT', + id + })/** + * Action creator for clearing the array of selected content + * @returns {Object} Returns a redux action. + */ + 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 + * @param scenario {string} The name of the scenario + * @returns {Object} Returns a redux action. + */ + export const RequestContentActions = (content, scenario?: string) => ({ + type: 'REQUEST_CONTENT_ACTIONS', + content, + scenario + }) + /** + * Action creator for the step getting the actions of a content successfully. + * @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) => { + return ({ + 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. + * @returns {Object} Returns a redux action with a response. + */ + export const RequestContentActionsFailure = (error: any) => ({ + 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 ec82a4b..e6571b8 100644 --- a/src/Epics.ts +++ b/src/Epics.ts @@ -2,8 +2,9 @@ import { Actions } from './Actions'; import { Reducers } from './Reducers'; import { ActionsObservable, combineEpics } from 'redux-observable'; -import { Observable } from '@reactivex/rxjs'; -import { Repository, Content, Collection, ODataApi, Authentication } from 'sn-client-js'; +import { Observable } from 'rxjs/Observable'; +import { Repository, Content, ContentTypes, Collection, ODataApi, Authentication } from 'sn-client-js'; +import 'rxjs/add/operator/mergeMap'; /** * Module for redux-observable Epics of the sensenet built-in OData actions. @@ -43,14 +44,24 @@ export module Epics { export const initSensenetStoreEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { return action$.ofType('INIT_SENSENET_STORE') .mergeMap(action => { + + store.dispatch(Actions.LoadRepository(dependencies.repository.Config)) + // dependencies.repository.Authentication.State.skipWhile(state => state === Authentication.LoginState.Pending) + // .first() + // .map(result => { + dependencies.repository.GetCurrentUser().subscribe(user => { - store.dispatch(Actions.UserChanged(user)) + if (user.Name === 'Visitor') { + store.dispatch(Actions.UserLoginFailure({ message: null })) + } + else { + store.dispatch(Actions.UserChanged(user)) + store.dispatch(Actions.UserLoginSuccess(user)) + } }) - store.dispatch(Actions.CheckLoginState()) - store.dispatch(Actions.LoadRepository(dependencies.repository.Config)) + return dependencies.repository.Load(action.path, action.options) .map((response) => { - store.dispatch(Actions.RequestContent(action.path, action.options)) return Actions.ReceiveLoadedContent(response, action.options) }) .catch(error => { @@ -83,7 +94,7 @@ export module Epics { .mergeMap(action => { return dependencies.repository.Load(action.id, action.options) .map((response) => { - store.dispatch(Actions.LoadContentActions(response, action.scenario)) + //store.dispatch(Actions.LoadContentActions(response, action.scenario)) return Actions.ReceiveLoadedContent(response, action.options) }) .catch(error => { @@ -99,8 +110,9 @@ export module Epics { export const loadContentActionsEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { return action$.ofType('LOAD_CONTENT_ACTIONS') .mergeMap(action => { - return action.content.Actions(action.scenario) - .map(Actions.ReceiveContentActions) + let c = dependencies.repository.HandleLoadedContent(action.content, ContentTypes.GenericContent); + return c.Actions(action.scenario) + .map(result => Actions.ReceiveContentActions(result)) .catch(error => Observable.of(Actions.ReceiveContentActionsFailure(error))) }) } @@ -299,7 +311,7 @@ export module Epics { .first() .map(result => { return result === Authentication.LoginState.Authenticated ? - Actions.UserLoginSuccess(result) + Actions.UserLoginBuffer(true) : Actions.UserLoginFailure({ message: null }); }) @@ -314,15 +326,28 @@ export module Epics { return action$.ofType('USER_LOGIN_REQUEST') .mergeMap(action => { return dependencies.repository.Authentication.Login(action.userName, action.password) + // .combineLatest(dependencies.repository.GetCurrentUser().skipWhile(u => u.Name === 'Visitor')) + // .skipWhile(u => u instanceof ContentTypes.User) + // .first() .map(result => { return result ? - Actions.UserLoginSuccess(result) + Actions.UserLoginBuffer(result) : Actions.UserLoginFailure({ message: 'Failed to log in.' }); }) .catch(error => Observable.of(Actions.UserLoginFailure(error))) }) } + 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) + }) + .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 * ```LogoutUserSuccess``` action if the ajax request ended successfully or catches the error if the request failed and sends the error message to the ```LogoutUserFailure``` action. @@ -335,6 +360,35 @@ export module Epics { .catch(error => Observable.of(Actions.UserLogoutFailure(error))) }) } + export const getContentActions = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { + return action$.ofType('REQUEST_CONTENT_ACTIONS') + .mergeMap(action => { + let c = dependencies.repository.HandleLoadedContent(action.content, ContentTypes.GenericContent); + return c.Actions(action.scenario) + .map(result => Actions.RequestContentActionsSuccess(result, action.content.Id)) + .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). @@ -358,7 +412,9 @@ export module Epics { restoreversionContentEpic, userLoginEpic, userLogoutEpic, - checkLoginStateEpic + checkLoginStateEpic, + getContentActions, + uploadFileEpic ); } diff --git a/src/Reducers.ts b/src/Reducers.ts index 6c0caec..18d024e 100644 --- a/src/Reducers.ts +++ b/src/Reducers.ts @@ -130,14 +130,13 @@ export module Reducers { export const userAvatarPath = (state = '', action) => { switch (action.type) { case 'USER_CHANGED': - return action.user.ImageData ? action.user.ImageData.__mediaresource.media_src : '' + return action.user.Avatar ? action.user.Avatar._deferred : '' default: return state } } - /** - * Reducer combining userName, fullName and userLanguage into a single object, ```user```. + * Reducer combining userName, fullName, userLanguage, userAvatarPath into a single object, ```user```. */ const user = combineReducers({ userName, @@ -182,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: @@ -195,7 +199,13 @@ export module Reducers { * @returns {Object} state. Returns the next state based on the action. */ export const entities = (state = {}, action) => { - if (action.response && (action.type !== 'USER_LOGIN_SUCCESS' && action.type !== 'LOAD_CONTENT_SUCCESS')) { + if (action.response && ( + action.type !== 'USER_LOGIN_SUCCESS' && + action.type !== 'USER_LOGIN_BUFFER' && + action.type !== 'LOAD_CONTENT_SUCCESS' && + action.type !== 'REQUEST_CONTENT_ACTIONS_SUCCESS' && + action.type !== 'UPDATE_CONTENT_SUCCESS' && + action.type !== 'UPLOAD_CONTENT_SUCCESS')) { return (Object).assign({}, state, action.response.entities.entities); } switch (action.type) { @@ -203,6 +213,13 @@ export module Reducers { let res = Object.assign({}, state); delete res[action.id]; return res; + 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; } @@ -234,40 +251,17 @@ export module Reducers { switch (action.type) { case 'FETCH_CONTENT_FAILURE': return action.message; - case 'CREATE_CONTENT_FAILURE': - case 'UPDATE_CONTENT_FAILURE': - case 'DELETE_CONTENT_FAILURE': - case 'CHECKIN_CONTENT_FAILURE': - case 'CHECKOUT_CONTENT_FAILURE': - case 'PUBLISH_CONTENT_FAILURE': - case 'APPROVE_CONTENT_FAILURE': - case 'REJECT_CONTENT_FAILURE': - case 'UNDOCHECKOUT_CONTENT_FAILURE': - case 'FORCEUNDOCHECKOUT_CONTENT_FAILURE': - case 'RESTOREVERSION_CONTENT_FAILURE': - case 'FETCH_CONTENT_REQUEST': case 'FETCH_CONTENT_SUCCESS': - case 'CREATE_CONTENT_REQUEST': case 'CREATE_CONTENT_SUCCESS': - case 'UPDATE_CONTENT_REQUEST': case 'UPDATE_CONTENT_SUCCESS': - case 'DELETE_CONTENT_REQUEST': case 'DELETE_CONTENT_SUCCESS': - case 'CHECKIN_CONTENT_REQUEST': case 'CHECKIN_CONTENT_SUCCESS': - case 'CHECKOUT_CONTENT_REQUEST': case 'CHECKOUT_CONTENT_SUCCESS': - case 'APPROVE_CONTENT_REQUEST': case 'APPROVE_CONTENT_SUCCESS': - case 'PUBLISH_CONTENT_REQUEST': case 'PUBLISH_CONTENT_SUCCESS': - case 'REJECT_CONTENT_REQUEST': case 'REJECT_CONTENT_SUCCESS': - case 'UNDOCHECKOUT_CONTENT_REQUEST': case 'UNDOCHECKOUT_CONTENT_SUCCESS': - case 'FORCEUNDOCHECKOUT_CONTENT_REQUEST': case 'FORCEUNDOCHECKOUT_CONTENT_SUCCESS': - case 'RESTOREVERSION_CONTENT_REQUEST': case 'RESTOREVERSION_CONTENT_SUCCESS': return null; default: @@ -280,8 +274,13 @@ export module Reducers { * @param {Object} action Represents an action that is called. * @returns {Object} state. Returns the next state based on the action. */ - export const childrenactions = (state = {}, action) => { - return state + export const childrenactions = (state = [], action) => { + switch (action.type) { + case 'REQUEST_CONTENT_ACTIONS_SUCCESS': + return action.response + default: + return state + } } /** * Reducer to handle Actions on the top property in the children object. @@ -343,8 +342,8 @@ export module Reducers { export const order = (state = {}, action) => { switch (action.type) { case 'FETCH_CONTENT_REQUEST': - if (action.options.order) - return action.options.order + if (action.options.orderby) + return action.options.orderby else return state default: @@ -385,6 +384,20 @@ export module Reducers { return state } } + /** + * Reducer to handle Actions on the isOpened property in the children object. + * @param {Object} [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 isOpened = (state = null, action) => { + switch (action.type) { + case 'REQUEST_CONTENT_ACTIONS_SUCCESS': + return action.id + default: + return state + } + } /** * Reducer combining ids, entities, isFetching, error, top, skip, query, order, filter and select into a single object, ```children```. */ @@ -393,13 +406,14 @@ export module Reducers { entities, isFetching, error: childrenerror, - // actions: childrenactions, + actions: childrenactions, top, skip, query, order, filter, - select + select, + isOpened }) /** * Reducer to handle Actions on the isSaved property in the contentState object. @@ -575,7 +589,17 @@ export module Reducers { * @returns {Object} state. Returns the next state based on the action. */ export const selected = (state = [], action) => { - return state; + switch (action.type) { + case 'SELECT_CONTENT': + return [...state, action.id] + case 'DESELECT_CONTENT': + const index = state.indexOf(action.id) + return [...state.slice(0, index), ...state.slice(index + 1)] + case 'CLEAR_SELECTION': + return [] + default: + return state + } } /** * Reducer combining session, children, currentcontent and selected into a single object, ```sensenet``` which will be the top-level one. @@ -612,7 +636,7 @@ export module Reducers { * @returns {string} Returns the error message. */ export const getError = (state: any) => { - return state.errorMessage + return state.error }; export const getAuthenticationStatus = (state) => { @@ -626,4 +650,20 @@ export module Reducers { export const getRepositoryUrl = (state) => { return state.session.repository.RepositoryUrl; } + + export const getSelectedContent = (state) => { + return state.selected + } + + export const getOpenedContent = (state) => { + return state.isOpened + } + + export const getChildrenActions = (state) => { + return state.actions + } + + export const getCurrentContent = (state) => { + return state.currentcontent.content + } } \ No newline at end of file diff --git a/test/ActionsTests.ts b/test/ActionsTests.ts index be472cc..a6a41b8 100644 --- a/test/ActionsTests.ts +++ b/test/ActionsTests.ts @@ -1,7 +1,7 @@ /// import { Actions } from '../src/Actions' import * as Chai from 'chai'; -import { Content, Mocks, IContentOptions, ContentTypes, Repository } from 'sn-client-js'; +import { Mocks, ContentTypes, Repository } from 'sn-client-js'; const expect = Chai.expect; describe('Actions', () => { @@ -31,9 +31,9 @@ describe('Actions', () => { type: 'FETCH_CONTENT_REQUEST', path: '/workspaces/project', options: {}, - contentType: Content + contentType: ContentTypes.Task } - expect(Actions.RequestContent(path, {}, Content)).to.deep.equal(expectedAction) + expect(Actions.RequestContent(path, {}, ContentTypes.Task)).to.deep.equal(expectedAction) }); it('should create an action to a fetch content request', () => { const expectedAction = { @@ -81,7 +81,7 @@ describe('Actions', () => { expect(Actions.LoadContent(123)).to.deep.equal(expectedAction) }); it('should create an action to receive a loaded content', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task); expect(Actions.ReceiveLoadedContent(content, { select: ['Id', 'DisplayName'] }).response.DisplayName).to.deep.equal('My content') }); @@ -96,7 +96,7 @@ describe('Actions', () => { }); describe('LoadContentActions', () => { it('should create an action to a load content actions request', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) const expectedAction = { type: 'LOAD_CONTENT_ACTIONS', content: content, @@ -105,7 +105,7 @@ describe('Actions', () => { expect(Actions.LoadContentActions(content, 'ListItem')).to.deep.equal(expectedAction) }); it('should create an action to receive a loaded contents actions', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) const expectedAction = { type: 'LOAD_CONTENT_ACTIONS_SUCCESS', actions: ['aa', 'bb'] @@ -122,7 +122,7 @@ describe('Actions', () => { }); describe('ReloadContent', () => { it('should create an action to a reload content request', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) const expectedAction = { type: 'RELOAD_CONTENT_REQUEST', content, @@ -131,7 +131,7 @@ describe('Actions', () => { expect(Actions.ReloadContent(content, 'edit')).to.deep.equal(expectedAction) }); it('should create an action to receive the reloaded content', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.ReceiveReloadedContent(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to content load request failure', () => { @@ -144,7 +144,7 @@ describe('Actions', () => { }); describe('ReloadContentFields', () => { it('should create an action to a reload fields of a content request', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) const expectedAction = { type: 'RELOAD_CONTENTFIELDS_REQUEST', content, @@ -153,7 +153,7 @@ describe('Actions', () => { expect(Actions.ReloadContentFields(content, ['Id', 'DisplayName'])).to.deep.equal(expectedAction) }); it('should create an action to receive the reloaded fields of a content', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.ReceiveReloadedContentFields(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to content load request failure', () => { @@ -165,10 +165,10 @@ describe('Actions', () => { }); }); describe('CreateContent', () => { - const content = Content.Create({ + const content = repo.CreateContent({ Id: 123, DisplayName: 'My Content' - }, ContentTypes.Task, repo); + }, ContentTypes.Task); it('should create an action to a create content request', () => { const expectedAction = { @@ -178,7 +178,7 @@ describe('Actions', () => { expect(Actions.CreateContent(content)).to.deep.equal(expectedAction) }); it('should create an action to a create content success', () => { - expect(Actions.CreateContentSuccess(content).response.entities.entities['123'].options.DisplayName).to.be.eq('My Content') + expect(Actions.CreateContentSuccess(content).response.entities.entities['123'].DisplayName).to.be.eq('My Content') }); it('should create an action to content creation failure', () => { const expectedAction = { @@ -199,7 +199,7 @@ describe('Actions', () => { })).to.deep.equal(expectedAction) }); it('should create an action to update content success', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.UpdateContentSuccess(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to content update request failure', () => { @@ -211,7 +211,7 @@ describe('Actions', () => { }); }); describe('DeleteContent', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to a delete content request', () => { const expectedAction = { type: 'DELETE_CONTENT_REQUEST', @@ -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) @@ -279,7 +279,7 @@ describe('Actions', () => { }); }); describe('CheckoutContent', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to a checkout content request', () => { const expectedAction = { type: 'CHECKOUT_CONTENT_REQUEST', @@ -288,7 +288,7 @@ describe('Actions', () => { expect(Actions.CheckOut(content)).to.deep.equal(expectedAction) }); it('should create an action to checkout content success', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.CheckOutSuccess(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to checkout content failure', () => { @@ -300,7 +300,7 @@ describe('Actions', () => { }); }); describe('CheckinContent', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to a checkin content request', () => { const expectedAction = { type: 'CHECKIN_CONTENT_REQUEST', @@ -318,7 +318,7 @@ describe('Actions', () => { expect(Actions.CheckIn(content)).to.deep.equal(expectedAction) }); it('should create an action to checkin content success', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.CheckInSuccess(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to checkin content failure', () => { @@ -330,7 +330,7 @@ describe('Actions', () => { }); }); describe('PublishContent', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to a publish content request', () => { const expectedAction = { type: 'PUBLISH_CONTENT_REQUEST', @@ -339,7 +339,7 @@ describe('Actions', () => { expect(Actions.Publish(content)).to.deep.equal(expectedAction) }); it('should create an action to publish content success', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.PublishSuccess(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to publish content failure', () => { @@ -351,7 +351,7 @@ describe('Actions', () => { }); }); describe('ApproveContent', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to an approve content request', () => { const expectedAction = { type: 'APPROVE_CONTENT_REQUEST', @@ -360,7 +360,7 @@ describe('Actions', () => { expect(Actions.Approve(content)).to.deep.equal(expectedAction) }); it('should create an action to approve content success', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.ApproveSuccess(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to approve content failure', () => { @@ -372,7 +372,7 @@ describe('Actions', () => { }); }); describe('RejectContent', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to an reject content request', () => { const expectedAction = { type: 'REJECT_CONTENT_REQUEST', @@ -390,7 +390,7 @@ describe('Actions', () => { expect(Actions.Reject(content)).to.deep.equal(expectedAction) }); it('should create an action to reject content success', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.RejectSuccess(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to reject content failure', () => { @@ -402,7 +402,7 @@ describe('Actions', () => { }); }); describe('UndoCheckoutContent', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to an undo-checkout content request', () => { const expectedAction = { type: 'UNDOCHECKOUT_CONTENT_REQUEST', @@ -411,7 +411,7 @@ describe('Actions', () => { expect(Actions.UndoCheckout(content)).to.deep.equal(expectedAction) }); it('should create an action to undo-checkout content success', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.UndoCheckoutSuccess(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to undo-checkout content failure', () => { @@ -423,7 +423,7 @@ describe('Actions', () => { }); }); describe('ForceUndoCheckoutContent', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to a force undo-checkout content request', () => { const expectedAction = { type: 'FORCEUNDOCHECKOUT_CONTENT_REQUEST', @@ -432,7 +432,7 @@ describe('Actions', () => { expect(Actions.ForceUndoCheckout(content)).to.deep.equal(expectedAction) }); it('should create an action to force undo-checkout content success', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.ForceUndoCheckoutSuccess(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to force undo-checkout content failure', () => { @@ -444,7 +444,7 @@ describe('Actions', () => { }); }); describe('RestoreVersion', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) it('should create an action to a version restore request', () => { const expectedAction = { type: 'RESTOREVERSION_CONTENT_REQUEST', @@ -454,7 +454,7 @@ describe('Actions', () => { expect(Actions.RestoreVersion(content, 'A.1.0')).to.deep.equal(expectedAction) }); it('should create an action to a version restore success', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo) + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) expect(Actions.RestoreVersionSuccess(content).response.DisplayName).to.deep.equal('My content') }); it('should create an action to a version restore failure', () => { @@ -475,11 +475,12 @@ describe('Actions', () => { expect(Actions.UserLogin('alba', 'alba')).to.deep.equal(expectedAction) }); it('should create an action to a user login success', () => { + const user = repo.CreateContent({ Name: 'alba' }, ContentTypes.User) const expectedAction = { type: 'USER_LOGIN_SUCCESS', - response: true + response: user } - expect(Actions.UserLoginSuccess(true)).to.deep.equal(expectedAction) + expect(Actions.UserLoginSuccess(user)).to.deep.equal(expectedAction) }); it('should create an action to a user login failure', () => { const expectedAction = { @@ -496,6 +497,15 @@ describe('Actions', () => { expect(Actions.UserLoginFailure({ message: 'The username or the password is not valid!', status: 403 })).to.deep.equal(expectedAction) }); }); + describe('UserLoginBuffer', () => { + it('should create an action to a user login buffering', () => { + const expectedAction = { + type: 'USER_LOGIN_BUFFER', + response: true + } + expect(Actions.UserLoginBuffer(true)).to.deep.equal(expectedAction) + }); + }); describe('UserLogout', () => { it('should create an action to a user logout request', () => { const expectedAction = { @@ -527,7 +537,7 @@ describe('Actions', () => { }); describe('UserChanged', () => { it('should return the user changed action', () => { - const user = Content.Create({ Name: 'alba' }, ContentTypes.User, repo) + const user = repo.CreateContent({ Name: 'alba' }, ContentTypes.User) const expectedAction = { type: 'USER_CHANGED', user @@ -544,4 +554,144 @@ describe('Actions', () => { expect(Actions.LoadRepository(repo)).to.deep.equal(expectedAction) }); }); + describe('SelectContent', () => { + it('should return the select content action', () => { + const expectedAction = { + type: 'SELECT_CONTENT', + id: 1 + } + expect(Actions.SelectContent(1)).to.deep.equal(expectedAction) + }) + }) + describe('DeSelectContent', () => { + it('should return the deselect content action', () => { + const expectedAction = { + type: 'DESELECT_CONTENT', + id: 1 + } + expect(Actions.DeSelectContent(1)).to.deep.equal(expectedAction) + }) + }) + describe('ClearSelection', () => { + it('should return the clear selection action', () => { + const expectedAction = { + type: 'CLEAR_SELECTION' + } + expect(Actions.ClearSelection()).to.deep.equal(expectedAction) + }) + }) + describe('RequestContentActions', () => { + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) + + it('should return the RequestContentActions action', () => { + const expectedAction = { + type: 'REQUEST_CONTENT_ACTIONS', + content: content, + scenario: 'DMSListItem' + } + expect(Actions.RequestContentActions(content, 'DMSListItem')).to.deep.equal(expectedAction) + }) + it('should return the RequestContentActionsSuccess action', () => { + const expectedAction = { + type: 'REQUEST_CONTENT_ACTIONS_SUCCESS', + response: [ + { + ActionName: 'Rename' + } + ], + id: 1 + } + expect(Actions.RequestContentActionsSuccess([{ ActionName: 'Rename' }], 1)).to.deep.equal(expectedAction) + }) + it('should return the RequestContentActionsFailure action', () => { + const expectedAction = { + type: 'REQUEST_CONTENT_ACTIONS_FAILURE', + message: 'error' + } + expect(Actions.RequestContentActionsFailure({ message: 'error' })).to.deep.equal(expectedAction) + }); + }) + describe('UploadContentActions', () => { + const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) + 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 5f3dcad..f244397 100644 --- a/test/EpicsTests.ts +++ b/test/EpicsTests.ts @@ -4,26 +4,29 @@ import { createEpicMiddleware } from 'redux-observable'; import { Mocks, ContentTypes, HttpProviders, Authentication, ODataApi, Content } from 'sn-client-js'; import { Epics } from '../src/Epics' import { Actions } from '../src/Actions' +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', Name: 'MyContent' }, 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; - beforeEach(() => { - (repo.httpProviderRef as Mocks.MockHttpProvider).setError({ message: 'XMLHttpRequest is not supported by your browser' }); +describe('Epics', () => { + beforeEach(() => { + (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(() => { @@ -37,33 +40,16 @@ describe('Epics', () => { path: '/workspaces/Project', options: { - select: [ - ['Id', 'Path', 'Name', 'Type'], - ['DisplayName', 'Description', 'Icon'] - ], + select: ['Id', 'Path', 'Name', 'Type', 'DisplayName', 'Description', 'Icon'], metadata: 'no', inlinecount: 'allpages', 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 } }); @@ -112,17 +98,31 @@ describe('Epics', () => { // }]); // }) // }) + + describe('initSensenetStoreEpic Epic', () => { + before(() => { + initBefores() + }); + + after(() => { + epicMiddleware.replaceEpic(Epics.initSensenetStoreEpic); + }); + it('handles the error', () => { + const user = repo.CreateContent({ Name: 'alba', Id: 123 }, ContentTypes.User); + 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(() => { - epicMiddleware.replaceEpic(Epics.fetchContentEpic); + epicMiddleware.replaceEpic(Epics.loadContentEpic); }); it('handles the error', () => { store.dispatch({ type: 'LOAD_CONTENT_REQUEST', path: '/workspaces/Project', options: {} }); @@ -130,49 +130,21 @@ 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.fetchContentEpic); + epicMiddleware.replaceEpic(Epics.reloadContentEpic); }); + it('handles the error', () => { - const content = repo.HandleLoadedContent({ DisplayName: 'My Content', Id: 123, Path: '/workspaces' }, ContentTypes.Task) content.Save('/workspaces') store.dispatch({ type: 'RELOAD_CONTENT_REQUEST', content, options: {} }); expect(store.getActions()).to.be.deep.eq([ @@ -182,71 +154,83 @@ describe('Epics', () => { options: {} }]); }) + it('handles the error', () => { + store.dispatch({ type: 'RELOAD_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'RELOAD_CONTENT_REQUEST', + content, + options: {} + }, + { type: 'RELOAD_CONTENT_FAILURE', error: 'error' }]); + }) }); 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.fetchContentEpic); + epicMiddleware.replaceEpic(Epics.reloadContentFieldsEpic); }); + 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: {} }); + store.dispatch({ type: 'RELOAD_CONTENTFIELDS_REQUEST', content, options: {}, fields: ['DisplayName'] }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'RELOAD_CONTENTFIELDS_REQUEST', + content: content, + options: {}, + fields: ['DisplayName'] + }]); + }) + it('handles the error', () => { + store.dispatch({ type: 'RELOAD_CONTENTFIELDS_FAILURE', error: 'error' }); expect(store.getActions()).to.be.deep.eq( [{ type: 'RELOAD_CONTENTFIELDS_REQUEST', content, - options: {} + 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); }); it('handles the error', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); - 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', () => { + store.dispatch({ type: 'CREATE_CONTENT_FAILURE', error: { message: 'error' } }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'CREATE_CONTENT_REQUEST', + 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( [{ @@ -254,21 +238,25 @@ 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); }); it('handles the error', () => { - const content = Content.Create({ DisplayName: 'My content', Id: 123, Path: '/workspaces' }, ContentTypes.Task, repo); store.dispatch({ type: 'DELETE_CONTENT_REQUEST', content, permanently: false }); expect(store.getActions()).to.be.deep.eq( [{ @@ -277,46 +265,54 @@ describe('Epics', () => { permanently: false }]); }) + it('handles the error', () => { + store.dispatch({ type: 'DELETE_CONTENT_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'DELETE_CONTENT_REQUEST', + content, + permanently: false + }, + { type: 'DELETE_CONTENT_FAILURE', error: 'error' }]); + }) }); 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( [{ @@ -324,22 +320,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( @@ -349,22 +349,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( @@ -373,22 +380,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( [{ @@ -396,22 +408,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( [{ @@ -420,22 +437,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( [{ @@ -443,22 +467,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( [{ @@ -466,22 +495,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( [{ @@ -490,14 +523,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(() => { @@ -505,24 +545,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) + const user = repo.CreateContent({ Name: 'alba', Id: 123 }, ContentTypes.User); 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' @@ -531,19 +573,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( [{ @@ -551,13 +589,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( [{ @@ -566,41 +601,129 @@ 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(); + initBefores() }); afterEach(() => { epicMiddleware.replaceEpic(Epics.userLoginEpic); }); - const user = Content.Create({ Name: 'alba', Id: '2' }, ContentTypes.User, repo) it('handles a loggedin user', () => { + const user = repo.CreateContent({ Name: 'alba', Id: 2, Path: '/Root' }, ContentTypes.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_SUCCESS', response: 2 }]); + { type: 'CHECK_LOGIN_STATE_REQUEST' } + ]); }) it('handles an error', () => { - (repo.Authentication as Mocks.MockAuthService).stateSubject.next(Authentication.LoginState.Unauthenticated); + const user = repo.HandleLoadedContent({ Name: 'alba', Id: 65535, Path: '/Root' }, ContentTypes.User); + repo.Authentication.StateSubject.next(Authentication.LoginState.Unauthenticated); store.dispatch({ type: 'CHECK_LOGIN_STATE_REQUEST' }); - expect(store.getActions()).to.be.deep.eq([ - { type: 'CHECK_LOGIN_STATE_REQUEST' }, - { type: 'USER_LOGIN_FAILURE', message: null }]); + expect(store.getActions()).to.be.deep.eq( + [ + // { + // type: 'USER_LOGIN_SUCCESS', + // response: user.GetFields() + // }, + // { type: 'CHECK_LOGIN_STATE_REQUEST' }, + // { type: '@@redux-observable/EPIC_END' }, + { type: 'CHECK_LOGIN_STATE_REQUEST' }]); + }) + }); + describe('getContentActions Epic', () => { + before(() => { + initBefores() + }); + + after(() => { + epicMiddleware.replaceEpic(Epics.getContentActions); + }); + it('handles the success', () => { + store.dispatch({ type: 'REQUEST_CONTENT_ACTIONS', content, scenario: 'DMSDemoScenario' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'REQUEST_CONTENT_ACTIONS', + content: content, + scenario: 'DMSDemoScenario' + }]); + }) + it('handles the error', () => { + store.dispatch({ type: 'REQUEST_CONTENT_ACTIONS_FAILURE', error: 'error' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'REQUEST_CONTENT_ACTIONS', + content, + scenario: 'DMSDemoScenario' + }, + { type: 'REQUEST_CONTENT_ACTIONS_FAILURE', error: 'error' }]); + }) + }); + describe('loadContentActionsEpic Epic', () => { + before(() => { + initBefores() + }); + + after(() => { + epicMiddleware.replaceEpic(Epics.loadContentActionsEpic); + }); + it('handles the success', () => { + store.dispatch({ type: 'LOAD_CONTENT_ACTIONS', content, scenario: 'DMSDemoScenario' }); + expect(store.getActions()).to.be.deep.eq( + [{ + type: 'LOAD_CONTENT_ACTIONS', + content, + scenario: 'DMSDemoScenario' + }, + ]); + }) + 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: 'error' }]); }) }); + describe('userLoginBufferEpic Epic', () => { + before(() => { + initBefores() + }); + + after(() => { + epicMiddleware.replaceEpic(Epics.userLoginBufferEpic); + }); + it('handles the success', () => { + store.dispatch({ type: 'USER_LOGIN_BUFFER', response: true }); + expect(store.getActions()).to.be.deep.eq( + [{ 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 25b2cf3..7da2a46 100644 --- a/test/ReducersTests.ts +++ b/test/ReducersTests.ts @@ -2,7 +2,7 @@ import { Reducers } from '../src/Reducers'; import { Actions } from '../src/Actions'; import * as Chai from 'chai'; -import { Authentication, Content, ContentTypes, Mocks } from 'sn-client-js'; +import { Authentication, Content, ContentTypes, Mocks, Enums } from 'sn-client-js'; const expect = Chai.expect; describe('Reducers', () => { describe('country reducer', () => { @@ -95,7 +95,7 @@ describe('Reducers', () => { expect(Reducers.userAvatarPath(undefined, { type: 'USER_CHANGED', user: { DisplayName: 'Alba Monday' } })).to.be.deep.equal(''); }); it('should return the logged-in users avatars path', () => { - expect(Reducers.userAvatarPath(undefined, { type: 'USER_CHANGED', user: { ImageData: { __mediaresource: { media_src: 'Alba Monday' } } } })).to.be.deep.equal('Alba Monday'); + expect(Reducers.userAvatarPath(undefined, { type: 'USER_CHANGED', user: { Avatar: { _deferred: 'Alba Monday' } } })).to.be.deep.equal('Alba Monday'); }); }) @@ -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', () => { @@ -171,6 +197,95 @@ describe('Reducers', () => { expect(Reducers.entities({}, { response: { entities: { entities: { a: 0, b: 2 } } } })) .to.be.deep.eq({ a: 0, b: 2 }); }); + it('should handle UPDATE_CONTENT_SUCCESS', () => { + const entities = { + 5145: { + Id: 5145, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + } + }; + expect(Reducers.entities(entities, { type: 'UPDATE_CONTENT_SUCCESS', response: { Id: 5145, DisplayName: 'aaa', Status: ['Active'] } })).to.be.deep.equal( + { + 5145: { + Id: 5145, + DisplayName: 'aaa', + Status: ['Active'] + }, + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + } + } + ); + }); + 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', () => { @@ -303,7 +418,18 @@ describe('Reducers', () => { }); describe('childrenactions reducer', () => { it('should return the initial state', () => { - expect(Reducers.childrenactions(undefined, {})).to.be.deep.equal({}); + expect(Reducers.childrenactions(undefined, {})).to.be.deep.equal([]); + }); + it('should handle REQUEST_CONTENT_ACTIONS_SUCCESS', () => { + const action = { + type: 'REQUEST_CONTENT_ACTIONS_SUCCESS', + response: [ + { + ActionName: 'Rename' + } + ] + } + expect(Reducers.childrenactions(undefined, action)).to.be.deep.equal([{ ActionName: 'Rename' }]); }); }); describe('top reducer', () => { @@ -344,7 +470,7 @@ describe('Reducers', () => { expect(Reducers.order(undefined, {})).to.be.deep.equal({}); }); it('should return "DisplayName desc"', () => { - expect(Reducers.order(undefined, { type: 'FETCH_CONTENT_REQUEST', options: { order: 'DisplayName desc' } })).to.be.eq('DisplayName desc'); + expect(Reducers.order(undefined, { type: 'FETCH_CONTENT_REQUEST', options: { orderby: 'DisplayName desc' } })).to.be.eq('DisplayName desc'); }); it('should return initial state', () => { expect(Reducers.order(undefined, { type: 'FETCH_CONTENT_REQUEST', options: {} })).to.be.deep.equal({}); @@ -372,6 +498,18 @@ describe('Reducers', () => { expect(Reducers.select(undefined, { type: 'FETCH_CONTENT_REQUEST', options: {} })).to.be.deep.equal({}); }); }); + describe('isOpened reducer', () => { + it('should return the initial state', () => { + expect(Reducers.isOpened(undefined, {})).to.be.eq(null) + }) + it('should return 1', () => { + const action = { + type: 'REQUEST_CONTENT_ACTIONS_SUCCESS', + id: 1 + } + expect(Reducers.isOpened(undefined, action)).to.be.eq(1) + }) + }) describe('isSaved reducer', () => { it('should return the initial state', () => { expect(Reducers.isSaved(undefined, {})).to.be.deep.equal(true); @@ -551,10 +689,10 @@ describe('Reducers', () => { it('should return fields of the content', () => { let repo: Mocks.MockRepository = new Mocks.MockRepository(); - let content = Content.Create({ + let content = repo.CreateContent({ Path: '/Root/Sites/Default_Site/tasks', - Status: 'active' as any - }, ContentTypes.Task, repo) + Status: Enums.Status.active + }, ContentTypes.Task) const action = { type: 'LOAD_CONTENT_SUCCESS', response: content @@ -567,10 +705,10 @@ describe('Reducers', () => { it('should return fields of the content', () => { let repo: Mocks.MockRepository = new Mocks.MockRepository(); - let content = Content.Create({ + let content = repo.CreateContent({ Path: '/Root/Sites/Default_Site/tasks', - Status: 'active' as any - }, ContentTypes.Task, repo) + Status: Enums.Status.active + }, ContentTypes.Task) const action = { type: 'RELOAD_CONTENT_SUCCESS', response: content @@ -587,10 +725,10 @@ describe('Reducers', () => { }); it('should return a content', () => { let repo: Mocks.MockRepository = new Mocks.MockRepository(); - let content = Content.Create({ + let content = repo.CreateContent({ Path: '/Root/Sites/Default_Site/tasks', - Status: 'active' as any - }, ContentTypes.Task, repo) + Status: Enums.Status.active + }, ContentTypes.Task) const action = { type: 'LOAD_CONTENT_SUCCESS', response: content @@ -599,10 +737,10 @@ describe('Reducers', () => { }); it('should return a content', () => { let repo: Mocks.MockRepository = new Mocks.MockRepository(); - let content = Content.Create({ + let content = repo.CreateContent({ Path: '/Root/Sites/Default_Site/tasks', - Status: 'active' as any - }, ContentTypes.Task, repo) + Status: Enums.Status.active + }, ContentTypes.Task) const action = { type: 'RELOAD_CONTENT_SUCCESS', response: content @@ -614,6 +752,40 @@ describe('Reducers', () => { it('should return the initial state', () => { expect(Reducers.selected(undefined, {})).to.deep.equal([]); }); + it('should return an array with one item with the id 1', () => { + const action = { + type: 'SELECT_CONTENT', + id: 1 + } + expect(Reducers.selected(undefined, action)).to.deep.equal([1]); + }) + it('should return an array with two items with the id 1 and 2', () => { + const action = { + type: 'SELECT_CONTENT', + id: 2 + } + expect(Reducers.selected([1], action)).to.deep.equal([1, 2]); + }) + it('should return an array with one item with the id 1', () => { + const action = { + type: 'DESELECT_CONTENT', + id: 2 + } + expect(Reducers.selected([1, 2], action)).to.deep.equal([1]); + }) + it('should return an empty array', () => { + const action = { + type: 'DESELECT_CONTENT', + id: 1 + } + expect(Reducers.selected([1], action)).to.deep.equal([]); + }) + it('should return an empty array', () => { + const action = { + type: 'CLEAR_SELECTION' + } + expect(Reducers.selected([1], action)).to.deep.equal([]); + }) }) describe('getContent', () => { const state = { @@ -676,7 +848,7 @@ describe('Reducers', () => { const state = { ids: [5145, 5146], isFetching: false, - errorMessage: 'error' + error: 'error' } it('should return the value of errorMessage from the current state', () => { expect(Reducers.getError(state)).to.be.eq('error'); @@ -716,4 +888,44 @@ describe('Reducers', () => { expect(Reducers.getRepositoryUrl(state)).to.be.eq('https://dmsservice.demo.sensenet.com'); }); }); + describe('getSelectedContent', () => { + const state = { + selected: [1, 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]) + }) + }) + describe('getOpenedContentId', () => { + const state = { + isOpened: 1 + } + it('should return 1 as the opened items id', () => { + expect(Reducers.getOpenedContent(state)).to.be.eq(1) + }) + }) + describe('getChildrenActions', () => { + const state = { + actions: [ + { + ActionName: 'Rename' + } + ] + } + it('should return 1 as the opened items id', () => { + expect(Reducers.getChildrenActions(state)).to.be.deep.equal([{ ActionName: 'Rename' }]) + }) + }) + describe('getCurrentContent', () => { + const state = { + currentcontent: { + content: { + DisplayName: 'my content' + } + } + } + it('should return the content', () => { + expect(Reducers.getCurrentContent(state)).to.be.deep.equal({ DisplayName: 'my content' }) + }) + }) }); \ No newline at end of file From 9f3c050586878e5b5a036b5b65c0b9d3c1f0a9db Mon Sep 17 00:00:00 2001 From: Aniko Litvanyi Date: Mon, 6 Nov 2017 15:03:55 +0100 Subject: [PATCH 04/13] [KFI]test(Epics): Fix Epic tests (#51) --- test/EpicsTests.ts | 236 ++++++++++++++++++++++----------------------- 1 file changed, 118 insertions(+), 118 deletions(-) diff --git a/test/EpicsTests.ts b/test/EpicsTests.ts index f244397..b235fe3 100644 --- a/test/EpicsTests.ts +++ b/test/EpicsTests.ts @@ -9,9 +9,9 @@ const expect = Chai.expect; import 'rxjs'; let store, repo: Mocks.MockRepository, epicMiddleware, mockStore, content; -const initBefores = () => { +const initBefores = (epic) => { repo = new Mocks.MockRepository(); - epicMiddleware = createEpicMiddleware(Epics.fetchContentEpic, { dependencies: { repository: repo } }) + epicMiddleware = createEpicMiddleware(epic, { dependencies: { repository: repo } }) mockStore = configureMockStore([epicMiddleware]); store = mockStore(); content = repo.HandleLoadedContent({ DisplayName: 'My Content', Id: 123, Path: '/workspaces', Name: 'MyContent' }, ContentTypes.Task) @@ -26,7 +26,7 @@ describe('Epics', () => { describe('fetchContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.fetchContentEpic) }); after(() => { @@ -49,59 +49,9 @@ describe('Epics', () => { }]); }) }); - - // 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() + initBefores(Epics.initSensenetStoreEpic) }); after(() => { @@ -110,15 +60,37 @@ describe('Epics', () => { it('handles the error', () => { const user = repo.CreateContent({ Name: 'alba', Id: 123 }, ContentTypes.User); store.dispatch({ type: 'INIT_SENSENET_STORE', path: '/workspaces', options: {} }); - expect(store.getActions()).to.be.deep.equal( - [{ type: 'INIT_SENSENET_STORE', path: '/workspaces', options: {} }]); + expect(store.getActions()).to.be.deep.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: 'LOAD_REPOSITORY', + repository: repo.Config + }, + { type: 'USER_LOGIN_FAILURE', message: null }]); }) }) describe('loadContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.loadContentEpic) }); after(() => { @@ -130,14 +102,27 @@ describe('Epics', () => { [{ type: 'LOAD_CONTENT_REQUEST', path: '/workspaces/Project', - options: {} + options: + { + select: ['Id', + 'Path', + 'Name', + 'Type', + 'DisplayName', + 'Description', + 'Icon'], + metadata: 'no', + inlinecount: 'allpages', + expand: undefined, + top: 1000 + } }]); }) }); describe('reloadContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.reloadContentEpic) }); after(() => { @@ -167,7 +152,7 @@ describe('Epics', () => { }); describe('reloadContentFields Epic', () => { before(() => { - initBefores() + initBefores(Epics.reloadContentFieldsEpic) }); after(() => { @@ -198,7 +183,7 @@ describe('Epics', () => { }); describe('createContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.createContentEpic) }); after(() => { @@ -210,6 +195,10 @@ describe('Epics', () => { [{ type: 'CREATE_CONTENT_REQUEST', content: content + }, + { + type: 'CREATE_CONTENT_SUCCESS', + response: { entities: { entities: { '123': content } }, result: 123 } }]); }) it('handles the error', () => { @@ -219,12 +208,16 @@ describe('Epics', () => { type: 'CREATE_CONTENT_REQUEST', content: content }, + { + type: 'CREATE_CONTENT_SUCCESS', + response: { entities: { entities: { '123': content } }, result: 123 } + }, { type: 'CREATE_CONTENT_FAILURE', error: { message: 'error' } }]); }) }); describe('updateContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.updateContentEpic) }); after(() => { @@ -236,6 +229,10 @@ describe('Epics', () => { [{ type: 'UPDATE_CONTENT_REQUEST', content + }, + { + type: 'UPDATE_CONTENT_SUCCESS', + response: content }]); }) it('handles the error', () => { @@ -245,12 +242,16 @@ describe('Epics', () => { type: 'UPDATE_CONTENT_REQUEST', content: content }, + { + type: 'UPDATE_CONTENT_SUCCESS', + response: content + }, { type: 'UPDATE_CONTENT_FAILURE', error: { message: 'error' } }]); }) }); describe('deleteContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.deleteContentEpic) }); after(() => { @@ -276,37 +277,37 @@ describe('Epics', () => { { type: 'DELETE_CONTENT_FAILURE', error: 'error' }]); }) }); - describe('deleteBatch Epic', () => { - before(() => { - initBefores() - }); + // 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' }]); - }) - }); + // 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('checkoutContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.checkoutContentEpic) }); after(() => { epicMiddleware.replaceEpic(Epics.checkoutContentEpic); @@ -332,7 +333,7 @@ describe('Epics', () => { }); describe('checkinContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.checkinContentEpic) }); after(() => { @@ -364,7 +365,7 @@ describe('Epics', () => { }); describe('publishContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.publishContentEpic) }); after(() => { @@ -393,7 +394,7 @@ describe('Epics', () => { }); describe('approveContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.approveContentEpic) }); after(() => { @@ -421,7 +422,7 @@ describe('Epics', () => { }); describe('rejectContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.rejectContentEpic) }); afterEach(() => { @@ -452,7 +453,7 @@ describe('Epics', () => { }); describe('undocheckoutContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.undocheckoutContentEpic) }); after(() => { @@ -480,7 +481,7 @@ describe('Epics', () => { }); describe('forceundocheckoutContent Epic', () => { before(() => { - initBefores() + initBefores(Epics.forceundocheckoutContentEpic) }); after(() => { @@ -508,7 +509,7 @@ describe('Epics', () => { }); describe('restoreVersion Epic', () => { before(() => { - initBefores() + initBefores(Epics.restoreversionContentEpic) }); after(() => { epicMiddleware.replaceEpic(Epics.restoreversionContentEpic); @@ -537,7 +538,7 @@ describe('Epics', () => { }); describe('login Epic', () => { before(() => { - initBefores() + initBefores(Epics.userLoginEpic) }); afterEach(() => { @@ -551,7 +552,8 @@ describe('Epics', () => { type: 'USER_LOGIN_REQUEST', username: 'alba', password: 'alba' - }]); + }, + { type: 'USER_LOGIN_FAILURE', message: 'Failed to log in.' }]); }) it('handles the loggedin user', () => { const user = repo.CreateContent({ Name: 'alba', Id: 123 }, ContentTypes.User); @@ -563,6 +565,7 @@ describe('Epics', () => { username: 'alba', password: 'alba' }, + { type: 'USER_LOGIN_FAILURE', message: 'Failed to log in.' }, { type: '@@redux-observable/EPIC_END' }, { type: 'USER_LOGIN_REQUEST', @@ -574,7 +577,7 @@ describe('Epics', () => { }); describe('logout Epic', () => { before(() => { - initBefores() + initBefores(Epics.userLogoutEpic) }); after(() => { @@ -589,7 +592,8 @@ 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); @@ -601,19 +605,20 @@ describe('Epics', () => { username: 'alba', password: 'alba' }, + { type: 'USER_LOGOUT_SUCCESS' }, { type: 'USER_LOGOUT_FAILURE', error: 'error' }]); }) }); describe('checkLoginState Epic', () => { beforeEach(() => { - initBefores() + initBefores(Epics.checkLoginStateEpic) }); afterEach(() => { epicMiddleware.replaceEpic(Epics.userLoginEpic); }); it('handles a loggedin user', () => { - const user = repo.CreateContent({ Name: 'alba', Id: 2, Path: '/Root' }, ContentTypes.User); + const user = repo.CreateContent({ Name: 'alba', Id: 2, Path: '/Root' }, ContentTypes.User); store.dispatch(Actions.UserLoginSuccess(user)); (repo.Authentication as Mocks.MockAuthService).StateSubject.next(Authentication.LoginState.Authenticated); store.dispatch({ type: 'CHECK_LOGIN_STATE_REQUEST' }); @@ -622,27 +627,22 @@ describe('Epics', () => { type: 'USER_LOGIN_SUCCESS', response: user }, - { type: 'CHECK_LOGIN_STATE_REQUEST' } - ]); + { type: 'CHECK_LOGIN_STATE_REQUEST' }, + { type: 'USER_LOGIN_BUFFER', response: true } + ]); }) it('handles an error', () => { const user = repo.HandleLoadedContent({ Name: 'alba', Id: 65535, Path: '/Root' }, ContentTypes.User); repo.Authentication.StateSubject.next(Authentication.LoginState.Unauthenticated); store.dispatch({ type: 'CHECK_LOGIN_STATE_REQUEST' }); expect(store.getActions()).to.be.deep.eq( - [ - // { - // type: 'USER_LOGIN_SUCCESS', - // response: user.GetFields() - // }, - // { type: 'CHECK_LOGIN_STATE_REQUEST' }, - // { type: '@@redux-observable/EPIC_END' }, - { type: 'CHECK_LOGIN_STATE_REQUEST' }]); + [{ type: 'CHECK_LOGIN_STATE_REQUEST' }, + { type: 'USER_LOGIN_FAILURE', message: null }]); }) }); describe('getContentActions Epic', () => { before(() => { - initBefores() + initBefores(Epics.getContentActions) }); after(() => { @@ -670,7 +670,7 @@ describe('Epics', () => { }); describe('loadContentActionsEpic Epic', () => { before(() => { - initBefores() + initBefores(Epics.loadContentActionsEpic) }); after(() => { @@ -699,7 +699,7 @@ describe('Epics', () => { }); describe('userLoginBufferEpic Epic', () => { before(() => { - initBefores() + initBefores(Epics.userLoginBufferEpic) }); after(() => { @@ -714,7 +714,7 @@ describe('Epics', () => { describe('uploadContentEpic Epic', () => { before(() => { - initBefores() + initBefores(Epics.uploadFileEpic) }); after(() => { From fe5b11d26eba6135bb6cd8b28db4f73fb32f7816 Mon Sep 17 00:00:00 2001 From: Aniko Litvanyi Date: Tue, 7 Nov 2017 16:23:53 +0100 Subject: [PATCH 05/13] Feat/batchactions (#52) * [KFI]feat(BatchActions): Add new reducer to handle batch action responses * [KFI]feat(BatchActions): Add copy and move batch actions * [KFI]feat(BatchActions): Modify batchActions reducers to handle general errors also * [KFI]docs(BatchActions): Add some docs to the new reducers * [KFI]test(BatchActions): Fix deleteBatch action tests to handle the new arguments * [KFI]test(DeleteBatch): Fix tests * [KFI]test(Reducers): Add tests to test batch response related reducers * [KFI]fix(BatchActions): Add a path param to copybatch and movebatch to hold the target path * [KFI]test(BatchActions): Add tests for testing the new batch actions * [KFI]fix(BatchActions): Improve deleteBatch Epic * [KFI]feat(Selection): Change select and deselect actions to handle a content except an id * [KFI]test(Selection): Fix selection related tests * [KFI]test(Selection): Fix selected reducer tests * [KFI]feat(Selection): Add a new reducer to hold and handle selected content items for batch actions * [KFI]fix(Selection): Fix selectedContentItems reducer and its tests * [KFI]feat(Selection): Add new functions to return to value of selectedIds and selectedContentItems r * [KFI]test(Selection): Add test for testing new selection reducers * [KFI]feat(DeleteBatch): Change id param to contentItems * [KFI]test(DeleteBatch): Fix deleteBatch related tests to handle content items as a param * [KFI]feat(DeleteBatch): Complete deleteBatch functionality * [KFI]test(DeleteBatch): Fix batch delete related tests * [KFI]fix(BatchActions): Change copy and move batch actions first param to a contenlist object * [KFI]test(BatchActions): Fix tests that are related to the changed param * [KFI]feat(MoveBatch): Add move batch action to the ids and entities reducers * [KFI]test(MoveBatch): Add moveBatch action related tests * [KFI]feat(BatchActionEpics): Add move- and copyBatch epics * [KFI]test(BatchEpics): Add copy- and movebatch epic tests --- src/Actions.ts | 88 +++++++++-- src/Epics.ts | 49 +++++- src/Reducers.ts | 101 ++++++++++-- test/ActionsTests.ts | 130 ++++++++++++--- test/EpicsTests.ts | 115 ++++++++++---- test/ReducersTests.ts | 359 ++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 751 insertions(+), 91 deletions(-) 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', () => { From cc9a16872db52d1bafe4771defdaded7079b6344 Mon Sep 17 00:00:00 2001 From: Aniko Litvanyi Date: Tue, 14 Nov 2017 12:59:37 +0100 Subject: [PATCH 06/13] Feat/addnew upload (#53) * [KFI]feat(Actions): Add a new param to requestContentActions to make it possible to add custom actio * [KFI]test(Actions): Fix RequestContentActions tests * [KFI]feat(Epics): Change getContentActions epic to handle custom listitems * [KFI]fix: Remove unused variables and add the check into the tsconfig.json to detect them in the fut * [KFI]feat(Reducers): Add new reducer to retrieving children entities object * [KFI]test(Reducers): Add test for testing getChildren reducer --- src/Actions.ts | 7 ++++--- src/Epics.ts | 6 +++--- src/Reducers.ts | 3 +++ src/Store.ts | 1 - test/ActionsTests.ts | 15 ++++++++++++--- test/EpicsTests.ts | 21 +-------------------- test/ReducersTests.ts | 33 +++++++++++++++++++++++++++++++-- tsconfig.json | 1 + 8 files changed, 55 insertions(+), 32 deletions(-) diff --git a/src/Actions.ts b/src/Actions.ts index fe4cab5..f411c86 100644 --- a/src/Actions.ts +++ b/src/Actions.ts @@ -1,6 +1,6 @@ import { normalize } from 'normalizr'; import { Schemas } from './Schema'; -import { Content, SavedContent, IContent, ODataApi, ContentTypes } from 'sn-client-js'; +import { Content, IContent, ODataApi, ContentTypes } from 'sn-client-js'; /** * Module that contains the action creators. @@ -789,10 +789,11 @@ export module Actions { * @param scenario {string} The name of the scenario * @returns {Object} Returns a redux action. */ - export const RequestContentActions = (content, scenario?: string) => ({ + export const RequestContentActions = (content, scenario?: string, customItems?: Object[]) => ({ type: 'REQUEST_CONTENT_ACTIONS', content, - scenario + scenario, + customItems: customItems || [] }) /** * Action creator for the step getting the actions of a content successfully. diff --git a/src/Epics.ts b/src/Epics.ts index 15958e4..db0983e 100644 --- a/src/Epics.ts +++ b/src/Epics.ts @@ -1,8 +1,8 @@ import { Actions } from './Actions'; import { Reducers } from './Reducers'; -import { ActionsObservable, combineEpics } from 'redux-observable'; -import { Repository, Content, ContentTypes, Collection, ODataApi, Authentication } from 'sn-client-js'; +import { combineEpics } from 'redux-observable'; +import { Repository, ContentTypes, Collection, Authentication } from 'sn-client-js'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/catch' @@ -400,7 +400,7 @@ export module Epics { .mergeMap(action => { let c = dependencies.repository.HandleLoadedContent(action.content, ContentTypes.GenericContent); return c.Actions(action.scenario) - .map(result => Actions.RequestContentActionsSuccess(result, action.content.Id)) + .map(result => Actions.RequestContentActionsSuccess([...result, ...action.customItems], action.content.Id)) .catch(error => Observable.of(Actions.RequestContentActionsFailure(error))) }) } diff --git a/src/Reducers.ts b/src/Reducers.ts index 903e216..b0843e4 100644 --- a/src/Reducers.ts +++ b/src/Reducers.ts @@ -749,4 +749,7 @@ export module Reducers { export const getCurrentContent = (state) => { return state.currentcontent.content } + export const getChildren = (state) => { + return state.entities + } } \ No newline at end of file diff --git a/src/Store.ts b/src/Store.ts index 4f7f383..83d668a 100644 --- a/src/Store.ts +++ b/src/Store.ts @@ -3,7 +3,6 @@ import { createLogger } from 'redux-logger' import { createEpicMiddleware } from 'redux-observable'; import { Epics } from './Epics'; import { Reducers } from './Reducers'; -import { Actions } from './Actions'; import { Repository } from 'sn-client-js'; /** diff --git a/test/ActionsTests.ts b/test/ActionsTests.ts index def0afc..bc44273 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, ODataApi } from 'sn-client-js'; +import { Mocks, ContentTypes, ODataApi } from 'sn-client-js'; const expect = Chai.expect; describe('Actions', () => { @@ -105,7 +105,6 @@ describe('Actions', () => { expect(Actions.LoadContentActions(content, 'ListItem')).to.deep.equal(expectedAction) }); it('should create an action to receive a loaded contents actions', () => { - const content = repo.CreateContent({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task) const expectedAction = { type: 'LOAD_CONTENT_ACTIONS_SUCCESS', actions: ['aa', 'bb'] @@ -679,10 +678,20 @@ describe('Actions', () => { const expectedAction = { type: 'REQUEST_CONTENT_ACTIONS', content: content, - scenario: 'DMSListItem' + scenario: 'DMSListItem', + customItems: [] } expect(Actions.RequestContentActions(content, 'DMSListItem')).to.deep.equal(expectedAction) }) + it('should return the RequestContentActions action', () => { + const expectedAction = { + type: 'REQUEST_CONTENT_ACTIONS', + content: content, + scenario: 'DMSListItem', + customItems: [{ DisplayName: 'aaa', Name: 'bbb', Icon: 'ccc' }] + } + expect(Actions.RequestContentActions(content, 'DMSListItem', [{ DisplayName: 'aaa', Name: 'bbb', Icon: 'ccc' }])).to.deep.equal(expectedAction) + }) it('should return the RequestContentActionsSuccess action', () => { const expectedAction = { type: 'REQUEST_CONTENT_ACTIONS_SUCCESS', diff --git a/test/EpicsTests.ts b/test/EpicsTests.ts index 99021a4..0b4cf7c 100644 --- a/test/EpicsTests.ts +++ b/test/EpicsTests.ts @@ -1,10 +1,9 @@ import * as Chai from 'chai'; import configureMockStore from 'redux-mock-store'; import { createEpicMiddleware } from 'redux-observable'; -import { Mocks, ContentTypes, HttpProviders, Authentication, ODataApi, Content } from 'sn-client-js'; +import { Mocks, ContentTypes, Authentication } from 'sn-client-js'; import { Epics } from '../src/Epics' import { Actions } from '../src/Actions' -import { Store } from '../src/Store' const expect = Chai.expect; import 'rxjs'; @@ -58,7 +57,6 @@ describe('Epics', () => { epicMiddleware.replaceEpic(Epics.initSensenetStoreEpic); }); it('handles the error', () => { - const user = repo.CreateContent({ Name: 'alba', Id: 123 }, ContentTypes.User); store.dispatch({ type: 'INIT_SENSENET_STORE', path: '/workspaces', options: {} }); expect(store.getActions()).to.be.deep.equal([ { @@ -615,7 +613,6 @@ describe('Epics', () => { { type: 'USER_LOGIN_FAILURE', message: 'Failed to log in.' }]); }) it('handles the loggedin user', () => { - const user = repo.CreateContent({ Name: 'alba', Id: 123 }, ContentTypes.User); store.dispatch({ type: 'USER_LOGIN_REQUEST', username: 'user', password: 'password' }); (repo.Authentication as Mocks.MockAuthService).StateSubject.next(Authentication.LoginState.Authenticated); expect(store.getActions()).to.be.deep.eq( @@ -691,7 +688,6 @@ describe('Epics', () => { ]); }) it('handles an error', () => { - const user = repo.HandleLoadedContent({ Name: 'alba', Id: 65535, Path: '/Root' }, ContentTypes.User); repo.Authentication.StateSubject.next(Authentication.LoginState.Unauthenticated); store.dispatch({ type: 'CHECK_LOGIN_STATE_REQUEST' }); expect(store.getActions()).to.be.deep.eq( @@ -770,19 +766,4 @@ describe('Epics', () => { [{ type: 'USER_LOGIN_BUFFER', response: true }]); }) }) - - describe('uploadContentEpic Epic', () => { - before(() => { - initBefores(Epics.uploadFileEpic) - }); - - 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 efd510a..07e56a5 100644 --- a/test/ReducersTests.ts +++ b/test/ReducersTests.ts @@ -1,8 +1,7 @@ /// import { Reducers } from '../src/Reducers'; -import { Actions } from '../src/Actions'; import * as Chai from 'chai'; -import { Authentication, Content, ContentTypes, Mocks, Enums } from 'sn-client-js'; +import { Authentication, ContentTypes, Mocks, Enums } from 'sn-client-js'; const expect = Chai.expect; describe('Reducers', () => { describe('country reducer', () => { @@ -1261,4 +1260,34 @@ describe('Reducers', () => { expect(Reducers.getCurrentContent(state)).to.be.deep.equal({ DisplayName: 'my content' }) }) }) + describe('getChildren', () => { + const state = { + entities: { + 5145: { + Id: 5145, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + } + } + } + it('should return the children object', () => { + expect(Reducers.getChildren(state)).to.be.deep.equal({ + 5145: { + Id: 5145, + DisplayName: 'Some Article', + Status: ['Active'] + }, + 5146: { + Id: 5146, + Displayname: 'Other Article', + Status: ['Completed'] + } + }) + }) + }) }); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 1b4890e..ad4671d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "preserveConstEnums": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, + "noUnusedLocals": true, "skipLibCheck": true, "outDir": "./dist" }, From 6c20ae6c18cbf9a5454eb0a2235cb68e0fe21020 Mon Sep 17 00:00:00 2001 From: Aniko Litvanyi Date: Wed, 15 Nov 2017 15:32:47 +0100 Subject: [PATCH 07/13] [KFI]feat(Authentication): Add stuff for google oauth (#54) * [KFI]feat(Authentication): Add initial stuff for google oauth * [KFI]test(Actions): Add google auth login test * [KFI]chore: Update dependencies --- package.json | 6 ++++-- src/Actions.ts | 7 +++++++ src/Epics.ts | 32 ++++++++++++++++++++++---------- test/ActionsTests.ts | 8 ++++++++ 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 56a35a6..9e0d84c 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ }, "homepage": "https://sensenet.com", "dependencies": { + "@types/mocha": "^2.2.42", "normalizr": "^3.2.3", "nyc": "^11.1.0", "redux": "^3.7.2", @@ -65,10 +66,10 @@ }, "devDependencies": { "@types/chai": "^4.0.4", - "@types/mocha": "^2.2.42", "@types/nock": "^8.2.1", "@types/orchestrator": "^0.3.0", "@types/redux-mock-store": "^0.0.11", + "@types/redux-logger": "^3.0.5", "chai": "^4.1.1", "codecov.io": "^0.1.6", "commitizen": "^2.9.6", @@ -84,7 +85,8 @@ "redux-mock-store": "^1.2.3", "redux-observable": "^0.16.0", "semantic-release": "^8.0.0", - "sn-client-js": "^3.0.0-development.2", + "sn-client-js": "^3.0.0", + "sn-client-auth-google": "^1.0.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 f411c86..08a303f 100644 --- a/src/Actions.ts +++ b/src/Actions.ts @@ -726,6 +726,13 @@ export module Actions { type: 'USER_LOGIN_FAILURE', message: (error.status === 403) ? 'The username or the password is not valid!' : error.message }) + /** + * Action creator for login a user to a sensenet portal with her google account. + * @returns {Object} Returns a redux action. + */ + export const UserLoginGoogle = () => ({ + type: 'USER_LOGIN_GOOGLE' + }) /** * Action creator for logout a user from a sensenet portal. * @returns {Object} Returns a redux action. diff --git a/src/Epics.ts b/src/Epics.ts index db0983e..af171ad 100644 --- a/src/Epics.ts +++ b/src/Epics.ts @@ -1,8 +1,8 @@ import { Actions } from './Actions'; import { Reducers } from './Reducers'; - import { combineEpics } from 'redux-observable'; import { Repository, ContentTypes, Collection, Authentication } from 'sn-client-js'; +import { GoogleOauthProvider } from 'sn-client-auth-google'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/catch' @@ -194,8 +194,8 @@ export module Epics { export const deleteBatchEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { return action$.ofType('DELETE_BATCH_REQUEST') .mergeMap(action => { - let contentItems = Object.keys(action.contentItems).map(id => { - return dependencies.repository.HandleLoadedContent(action.contentItems[id], action.contentItems.__contentType); + 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) => { @@ -211,8 +211,8 @@ export module Epics { 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); + 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) => { @@ -228,8 +228,8 @@ export module Epics { 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); + 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) => { @@ -361,9 +361,6 @@ export module Epics { return action$.ofType('USER_LOGIN_REQUEST') .mergeMap(action => { return dependencies.repository.Authentication.Login(action.userName, action.password) - // .combineLatest(dependencies.repository.GetCurrentUser().skipWhile(u => u.Name === 'Visitor')) - // .skipWhile(u => u instanceof ContentTypes.User) - // .first() .map(result => { return result ? Actions.UserLoginBuffer(result) @@ -373,6 +370,20 @@ export module Epics { .catch(error => Observable.of(Actions.UserLoginFailure(error))) }) } + /** + * Epic to login a user to a sensenet portal. It is related to three redux actions, returns ```LoginUser``` action and sends the response to the + * ```LoginUserSuccess``` action if the ajax request ended successfully or catches the error if the request failed and sends the error message to the ```LoginUserFailure``` action. + */ + export const userLoginGoogleEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { + return action$.ofType('USER_LOGIN_GOOGLE') + .mergeMap(action => { + return Observable.of(dependencies.repository.Authentication.GetOauthProvider(GoogleOauthProvider).Login()) + .map(result => { + return Actions.UserLoginBuffer(true) + }) + .catch(error => Observable.of(Actions.UserLoginFailure(error))) + }) + } export const userLoginBufferEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => { return action$.ofType('USER_LOGIN_BUFFER') .mergeMap(action => { @@ -448,6 +459,7 @@ export module Epics { forceundocheckoutContentEpic, restoreversionContentEpic, userLoginEpic, + userLoginGoogleEpic, userLogoutEpic, checkLoginStateEpic, getContentActions, diff --git a/test/ActionsTests.ts b/test/ActionsTests.ts index bc44273..18881ac 100644 --- a/test/ActionsTests.ts +++ b/test/ActionsTests.ts @@ -595,6 +595,14 @@ describe('Actions', () => { expect(Actions.UserLoginBuffer(true)).to.deep.equal(expectedAction) }); }); + describe('UserLoginGoogle', () => { + it('should create an action to a user login with google', () => { + const expectedAction = { + type: 'USER_LOGIN_GOOGLE' + } + expect(Actions.UserLoginGoogle()).to.deep.equal(expectedAction) + }); + }); describe('UserLogout', () => { it('should create an action to a user logout request', () => { const expectedAction = { From 5380fa9d2d0a1f9fdf704fc8f780b5e2a26d3266 Mon Sep 17 00:00:00 2001 From: Lajos Date: Fri, 8 Dec 2017 10:44:02 +0100 Subject: [PATCH 08/13] [KFI]fix(typedoc): Fixed project&readme name (#59) --- gulpfile.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index a3900cc..9a1d67f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -18,12 +18,12 @@ gulp.task("typedoc", function () { target: "es2015", includeDeclarations: false, out: "./documentation/html", - name: "sn-client-js", + name: "sn-redux", theme: "default", ignoreCompilerErrors: true, version: true, mode: "modules", - readme: "sn-client-js/README.md", + readme: "sn-redux/README.md", excludeExternals: true, excludePrivate: true, includes: "docs", @@ -45,12 +45,12 @@ gulp.task("typedoc:md:generate", function () { target: "es2015", includeDeclarations: false, out: "./documentation/markdown", - name: "sn-client-js", + name: "sn-redux", theme: "node_modules/typedoc-md-theme/bin", ignoreCompilerErrors: true, version: true, mode: "modules", - readme: "sn-client-js/README.md", + readme: "sn-redux/README.md", excludeExternals: true, excludePrivate: true, includes: "docs" From 200527fe88634362e0cfc6477e04318a1da06dfa Mon Sep 17 00:00:00 2001 From: gallayl Date: Wed, 11 Jul 2018 14:27:57 +0200 Subject: [PATCH 09/13] [KFI]refactor(LoginState): removed checkLoginState action, added loginStateChanged, subscribed to ch --- src/Actions.ts | 9 +++++---- src/Reducers.ts | 19 +++---------------- src/Store.ts | 9 +++++++-- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Actions.ts b/src/Actions.ts index 5efa911..1c1d8af 100644 --- a/src/Actions.ts +++ b/src/Actions.ts @@ -112,7 +112,7 @@ /** */ import { GoogleOauthProvider } from '@sensenet/authentication-google' -import { IContent, IODataResponse, Repository, Upload } from '@sensenet/client-core' +import { IContent, IODataResponse, LoginState, Repository, Upload } from '@sensenet/client-core' import { IODataBatchResponse } from '@sensenet/client-core/dist/Models/IODataBatchResponse' import { IODataParams } from '@sensenet/client-core/dist/Models/IODataParams' import { IActionModel, Schema } from '@sensenet/default-content-types' @@ -396,8 +396,9 @@ export const restoreVersion = (idOrPath: number | * Action creator for check user state in a sensenet application. * @returns {Object} Returns a redux action with the properties. */ -export const checkLoginState = () => ({ - type: 'CHECK_LOGIN_STATE', +export const loginStateChanged = (loginState: LoginState) => ({ + type: 'USER_LOGIN_STATE_CHANGED', + loginState, }) /** * Action creator for user changes. @@ -441,7 +442,7 @@ export const userLogin = (userName: string, password: string) => ({ * Action creator for login a user to a sensenet portal with her google account. * @returns {Object} Returns a redux action. */ -export const userLoginGoogle = (provider: GoogleOauthProvider, token?: string ) => ({ +export const userLoginGoogle = (provider: GoogleOauthProvider, token?: string) => ({ type: 'USER_LOGIN_GOOGLE', async payload(repository: Repository) { const response = await provider.login(token) diff --git a/src/Reducers.ts b/src/Reducers.ts index ac28264..89a49fb 100644 --- a/src/Reducers.ts +++ b/src/Reducers.ts @@ -70,23 +70,10 @@ export const language = (state = 'en-US', action) => { */ export const loginState = (state = LoginState.Pending, action) => { switch (action.type) { - case 'USER_LOGIN_LOADING': - return LoginState.Pending - case 'USER_LOGIN_SUCCESS': - return action.payload ? - LoginState.Authenticated : - LoginState.Unauthenticated - case 'USER_LOGOUT_SUCCESS': - return LoginState.Unauthenticated - case 'USER_LOGIN_FAILURE': - return LoginState.Unauthenticated - case 'USER_LOGOUT_FAILURE': - return LoginState.Unauthenticated - case 'USER_CHANGED': - return !action.user || action.user.Name === 'Visitor' ? LoginState.Unauthenticated : LoginState.Authenticated - default: - return state + case 'USER_LOGIN_STATE_CHANGED': + return action.loginState } + return state } /** * Reducer to handle Actions on the loginError property in the session object. diff --git a/src/Store.ts b/src/Store.ts index 15c0c03..bd9ac3d 100644 --- a/src/Store.ts +++ b/src/Store.ts @@ -137,8 +137,13 @@ export const createSensenetStore: (options: CreateStoreOptions) => Store { - store.dispatch(Actions.loadRepository(repo.configuration)) + store.dispatch(Actions.loadRepository(repo.configuration)) + + repo.authentication.state.subscribe((state) => { + store.dispatch(Actions.loginStateChanged(state)) + }) + + repo.authentication.currentUser.subscribe((user) => { store.dispatch(Actions.userChanged(user)) }, true) return store From cc091d23ec30dd4f3c3500e33a74a53d2ec02bd6 Mon Sep 17 00:00:00 2001 From: gallayl Date: Wed, 11 Jul 2018 14:28:17 +0200 Subject: [PATCH 10/13] [KFI]test(loginStateChanged): updated tests --- test/ActionsTests.ts | 9 +++++---- test/ReducersTests.ts | 31 +++---------------------------- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/test/ActionsTests.ts b/test/ActionsTests.ts index 3572d7a..524eb8e 100644 --- a/test/ActionsTests.ts +++ b/test/ActionsTests.ts @@ -1,6 +1,6 @@ import { addGoogleAuth } from '@sensenet/authentication-google' import { JwtService } from '@sensenet/authentication-jwt' -import { Repository } from '@sensenet/client-core' +import { LoginState, Repository } from '@sensenet/client-core' import { File as SNFile, Task, User } from '@sensenet/default-content-types' import { promiseMiddleware } from '@sensenet/redux-promise-middleware' import * as Chai from 'chai' @@ -586,12 +586,13 @@ describe('Actions', () => { }) }) }) - describe('CheckLoginState', () => { + describe('loginStateChanged', () => { it('should return the current authentication state', () => { const expectedAction = { - type: 'CHECK_LOGIN_STATE', + type: 'USER_LOGIN_STATE_CHANGED', + loginState: LoginState.Unauthenticated, } - expect(Actions.checkLoginState()).to.deep.equal(expectedAction) + expect(Actions.loginStateChanged(LoginState.Unauthenticated)).to.deep.equal(expectedAction) }) }) describe('UserChanged', () => { diff --git a/test/ReducersTests.ts b/test/ReducersTests.ts index f154d71..e7e1280 100644 --- a/test/ReducersTests.ts +++ b/test/ReducersTests.ts @@ -1,5 +1,5 @@ import { LoginState, Repository } from '@sensenet/client-core' -import { Status, Task, User } from '@sensenet/default-content-types' +import { Status, Task } from '@sensenet/default-content-types' import * as Chai from 'chai' import * as Reducers from '../src/Reducers' const expect = Chai.expect @@ -28,33 +28,8 @@ describe('Reducers', () => { it('should return the initial state', () => { expect(Reducers.loginState(undefined, {})).to.be.deep.equal(LoginState.Pending) }) - it('should return that a user is logged-in', () => { - expect(Reducers.loginState(undefined, { type: 'USER_LOGIN_SUCCESS', payload: true })).to.be.deep.equal(LoginState.Authenticated) - }) - it('should return theres no authenticated user', () => { - console.log('aaa') - expect(Reducers.loginState(undefined, { type: 'USER_LOGIN_SUCCESS', payload: false })).to.be.deep.equal(LoginState.Unauthenticated) - }) - it('should return theres no authenticated user', () => { - expect(Reducers.loginState(undefined, { type: 'USER_LOGOUT_SUCCESS' })).to.be.deep.equal(LoginState.Unauthenticated) - }) - it('should return theres no authenticated user', () => { - expect(Reducers.loginState(undefined, { type: 'USER_LOGIN_FAILURE' })).to.be.deep.equal(LoginState.Unauthenticated) - }) - it('should return pending', () => { - expect(Reducers.loginState(undefined, { type: 'USER_LOGIN_LOADING' })).to.be.deep.equal(LoginState.Pending) - }) - it('should return theres no authenticated user', () => { - expect(Reducers.loginState(undefined, { type: 'USER_LOGOUT_FAILURE' })).to.be.deep.equal(LoginState.Unauthenticated) - }) - it('should return the state by the current users name', () => { - expect(Reducers.loginState(undefined, { type: 'USER_CHANGED' })).to.be.deep.equal(LoginState.Unauthenticated) - }) - it('should return the state by the current users name', () => { - expect(Reducers.loginState(undefined, { type: 'USER_CHANGED', user: { Name: 'Visitor' } as User })).to.be.deep.equal(LoginState.Unauthenticated) - }) - it('should return the state by the current users name', () => { - expect(Reducers.loginState(undefined, { type: 'USER_CHANGED', user: { Name: 'alba' } as User })).to.be.deep.equal(LoginState.Authenticated) + it('should return the new state', () => { + expect(Reducers.loginState(undefined, { type: 'USER_LOGIN_STATE_CHANGED', loginState: LoginState.Authenticated })).to.be.deep.equal(LoginState.Authenticated) }) }) From 772ed52d2a3b565aaaa2a79df97384b2cf4d7134 Mon Sep 17 00:00:00 2001 From: gallayl Date: Wed, 11 Jul 2018 15:02:49 +0200 Subject: [PATCH 11/13] [KFI]build(fixed vscode test debug profile path): --- .vscode/launch.json | 60 ++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index fe89608..d08f7fc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,31 +1,31 @@ { - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Mocha Tests", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "args": [ - "-u", - "tdd", - "--timeout", - "999999", - "--colors", - "-p", - "${workspaceRoot}\\tsconfig.json", - "${workspaceRoot}/dist/test/index.js" - ], - "internalConsoleOptions": "openOnSessionStart" - }, - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceRoot}\\dist\\src\\sn-redux.js", - "outFiles": [ - "${workspaceRoot}/out/**/*.js" - ] - } - ] - } \ No newline at end of file + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Mocha Tests", + "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "-p", + "${workspaceRoot}\\tsconfig.json", + "${workspaceRoot}/temp/test/index.js" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceRoot}\\dist\\src\\sn-redux.js", + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ] + } + ] +} \ No newline at end of file From c34de310104d7dcd9c6c0ff10fe22fa3d0aeb19a Mon Sep 17 00:00:00 2001 From: gallayl Date: Wed, 11 Jul 2018 15:39:24 +0200 Subject: [PATCH 12/13] [KFI]refactor(project): updated dependencies, fixed types --- package-lock.json | 805 ++++++++++++++++++++++++------------------ package.json | 22 +- src/Reducers.ts | 21 +- src/Store.ts | 6 +- test/ActionsTests.ts | 46 +-- test/ReducersTests.ts | 4 +- tsconfig.json | 1 - 7 files changed, 493 insertions(+), 412 deletions(-) diff --git a/package-lock.json b/package-lock.json index b40aed4..a7cc00f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,21 +122,30 @@ } }, "@sensenet/authentication-jwt": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sensenet/authentication-jwt/-/authentication-jwt-1.0.0.tgz", - "integrity": "sha512-PwDHr9OUfrwh2ZrmcD2MJKHlQOD1DXDknNt1vbJ+ZAJzq659TftXYdxbcVO9twryuj7ib0J/pk8wiMK5Wsu6Qw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@sensenet/authentication-jwt/-/authentication-jwt-1.0.2.tgz", + "integrity": "sha512-+/Pp1W8U36Lj5+N32g6RZMqdSq9NhUTyeFQCp08FD9fR0UDYv+qZdeef6dbkOlgGffhx3AyuY5i42wt8xqP6CQ==", "requires": { - "@sensenet/client-core": "^1.0.0" + "@sensenet/client-core": "^1.2.0" } }, "@sensenet/client-core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sensenet/client-core/-/client-core-1.1.0.tgz", - "integrity": "sha512-Tm5ZwfFVxS2Wh+7KVmV49lWLbH/OZHsYTJkPolTkIj/V+bLWuw2OMF/1XdbCyuoKbpJJ2Q184FYY7xATPxeW5w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sensenet/client-core/-/client-core-1.2.1.tgz", + "integrity": "sha512-FyMsK5AgJPQ/Ur2S3rJupq3E9t68BaDosCnyI9TQ536mgWZQZqNQfkiuU/s3ju/ENRIZRRtZW/WhuKiSQ98vTg==", "requires": { "@sensenet/client-utils": "^1.0.1", "@sensenet/default-content-types": "^1.1.0", - "@sensenet/query": "^1.0.1" + "@sensenet/query": "^1.0.1", + "@types/uuid": "^3.4.3", + "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } } }, "@sensenet/client-utils": { @@ -160,22 +169,21 @@ "integrity": "sha512-l+Qau6AtNUrGaqwQJB7OfqNTt005YLb0I70vFpYe7e9VYGyPFKi4MT3gCOtD4M5EDqG2pG0A3WaWhqV58cSGzg==" }, "@types/chai": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.3.tgz", - "integrity": "sha512-f5dXGzOJycyzSMdaXVhiBhauL4dYydXwVpavfQ1mVCaGjR56a9QfklXObUxlIY9bGTmCPHEEZ04I16BZ/8w5ww==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.4.tgz", + "integrity": "sha512-h6+VEw2Vr3ORiFCyyJmcho2zALnUq9cvdB/IO8Xs9itrJVCenC7o26A6+m7D0ihTTr65eS259H5/Ghl/VjYs6g==", "dev": true }, "@types/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-YeDiSEzznwZwwp766SJ6QlrTyBYUGPSIwmREHVTmktUYiT/WADdWtpt9iH0KuUSf8lZLdI4lP0X6PBzPo5//JQ==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.4.tgz", + "integrity": "sha512-XMHApnKWI0jvXU5gLcSTsRjJBpSzP0BG+2oGv98JFyS4a5R0tRy0oshHBRndb3BuHb9AwDKaUL8Ja7GfUvsG4g==", "dev": true }, "@types/node": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.2.tgz", - "integrity": "sha512-9NfEUDp3tgRhmoxzTpTo+lq+KIVFxZahuRX0LHF/9IzKHaWuoWsIrrJ61zw5cnnlGINX8lqJzXYfQTOICS5Q+A==", - "dev": true + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.2.tgz", + "integrity": "sha512-m9zXmifkZsMHZBOyxZWilMwmTlpC8x5Ty360JKTiXvlXZfBWYpsg9ZZvP/Ye+iZUh+Q+MxDLjItVTWIsfwz+8Q==" }, "@types/redux-logger": { "version": "3.0.6", @@ -184,6 +192,40 @@ "dev": true, "requires": { "redux": "^3.6.0" + }, + "dependencies": { + "redux": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "dev": true, + "requires": { + "lodash": "^4.2.1", + "lodash-es": "^4.2.1", + "loose-envify": "^1.1.0", + "symbol-observable": "^1.0.3" + } + } + } + }, + "@types/uuid": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.3.tgz", + "integrity": "sha512-5fRLCYhLtDb3hMWqQyH10qtF+Ud2JnNCXTCZ+9ktNdCcgslcuXkDTkFcJNk++MT29yDntDnlF1+jD+uVGumsbw==", + "requires": { + "@types/node": "*" + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "ansi-regex": { @@ -207,6 +249,12 @@ "sprintf-js": "~1.0.2" } }, + "argv": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz", + "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=", + "dev": true + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -222,39 +270,23 @@ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, - "asn1": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", - "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", - "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", - "dev": true, - "optional": true - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", - "dev": true, - "optional": true + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, - "aws-sign2": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", - "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", - "dev": true, - "optional": true + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", + "dev": true }, "babel-code-frame": { "version": "6.26.0", @@ -294,22 +326,14 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "bl": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", - "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", - "dev": true, - "requires": { - "readable-stream": "~1.0.26" - } - }, - "boom": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", - "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, + "optional": true, "requires": { - "hoek": "0.9.x" + "tweetnacl": "^0.14.3" } }, "brace-expansion": { @@ -334,12 +358,6 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, - "caseless": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.6.0.tgz", - "integrity": "sha1-gWfBq4OX+1u5X5bSjlqBxQ8kesQ=", - "dev": true - }, "chai": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", @@ -397,14 +415,155 @@ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, - "codecov.io": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/codecov.io/-/codecov.io-0.1.6.tgz", - "integrity": "sha1-Wd/QLaH/McL7K5Uq2K0W/TeBtyg=", + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "codecov": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.0.4.tgz", + "integrity": "sha512-KJyzHdg9B8U9LxXa7hS6jnEW5b1cNckLYc2YpnJ1nEFiOW+/iSzDHp+5MYEIQd9fN3/tC6WmGZmYiwxzkuGp/A==", "dev": true, "requires": { - "request": "2.42.0", - "urlgrey": "0.4.0" + "argv": "^0.0.2", + "ignore-walk": "^3.0.1", + "request": "^2.87.0", + "urlgrey": "^0.4.4" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "~1.33.0" + } + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "urlgrey": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz", + "integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + } } }, "color-convert": { @@ -422,20 +581,10 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "combined-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", - "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", - "dev": true, - "optional": true, - "requires": { - "delayed-stream": "0.0.5" - } - }, "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, "concat-map": { @@ -450,23 +599,23 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, - "cryptiles": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", - "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, - "optional": true, "requires": { - "boom": "0.4.x" + "assert-plus": "^1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } } }, - "ctype": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", - "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", - "dev": true, - "optional": true - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -490,18 +639,6 @@ "type-detect": "^4.0.0" } }, - "deep-equal": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz", - "integrity": "sha1-skbCuApXCkfBG+HZvRBw7IeLh84=", - "dev": true - }, - "defined": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", - "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", - "dev": true - }, "del": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", @@ -516,24 +653,21 @@ "rimraf": "^2.2.8" } }, - "delayed-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", - "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", - "dev": true, - "optional": true - }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, - "duplexer": { + "ecc-jsbn": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } }, "escape-string-regexp": { "version": "1.0.5", @@ -553,23 +687,29 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "forever-agent": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", - "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "dev": true }, - "form-data": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", - "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", - "dev": true, - "optional": true, - "requires": { - "async": "~0.9.0", - "combined-stream": "~0.0.4", - "mime": "~1.2.11" - } + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true }, "fs.realpath": { "version": "1.0.0", @@ -583,6 +723,23 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -625,11 +782,27 @@ } }, "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "^5.1.0", + "har-schema": "^2.0.0" + } + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -640,46 +813,24 @@ } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "hawk": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", - "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", - "dev": true, - "optional": true, - "requires": { - "boom": "0.4.x", - "cryptiles": "0.2.x", - "hoek": "0.9.x", - "sntp": "0.2.x" - } - }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, - "hoek": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", - "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", - "dev": true - }, - "http-signature": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", - "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, - "optional": true, "requires": { - "asn1": "0.1.11", - "assert-plus": "^0.1.5", - "ctype": "0.5.3" + "minimatch": "^3.0.4" } }, "inflight": { @@ -731,10 +882,16 @@ "path-is-inside": "^1.0.1" } }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, "istanbul-lib-coverage": { @@ -773,33 +930,68 @@ "esprima": "^4.0.0" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, "jsesc": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", "dev": true }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } }, "lodash": { "version": "4.17.10", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true }, "lodash-es": { "version": "4.17.10", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz", - "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==" + "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==", + "dev": true }, "lodash.isplainobject": { "version": "4.0.6", @@ -815,17 +1007,10 @@ "js-tokens": "^3.0.0" } }, - "mime": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", - "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", - "dev": true, - "optional": true - }, - "mime-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", - "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", "dev": true }, "minimatch": { @@ -853,22 +1038,22 @@ } }, "mocha": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.1.1.tgz", - "integrity": "sha512-kKKs/H1KrMMQIEsWNxGmb4/BGsmj0dkeyotEvbrAuQ01FcWRLssUNXCEUZk6SZtyJBi6EE7SL0zDDtItw1rGhw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { "browser-stdout": "1.3.1", - "commander": "2.11.0", + "commander": "2.15.1", "debug": "3.1.0", "diff": "3.5.0", "escape-string-regexp": "1.0.5", "glob": "7.1.2", - "growl": "1.10.3", + "growl": "1.10.5", "he": "1.1.1", "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "4.4.0" + "supports-color": "5.4.0" } }, "ms": { @@ -877,12 +1062,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", - "dev": true - }, "normalizr": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/normalizr/-/normalizr-3.2.4.tgz", @@ -2941,13 +3120,6 @@ } } }, - "oauth-sign": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.4.0.tgz", - "integrity": "sha1-8ilW8x6nFRqCHl8vsywRPK2Ln2k=", - "dev": true, - "optional": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2993,6 +3165,12 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -3018,36 +3196,15 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true, - "optional": true - }, - "qs": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-1.2.2.tgz", - "integrity": "sha1-GbV/8k3CqZzh+L32r82ln472H4g=", "dev": true }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, "redux": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz", + "integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==", "requires": { - "lodash": "^4.2.1", - "lodash-es": "^4.2.1", "loose-envify": "^1.1.0", - "symbol-observable": "^1.0.3" + "symbol-observable": "^1.2.0" } }, "redux-logger": { @@ -3059,37 +3216,14 @@ } }, "redux-mock-store": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.1.tgz", - "integrity": "sha512-B+iZ98ESHw4EAWVLKUknQlop1OdLKOayGRmd6KavNtC0zoSsycD8hTt0hEr1eUTw2gmYJOdfBY5QAgZweTUcLQ==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.3.tgz", + "integrity": "sha512-ryhkkb/4D4CUGpAV2ln1GOY/uh51aczjcRz9k2L2bPx/Xja3c5pSGJJPyR25GNVRXtKIExScdAgFdiXp68GmJA==", "dev": true, "requires": { "lodash.isplainobject": "^4.0.6" } }, - "request": { - "version": "2.42.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.42.0.tgz", - "integrity": "sha1-VyvQFIk4VkBArHqxSLlkI6BjMEo=", - "dev": true, - "requires": { - "aws-sign2": "~0.5.0", - "bl": "~0.9.0", - "caseless": "~0.6.0", - "forever-agent": "~0.5.0", - "form-data": "~0.1.0", - "hawk": "1.1.1", - "http-signature": "~0.10.0", - "json-stringify-safe": "~5.0.0", - "mime-types": "~1.0.1", - "node-uuid": "~1.4.0", - "oauth-sign": "~0.4.0", - "qs": "~1.2.0", - "stringstream": "~0.0.4", - "tough-cookie": ">=0.12.0", - "tunnel-agent": "~0.4.0" - } - }, "resolve": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", @@ -3099,15 +3233,6 @@ "path-parse": "^1.0.5" } }, - "resumer": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", - "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", - "dev": true, - "requires": { - "through": "~2.3.4" - } - }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -3117,65 +3242,67 @@ "glob": "^7.0.5" } }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, - "sntp": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", - "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", - "dev": true, - "optional": true, - "requires": { - "hoek": "0.9.x" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, - "split": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", - "integrity": "sha1-Zwl8YB1pfOE2j0GPBs0gHPBSGlc=", - "dev": true, - "requires": { - "through": "2" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "dev": true, "requires": { - "duplexer": "~0.1.1" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", - "dev": true, - "optional": true - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -3186,12 +3313,12 @@ } }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^2.0.0" + "has-flag": "^3.0.0" } }, "symbol-observable": { @@ -3199,28 +3326,6 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, - "tape": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tape/-/tape-2.3.0.tgz", - "integrity": "sha1-Df7scJIn+8yRcKvn8EaWKycUMds=", - "dev": true, - "requires": { - "deep-equal": "~0.1.0", - "defined": "~0.0.0", - "inherits": "~2.0.1", - "jsonify": "~0.0.0", - "resumer": "~0.0.0", - "split": "~0.2.10", - "stream-combiner": "~0.0.2", - "through": "~2.3.4" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -3232,7 +3337,6 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, - "optional": true, "requires": { "punycode": "^1.4.1" } @@ -3286,11 +3390,12 @@ "tslib": "^1.8.1" } }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true }, "type-detect": { "version": "4.0.8", @@ -3299,18 +3404,28 @@ "dev": true }, "typescript": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz", - "integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", "dev": true }, - "urlgrey": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.0.tgz", - "integrity": "sha1-8GU1cED7NcOzEdTl3DZITZbb6gY=", + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "tape": "2.3.0" + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } } }, "wrappy": { diff --git a/package.json b/package.json index 2e835c0..31ea9f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sensenet/redux", - "version": "4.1.0", + "version": "4.1.1", "description": "A set of redux actions, reducers and redux-ovbservable epics for sensenet", "main": "dist/index.js", "scripts": { @@ -60,25 +60,25 @@ "homepage": "https://sensenet.com", "dependencies": { "@sensenet/authentication-google": "^2.0.0", - "@sensenet/authentication-jwt": "^1.0.0", - "@sensenet/client-core": "^1.1.0", + "@sensenet/authentication-jwt": "^1.0.2", + "@sensenet/client-core": "^1.2.1", "@sensenet/default-content-types": "^1.1.0", "@sensenet/redux-promise-middleware": "^1.0.0", "normalizr": "^3.2.3", - "redux": "^3.7.2", + "redux": "^4.0.0", "redux-logger": "^3.0.6" }, "devDependencies": { - "@types/chai": "^4.1.0", - "@types/mocha": "^5.0.0", + "@types/chai": "^4.1.4", + "@types/mocha": "^5.2.4", + "@types/node": "^10.5.2", "@types/redux-logger": "^3.0.5", - "@types/node": "^10.1.4", "chai": "^4.1.1", - "codecov.io": "^0.1.6", + "codecov": "^3.0.4", "del": "^3.0.0", - "mocha": "^5.0.0", + "mocha": "^5.2.0", "nyc": "^12.0.2", - "redux-mock-store": "^1.5.1", + "redux-mock-store": "^1.5.3", "rimraf": "^2.6.1", "tslint": "^5.6.0", "typescript": "^2.9.1" @@ -89,4 +89,4 @@ } }, "typings": "./dist/index.d.ts" -} +} \ No newline at end of file diff --git a/src/Reducers.ts b/src/Reducers.ts index 89a49fb..7f79d9d 100644 --- a/src/Reducers.ts +++ b/src/Reducers.ts @@ -5,7 +5,7 @@ /** */ -import { ConstantContent, IContent, LoginState } from '@sensenet/client-core' +import { ConstantContent, IContent, LoginState, Repository } from '@sensenet/client-core' import { IODataBatchResponse } from '@sensenet/client-core/dist/Models/IODataBatchResponse' import { combineReducers, Reducer } from 'redux' @@ -21,17 +21,7 @@ export interface SelectStateType { */ export interface BatchResponseStateType { response: IODataBatchResponse, - error: object, -} -/** - * Interface to define state type for sensenet Reducer. - */ -export interface SensenetStateType { - session: object, - children: object, - currentcontent: object, - selected: SelectStateType, - batchResponses: BatchResponseStateType, + error: any, } /** @@ -172,7 +162,7 @@ const user = combineReducers({ * @param {object} action Represents an action that is called. * @returns {object} state. Returns the next state based on the action. */ -export const repository = (state = null, action) => { +export const repository = (state = null, action: { type: string, repository: Repository }) => { switch (action.type) { case 'LOAD_REPOSITORY': return action.repository @@ -735,14 +725,15 @@ export const batchResponseError = (state = '', action) => { /** * Reducer combining response and error into a single object, ```batchResponses```. */ -const batchResponses = combineReducers({ +const batchResponses: Reducer = combineReducers({ response: odataBatchResponse, error: batchResponseError, }) + /** * Reducer combining session, children, currentcontent and selected into a single object, ```sensenet``` which will be the top-level one. */ -export const sensenet: Reducer = combineReducers({ +export const sensenet = combineReducers({ session, children, currentcontent, diff --git a/src/Store.ts b/src/Store.ts index bd9ac3d..1b1586d 100644 --- a/src/Store.ts +++ b/src/Store.ts @@ -30,7 +30,7 @@ */ import { Repository } from '@sensenet/client-core' import { promiseMiddleware } from '@sensenet/redux-promise-middleware' -import { applyMiddleware, compose, createStore, Middleware, Reducer, Store, StoreEnhancer } from 'redux' +import { AnyAction, applyMiddleware, compose, createStore, Middleware, Reducer, Store, StoreEnhancer } from 'redux' import { createLogger } from 'redux-logger' import * as Actions from './Actions' @@ -127,9 +127,9 @@ export const createSensenetStore: (options: CreateStoreOptions) => Store( + const store = createStore, Partial>( options.rootReducer, - options.persistedState || {} as T, + options.persistedState || {}, composeEnhancers( applyMiddleware(...middlewareArray), ...enhancerArray, diff --git a/test/ActionsTests.ts b/test/ActionsTests.ts index 524eb8e..ea60107 100644 --- a/test/ActionsTests.ts +++ b/test/ActionsTests.ts @@ -1,13 +1,11 @@ -import { addGoogleAuth } from '@sensenet/authentication-google' +import { GoogleOauthProvider } from '@sensenet/authentication-google' import { JwtService } from '@sensenet/authentication-jwt' import { LoginState, Repository } from '@sensenet/client-core' import { File as SNFile, Task, User } from '@sensenet/default-content-types' import { promiseMiddleware } from '@sensenet/redux-promise-middleware' -import * as Chai from 'chai' -import * as configureStore from 'redux-mock-store' +import { expect } from 'chai' +import configureStore from 'redux-mock-store' import * as Actions from '../src/Actions' -import { MockTokenFactory } from './MockTokenFactory' -const expect = Chai.expect declare const global: any @@ -94,28 +92,6 @@ const jwtMockResponse = { }, } as Response -const googleMockResponse = { - ok: true, - status: 200, - json: async () => { - return { - access: MockTokenFactory.CreateValid().toString(), - refresh: MockTokenFactory.CreateValid().toString(), - } - }, -} as Response - -const googleFalseMockResponse = { - ok: true, - status: 200, - json: async () => { - return { - access: MockTokenFactory.CreateNotValidYet().toString(), - refresh: MockTokenFactory.CreateNotValidYet().toString(), - } - }, -} as Response - describe('Actions', () => { const path = '/workspaces/project' // tslint:disable-next-line:variable-name @@ -629,15 +605,15 @@ describe('Actions', () => { }) }) describe('UserLoginGoogle', () => { - repo = new Repository({ repositoryUrl: 'https://dmsservice.demo.sensenet.com/' }, async () => googleMockResponse) - const jwt = new JwtService(repo) - const googleOauthProvider = addGoogleAuth(jwt, { clientId: '' }) + const googleOauthProvider = { + login: async () => true, + } as GoogleOauthProvider it('should create an action to a user login with google', () => { expect(Actions.userLoginGoogle(googleOauthProvider).type).to.eql('USER_LOGIN_GOOGLE') }) describe('serviceChecks()', () => { - context('Given provider.login() resolves', () => { - let data + context('Given provider.login() resolves', async () => { + let data: boolean beforeEach(async () => { data = await Actions.userLoginGoogle(googleOauthProvider, 'gasgsdagsdagd.dgsgfshdfhs').payload(repo) }) @@ -654,9 +630,9 @@ describe('Actions', () => { }) describe('serviceChecks() false', () => { context('Given provider.login() resolves', () => { - repo = new Repository({ repositoryUrl: 'https://dmsservice.demo.sensenet.com/' }, async () => googleFalseMockResponse) - const jwt2 = new JwtService(repo) - const googleOauthProvider2 = addGoogleAuth(jwt2, { clientId: '' }) + const googleOauthProvider2 = { + login: async () => false, + } as GoogleOauthProvider let data beforeEach(async () => { data = await Actions.userLoginGoogle(googleOauthProvider2, 'gasgsdagsdagd.dgsgfshdfhs').payload(repo) diff --git a/test/ReducersTests.ts b/test/ReducersTests.ts index e7e1280..488883c 100644 --- a/test/ReducersTests.ts +++ b/test/ReducersTests.ts @@ -1114,11 +1114,11 @@ describe('Reducers', () => { describe('repository reducer', () => { const repository = new Repository({}, async () => ({ ok: true } as any)) it('should return the initial state', () => { - expect(Reducers.repository(undefined, {})).to.be.deep.equal(null) + expect(Reducers.repository(undefined, {} as any)).to.be.deep.equal(null) }) it('should return the repository config', () => { - expect(Reducers.repository(null, { type: 'LOAD_REPOSITORY', repository: repository.configuration })).to.be.deep.equal(repository.configuration) + expect(Reducers.repository(null, { type: 'LOAD_REPOSITORY', repository: repository.configuration as any })).to.be.deep.equal(repository.configuration) }) }) diff --git a/tsconfig.json b/tsconfig.json index fe6ab63..e66a212 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,6 @@ "target": "es2015", "module": "commonjs", "moduleResolution": "node", - "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, From 1f0a06e86eac10e367584c8072f939a4c2cc973a Mon Sep 17 00:00:00 2001 From: gallayl Date: Wed, 11 Jul 2018 15:53:53 +0200 Subject: [PATCH 13/13] [KFI]chore(package): bumped version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a7cc00f..5d395c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@sensenet/redux", - "version": "4.1.0", + "version": "4.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 31ea9f9..8c17e1a 100644 --- a/package.json +++ b/package.json @@ -89,4 +89,4 @@ } }, "typings": "./dist/index.d.ts" -} \ No newline at end of file +}