diff --git a/src/components/CallView/shared/LocalVideo.vue b/src/components/CallView/shared/LocalVideo.vue index 40120e4d1bb..f88b4e8af1d 100644 --- a/src/components/CallView/shared/LocalVideo.vue +++ b/src/components/CallView/shared/LocalVideo.vue @@ -82,6 +82,7 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import VideoBackground from './VideoBackground.vue' import video from '../../../mixins/video.js' +import { useGuestNameStore } from '../../../stores/guestName.js' import { ConnectionState } from '../../../utils/webrtc/models/CallParticipantModel.js' export default { @@ -135,6 +136,11 @@ export default { }, }, + setup() { + const guestNameStore = useGuestNameStore() + return { guestNameStore } + }, + data() { return { notificationHandle: null, @@ -195,7 +201,7 @@ export default { }, guestName() { - return this.$store.getters.getGuestName( + return this.guestNameStore.getGuestName( this.$store.getters.getToken(), this.sessionHash, ) diff --git a/src/components/CallView/shared/ReactionToaster.vue b/src/components/CallView/shared/ReactionToaster.vue index 29e90917355..9050607b8e4 100644 --- a/src/components/CallView/shared/ReactionToaster.vue +++ b/src/components/CallView/shared/ReactionToaster.vue @@ -48,6 +48,8 @@ import usernameToColor from '@nextcloud/vue/dist/Functions/usernameToColor.js' import TransitionWrapper from '../../TransitionWrapper.vue' +import { useGuestNameStore } from '../../../stores/guestName.js' + export default { name: 'ReactionToaster', @@ -79,6 +81,11 @@ export default { }, }, + setup() { + const guestNameStore = useGuestNameStore() + return { guestNameStore } + }, + data() { return { registeredModels: {}, @@ -147,7 +154,7 @@ export default { id: model.attributes.peerId, reaction, name: isLocalModel - ? this.$store.getters.getDisplayName() || this.$store.getters.getGuestName() + ? this.$store.getters.getDisplayName() || t('spreed', 'Guest') : this.getParticipantName(model), seed: Math.random(), }) @@ -176,7 +183,7 @@ export default { return participant.displayName } - return this.$store.getters.getGuestName(this.token, Hex.stringify(SHA1(peerId))) + return this.guestNameStore.getGuestName(this.token, Hex.stringify(SHA1(peerId))) }, styled(name, seed) { diff --git a/src/components/CallView/shared/Screen.vue b/src/components/CallView/shared/Screen.vue index ae86d8240ba..4b8bf4ea94f 100644 --- a/src/components/CallView/shared/Screen.vue +++ b/src/components/CallView/shared/Screen.vue @@ -41,6 +41,8 @@ import SHA1 from 'crypto-js/sha1.js' import VideoBottomBar from './VideoBottomBar.vue' +import { useGuestNameStore } from '../../../stores/guestName.js' + export default { name: 'Screen', @@ -72,6 +74,11 @@ export default { }, }, + setup() { + const guestNameStore = useGuestNameStore() + return { guestNameStore } + }, + computed: { model() { if (this.callParticipantModel) { @@ -103,7 +110,7 @@ export default { // for registered users, so do not fall back to the guest name in // the store either until the connection was made. if (!this.callParticipantModel.attributes.userId && !remoteParticipantName && remoteParticipantName !== undefined) { - remoteParticipantName = this.$store.getters.getGuestName( + remoteParticipantName = this.guestNameStore.getGuestName( this.$store.getters.getToken(), this.remoteSessionHash, ) diff --git a/src/components/CallView/shared/VideoVue.spec.js b/src/components/CallView/shared/VideoVue.spec.js index 0f5edb791e3..a7ab4852fe0 100644 --- a/src/components/CallView/shared/VideoVue.spec.js +++ b/src/components/CallView/shared/VideoVue.spec.js @@ -21,6 +21,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils' import { cloneDeep } from 'lodash' +import { createPinia, setActivePinia } from 'pinia' import Vuex from 'vuex' import VideoVue from './VideoVue.vue' @@ -87,6 +88,7 @@ describe('VideoVue.vue', () => { beforeEach(() => { localVue = createLocalVue() localVue.use(Vuex) + setActivePinia(createPinia()) testStoreConfig = cloneDeep(storeConfig) // eslint-disable-next-line import/no-named-as-default-member diff --git a/src/components/CallView/shared/VideoVue.vue b/src/components/CallView/shared/VideoVue.vue index 58e59db2cdc..2342428074d 100644 --- a/src/components/CallView/shared/VideoVue.vue +++ b/src/components/CallView/shared/VideoVue.vue @@ -111,6 +111,7 @@ import VideoBottomBar from './VideoBottomBar.vue' import { ATTENDEE } from '../../../constants.js' import video from '../../../mixins/video.js' import { EventBus } from '../../../services/EventBus.js' +import { useGuestNameStore } from '../../../stores/guestName.js' import { ConnectionState } from '../../../utils/webrtc/models/CallParticipantModel.js' export default { @@ -187,6 +188,11 @@ export default { }, }, + setup() { + const guestNameStore = useGuestNameStore() + return { guestNameStore } + }, + data() { return { videoAspectRatio: null, @@ -393,7 +399,7 @@ export default { // for registered users, so do not fall back to the guest name in // the store either until the connection was made. if (!this.model.attributes.userId && !participantName && participantName !== undefined) { - participantName = this.$store.getters.getGuestName( + participantName = this.guestNameStore.getGuestName( this.$store.getters.getToken(), this.sessionHash, ) diff --git a/src/components/MediaSettings/MediaSettings.vue b/src/components/MediaSettings/MediaSettings.vue index 423cb6d122d..157b2ab720d 100644 --- a/src/components/MediaSettings/MediaSettings.vue +++ b/src/components/MediaSettings/MediaSettings.vue @@ -208,6 +208,7 @@ import { CALL, VIRTUAL_BACKGROUND } from '../../constants.js' import { devices } from '../../mixins/devices.js' import isInLobby from '../../mixins/isInLobby.js' import BrowserStorage from '../../services/BrowserStorage.js' +import { useGuestNameStore } from '../../stores/guestName.js' import { localMediaModel } from '../../utils/webrtc/index.js' export default { @@ -242,7 +243,8 @@ export default { setup() { const isInCall = useIsInCall() - return { isInCall } + const guestNameStore = useGuestNameStore() + return { isInCall, guestNameStore } }, data() { @@ -271,7 +273,7 @@ export default { }, guestName() { - return this.$store.getters.getGuestName( + return this.guestNameStore.getGuestName( this.$store.getters.getToken(), this.$store.getters.getActorId(), ) diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.spec.js b/src/components/MessagesList/MessagesGroup/Message/Message.spec.js index 296f709eb7f..a96c6a5515a 100644 --- a/src/components/MessagesList/MessagesGroup/Message/Message.spec.js +++ b/src/components/MessagesList/MessagesGroup/Message/Message.spec.js @@ -1,6 +1,7 @@ import { createLocalVue, mount, shallowMount } from '@vue/test-utils' import flushPromises from 'flush-promises' // TODO fix after migration to @vue/test-utils v2.0.0 import { cloneDeep } from 'lodash' +import { createPinia, setActivePinia } from 'pinia' import vOutsideEvents from 'vue-outside-events' import Vuex, { Store } from 'vuex' @@ -57,6 +58,7 @@ describe('Message.vue', () => { localVue = createLocalVue() localVue.use(vOutsideEvents) localVue.use(Vuex) + setActivePinia(createPinia()) conversationProps = { token: TOKEN, diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.vue b/src/components/MessagesList/MessagesGroup/Message/Message.vue index 0c841fb10c0..9db4d8505cf 100644 --- a/src/components/MessagesList/MessagesGroup/Message/Message.vue +++ b/src/components/MessagesList/MessagesGroup/Message/Message.vue @@ -263,6 +263,7 @@ import { useIsInCall } from '../../../../composables/useIsInCall.js' import { ATTENDEE, CONVERSATION, PARTICIPANT } from '../../../../constants.js' import participant from '../../../../mixins/participant.js' import { EventBus } from '../../../../services/EventBus.js' +import { useGuestNameStore } from '../../../../stores/guestName.js' const isTranslationAvailable = getCapabilities()?.spreed?.config?.chat?.translations?.length > 0 @@ -437,7 +438,8 @@ export default { setup() { const isInCall = useIsInCall() - return { isInCall, isTranslationAvailable } + const guestNameStore = useGuestNameStore() + return { isInCall, isTranslationAvailable, guestNameStore } }, expose: ['highlightMessage'], @@ -856,7 +858,7 @@ export default { const displayName = reaction.actorDisplayName.trim() if (reaction.actorType === ATTENDEE.ACTOR_TYPE.GUESTS) { - return this.$store.getters.getGuestNameWithGuestSuffix(this.token, reaction.actorId) + return this.guestNameStore.getGuestNameWithGuestSuffix(this.token, reaction.actorId) } if (displayName === '') { diff --git a/src/components/MessagesList/MessagesGroup/MessagesGroup.spec.js b/src/components/MessagesList/MessagesGroup/MessagesGroup.spec.js index 04e1aa44e23..bc00b2f4cf0 100644 --- a/src/components/MessagesList/MessagesGroup/MessagesGroup.spec.js +++ b/src/components/MessagesList/MessagesGroup/MessagesGroup.spec.js @@ -1,5 +1,6 @@ import { createLocalVue, shallowMount } from '@vue/test-utils' import { cloneDeep } from 'lodash' +import { createPinia, setActivePinia } from 'pinia' import Vuex from 'vuex' import MessagesGroup from './MessagesGroup.vue' @@ -7,21 +8,23 @@ import MessagesSystemGroup from './MessagesSystemGroup.vue' import { ATTENDEE } from '../../../constants.js' import storeConfig from '../../../store/storeConfig.js' +import { useGuestNameStore } from '../../../stores/guestName.js' describe('MessagesGroup.vue', () => { const TOKEN = 'XXTOKENXX' let store let localVue let testStoreConfig - let getGuestNameMock + let guestNameStore beforeEach(() => { localVue = createLocalVue() localVue.use(Vuex) + setActivePinia(createPinia()) + + guestNameStore = useGuestNameStore() testStoreConfig = cloneDeep(storeConfig) - getGuestNameMock = jest.fn() - testStoreConfig.modules.guestNameStore.getters.getGuestName = () => getGuestNameMock // eslint-disable-next-line import/no-named-as-default-member store = new Vuex.Store(testStoreConfig) }) @@ -179,7 +182,13 @@ describe('MessagesGroup.vue', () => { }) test('renders guest display name', () => { - getGuestNameMock.mockReturnValue('guest-one-display-name') + // Arrange + guestNameStore.addGuestName({ + token: TOKEN, + actorId: 'actor-1', + actorDisplayName: 'guest-one-display-name', + }, { noUpdate: false }) + const wrapper = shallowMount(MessagesGroup, { localVue, store, @@ -232,8 +241,6 @@ describe('MessagesGroup.vue', () => { message = messagesEl.at(1) expect(message.attributes('id')).toBe('110') expect(message.attributes('actorid')).toBe('actor-1') - - expect(getGuestNameMock).toHaveBeenCalledWith(TOKEN, 'actor-1') }) test('renders deleted guest display name', () => { diff --git a/src/components/MessagesList/MessagesGroup/MessagesGroup.vue b/src/components/MessagesList/MessagesGroup/MessagesGroup.vue index b12f1cc2b3c..0a1a71ba8b0 100644 --- a/src/components/MessagesList/MessagesGroup/MessagesGroup.vue +++ b/src/components/MessagesList/MessagesGroup/MessagesGroup.vue @@ -48,6 +48,7 @@ import AuthorAvatar from './AuthorAvatar.vue' import Message from './Message/Message.vue' import { ATTENDEE } from '../../../constants.js' +import { useGuestNameStore } from '../../../stores/guestName.js' export default { name: 'MessagesGroup', @@ -85,6 +86,11 @@ export default { }, }, + setup() { + const guestNameStore = useGuestNameStore() + return { guestNameStore } + }, + expose: ['highlightMessage'], computed: { @@ -113,7 +119,7 @@ export default { const displayName = this.messages[0].actorDisplayName.trim() if (this.actorType === ATTENDEE.ACTOR_TYPE.GUESTS) { - return this.$store.getters.getGuestName(this.token, this.actorId) + return this.guestNameStore.getGuestName(this.token, this.actorId) } if (displayName === '') { diff --git a/src/components/NewMessage/NewMessageTypingIndicator.vue b/src/components/NewMessage/NewMessageTypingIndicator.vue index 732ebf1806e..61b4813ec03 100644 --- a/src/components/NewMessage/NewMessageTypingIndicator.vue +++ b/src/components/NewMessage/NewMessageTypingIndicator.vue @@ -45,6 +45,8 @@ import escapeHtml from 'escape-html' import AvatarWrapper from '../AvatarWrapper/AvatarWrapper.vue' +import { useGuestNameStore } from '../../stores/guestName.js' + export default { name: 'NewMessageTypingIndicator', components: { AvatarWrapper }, @@ -59,6 +61,11 @@ export default { }, }, + setup() { + const guestNameStore = useGuestNameStore() + return { guestNameStore } + }, + computed: { isGuest() { return this.$store.getters.getActorType() === 'guests' @@ -131,7 +138,7 @@ export default { return participant.displayName } - return this.$store.getters.getGuestName(this.token, participant.actorId) + return this.guestNameStore.getGuestName(this.token, participant.actorId) }, }, } diff --git a/src/components/SetGuestUsername.vue b/src/components/SetGuestUsername.vue index 4da02fe5110..d4e780d5555 100644 --- a/src/components/SetGuestUsername.vue +++ b/src/components/SetGuestUsername.vue @@ -55,6 +55,7 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' import { setGuestUserName } from '../services/participantsService.js' +import { useGuestNameStore } from '../stores/guestName.js' export default { name: 'SetGuestUsername', @@ -65,6 +66,11 @@ export default { Pencil, }, + setup() { + const guestNameStore = useGuestNameStore() + return { guestNameStore } + }, + data() { return { guestUserName: '', @@ -121,11 +127,11 @@ export default { const previousName = this.$store.getters.getDisplayName() try { this.$store.dispatch('setDisplayName', this.guestUserName) - this.$store.dispatch('forceGuestName', { + this.guestNameStore.addGuestName({ token: this.token, actorId: this.$store.getters.getActorId(), actorDisplayName: this.guestUserName, - }) + }, { noUpdate: false }) await setGuestUserName(this.token, this.guestUserName) if (this.guestUserName !== '') { localStorage.setItem('nick', this.guestUserName) @@ -135,11 +141,11 @@ export default { this.isEditingUsername = false } catch (exception) { this.$store.dispatch('setDisplayName', previousName) - this.$store.dispatch('forceGuestName', { + this.guestNameStore.addGuestName({ token: this.token, actorId: this.$store.getters.getActorId(), actorDisplayName: previousName, - }) + }, { noUpdate: false }) console.debug(exception) } }, diff --git a/src/mixins/getParticipants.js b/src/mixins/getParticipants.js index e39c7f8aed2..ea5df10154c 100644 --- a/src/mixins/getParticipants.js +++ b/src/mixins/getParticipants.js @@ -31,6 +31,7 @@ import { emit } from '@nextcloud/event-bus' import { PARTICIPANT } from '../constants.js' import { EventBus } from '../services/EventBus.js' import { fetchParticipants } from '../services/participantsService.js' +import { useGuestNameStore } from '../stores/guestName.js' import CancelableRequest from '../utils/cancelableRequest.js' import isInLobby from './isInLobby.js' @@ -38,6 +39,11 @@ const getParticipants = { mixins: [isInLobby], + setup() { + const guestNameStore = useGuestNameStore() + return { guestNameStore } + }, + data() { return { participantsInitialised: false, @@ -137,11 +143,11 @@ const getParticipants = { }) if (participant.participantType === PARTICIPANT.TYPE.GUEST || participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR) { - this.$store.dispatch('forceGuestName', { + this.guestNameStore.addGuestName({ token, actorId: Hex.stringify(SHA1(participant.sessionIds[0])), actorDisplayName: participant.displayName, - }) + }, { noUpdate: false }) } else if (participant.actorType === 'users' && hasUserStatuses) { emit('user_status:status.updated', { status: participant.status, diff --git a/src/store/guestNameStore.js b/src/store/guestNameStore.js deleted file mode 100644 index 48da79d9d09..00000000000 --- a/src/store/guestNameStore.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * @copyright Copyright (c) 2019 Joas Schilling - * - * @author Joas Schilling - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ -import Vue from 'vue' - -const state = { - guestNames: { - }, -} - -const getters = { - /** - * Gets the participants array - * - * @param {object} state the state object. - * @return {Array} the participants array (if there are participants in the store) - */ - getGuestName: (state) => (token, actorId) => { - if (state.guestNames[token] && state.guestNames[token][actorId]) { - return state.guestNames[token][actorId] - } - return t('spreed', 'Guest') - }, - - getGuestNameWithGuestSuffix: (state, getters) => (token, actorId) => { - const displayName = getters.getGuestName(token, actorId) - if (displayName === t('spreed', 'Guest')) { - return displayName - } - return t('spreed', '{guest} (guest)', { guest: displayName }) - }, -} - -const mutations = { - /** - * Adds a guest name to the store - * - * @param {object} state current store state - * @param {object} data the wrapping object; - * @param {boolean} data.noUpdate Only set the guest name if it was not set before - * @param {string} data.token the token of the conversation - * @param {string} data.actorId the guest - * @param {string} data.actorDisplayName the display name to set - */ - addGuestName(state, { noUpdate, token, actorId, actorDisplayName }) { - if (!state.guestNames[token]) { - Vue.set(state.guestNames, token, []) - } - if (!state.guestNames[token][actorId]) { - Vue.set(state.guestNames[token], actorId, t('spreed', 'Guest')) - } else if (noUpdate) { - return - } - state.guestNames[token][actorId] = actorDisplayName - }, -} - -const actions = { - - /** - * Add guest name of a chat message to the store - * - * @param {object} context default store context - * @param {object} data the wrapping object; - * @param {string} data.token the token of the conversation - * @param {string} data.actorId the guest - * @param {string} data.actorDisplayName the display name to set - */ - setGuestNameIfEmpty(context, { token, actorId, actorDisplayName }) { - context.commit('addGuestName', { noUpdate: true, token, actorId, actorDisplayName }) - }, - - /** - * Add guest name of a chat message to the store - * - * @param {object} context default store context - * @param {object} data the wrapping object; - * @param {string} data.token the token of the conversation - * @param {string} data.actorId the guest - * @param {string} data.actorDisplayName the display name to set - */ - forceGuestName(context, { token, actorId, actorDisplayName }) { - context.commit('addGuestName', { noUpdate: false, token, actorId, actorDisplayName }) - }, -} - -export default { state, mutations, getters, actions } diff --git a/src/store/guestNameStore.spec.js b/src/store/guestNameStore.spec.js deleted file mode 100644 index bc28e3018db..00000000000 --- a/src/store/guestNameStore.spec.js +++ /dev/null @@ -1,97 +0,0 @@ -import { createLocalVue } from '@vue/test-utils' -import { cloneDeep } from 'lodash' -import Vuex from 'vuex' - -import guestNameStore from './guestNameStore.js' - -describe('guestNameStore', () => { - let localVue = null - let store = null - - beforeEach(() => { - localVue = createLocalVue() - localVue.use(Vuex) - - // eslint-disable-next-line import/no-named-as-default-member - store = new Vuex.Store(cloneDeep(guestNameStore)) - }) - - afterEach(() => { - jest.clearAllMocks() - }) - - test('sets guest name if empty', () => { - store.dispatch('setGuestNameIfEmpty', { - token: 'token-1', - actorId: 'actor-id1', - actorDisplayName: 'actor-display-name-one', - }) - store.dispatch('setGuestNameIfEmpty', { - token: 'token-1', - actorId: 'actor-id2', - actorDisplayName: 'actor-display-name-two', - }) - - expect(store.getters.getGuestName('token-1', 'actor-id1')).toBe('actor-display-name-one') - expect(store.getters.getGuestName('token-1', 'actor-id2')).toBe('actor-display-name-two') - expect(store.getters.getGuestName('token-2', 'actor-id1')).toBe('Guest') - expect(store.getters.getGuestName('token-1', 'actor-id3')).toBe('Guest') - }) - - test('does not override guest name if not empty', () => { - store.dispatch('setGuestNameIfEmpty', { - token: 'token-1', - actorId: 'actor-id1', - actorDisplayName: 'actor-display-name-one', - }) - - // attempt overwriting - store.dispatch('setGuestNameIfEmpty', { - token: 'token-1', - actorId: 'actor-id1', - actorDisplayName: 'actor-display-name-another', - }) - - expect(store.getters.getGuestName('token-1', 'actor-id1')).toBe('actor-display-name-one') - }) - - test('force override guest name', () => { - store.dispatch('setGuestNameIfEmpty', { - token: 'token-1', - actorId: 'actor-id1', - actorDisplayName: 'actor-display-name-one', - }) - - // attempt overwriting - store.dispatch('forceGuestName', { - token: 'token-1', - actorId: 'actor-id1', - actorDisplayName: 'actor-display-name-another', - }) - - expect(store.getters.getGuestName('token-1', 'actor-id1')).toBe('actor-display-name-another') - }) - - test('clear guest name', () => { - store.dispatch('setGuestNameIfEmpty', { - token: 'token-1', - actorId: 'actor-id1', - actorDisplayName: 'actor-display-name-one', - }) - - store.dispatch('forceGuestName', { - token: 'token-1', - actorId: 'actor-id1', - actorDisplayName: '', - }) - - expect(store.getters.getGuestName('token-1', 'actor-id1')).toBe('Guest') - }) - - test('translates default guest name', () => { - expect(store.getters.getGuestName('token-1', 'actor-id0')).toBe('Guest') - - expect(global.t).toHaveBeenCalledWith('spreed', 'Guest') - }) - -}) diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js index aac2cd22aa7..66022eb10ea 100644 --- a/src/store/messagesStore.js +++ b/src/store/messagesStore.js @@ -40,6 +40,7 @@ import { addReactionToMessage, removeReactionFromMessage, } from '../services/messagesService.js' +import { useGuestNameStore } from '../stores/guestName.js' import CancelableRequest from '../utils/cancelableRequest.js' /** @@ -819,7 +820,8 @@ const actions = { response.data.ocs.data.forEach(message => { if (message.actorType === ATTENDEE.ACTOR_TYPE.GUESTS) { // update guest display names cache - context.dispatch('setGuestNameIfEmpty', message) + const guestNameStore = useGuestNameStore() + guestNameStore.addGuestName(message, { noUpdate: true }) } context.dispatch('processMessage', message) newestKnownMessageId = Math.max(newestKnownMessageId, message.id) @@ -909,7 +911,8 @@ const actions = { response.data.ocs.data.forEach(message => { if (message.actorType === ATTENDEE.ACTOR_TYPE.GUESTS) { // update guest display names cache - context.dispatch('setGuestNameIfEmpty', message) + const guestNameStore = useGuestNameStore() + guestNameStore.addGuestName(message, { noUpdate: true }) } context.dispatch('processMessage', message) newestKnownMessageId = Math.max(newestKnownMessageId, message.id) @@ -1034,7 +1037,8 @@ const actions = { // update guest display names cache, // force in case the display name has changed since // the last fetch - context.dispatch('forceGuestName', message) + const guestNameStore = useGuestNameStore() + guestNameStore.addGuestName(message, { noUpdate: false }) } context.dispatch('processMessage', message) if (!lastMessage || message.id > lastMessage.id) { diff --git a/src/store/messagesStore.spec.js b/src/store/messagesStore.spec.js index 1195aa05b3e..1c0514dd21d 100644 --- a/src/store/messagesStore.spec.js +++ b/src/store/messagesStore.spec.js @@ -2,6 +2,7 @@ import { createLocalVue } from '@vue/test-utils' import flushPromises from 'flush-promises' import mockConsole from 'jest-mock-console' import { cloneDeep } from 'lodash' +import { createPinia, setActivePinia } from 'pinia' import Vuex from 'vuex' import { showError } from '@nextcloud/dialogs' @@ -17,6 +18,7 @@ import { lookForNewMessages, postNewMessage, } from '../services/messagesService.js' +import { useGuestNameStore } from '../stores/guestName.js' import { generateOCSErrorResponse, generateOCSResponse } from '../test-helpers.js' import CancelableRequest from '../utils/cancelableRequest.js' import messagesStore from './messagesStore.js' @@ -46,6 +48,7 @@ describe('messagesStore', () => { beforeEach(() => { localVue = createLocalVue() localVue.use(Vuex) + setActivePinia(createPinia()) testStoreConfig = cloneDeep(messagesStore) @@ -764,16 +767,17 @@ describe('messagesStore', () => { describe('fetchMessages', () => { let updateLastCommonReadMessageAction - let setGuestNameIfEmptyAction + let addGuestNameAction let cancelFunctionMock beforeEach(() => { testStoreConfig = cloneDeep(messagesStore) + const guestNameStore = useGuestNameStore() updateLastCommonReadMessageAction = jest.fn() - setGuestNameIfEmptyAction = jest.fn() + addGuestNameAction = jest.fn() testStoreConfig.actions.updateLastCommonReadMessage = updateLastCommonReadMessageAction - testStoreConfig.actions.setGuestNameIfEmpty = setGuestNameIfEmptyAction + guestNameStore.addGuestName = addGuestNameAction cancelFunctionMock = jest.fn() CancelableRequest.mockImplementation((request) => { @@ -827,7 +831,7 @@ describe('messagesStore', () => { expect(updateLastCommonReadMessageAction) .toHaveBeenCalledWith(expect.anything(), { token: TOKEN, lastCommonReadMessage: 123 }) - expect(setGuestNameIfEmptyAction).toHaveBeenCalledWith(expect.anything(), messages[1]) + expect(addGuestNameAction).toHaveBeenCalledWith(messages[1], { noUpdate: true }) expect(store.getters.messagesList(TOKEN)).toStrictEqual(messages) expect(store.getters.getFirstKnownMessageId(TOKEN)).toBe(100) @@ -875,7 +879,7 @@ describe('messagesStore', () => { expect(updateLastCommonReadMessageAction) .toHaveBeenCalledWith(expect.anything(), { token: TOKEN, lastCommonReadMessage: 123 }) - expect(setGuestNameIfEmptyAction).toHaveBeenCalledWith(expect.anything(), messages[1]) + expect(addGuestNameAction).toHaveBeenCalledWith(messages[1], { noUpdate: true }) expect(store.getters.messagesList(TOKEN)).toStrictEqual(messages) expect(store.getters.getFirstKnownMessageId(TOKEN)).toBe(100) @@ -918,16 +922,17 @@ describe('messagesStore', () => { describe('get message context', () => { let updateLastCommonReadMessageAction - let setGuestNameIfEmptyAction + let addGuestNameAction let cancelFunctionMock beforeEach(() => { testStoreConfig = cloneDeep(messagesStore) + const guestNameStore = useGuestNameStore() updateLastCommonReadMessageAction = jest.fn() - setGuestNameIfEmptyAction = jest.fn() + addGuestNameAction = jest.fn() testStoreConfig.actions.updateLastCommonReadMessage = updateLastCommonReadMessageAction - testStoreConfig.actions.setGuestNameIfEmpty = setGuestNameIfEmptyAction + guestNameStore.addGuestName = addGuestNameAction cancelFunctionMock = jest.fn() CancelableRequest.mockImplementation((request) => { @@ -979,7 +984,7 @@ describe('messagesStore', () => { expect(updateLastCommonReadMessageAction) .toHaveBeenCalledWith(expect.anything(), { token: TOKEN, lastCommonReadMessage: 1 }) - expect(setGuestNameIfEmptyAction).toHaveBeenCalledWith(expect.anything(), messages[1]) + expect(addGuestNameAction).toHaveBeenCalledWith(messages[1], { noUpdate: true }) expect(store.getters.messagesList(TOKEN)).toStrictEqual(messages) expect(store.getters.getFirstKnownMessageId(TOKEN)).toBe(1) @@ -1050,7 +1055,7 @@ describe('messagesStore', () => { expect(updateLastCommonReadMessageAction).toHaveBeenNthCalledWith(1, expect.anything(), { token: TOKEN, lastCommonReadMessage: 2 }) expect(updateLastCommonReadMessageAction).toHaveBeenNthCalledWith(2, expect.anything(), { token: TOKEN, lastCommonReadMessage: 2 }) - expect(setGuestNameIfEmptyAction).toHaveBeenCalledWith(expect.anything(), messagesContext[1]) + expect(addGuestNameAction).toHaveBeenCalledWith(messagesContext[1], { noUpdate: true }) expect(store.getters.messagesList(TOKEN)).toStrictEqual([...messagesFetch, ...messagesContext]) expect(store.getters.getFirstKnownMessageId(TOKEN)).toBe(1) @@ -1062,7 +1067,7 @@ describe('messagesStore', () => { let updateLastCommonReadMessageAction let updateConversationLastMessageAction let updateUnreadMessagesMutation - let forceGuestNameAction + let addGuestNameAction let cancelFunctionMocks let conversationMock let getActorIdMock @@ -1071,6 +1076,7 @@ describe('messagesStore', () => { beforeEach(() => { testStoreConfig = cloneDeep(messagesStore) + const guestNameStore = useGuestNameStore() conversationMock = jest.fn() getActorIdMock = jest.fn() @@ -1084,10 +1090,10 @@ describe('messagesStore', () => { updateConversationLastMessageAction = jest.fn() updateLastCommonReadMessageAction = jest.fn() updateUnreadMessagesMutation = jest.fn() - forceGuestNameAction = jest.fn() + addGuestNameAction = jest.fn() testStoreConfig.actions.updateConversationLastMessage = updateConversationLastMessageAction testStoreConfig.actions.updateLastCommonReadMessage = updateLastCommonReadMessageAction - testStoreConfig.actions.forceGuestName = forceGuestNameAction + guestNameStore.addGuestName = addGuestNameAction testStoreConfig.mutations.updateUnreadMessages = updateUnreadMessagesMutation cancelFunctionMocks = [] @@ -1149,7 +1155,7 @@ describe('messagesStore', () => { expect(updateLastCommonReadMessageAction) .toHaveBeenCalledWith(expect.anything(), { token: TOKEN, lastCommonReadMessage: 123 }) - expect(forceGuestNameAction).toHaveBeenCalledWith(expect.anything(), messages[1]) + expect(addGuestNameAction).toHaveBeenCalledWith(messages[1], { noUpdate: false }) expect(store.getters.messagesList(TOKEN)).toStrictEqual(messages) expect(store.getters.getLastKnownMessageId(TOKEN)).toBe(100) diff --git a/src/store/storeConfig.js b/src/store/storeConfig.js index ad9c53628a1..f11797ea121 100644 --- a/src/store/storeConfig.js +++ b/src/store/storeConfig.js @@ -26,7 +26,6 @@ import breakoutRoomsStore from './breakoutRoomsStore.js' import callViewStore from './callViewStore.js' import conversationsStore from './conversationsStore.js' import fileUploadStore from './fileUploadStore.js' -import guestNameStore from './guestNameStore.js' import integrationsStore from './integrationsStore.js' import messagesStore from './messagesStore.js' import newGroupConversationStore from './newGroupConversationStore.js' @@ -49,7 +48,6 @@ export default { callViewStore, conversationsStore, fileUploadStore, - guestNameStore, messagesStore, newGroupConversationStore, participantsStore, diff --git a/src/stores/__tests__/guestName.spec.js b/src/stores/__tests__/guestName.spec.js new file mode 100644 index 00000000000..48a7625ab79 --- /dev/null +++ b/src/stores/__tests__/guestName.spec.js @@ -0,0 +1,138 @@ +import { createPinia, setActivePinia } from 'pinia' + +import { useGuestNameStore } from '../guestName.js' + +describe('guestNameStore', () => { + let store + + beforeEach(() => { + setActivePinia(createPinia()) + store = useGuestNameStore() + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + test('sets guest name if empty', () => { + // Arrange + const actor1 = { + token: 'token-1', + actorId: 'actor-id1', + actorDisplayName: 'actor-display-name-one', + } + + // Act + store.addGuestName(actor1, { noUpdate: true }) + + // Assert + expect(store.getGuestName('token-1', 'actor-id1')).toBe('actor-display-name-one') + // non-existing token + expect(store.getGuestName('token-2', 'actor-id1')).toBe('Guest') + // non-existing actorId + expect(store.getGuestName('token-1', 'actor-id2')).toBe('Guest') + }) + + test('does not overwrite guest name if not empty', () => { + // Arrange + const actor1 = { + token: 'token-1', + actorId: 'actor-id1', + actorDisplayName: 'actor-display-name-one', + } + const actor1Altered = { + token: 'token-1', + actorId: 'actor-id1', + actorDisplayName: 'actor-display-name-another', + } + + // Act + store.addGuestName(actor1, { noUpdate: true }) + // attempt overwriting + store.addGuestName(actor1Altered, { noUpdate: true }) + + // Assert + expect(store.getGuestName('token-1', 'actor-id1')).toBe('actor-display-name-one') + }) + + test('forces overwriting guest name', () => { + // Arrange + const actor1 = { + token: 'token-1', + actorId: 'actor-id1', + actorDisplayName: 'actor-display-name-one', + } + const actor1Altered = { + token: 'token-1', + actorId: 'actor-id1', + actorDisplayName: 'actor-display-name-another', + } + + // Act + store.addGuestName(actor1, { noUpdate: false }) + // attempt overwriting + store.addGuestName(actor1Altered, { noUpdate: false }) + + // Assert + expect(store.getGuestName('token-1', 'actor-id1')).toBe('actor-display-name-another') + }) + + test('clear guest name', () => { + // Arrange + const actor1 = { + token: 'token-1', + actorId: 'actor-id1', + actorDisplayName: 'actor-display-name-one', + } + + const actor1Altered = { + token: 'token-1', + actorId: 'actor-id1', + actorDisplayName: '', + } + + // Act + store.addGuestName(actor1, { noUpdate: true }) + store.addGuestName(actor1Altered, { noUpdate: false }) + + // Assert + expect(store.getGuestName('token-1', 'actor-id1')).toBe('Guest') + }) + + test('translates default guest name', () => { + + expect(store.getGuestName('token-1', 'actor-id0')).toBe('Guest') + expect(global.t).toHaveBeenCalledWith('spreed', 'Guest') + }) + + test('gets suffix with guest display name', () => { + // Arrange + const actor1 = { + token: 'token-1', + actorId: 'actor-id1', + actorDisplayName: 'actor-display-name-one', + } + + store.addGuestName(actor1, { noUpdate: false }) + + // Assert + expect(store.getGuestNameWithGuestSuffix('token-1', 'actor-id1')).toBe('{guest} (guest)') + expect(global.t).toHaveBeenCalledWith('spreed', '{guest} (guest)', { guest: 'actor-display-name-one' }) + }) + + test('does not get suffix for translatable default guest name', () => { + // Arrange + const actor1 = { + token: 'token-1', + actorId: 'actor-id1', + actorDisplayName: t('spreed', 'Guest'), + } + + store.addGuestName(actor1, { noUpdate: false }) + + // Assert + expect(store.getGuestNameWithGuestSuffix('token-1', 'actor-id1')).toBe('Guest') + expect(global.t).toHaveBeenCalledWith('spreed', 'Guest') + }) + +}) diff --git a/src/stores/guestName.js b/src/stores/guestName.js new file mode 100644 index 00000000000..8c986136e3c --- /dev/null +++ b/src/stores/guestName.js @@ -0,0 +1,87 @@ +/** + * @copyright Copyright (c) 2019 Joas Schilling + * + * @author Joas Schilling + * @author Dorra Jaouad + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +import { defineStore } from 'pinia' +import Vue from 'vue' + +export const useGuestNameStore = defineStore('guestName', { + state: () => ({ + guestNames: {}, + }), + + actions: { + /** + * Gets the participant display name + * + * @param {string} token the conversation's token + * @param {string} actorId the participant actorId + * @return {string} the participant name + */ + getGuestName(token, actorId) { + return this.guestNames[token]?.[actorId] ?? t('spreed', 'Guest') + }, + + /** + * Gets the participant display name with suffix + * if the display name is not default translatable Guest + * + * @param {string} token the conversation's token + * @param {string} actorId the participant actorId + * @return {string} the participant name with/without suffix + */ + getGuestNameWithGuestSuffix(token, actorId) { + const displayName = this.getGuestName(token, actorId) + if (displayName === t('spreed', 'Guest')) { + return displayName + } + return t('spreed', '{guest} (guest)', { + guest: displayName, + }) + }, + + /** + * Adds a guest name to the store + * + * @param {object} data the wrapping object + * @param {string} data.token the token of the conversation + * @param {string} data.actorId the guest + * @param {string} data.actorDisplayName the display name to set + * @param {object} options options + * @param {boolean} options.noUpdate Override the display name or set it if it is empty + */ + addGuestName({ token, actorId, actorDisplayName }, { noUpdate }) { + if (!this.guestNames[token]) { + Vue.set(this.guestNames, token, {}) + + } + if (!this.guestNames[token][actorId] || actorDisplayName === '') { + Vue.set(this.guestNames[token], actorId, t('spreed', 'Guest')) + } else if (noUpdate) { + return + } + + if (actorDisplayName) { + Vue.set(this.guestNames[token], actorId, actorDisplayName) + } + }, + }, +})