fix: hydrate realtime conversations before rendering
Build and publish Docker images / Build and publish images (push) Successful in 3m16s

This commit is contained in:
2026-09-14 21:39:56 +08:00
parent 9b8d575657
commit 5a0e9ecada
4 changed files with 149 additions and 10 deletions
@@ -51,10 +51,19 @@ const actions = {
getConversation: async ({ commit }, conversationId) => {
try {
const response = await ConversationApi.show(conversationId);
commit(types.UPDATE_CONVERSATION, response.data);
commit(`contacts/${types.SET_CONTACT_ITEM}`, response.data.meta.sender);
const conversation = response.data;
commit(types.UPDATE_CONVERSATION, conversation);
if (Array.isArray(conversation.messages)) {
commit(types.SET_MISSING_MESSAGES, {
id: conversation.id,
data: conversation.messages,
});
}
commit(`contacts/${types.SET_CONTACT_ITEM}`, conversation.meta.sender);
return conversation;
} catch (error) {
// Ignore error
return null;
}
},
@@ -225,11 +234,16 @@ const actions = {
commit(types.CLEAR_ALL_MESSAGES_LOADED, data.id);
if (data.dataFetched === undefined) {
try {
await dispatch('fetchPreviousMessages', {
after,
before: data.messages[0].id,
conversationId: data.id,
});
if (!data.messages?.length) {
const conversation = await dispatch('getConversation', data.id);
if (!conversation) return;
} else {
await dispatch('fetchPreviousMessages', {
after,
before: data.messages[0].id,
conversationId: data.id,
});
}
commit(types.SET_CHAT_DATA_FETCHED, data.id);
} catch (error) {
// Ignore error
@@ -435,7 +449,9 @@ const actions = {
) {
commit(types.ADD_CONVERSATION, conversation);
dispatch('contacts/setContact', sender);
return true;
}
return false;
},
addMentions({ dispatch, rootState }, conversation) {
@@ -70,6 +70,31 @@ describe('#actions', () => {
['contacts/SET_CONTACT_ITEM', { id: 1, name: 'Contact 1' }],
]);
});
it('hydrates messages returned with the conversation', async () => {
const messages = [{ id: 2, conversation_id: 1, content: 'hello' }];
axios.get.mockResolvedValue({
data: {
id: 1,
messages,
meta: { sender: { id: 1, name: 'Contact 1' } },
},
});
await actions.getConversation({ commit }, 1);
expect(commit.mock.calls).toEqual([
[
types.UPDATE_CONVERSATION,
{
id: 1,
messages,
meta: { sender: { id: 1, name: 'Contact 1' } },
},
],
[types.SET_MISSING_MESSAGES, { id: 1, data: messages }],
['contacts/SET_CONTACT_ITEM', { id: 1, name: 'Contact 1' }],
]);
});
it('sends correct actions if API is error', async () => {
axios.get.mockRejectedValue({ message: 'Incorrect header' });
await actions.getConversation({ commit });
@@ -957,6 +982,20 @@ describe('#addMentions', () => {
expect(localDispatch).not.toHaveBeenCalled();
});
it('hydrates a realtime conversation when its event has no messages', async () => {
const localCommit = vi.fn();
const localDispatch = vi.fn().mockResolvedValue({ id: 42 });
const data = { id: 42, messages: [] };
await actions.setActiveChat(
{ commit: localCommit, dispatch: localDispatch },
{ data }
);
expect(localDispatch).toHaveBeenCalledWith('getConversation', 42);
expect(localCommit).toHaveBeenCalledWith(types.SET_CHAT_DATA_FETCHED, 42);
});
it('should commit SET_CHAT_DATA_FETCHED by ID, not mutate the data object directly (race condition fix)', async () => {
const localCommit = vi.fn();
const localDispatch = vi.fn().mockResolvedValue();