Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -378,10 +378,6 @@ describe('MessageButtonsBar.vue', () => {
id: 100,
updateVisually: true,
})

expect(fetchConversationAction).toHaveBeenCalledWith(expect.anything(), {
token: TOKEN,
})
})

test('copies message link', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -722,9 +722,6 @@ export default {
id: this.previousMessageId,
updateVisually: true,
})

// reload conversation to update additional attributes that have computed values
await this.$store.dispatch('fetchConversation', { token: this.token })
},

handleReactionClick(selectedEmoji) {
Expand Down
13 changes: 2 additions & 11 deletions src/store/conversationsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -649,23 +649,14 @@ const actions = {
commit('addConversation', conversation)
},

async markConversationRead({ commit, getters }, token) {
if (!getters.conversations[token]) {
return
}

commit('updateUnreadMessages', { token, unreadMessages: 0, unreadMention: false, unreadMentionDirect: false })
},

async markConversationUnread({ commit, dispatch, getters }, { token }) {
if (!getters.conversations[token]) {
return
}

try {
await setConversationUnread(token)
commit('updateUnreadMessages', { token, unreadMessages: 1 })
await dispatch('fetchConversation', { token })
const response = await setConversationUnread(token)
dispatch('addConversation', response.data.ocs.data)
} catch (error) {
console.error('Error while setting the conversation as unread: ', error)
}
Expand Down
34 changes: 27 additions & 7 deletions src/store/conversationsStore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
setCallPermissions,
setConversationUnread,
} from '../services/conversationsService.js'
import { updateLastReadMessage } from '../services/messagesService.js'
import { useTalkHashStore } from '../stores/talkHash.js'
import { generateOCSErrorResponse, generateOCSResponse } from '../test-helpers.js'

Expand All @@ -58,6 +59,10 @@ jest.mock('../services/conversationsService', () => ({
setConversationUnread: jest.fn(),
}))

jest.mock('../services/messagesService', () => ({
updateLastReadMessage: jest.fn(),
}))

jest.mock('@nextcloud/event-bus')

jest.mock('../services/BrowserStorage.js', () => ({
Expand Down Expand Up @@ -99,6 +104,7 @@ describe('conversationsStore', () => {
actorId: 'actor-id',
defaultPermissions: PARTICIPANT.PERMISSIONS.CUSTOM,
callPermissions: PARTICIPANT.PERMISSIONS.CUSTOM,
lastMessage: { ...previousLastMessage },
}

testStoreConfig = cloneDeep(storeConfig)
Expand Down Expand Up @@ -914,34 +920,48 @@ describe('conversationsStore', () => {
describe('read marker', () => {
beforeEach(() => {
store = new Vuex.Store(testStoreConfig)
store.commit('setUserId', 'current-user')
})

test('marks conversation as read by clearing unread counters', () => {
test('marks conversation as read by clearing unread counters', async () => {
// Arrange
testConversation.unreadMessages = 1024
testConversation.unreadMention = true

store.dispatch('addConversation', testConversation)

store.dispatch('markConversationRead', testToken)
const response = generateOCSResponse({
payload: {
...testConversation,
unreadMessages: 0,
unreadMention: false,
}
})
updateLastReadMessage.mockResolvedValue(response)

// Act
store.dispatch('clearLastReadMessage', { token: testToken })
await flushPromises()

// Assert
const changedConversation = store.getters.conversation(testToken)
expect(changedConversation.unreadMessages).toBe(0)
expect(changedConversation.unreadMention).toBe(false)
})

test('marks conversation as unread', async () => {
// Arrange
testConversation.unreadMessages = 0
store.dispatch('addConversation', testConversation)

const response = generateOCSResponse({ payload: { ...testConversation, unreadMessages: 1 } })
fetchConversation.mockResolvedValue(response)
store.dispatch('markConversationUnread', { token: testToken })
setConversationUnread.mockResolvedValue(response)

// Act
store.dispatch('markConversationUnread', { token: testToken })
await flushPromises()

// Assert
expect(setConversationUnread).toHaveBeenCalledWith(testConversation.token)
expect(fetchConversation).toHaveBeenCalledWith(testConversation.token)

const changedConversation = store.getters.conversation(testToken)
expect(changedConversation.unreadMessages).toBe(1)
})
Expand Down
24 changes: 16 additions & 8 deletions src/store/messagesStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import SHA256 from 'crypto-js/sha256.js'
import cloneDeep from 'lodash/cloneDeep.js'
import Vue from 'vue'

import { getCapabilities } from '@nextcloud/capabilities'
import { showError } from '@nextcloud/dialogs'

import {
Expand All @@ -49,6 +50,8 @@ import { useReactionsStore } from '../stores/reactions.js'
import { useSharedItemsStore } from '../stores/sharedItems.js'
import CancelableRequest from '../utils/cancelableRequest.js'

const markAsReadWithoutLast = getCapabilities()?.spreed?.features?.includes('chat-read-last')

/**
* Returns whether the given message contains a mention to self, directly
* or indirectly through a global mention.
Expand Down Expand Up @@ -817,17 +820,20 @@ const actions = {
*
* @param {object} context default store context;
* @param {object} data the wrapping object;
* @param {object} data.token the token of the conversation to be updated;
* @param {string} data.token the token of the conversation to be updated;
* @param {boolean} data.updateVisually whether to also clear the marker visually in the UI;
*/
async clearLastReadMessage(context, { token, updateVisually = false }) {
const conversation = context.getters.conversations[token]
if (!conversation || !conversation.lastMessage) {
const conversation = context.getters.conversation(token)
if (markAsReadWithoutLast) {
context.dispatch('updateLastReadMessage', { token, id: null, updateVisually })
return
}
if (!conversation?.lastMessage?.id) {
return
}
// set the id to the last message
context.dispatch('updateLastReadMessage', { token, id: conversation.lastMessage.id, updateVisually })
context.dispatch('markConversationRead', token)
},

/**
Expand All @@ -836,18 +842,19 @@ const actions = {
*
* @param {object} context default store context;
* @param {object} data the wrapping object;
* @param {object} data.token the token of the conversation to be updated;
* @param {number} data.id the id of the message on which to set the read marker;
* @param {string} data.token the token of the conversation to be updated;
* @param {number|null} data.id the id of the message on which to set the read marker;
* @param {boolean} data.updateVisually whether to also update the marker visually in the UI;
*/
async updateLastReadMessage(context, { token, id = 0, updateVisually = false }) {
const conversation = context.getters.conversations[token]
const conversation = context.getters.conversation(token)
if (!conversation || conversation.lastReadMessage === id) {
return
}

if (id === 0) {
console.warn('updateLastReadMessage: should not set read marker with id=0')
return
}

// optimistic early commit to avoid indicator flickering
Expand All @@ -858,7 +865,8 @@ const actions = {

if (context.getters.getUserId()) {
// only update on server side if there's an actual user, not guest
await updateLastReadMessage(token, id)
const response = await updateLastReadMessage(token, id)
context.dispatch('addConversation', response.data.ocs.data)
}
},

Expand Down
Loading