diff options
author | Vincent Petry <vincent@nextcloud.com> | 2021-04-27 10:04:53 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-27 10:04:53 +0200 |
commit | 5616b52d141bca979fca2fe7c7dd07a087809189 (patch) | |
tree | 3d54a5829f2735d0563dad2491be55d3fed19914 /src/store | |
parent | 0db3046bcc05973110e8e4e625004b3392b9e2e7 (diff) | |
parent | fa52e80bb8e9904d37d54aa92d5163a5f179b4df (diff) |
Merge pull request #5512 from nextcloud/tests/noid/more-js-store-tests
More JS tests for stores
Diffstat (limited to 'src/store')
-rw-r--r-- | src/store/callViewStore.spec.js | 286 | ||||
-rw-r--r-- | src/store/conversationsStore.js | 2 | ||||
-rw-r--r-- | src/store/conversationsStore.spec.js | 479 | ||||
-rw-r--r-- | src/store/index.js | 44 | ||||
-rw-r--r-- | src/store/storeConfig.js | 63 |
5 files changed, 832 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..0f105c081 --- /dev/null +++ b/src/store/conversationsStore.spec.js @@ -0,0 +1,479 @@ +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) + }) + + |