summaryrefslogtreecommitdiffstats
path: root/src/store
diff options
context:
space:
mode:
authorVincent Petry <vincent@nextcloud.com>2021-04-23 14:57:39 +0200
committerVincent Petry <vincent@nextcloud.com>2021-04-23 18:16:10 +0200
commite6a5f390c7857d947678ec50cd0e87f113ddbf35 (patch)
tree3ea08cadaf514b73dba5d8a3e45e1f8a6e7aec47 /src/store
parentde6d64702abdb1e487855f26824c19f05c3bdc7b (diff)
More JS tests for stores
Added more JS tests for stores. Extracted global store config to a separate file to be able to include it in tests. Signed-off-by: Vincent Petry <vincent@nextcloud.com>
Diffstat (limited to 'src/store')
-rw-r--r--src/store/callViewStore.spec.js286
-rw-r--r--src/store/conversationsStore.js2
-rw-r--r--src/store/conversationsStore.spec.js478
-rw-r--r--src/store/index.js44
-rw-r--r--src/store/storeConfig.js63
5 files changed, 831 insertions, 42 deletions
diff --git a/src/store/callViewStore.spec.js b/src/store/callViewStore.spec.js
new file mode 100644
index 000000000..02f333447
--- /dev/null
+++ b/src/store/callViewStore.spec.js
@@ -0,0 +1,286 @@
+import { createLocalVue } from '@vue/test-utils'
+import storeConfig from './storeConfig'
+import Vuex from 'vuex'
+import { cloneDeep } from 'lodash'
+import {
+ CONVERSATION,
+} from '../constants'
+
+describe('callViewStore', () => {
+ let localVue = null
+ let store = null
+
+ beforeEach(() => {
+ localVue = createLocalVue()
+ localVue.use(Vuex)
+
+ const testStoreConfig = cloneDeep(storeConfig)
+
+ // remove participant store to avoid participant interaction
+ testStoreConfig.modules.participantsStore = {}
+
+ store = new Vuex.Store(testStoreConfig)
+
+ // to fully reset the state between tests, clear the storage
+ localStorage.clear()
+
+ // and reset all mocks
+ jest.clearAllMocks()
+ })
+
+ describe('raised hand', () => {
+ test('get whether participants raised hands with single session id', () => {
+ store.dispatch('setParticipantHandRaised', {
+ sessionId: 'session-id-1',
+ raisedHand: { state: true, timestamp: 1 },
+ })
+ store.dispatch('setParticipantHandRaised', {
+ sessionId: 'session-id-2',
+ raisedHand: { state: true, timestamp: 2 },
+ })
+
+ expect(store.getters.getParticipantRaisedHand(['session-id-1']))
+ .toStrictEqual({ state: true, timestamp: 1 })
+
+ expect(store.getters.getParticipantRaisedHand(['session-id-2']))
+ .toStrictEqual({ state: true, timestamp: 2 })
+
+ expect(store.getters.getParticipantRaisedHand(['session-id-another']))
+ .toStrictEqual({ state: false, timestamp: null })
+ })
+
+ test('get raised hands after lowering', () => {
+ store.dispatch('setParticipantHandRaised', {
+ sessionId: 'session-id-2',
+ raisedHand: { state: true, timestamp: 1 },
+ })
+ store.dispatch('setParticipantHandRaised', {
+ sessionId: 'session-id-2',
+ raisedHand: { state: false, timestamp: 3 },
+ })
+
+ expect(store.getters.getParticipantRaisedHand(['session-id-2']))
+ .toStrictEqual({ state: false, timestamp: null })
+ })
+
+ test('clears raised hands state after leaving call', () => {
+ store.dispatch('setParticipantHandRaised', {
+ sessionId: 'session-id-2',
+ raisedHand: { state: true, timestamp: 1 },
+ })
+ store.dispatch('leaveCall')
+
+ expect(store.getters.getParticipantRaisedHand(['session-id-2']))
+ .toStrictEqual({ state: false, timestamp: null })
+ })
+
+ test('get raised hands with multiple session ids only returns first found', () => {
+ store.dispatch('setParticipantHandRaised', {
+ sessionId: 'session-id-2',
+ raisedHand: { state: true, timestamp: 1 },
+ })
+ store.dispatch('setParticipantHandRaised', {
+ sessionId: 'session-id-3',
+ raisedHand: { state: true, timestamp: 1 },
+ })
+
+ expect(store.getters.getParticipantRaisedHand(['session-id-1', 'session-id-2', 'session-id-3']))
+ .toStrictEqual({ state: true, timestamp: 1 })
+ })
+ })
+
+ describe('call view mode and presentation', () => {
+ test('restores grid state when joining call (true)', () => {
+ localStorage.getItem.mockReturnValueOnce('true')
+
+ store.dispatch('joinCall', { token: 'XXTOKENXX' })
+
+ expect(localStorage.getItem).toHaveBeenCalled()
+ expect(localStorage.getItem.mock.calls[0][0]).toEqual(expect.stringMatching(/callprefs-XXTOKENXX-isgrid$/))
+
+ expect(store.getters.isGrid).toBe(true)
+ expect(store.getters.isStripeOpen).toBe(true)
+ })
+
+ test('restores grid state when joining call (false)', () => {
+ localStorage.getItem.mockReturnValueOnce('false')
+
+ store.dispatch('joinCall', { token: 'XXTOKENXX' })
+
+ expect(localStorage.getItem).toHaveBeenCalled()
+ expect(localStorage.getItem.mock.calls[0][0]).toEqual(expect.stringMatching(/callprefs-XXTOKENXX-isgrid$/))
+
+ expect(store.getters.isGrid).toBe(false)
+ expect(store.getters.isStripeOpen).toBe(true)
+ })
+
+ function testDefaultGridState(conversationType, state) {
+ localStorage.getItem.mockReturnValueOnce(null)
+
+ // using commit instead of dispatch because the action
+ // also processes participants
+ store.commit('addConversation', {
+ token: 'XXTOKENXX',
+ type: conversationType,
+ })
+ store.dispatch('joinCall', { token: 'XXTOKENXX' })
+
+ expect(localStorage.getItem).toHaveBeenCalled()
+ expect(localStorage.getItem.mock.calls[0][0]).toEqual(expect.stringMatching(/callprefs-XXTOKENXX-isgrid$/))
+
+ expect(store.getters.isGrid).toBe(state)
+ expect(store.getters.isStripeOpen).toBe(true)
+ }
+
+ test('sets default grid state when joining call in group conversation', () => {
+ testDefaultGridState(CONVERSATION.TYPE.GROUP, true)
+ })
+
+ test('sets default grid state when joining call in public conversation', () => {
+ testDefaultGridState(CONVERSATION.TYPE.PUBLIC, true)
+ })
+
+ test('sets default grid state when joining call in one to one conversation', () => {
+ testDefaultGridState(CONVERSATION.TYPE.ONE_TO_ONE, false)
+ })
+
+ test('switching call view mode saves in local storage', () => {
+ store.dispatch('updateToken', 'XXTOKENXX')
+
+ store.dispatch('setCallViewMode', {
+ isGrid: true,
+ isStripeOpen: false,
+ })
+
+ expect(store.getters.isGrid).toEqual(true)
+ expect(store.getters.isStripeOpen).toEqual(false)
+
+ expect(localStorage.setItem).toHaveBeenCalled()
+ expect(localStorage.setItem.mock.calls[0][0]).toEqual(expect.stringMatching(/callprefs-XXTOKENXX-isgrid$/))
+ expect(localStorage.setItem.mock.calls[0][1]).toBe(true)
+
+ store.dispatch('setCallViewMode', {
+ isGrid: false,
+ isStripeOpen: true,
+ })
+
+ expect(store.getters.isGrid).toEqual(false)
+ expect(store.getters.isStripeOpen).toEqual(true)
+
+ expect(localStorage.setItem).toHaveBeenCalled()
+ expect(localStorage.setItem.mock.calls[1][0]).toEqual(expect.stringMatching(/callprefs-XXTOKENXX-isgrid$/))
+ expect(localStorage.setItem.mock.calls[1][1]).toBe(false)
+ })
+
+ test('start presentation switches off grid view and restores when it ends', () => {
+ [{
+ isGrid: true,
+ isStripeOpen: true,
+ }, {
+ isGrid: false,
+ isStripeOpen: false,
+ }].forEach((testState) => {
+ store.dispatch('setCallViewMode', testState)
+
+ store.dispatch('startPresentation')
+
+ expect(store.getters.isGrid).toEqual(false)
+ expect(store.getters.isStripeOpen).toEqual(false)
+
+ store.dispatch('stopPresentation')
+
+ expect(store.getters.isGrid).toEqual(testState.isGrid)
+ expect(store.getters.isStripeOpen).toEqual(testState.isStripeOpen)
+ })
+ })
+
+ test('switching modes during presentation does not resets it after it ends', () => {
+ store.dispatch('setCallViewMode', {
+ isGrid: true,
+ isStripeOpen: true,
+ })
+
+ store.dispatch('startPresentation')
+
+ // switch during presentation
+ store.dispatch('setCallViewMode', {
+ isGrid: true,
+ isStripeOpen: true,
+ })
+
+ store.dispatch('stopPresentation')
+
+ // state kept, not restored
+ expect(store.getters.isGrid).toEqual(true)
+ expect(store.getters.isStripeOpen).toEqual(true)
+ })
+
+ test('starting presentation twice does not mess up remembered state', () => {
+ store.dispatch('setCallViewMode', {
+ isGrid: true,
+ isStripeOpen: true,
+ })
+
+ expect(store.getters.presentationStarted).toBe(false)
+
+ store.dispatch('startPresentation')
+
+ expect(store.getters.presentationStarted).toBe(true)
+
+ // switch during presentation
+ store.dispatch('setCallViewMode', {
+ isGrid: true,
+ isStripeOpen: true,
+ })
+
+ store.dispatch('startPresentation')
+
+ // state kept
+ expect(store.getters.isGrid).toEqual(true)
+ expect(store.getters.isStripeOpen).toEqual(true)
+
+ expect(store.getters.presentationStarted).toBe(true)
+
+ store.dispatch('stopPresentation')
+
+ expect(store.getters.presentationStarted).toBe(false)
+
+ // state kept, not restored
+ expect(store.getters.isGrid).toEqual(true)
+ expect(store.getters.isStripeOpen).toEqual(true)
+ })
+
+ test('stopping presentation twice does not mess up remembered state', () => {
+ store.dispatch('setCallViewMode', {
+ isGrid: true,
+ isStripeOpen: true,
+ })
+
+ expect(store.getters.presentationStarted).toBe(false)
+
+ store.dispatch('startPresentation')
+
+ expect(store.getters.presentationStarted).toBe(true)
+
+ store.dispatch('stopPresentation')
+
+ expect(store.getters.presentationStarted).toBe(false)
+
+ expect(store.getters.isGrid).toEqual(true)
+ expect(store.getters.isStripeOpen).toEqual(true)
+
+ store.dispatch('setCallViewMode', {
+ isGrid: false,
+ isStripeOpen: false,
+ })
+
+ store.dispatch('stopPresentation')
+
+ expect(store.getters.presentationStarted).toBe(false)
+
+ // state kept, not reset
+ expect(store.getters.isGrid).toEqual(false)
+ expect(store.getters.isStripeOpen).toEqual(false)
+ })
+ })
+})
diff --git a/src/store/conversationsStore.js b/src/store/conversationsStore.js
index 7a61733a7..ae044a4ca 100644
--- a/src/store/conversationsStore.js
+++ b/src/store/conversationsStore.js
@@ -174,6 +174,7 @@ const actions = {
* @param {object} context default store context;
*/
purgeConversationsStore(context) {
+ // TODO: also purge messages ??
context.commit('purgeConversationsStore')
},
@@ -200,6 +201,7 @@ const actions = {
return
}
+ // FIXME: logic is reversed
if (isFavorite) {
await removeFromFavorites(token)
} else {
diff --git a/src/store/conversationsStore.spec.js b/src/store/conversationsStore.spec.js
new file mode 100644
index 000000000..fc1e97159
--- /dev/null
+++ b/src/store/conversationsStore.spec.js
@@ -0,0 +1,478 @@
+import { createLocalVue } from '@vue/test-utils'
+import storeConfig from './storeConfig'
+import Vuex from 'vuex'
+import { cloneDeep } from 'lodash'
+import {
+ CONVERSATION,
+ WEBINAR,
+ PARTICIPANT,
+ ATTENDEE,
+} from '../constants'
+import {
+ makePublic,
+ makePrivate,
+ addToFavorites,
+ removeFromFavorites,
+ changeLobbyState,
+ changeReadOnlyState,
+ changeListable,
+ setConversationName,
+ setConversationDescription,
+ setSIPEnabled,
+ fetchConversation,
+} from '../services/conversationsService'
+
+jest.mock('../services/conversationsService', () => ({
+ makePublic: jest.fn(),
+ makePrivate: jest.fn(),
+ addToFavorites: jest.fn(),
+ removeFromFavorites: jest.fn(),
+ changeLobbyState: jest.fn(),
+ changeReadOnlyState: jest.fn(),
+ changeListable: jest.fn(),
+ setConversationName: jest.fn(),
+ setConversationDescription: jest.fn(),
+ setSIPEnabled: jest.fn(),
+ fetchConversation: jest.fn(),
+}))
+
+describe('conversationsStore', () => {
+ const testToken = 'XXTOKENXX'
+ let testStoreConfig = null
+ let testConversation
+ let localVue = null
+ let store = null
+ let addParticipantOnceAction = null
+
+ beforeEach(() => {
+ localVue = createLocalVue()
+ localVue.use(Vuex)
+
+ testConversation = {
+ token: testToken,
+ participantFlags: PARTICIPANT.CALL_FLAG.DISCONNECTED,
+ participantType: PARTICIPANT.TYPE.USER,
+ lastPing: 600,
+ sessionId: 'session-id-1',
+ attendeeId: 'attendee-id-1',
+ actorType: ATTENDEE.ACTOR_TYPE.USERS,
+ actorId: 'actor-id',
+ }
+
+ testStoreConfig = cloneDeep(storeConfig)
+
+ addParticipantOnceAction = jest.fn()
+ testStoreConfig.modules.participantsStore.actions.addParticipantOnce = addParticipantOnceAction
+ })
+
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
+
+ describe('conversation list', () => {
+ let deleteMessagesAction = null
+
+ beforeEach(() => {
+ deleteMessagesAction = jest.fn()
+ testStoreConfig.modules.messagesStore.actions.deleteMessages = deleteMessagesAction
+
+ store = new Vuex.Store(testStoreConfig)
+ })
+
+ test('adds conversation to the store, with current user as participant', () => {
+ store.dispatch('setCurrentUser', {
+ uid: 'current-user',
+ displayName: 'display-name',
+ })
+ store.dispatch('addConversation', testConversation)
+
+ expect(store.getters.conversation(testToken)).toBe(testConversation)
+ expect(store.getters.conversation('ANOTHER')).toBeUndefined()
+
+ expect(addParticipantOnceAction).toHaveBeenCalled()
+ expect(addParticipantOnceAction.mock.calls[0][1]).toStrictEqual({
+ token: testToken,
+ participant: {
+ actorId: 'actor-id',
+ actorType: 'users',
+ attendeeId: 'attendee-id-1',
+ displayName: 'display-name',
+ inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED,
+ lastPing: 600,
+ participantType: PARTICIPANT.TYPE.USER,
+ sessionIds: [
+ 'session-id-1',
+ ],
+ userId: 'current-user',
+ },
+ })
+ })
+
+ test('adds conversation to the store, with empty user id for guests', () => {
+ store.dispatch('setCurrentParticipant', {
+ actorId: 'guestActorId',
+ sessionId: 'XXSESSIONIDXX',
+ participantType: PARTICIPANT.TYPE.GUEST,
+ })
+
+ store.dispatch('addConversation', testConversation)
+
+ expect(store.getters.conversation(testToken)).toBe(testConversation)
+
+ expect(addParticipantOnceAction).toHaveBeenCalled()
+ expect(addParticipantOnceAction.mock.calls[0][1]).toStrictEqual({
+ token: testToken,
+ participant: {
+ // the one from the conversation is taken...
+ actorId: 'actor-id',
+ actorType: 'users',
+ attendeeId: 'attendee-id-1',
+ displayName: '',
+ inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED,
+ lastPing: 600,
+ participantType: PARTICIPANT.TYPE.USER,
+ sessionIds: [
+ 'session-id-1',
+ ],
+ userId: '',
+ },
+ })
+ })
+
+ test('deletes messages with conversation', () => {
+ store.dispatch('setCurrentUser', {
+ uid: 'current-user',
+ displayName: 'display-name',
+ })
+ store.dispatch('addConversation', testConversation)
+
+ store.dispatch('deleteConversation', testToken)
+ expect(deleteMessagesAction).toHaveBeenCalled()
+
+ expect(store.getters.conversation(testToken)).toBeUndefined()
+ })
+
+ test('purges all conversations', () => {
+ const testConversation2 = Object.assign({}, testConversation, {
+ token: 'XXANOTHERXX',
+ })
+ store.dispatch('addConversation', testConversation)
+ store.dispatch('addConversation', testConversation2)
+
+ store.dispatch('purgeConversationsStore')
+
+ expect(store.getters.conversation(testToken)).toBeUndefined()
+ expect(store.getters.conversation('XXANOTHERXX')).toBeUndefined()
+ })
+
+ test('fetches a single conversation', async() => {
+ fetchConversation.mockResolvedValue({
+ data: {
+ ocs: {
+ data: testConversation,
+ },
+ },
+ })
+
+ await store.dispatch('fetchConversation', { token: testToken })
+
+ expect(fetchConversation).toHaveBeenCalledWith(testToken)
+
+ const fetchedConversation = store.getters.conversation(testToken)
+ expect(fetchedConversation).toBe(testConversation)
+ })
+ })
+
+ describe('conversation settings', () => {
+ beforeEach(() => {
+ store = new Vuex.Store(testStoreConfig)
+ })
+
+ test('make public', async() => {
+ testConversation.type = CONVERSATION.TYPE.GROUP
+
+ store.dispatch('addConversation', testConversation)
+
+ makePublic.mockResolvedValue()
+
+ await store.dispatch('toggleGuests', {
+ token: testToken,
+ allowGuests: true,
+ })
+
+ expect(makePublic).toHaveBeenCalledWith(testToken)
+
+ const changedConversation = store.getters.conversation(testToken)
+ expect(changedConversation.type).toEqual(CONVERSATION.TYPE.PUBLIC)
+ })
+
+ test('make non-public', async() => {
+ testConversation.type = CONVERSATION.TYPE.PUBLIC
+
+ store.dispatch('addConversation', testConversation)
+
+ makePrivate.mockResolvedValue()
+
+ await store.dispatch('toggleGuests', {
+ token: testToken,
+ allowGuests: false,
+ })
+
+ expect(makePrivate).toHaveBeenCalledWith(testToken)
+
+ const changedConversation = store.getters.conversation(testToken)
+ expect(changedConversation.type).toEqual(CONVERSATION.TYPE.GROUP)
+ })
+
+ test('set favorite', async() => {
+ testConversation.isFavorite = false
+
+ store.dispatch('addConversation', testConversation)
+
+ addToFavorites.mockResolvedValue()
+
+ await store.dispatch('toggleFavorite', {
+ token: testToken,
+ isFavorite: false,
+ })
+
+ expect(addToFavorites).toHaveBeenCalledWith(testToken)
+
+ const changedConversation = store.getters.conversation(testToken)
+ expect(changedConversation.isFavorite).toBe(true)
+ })
+
+ test('unset favorite', async() => {
+ testConversation.isFavorite = true
+
+ store.dispatch('addConversation', testConversation)
+
+ removeFromFavorites.mockResolvedValue()
+
+ await store.dispatch('toggleFavorite', {
+ token: testToken,
+ isFavorite: true,
+ })
+
+ expect(removeFromFavorites).toHaveBeenCalledWith(testToken)
+
+ const changedConversation = store.getters.conversation(testToken)
+ expect(changedConversation.isFavorite).toBe(false)
+ })
+
+ test('enable lobby', async() => {
+ testConversation.lobbyState = WEBINAR.LOBBY.NONE
+
+ store.dispatch('addConversation', testConversation)
+
+ changeLobbyState.mockResolvedValue()
+
+ await store.dispatch('toggleLobby', {
+ token: testToken,
+ enableLobby: true,
+ })
+
+ expect(changeLobbyState).toHaveBeenCalledWith(testToken, WEBINAR.LOBBY.NON_MODERATORS)
+
+ const changedConversation = store.getters.conversation(testToken)
+ expect(changedConversation.lobbyState).toBe(WEBINAR.LOBBY.NON_MODERATORS)
+ })
+
+ test('disable lobby', async() => {
+ testConversation.lobbyState = WEBINAR.LOBBY.NON_MODERATORS
+
+ store.dispatch('addConversation', testConversation)
+
+ changeLobbyState.mockResolvedValue()
+
+ await store.dispatch('toggleLobby', {
+ token: testToken,
+ enableLobby: false,
+ })
+
+ expect(changeLobbyState).toHaveBeenCalledWith(testToken, WEBINAR.LOBBY.NONE)
+
+ const changedConversation = store.getters.conversation(testToken)
+ expect(changedConversation.lobbyState).toBe(WEBINAR.LOBBY.NONE)
+ })
+
+ test('set conversation name', async() => {
+ testConversation.displayName = 'initial name'
+
+ store.dispatch('addConversation', testConversation)
+