From 92a16159a67df75be282f2a62328aa02efadc2e4 Mon Sep 17 00:00:00 2001 From: "xeronimus@gmail.com" Date: Mon, 15 Jun 2020 17:59:56 +0200 Subject: [PATCH] #58 better test coverage for eventAction reducing --- .../services/clearStoryEstimationsOfUser.js | 29 ++ client/app/services/eventReducer.js | 85 ++--- .../backlogModifyingTest.js | 290 ++++++++++++++ .../disconnectAndKickTest.js | 113 ++++++ .../estimatingTest.js | 262 +++++++++++++ .../includeAndExcludeTest.js | 79 ++++ .../reduceMultipleEventActions.js | 7 + .../roomJoiningAndLeavingTest.js | 324 ++++++++++++++++ client/test/unit/eventReducerTest.js | 355 ++---------------- 9 files changed, 1170 insertions(+), 374 deletions(-) create mode 100644 client/app/services/clearStoryEstimationsOfUser.js create mode 100644 client/test/unit/eventActionReducersScenarios/backlogModifyingTest.js create mode 100644 client/test/unit/eventActionReducersScenarios/disconnectAndKickTest.js create mode 100644 client/test/unit/eventActionReducersScenarios/estimatingTest.js create mode 100644 client/test/unit/eventActionReducersScenarios/includeAndExcludeTest.js create mode 100644 client/test/unit/eventActionReducersScenarios/reduceMultipleEventActions.js create mode 100644 client/test/unit/eventActionReducersScenarios/roomJoiningAndLeavingTest.js diff --git a/client/app/services/clearStoryEstimationsOfUser.js b/client/app/services/clearStoryEstimationsOfUser.js new file mode 100644 index 00000000..3ee9a0b0 --- /dev/null +++ b/client/app/services/clearStoryEstimationsOfUser.js @@ -0,0 +1,29 @@ +/** + * Removes all the estimations of the given user from all the stories + * This is done, when a user leaves the room. + * + * @param {object} storiesObjectFromReduxState + * @param {string} userId + * @return {object} the modified "stories" object + */ +export default function clearStoryEstimationsOfUser(storiesObjectFromReduxState, userId) { + const storyArray = Object.values(storiesObjectFromReduxState); + + return storyArray.reduce((result, currentStory) => { + const estimationArrayOfCurrentStory = Object.keys(currentStory.estimations); + const estimationOfUser = estimationArrayOfCurrentStory.find((uid) => userId === uid); + + if (!estimationOfUser) { + result[currentStory.id] = currentStory; + return result; + } + + const modifiedEstimations = {...currentStory.estimations}; + delete modifiedEstimations[userId]; + result[currentStory.id] = { + ...currentStory, + estimations: modifiedEstimations + }; + return result; + }, {}); +} diff --git a/client/app/services/eventReducer.js b/client/app/services/eventReducer.js index 7745b8c0..029542f7 100644 --- a/client/app/services/eventReducer.js +++ b/client/app/services/eventReducer.js @@ -3,6 +3,7 @@ import {EVENT_ACTION_TYPES} from '../actions/types'; import clientSettingsStore from '../store/clientSettingsStore'; import initialState from '../store/initialState'; import {getCardConfigForValue} from './getCardConfigForValue'; +import clearStoryEstimationsOfUser from './clearStoryEstimationsOfUser'; /** * The event reducer handles backend-event actions. @@ -144,37 +145,22 @@ const eventActionHandlers = { */ [EVENT_ACTION_TYPES.leftRoom]: { fn: (state, payload, event) => { - const isOwnUser = state.userId === event.userId; - - if (isOwnUser) { - // you (or you in another browser) left the room - - // set the page title + // If your user (in this or in another browser) left the room + if (state.userId === event.userId) { document.title = 'PoinZ'; - - // let's reset our state return {...initialState()}; - } else { - // someone else left the room - - const modifiedStories = Object.values(state.stories).reduce((result, currentStory) => { - const leavingUserHasEstimatedStory = Object.keys(currentStory.estimations).find( - (userId) => userId === event.userId - ); - if (!leavingUserHasEstimatedStory) { - result[currentStory.id] = currentStory; - } else { - const modifiedEstimations = {...currentStory.estimations}; - delete modifiedEstimations[event.userId]; - result[currentStory.id] = {...currentStory, estimations: modifiedEstimations}; - } - return result; - }, {}); - const modifiedUsers = {...state.users}; - delete modifiedUsers[event.userId]; - - return {...state, stories: modifiedStories, users: modifiedUsers}; } + + // If someone else left the room + const modifiedStories = clearStoryEstimationsOfUser(state.stories, event.userId); + const modifiedUsers = {...state.users}; + delete modifiedUsers[event.userId]; + + return { + ...state, + stories: modifiedStories, + users: modifiedUsers + }; }, log: (username, payload, oldState, newState, event) => `User ${oldState.users[event.userId].username} left the room` @@ -185,24 +171,16 @@ const eventActionHandlers = { */ [EVENT_ACTION_TYPES.kicked]: { fn: (state, payload) => { - const modifiedStories = Object.values(state.stories).reduce((result, currentStory) => { - // one of the few places, where we need to take the userId from the payload (the user that was kicked, not the "kicking" user) - const leavingUserHasEstimatedStory = Object.keys(currentStory.estimations).find( - (userId) => userId === payload.userId - ); - if (!leavingUserHasEstimatedStory) { - result[currentStory.id] = currentStory; - } else { - const modifiedEstimations = {...currentStory.estimations}; - delete modifiedEstimations[payload.userId]; - result[currentStory.id] = {...currentStory, estimations: modifiedEstimations}; - } - return result; - }, {}); + // We need to take the userId from the payload (the user that was kicked, not the "kicking" user) + const modifiedStories = clearStoryEstimationsOfUser(state.stories, payload.userId); const modifiedUsers = {...state.users}; delete modifiedUsers[payload.userId]; - return {...state, stories: modifiedStories, users: modifiedUsers}; + return { + ...state, + stories: modifiedStories, + users: modifiedUsers + }; }, log: (username, payload, oldState, modifiedState, event) => `User "${oldState.users[event.userId].username}" was kicked from the room by user "${ @@ -215,17 +193,20 @@ const eventActionHandlers = { */ [EVENT_ACTION_TYPES.connectionLost]: { fn: (state, payload, event) => { - if (state.users[event.userId]) { - return { - ...state, - users: { - ...state.users, - [event.userId]: {...state.users[event.userId], disconnected: true} - } - }; - } else { + if (!state.users || !state.users[event.userId]) { return state; } + + return { + ...state, + users: { + ...state.users, + [event.userId]: { + ...state.users[event.userId], + disconnected: true + } + } + }; }, log: (username) => `${username} lost the connection` }, diff --git a/client/test/unit/eventActionReducersScenarios/backlogModifyingTest.js b/client/test/unit/eventActionReducersScenarios/backlogModifyingTest.js new file mode 100644 index 00000000..10867463 --- /dev/null +++ b/client/test/unit/eventActionReducersScenarios/backlogModifyingTest.js @@ -0,0 +1,290 @@ +import {v4 as uuid} from 'uuid'; + +import initialState from '../../../app/store/initialState.js'; +import {EVENT_ACTION_TYPES} from '../../../app/actions/types'; +import reduceMultipleEventActions from './reduceMultipleEventActions'; +import eventReducer from '../../../app/services/eventReducer'; + +test('Adding stories', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + id: otherUserId, + disconnected: false, + excluded: false, + username: 'Other John' + } + }, + stories: {} + } + }; + + const firstStoryId = uuid(); + const secondStoryId = uuid(); + const eventActions = [ + { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'storyAdded', + roomId, + payload: { + title: 'FirstStory', + description: 'description one', + id: firstStoryId, + estimations: {}, + createdAt: 1592115935676 + } + }, + type: EVENT_ACTION_TYPES.storyAdded + }, + { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'storySelected', + roomId, + payload: { + storyId: firstStoryId + } + }, + type: EVENT_ACTION_TYPES.storySelected + }, + { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'storyAdded', + roomId, + payload: { + title: 'Second story', + description: 'dscription second... from other john', + id: secondStoryId, + estimations: {}, + createdAt: 1592115972307 + } + }, + type: 'STORY_ADDED' + } + ]; + + modifiedState = reduceMultipleEventActions(startingState, eventActions); + expect(modifiedState.selectedStory).toEqual(firstStoryId); + expect(modifiedState.stories).toEqual({ + [secondStoryId]: { + createdAt: 1592115972307, + description: 'dscription second... from other john', + estimations: {}, + id: secondStoryId, + title: 'Second story' + }, + [firstStoryId]: { + createdAt: 1592115935676, + description: 'description one', + estimations: {}, + id: firstStoryId, + title: 'FirstStory' + } + }); +}); + +test('Editing stories', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + const firstStoryId = uuid(); + const secondStoryId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + id: otherUserId, + disconnected: false, + excluded: false, + username: 'Other John' + } + }, + selectedStory: firstStoryId, + stories: { + [secondStoryId]: { + createdAt: 1592115972307, + description: 'dscription second... from other john', + estimations: {}, + id: secondStoryId, + title: 'Second story' + }, + [firstStoryId]: { + createdAt: 1592115935676, + description: 'description one', + estimations: {}, + id: firstStoryId, + title: 'FirstStory' + } + } + } + }; + + const eventActions = [ + { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'storyChanged', + roomId, + payload: { + storyId: firstStoryId, + title: 'Edited first Story', + description: 'With edited description' + } + }, + type: EVENT_ACTION_TYPES.storyChanged + }, + { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'storyChanged', + roomId, + payload: { + storyId: secondStoryId, + title: 'Edited second Story', + description: 'With edited description (2)' + } + }, + type: EVENT_ACTION_TYPES.storyChanged + } + ]; + + modifiedState = reduceMultipleEventActions(startingState, eventActions); + expect(modifiedState.selectedStory).toEqual(firstStoryId); + expect(modifiedState.stories).toEqual({ + [secondStoryId]: { + createdAt: 1592115972307, + description: 'With edited description (2)', + estimations: {}, + id: secondStoryId, + editMode: false, + title: 'Edited second Story' + }, + [firstStoryId]: { + createdAt: 1592115935676, + description: 'With edited description', + estimations: {}, + id: firstStoryId, + editMode: false, + title: 'Edited first Story' + } + }); +}); + +test('Deleting stories', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + const firstStoryId = uuid(); + const secondStoryId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + id: otherUserId, + disconnected: false, + excluded: false, + username: 'Other John' + } + }, + selectedStory: firstStoryId, + stories: { + [secondStoryId]: { + createdAt: 1592115972307, + description: 'dscription second... from other john', + estimations: {}, + id: secondStoryId, + title: 'Second story' + }, + [firstStoryId]: { + createdAt: 1592115935676, + description: 'description one', + estimations: {}, + id: firstStoryId, + title: 'FirstStory' + } + } + } + }; + + const storyDeletedAction = { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'storyDeleted', + roomId, + payload: { + storyId: firstStoryId, + title: 'sdgasdg' + } + }, + type: EVENT_ACTION_TYPES.storyDeleted + }; + + modifiedState = eventReducer(startingState, storyDeletedAction); + expect(modifiedState.selectedStory).toEqual(firstStoryId); + expect(modifiedState.stories).toEqual({ + [secondStoryId]: startingState.stories[secondStoryId] + }); +}); diff --git a/client/test/unit/eventActionReducersScenarios/disconnectAndKickTest.js b/client/test/unit/eventActionReducersScenarios/disconnectAndKickTest.js new file mode 100644 index 00000000..7ee3884a --- /dev/null +++ b/client/test/unit/eventActionReducersScenarios/disconnectAndKickTest.js @@ -0,0 +1,113 @@ +import {v4 as uuid} from 'uuid'; + +import eventReducer from '../../../app/services/eventReducer'; +import initialState from '../../../app/store/initialState.js'; +import {EVENT_ACTION_TYPES} from '../../../app/actions/types'; +import clientSettingsStore from '../../../app/store/clientSettingsStore'; + +test('Two users in a room, the other one disconnects, then you kick him', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + const storyId = uuid(); + const storyTwoId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + disconnected: false, + id: otherUserId, + username: 'The other One' + } + }, + stories: { + [storyId]: { + title: 'First Story', + description: 'With description', + id: storyId, + estimations: { + [otherUserId]: 8, + [ownUserId]: 5 + }, + createdAt: 1592120422988 + }, + [storyTwoId]: { + title: 'Some other Story', + description: 'Also with a description', + id: storyId, + estimations: { + [ownUserId]: 5 + }, + createdAt: 1592120422988 + } + } + } + }; + clientSettingsStore.setPresetUserId(ownUserId); + + const connectionLostAction = { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'connectionLost', + roomId, + payload: {} + }, + type: EVENT_ACTION_TYPES.connectionLost + }; + modifiedState = eventReducer(startingState, connectionLostAction); + + // in contrast to "roomLeft", on "connectionLost", user object stays. is marked as disconnected + expect(modifiedState.users[otherUserId]).toEqual({ + disconnected: true, + id: otherUserId, + username: 'The other One' + }); + + // also the estimation of the disconnected user is still in state + expect(modifiedState.stories[storyId].estimations).toEqual({ + [otherUserId]: 8, + [ownUserId]: 5 + }); + + const kickedAction = { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'kicked', + roomId, + payload: { + userId: otherUserId + } + }, + type: EVENT_ACTION_TYPES.kicked + }; + modifiedState = eventReducer(startingState, kickedAction); + + // now only our own user is left + expect(modifiedState.users).toEqual({ + [ownUserId]: startingState.users[ownUserId] + }); + + // now the estimation of the kicked user was removed + expect(modifiedState.stories[storyId].estimations).toEqual({ + [ownUserId]: 5 + }); +}); diff --git a/client/test/unit/eventActionReducersScenarios/estimatingTest.js b/client/test/unit/eventActionReducersScenarios/estimatingTest.js new file mode 100644 index 00000000..e113130c --- /dev/null +++ b/client/test/unit/eventActionReducersScenarios/estimatingTest.js @@ -0,0 +1,262 @@ +import {v4 as uuid} from 'uuid'; + +import initialState from '../../../app/store/initialState'; +import {EVENT_ACTION_TYPES} from '../../../app/actions/types'; +import reduceMultipleEventActions from './reduceMultipleEventActions'; +import eventReducer from '../../../app/services/eventReducer'; + +test('Estimation with two users', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + const firstStoryId = uuid(); + const secondStoryId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + id: otherUserId, + disconnected: false, + excluded: false, + username: 'Other John' + } + }, + selectedStory: firstStoryId, + stories: { + [secondStoryId]: { + createdAt: 1592115972307, + description: 'dscription second... from other john', + estimations: {}, + id: secondStoryId, + title: 'Second story' + }, + [firstStoryId]: { + createdAt: 1592115935676, + description: 'description one', + estimations: {}, + id: firstStoryId, + title: 'FirstStory' + } + } + } + }; + + // own user estimates firstStory + const ownUserEstimateGivenAction = { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'storyEstimateGiven', + roomId, + payload: { + value: 3, + storyId: firstStoryId + } + }, + type: EVENT_ACTION_TYPES.storyEstimateGiven + }; + modifiedState = eventReducer(startingState, ownUserEstimateGivenAction); + + expect(modifiedState.stories[firstStoryId]).toEqual({ + createdAt: 1592115935676, + description: 'description one', + estimations: { + [ownUserId]: 3 + }, + id: firstStoryId, + title: 'FirstStory' + }); + + // own user cleares his estimation on firstStory + const ownUserEstimationClearedAction = { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'storyEstimateCleared', + roomId, + payload: { + storyId: firstStoryId + } + }, + type: EVENT_ACTION_TYPES.storyEstimateCleared + }; + modifiedState = eventReducer(startingState, ownUserEstimationClearedAction); + + expect(modifiedState.stories[firstStoryId]).toEqual({ + createdAt: 1592115935676, + description: 'description one', + estimations: { + // now empty again + }, + id: firstStoryId, + title: 'FirstStory' + }); + + // now both users estimate 5 -> revealed and consensusAchieved + const eventActions = [ + { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'storyEstimateGiven', + roomId, + payload: { + value: 5, + storyId: firstStoryId + } + }, + type: EVENT_ACTION_TYPES.storyEstimateGiven + }, + { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'storyEstimateGiven', + roomId, + payload: { + value: 5, + storyId: firstStoryId + } + }, + type: EVENT_ACTION_TYPES.storyEstimateGiven + }, + { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'revealed', + roomId, + payload: { + storyId: firstStoryId, + manually: false + } + }, + type: EVENT_ACTION_TYPES.revealed + }, + { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'consensusAchieved', + roomId, + payload: { + storyId: firstStoryId, + value: 5 + } + }, + type: EVENT_ACTION_TYPES.consensusAchieved + } + ]; + + modifiedState = reduceMultipleEventActions(startingState, eventActions); + + expect(modifiedState.stories[firstStoryId]).toEqual({ + createdAt: 1592115935676, + description: 'description one', + estimations: { + [ownUserId]: 5, + [otherUserId]: 5 + }, + revealed: true, + consensus: 5, + id: firstStoryId, + title: 'FirstStory' + }); +}); + +test('New estimation round with two users', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + const firstStoryId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + id: otherUserId, + disconnected: false, + excluded: false, + username: 'Other John' + } + }, + selectedStory: firstStoryId, + stories: { + [firstStoryId]: { + createdAt: 1592115935676, + description: 'description one', + estimations: { + [ownUserId]: 5, + [otherUserId]: 8 + }, + revealed: true, + id: firstStoryId, + title: 'FirstStory' + } + } + } + }; + + // own user starts a new round on the firstStory + const ownUserNewEstimationRoundAction = { + event: { + id: uuid(), + userId: firstStoryId, + correlationId: uuid(), + name: 'newEstimationRoundStarted', + roomId, + payload: { + storyId: firstStoryId + } + }, + type: EVENT_ACTION_TYPES.newEstimationRoundStarted + }; + modifiedState = eventReducer(startingState, ownUserNewEstimationRoundAction); + + expect(modifiedState.stories).toEqual({ + [firstStoryId]: { + createdAt: 1592115935676, + description: 'description one', + id: firstStoryId, + estimations: {}, // old values removed + consensus: undefined, // consensus set to undefined + revealed: false, // revealed flag set to false + title: 'FirstStory' + } + }); +}); diff --git a/client/test/unit/eventActionReducersScenarios/includeAndExcludeTest.js b/client/test/unit/eventActionReducersScenarios/includeAndExcludeTest.js new file mode 100644 index 00000000..a5e8a984 --- /dev/null +++ b/client/test/unit/eventActionReducersScenarios/includeAndExcludeTest.js @@ -0,0 +1,79 @@ +import {v4 as uuid} from 'uuid'; + +import initialState from '../../../app/store/initialState'; +import {EVENT_ACTION_TYPES} from '../../../app/actions/types'; +import eventReducer from '../../../app/services/eventReducer'; + +test('Exclude and Include', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + id: otherUserId, + disconnected: false, + username: 'Other John' + } + } + } + }; + + // own user excluded himself + const excludedAction = { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'excludedFromEstimations', + roomId, + payload: {} + }, + type: EVENT_ACTION_TYPES.excludedFromEstimations + }; + modifiedState = eventReducer(startingState, excludedAction); + + expect(modifiedState.users[ownUserId]).toEqual({ + disconnected: false, + excluded: true, + id: ownUserId, + username: 'Jim' + }); + + // own user included himself again + const includedAction = { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'includedInEstimations', + roomId, + payload: {} + }, + type: EVENT_ACTION_TYPES.includedInEstimations + }; + modifiedState = eventReducer(startingState, includedAction); + + expect(modifiedState.users[ownUserId]).toEqual({ + disconnected: false, + excluded: false, + id: ownUserId, + username: 'Jim' + }); +}); diff --git a/client/test/unit/eventActionReducersScenarios/reduceMultipleEventActions.js b/client/test/unit/eventActionReducersScenarios/reduceMultipleEventActions.js new file mode 100644 index 00000000..54447a1f --- /dev/null +++ b/client/test/unit/eventActionReducersScenarios/reduceMultipleEventActions.js @@ -0,0 +1,7 @@ +import eventReducer from '../../../app/services/eventReducer'; + +export default function reduceMultipleEventActions(startingState, actions) { + let modfifiedState = startingState; + actions.forEach((e) => (modfifiedState = eventReducer(modfifiedState, e))); + return modfifiedState; +} diff --git a/client/test/unit/eventActionReducersScenarios/roomJoiningAndLeavingTest.js b/client/test/unit/eventActionReducersScenarios/roomJoiningAndLeavingTest.js new file mode 100644 index 00000000..b0330828 --- /dev/null +++ b/client/test/unit/eventActionReducersScenarios/roomJoiningAndLeavingTest.js @@ -0,0 +1,324 @@ +import {v4 as uuid} from 'uuid'; + +import initialState from '../../../app/store/initialState.js'; +import {EVENT_ACTION_TYPES} from '../../../app/actions/types'; +import clientSettingsStore from '../../../app/store/clientSettingsStore'; +import reduceMultipleEventActions from './reduceMultipleEventActions'; +import eventReducer from '../../../app/services/eventReducer'; + +test('You joining a new room', () => { + const startingState = initialState(); + let modifiedState; + + const cmdId = uuid(); + const userId = uuid(); + const roomId = uuid(); + const eventActions = [ + { + event: { + id: uuid(), + userId: userId, + correlationId: cmdId, + name: 'roomCreated', + roomId, + payload: { + username: 'Chrome', + email: 'test@super.com' + } + }, + type: EVENT_ACTION_TYPES.roomCreated + }, + { + event: { + id: uuid(), + userId: userId, + correlationId: cmdId, + name: 'joinedRoom', + roomId, + payload: { + users: { + [userId]: { + disconnected: false, + id: userId + } + }, + stories: {} + } + }, + type: EVENT_ACTION_TYPES.joinedRoom + }, + { + event: { + id: uuid(), + userId: userId, + correlationId: uuid(), + name: 'usernameSet', + roomId, + payload: { + username: 'Chrome' + } + }, + type: EVENT_ACTION_TYPES.usernameSet + }, + { + event: { + id: uuid(), + userId: userId, + correlationId: uuid(), + name: 'emailSet', + roomId, + payload: { + email: 'test@super.com' + } + }, + type: EVENT_ACTION_TYPES.emailSet + } + ]; + + modifiedState = reduceMultipleEventActions(startingState, eventActions); + + expect(modifiedState.roomId).toEqual(roomId); + expect(modifiedState.userId).toEqual(userId); + expect(modifiedState.users).toEqual({ + [userId]: { + disconnected: false, + id: userId, + username: 'Chrome', + email: 'test@super.com' + } + }); + + expect(clientSettingsStore.getPresetUserId()).toEqual(userId); + expect(clientSettingsStore.getPresetUsername()).toEqual('Chrome'); + expect(clientSettingsStore.getPresetEmail()).toEqual('test@super.com'); +}); + +test('You in a room, other user joins', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + } + }, + stories: {} + } + }; + clientSettingsStore.setPresetUserId(ownUserId); + + const eventActions = [ + { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'joinedRoom', + roomId, + payload: { + users: { + [ownUserId]: { + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + id: otherUserId, + disconnected: false, + excluded: false + } + }, + stories: {} + } + }, + type: EVENT_ACTION_TYPES.joinedRoom + }, + { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'usernameSet', + roomId, + payload: { + username: 'Other John' + } + }, + type: EVENT_ACTION_TYPES.usernameSet + } + ]; + + modifiedState = reduceMultipleEventActions(startingState, eventActions); + + expect(modifiedState.roomId).toEqual(roomId); + expect(modifiedState.userId).toEqual(ownUserId); + expect(modifiedState.users).toEqual({ + [otherUserId]: { + disconnected: false, + excluded: false, + id: otherUserId, + username: 'Other John' + }, + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + } + }); + + expect(clientSettingsStore.getPresetUserId()).toEqual(ownUserId); +}); + +test('Two users in a room, the other leaves', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + const storyId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + disconnected: false, + id: otherUserId, + username: 'The other One' + } + }, + stories: { + [storyId]: { + title: 'First Story', + description: 'With description', + id: storyId, + estimations: { + [otherUserId]: 8 + }, + createdAt: 1592120422988 + } + } + } + }; + clientSettingsStore.setPresetUserId(ownUserId); + + const leftRoomAction = { + event: { + id: uuid(), + userId: otherUserId, + correlationId: uuid(), + name: 'leftRoom', + roomId, + payload: {} + }, + type: EVENT_ACTION_TYPES.leftRoom + }; + + modifiedState = eventReducer(startingState, leftRoomAction); + + expect(modifiedState.users).toEqual({ + [ownUserId]: startingState.users[ownUserId] + }); + + expect(modifiedState.stories).toEqual({ + [storyId]: { + title: 'First Story', + description: 'With description', + id: storyId, + estimations: { + /* estimations from leaving user are removed */ + }, + createdAt: 1592120422988 + } + }); +}); + +test('Two users in a room, you leave', () => { + let modifiedState; + + const ownUserId = uuid(); + const otherUserId = uuid(); + const roomId = uuid(); + const storyId = uuid(); + + const startingState = { + ...initialState(), + ...{ + presetUsername: 'Jim', + presetEmail: null, + presetUserId: null, + userMenuShown: false, + roomId, + userId: ownUserId, + users: { + [ownUserId]: { + disconnected: false, + id: ownUserId, + username: 'Jim' + }, + [otherUserId]: { + disconnected: false, + id: otherUserId, + username: 'The other One' + } + }, + stories: { + [storyId]: { + title: 'First Story', + description: 'With description', + id: storyId, + estimations: { + [otherUserId]: 8 + }, + createdAt: 1592120422988 + } + } + } + }; + clientSettingsStore.setPresetUserId(ownUserId); + + const leftRoomAction = { + event: { + id: uuid(), + userId: ownUserId, + correlationId: uuid(), + name: 'leftRoom', + roomId, + payload: {} + }, + type: EVENT_ACTION_TYPES.leftRoom + }; + + modifiedState = eventReducer(startingState, leftRoomAction); + + // When own user leaves, tate is reset. except, actionLog has one new entry (log is added after event reduced). + + expect(modifiedState.actionLog.length).toBe(1); + // so we set it to an empty array manually here + modifiedState.actionLog = []; + expect(modifiedState).toEqual(initialState()); +}); diff --git a/client/test/unit/eventReducerTest.js b/client/test/unit/eventReducerTest.js index f540f851..0cfd01bc 100644 --- a/client/test/unit/eventReducerTest.js +++ b/client/test/unit/eventReducerTest.js @@ -2,349 +2,60 @@ import {v4 as uuid} from 'uuid'; import eventReducer from '../../app/services/eventReducer'; import {EVENT_ACTION_TYPES} from '../../app/actions/types'; -import clientSettingsStore from '../../app/store/clientSettingsStore'; +import initialState from '../../app/store/initialState.js'; -/** - * Tests the event reducing functions for various events. - * - * Ensures that events modify the client app state as expected. - * - * // TODO: test every incoming event? - **/ -test(EVENT_ACTION_TYPES.roomCreated, () => { - const startingState = { - roomId: 'myRoom', - actionLog: [] - }; - const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.roomCreated, - event: { - roomId: 'myRoom', - payload: {} - } - }); - expect(modifiedState.roomId).toEqual(startingState.roomId); -}); +test('happy case: reduces an eventAction and writes log item', () => { + const roomId = uuid(); -test(EVENT_ACTION_TYPES.storyAdded, () => { const startingState = { - roomId: 'someRoom', - stories: {}, - actionLog: [] + ...initialState(), + roomId, + users: {} }; + const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.storyAdded, event: { - roomId: 'someRoom', - payload: { - id: 'story334', - title: 'the new feature x', - description: 'will be great!', - estimations: {} - } - } - }); - expect(modifiedState.stories).toEqual({ - story334: { - description: 'will be great!', - estimations: {}, - id: 'story334', - title: 'the new feature x' - } + userId: uuid(), + roomId + }, + type: EVENT_ACTION_TYPES.connectionLost }); + + expect(modifiedState).toBeDefined(); + expect(modifiedState.actionLog.length).toBe(1); }); -test(EVENT_ACTION_TYPES.storyDeleted, () => { +test('ignores actions where roomId does not match', () => { + const roomId = uuid(); + const startingState = { - roomId: 'someRoom', - stories: { - story01: { - title: 'aaaa', - description: '', - id: 'e9eaee24-92c2-410a-a7b4-e9c796d68369', - estimations: {}, - createdAt: 1485359114569 - }, - story02: { - title: 'asdf', - description: 'af', - id: '3b8b38dd-1456-46d8-8174-2e981ad746f1', - estimations: {}, - createdAt: 1485425539399 - } - } + ...initialState(), + roomId }; const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.storyDeleted, event: { - roomId: 'someRoom', - payload: { - storyId: 'story01' - } - } - }); - - expect(modifiedState.stories).toEqual({ - story02: { - title: 'asdf', - description: 'af', - id: '3b8b38dd-1456-46d8-8174-2e981ad746f1', - estimations: {}, - createdAt: 1485425539399 + roomId: 'not-matching-room' } }); -}); - -describe(EVENT_ACTION_TYPES.joinedRoom, () => { - test('someone else joined', () => { - const startingState = { - userId: 'myUserId', - roomId: 'ourRoom', - users: { - myUserId: { - username: 'tester1' - } - } - }; - - const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.joinedRoom, - event: { - roomId: 'ourRoom', - userId: 'theNewUser', - payload: { - users: { - myUserId: { - id: 'myUserId' - }, - theNewUser: { - id: 'theNewUser' - } - } - } - } - }); - - expect(modifiedState.users).toEqual( - { - myUserId: { - username: 'tester1' - }, - theNewUser: { - id: 'theNewUser' - } - }, - 'The new user must be added to the room.users object. Nothing else must be changed.' - ); - }); - test('you joined', () => { - const startingState = { - roomId: 'myRoom', - waitingForJoin: true - }; - - const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.joinedRoom, - event: { - roomId: 'myRoom', - userId: 'myUserId', - payload: { - selectedStory: 'storyOne', - stories: { - storyOne: {} - }, - users: { - myUserId: { - id: 'myUserId' - } - } - } - } - }); - - expect(modifiedState.roomId).toEqual('myRoom'); - expect(modifiedState.userId).toEqual('myUserId'); - expect(modifiedState.selectedStory).toEqual('storyOne'); - expect(modifiedState.stories).toEqual({ - storyOne: {} - }); - expect(modifiedState.users).toEqual({ - myUserId: { - id: 'myUserId' - } - }); - }); + expect(modifiedState).toBe(startingState); }); -describe(EVENT_ACTION_TYPES.leftRoom, () => { - test('someone else left', () => { - const startingState = { - userId: 'myUser', - roomId: 'myRoom', - users: { - myUser: {username: 'My User'}, - someoneElse: {username: 'Someone Else'} // <<-- this user will leave - }, - stories: { - someStoryId: { - title: 'testTitle', - description: 'testDescr', - id: 'someStoryId', - estimations: { - someoneElse: 1 // <<-- the other user has an estimation - }, - createdAt: 1579874949137, - revealed: true - } - } - }; - - const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.leftRoom, - event: { - userId: 'someoneElse', - roomId: 'myRoom', - payload: {} - } - }); - - expect(modifiedState.users).toEqual({myUser: {username: 'My User'}}); - expect(modifiedState.stories.someStoryId.estimations).toEqual({}); // <<- estimation of leaving user must be removed - }); +test('ignores actions where no matching eventAction handler', () => { + const roomId = uuid(); - test('you left', () => { - // populate localstorage manually - const userId = 'userId_' + uuid(); - const username = 'userId_' + uuid(); - const email = 'userId_' + uuid(); - clientSettingsStore.setPresetUserId(userId); - clientSettingsStore.setPresetUsername(username); - clientSettingsStore.setPresetEmail(email); - - const startingState = { - userId: 'myUser', - roomId: 'myRoom', - users: { - myUser: {username: 'My User'}, - someoneElse: {username: 'Someone Else'} - }, - stories: {} - }; - - const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.leftRoom, - event: { - roomId: 'myRoom', - userId: 'myUser', - payload: {} - } - }); + const startingState = { + ...initialState(), + roomId + }; - // manually remove action log. will have additional items in it, which is expected - modifiedState.actionLog = []; - expect(modifiedState).toMatchObject({ - presetUsername: username, - presetEmail: email, - presetUserId: userId, - userMenuShown: false, - actionLog: [], - pendingCommands: {} - }); + const modifiedState = eventReducer(startingState, { + event: { + roomId + }, + type: 'does notmatch' }); -}); - -describe(EVENT_ACTION_TYPES.kicked, () => { - test('someone kicked a disconnected user from the room', () => { - const startingState = { - userId: 'myUser', - roomId: 'myRoom', - users: { - myUser: {}, - someoneElse: {} - }, - stories: {} - }; - const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.kicked, - event: { - roomId: 'myRoom', - payload: { - userId: 'someoneElse' - }, - userId: 'myUser' - } - }); - - expect(modifiedState.users).toEqual({myUser: {}}); - }); + expect(modifiedState).toBe(startingState); }); - -describe( - EVENT_ACTION_TYPES.includedInEstimations + ' ' + EVENT_ACTION_TYPES.excludedFromEstimations, - () => { - test('someone marked himself as included in estimations', () => { - const startingState = { - userId: 'myUser', - roomId: 'myRoom', - users: { - myUser: { - excluded: true - }, - someoneElse: {} - }, - stories: {} - }; - - const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.includedInEstimations, - event: { - roomId: 'myRoom', - payload: { - userId: 'myUser' - }, - userId: 'myUser' - } - }); - - expect(modifiedState.users).toEqual({ - myUser: { - excluded: false - }, - someoneElse: {} - }); - }); - - test('someone marked himself as excluded from estimations', () => { - const startingState = { - userId: 'myUser', - roomId: 'myRoom', - users: { - myUser: {}, - someoneElse: {} - }, - stories: {} - }; - - const modifiedState = eventReducer(startingState, { - type: EVENT_ACTION_TYPES.excludedFromEstimations, - event: { - roomId: 'myRoom', - payload: { - userId: 'myUser' - }, - userId: 'myUser' - } - }); - - expect(modifiedState.users).toEqual({ - myUser: { - excluded: true - }, - someoneElse: {} - }); - }); - } -);