diff --git a/src/components/Channel/__tests__/Channel.test.js b/src/components/Channel/__tests__/Channel.test.js index 2295e690e1..75508f28eb 100644 --- a/src/components/Channel/__tests__/Channel.test.js +++ b/src/components/Channel/__tests__/Channel.test.js @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { fireEvent, render, waitFor } from '@testing-library/react'; +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import { Channel } from '../Channel'; @@ -22,12 +22,16 @@ import { threadRepliesApi, useMockedApis, } from '../../../mock-builders'; +import { MessageList } from '../../MessageList'; +import { Thread } from '../../Thread'; jest.mock('../../Loading', () => ({ LoadingErrorIndicator: jest.fn(() =>
), LoadingIndicator: jest.fn(() =>
loading
), })); +const MockAvatar = ({ user }) =>
{user.custom}
; + let chatClient; let channel; @@ -63,12 +67,12 @@ const ActiveChannelSetter = ({ activeChannel }) => { return null; }; -const user = generateUser({ id: 'id', name: 'name' }); +const user = generateUser({ custom: 'custom-value', id: 'id', name: 'name' }); // create a full message state so we can properly test `loadMore` const messages = []; for (let i = 0; i < 25; i++) { - messages.push(generateMessage()); + messages.push(generateMessage({ user })); } const pinnedMessages = [generateMessage({ pinned: true, user })]; @@ -110,7 +114,7 @@ describe('Channel', () => { }); it('should render the EmptyPlaceholder prop if the channel is not provided by the ChatContext', () => { - const { getByText } = render(empty
}>); + const { getByText } = render(empty} />); expect(getByText('empty')).toBeInTheDocument(); }); @@ -741,6 +745,100 @@ describe('Channel', () => { await waitFor(() => expect(newThreadMessageWasAdded).toBe(true)); }); + + it('should update user data in MessageList based on updated_at', async () => { + const updatedAttribute = { custom: 'newCustomValue' }; + const dispatchUserUpdatedEvent = createChannelEventDispatcher( + { + user: { ...user, ...updatedAttribute, updated_at: new Date().toISOString() }, + }, + 'user.updated', + ); + renderComponent({ Avatar: MockAvatar, children: }); + + await waitFor(() => + expect(screen.queryByText(updatedAttribute.custom)).not.toBeInTheDocument(), + ); + act(() => { + dispatchUserUpdatedEvent(); + }); + await waitFor(() => + expect(screen.queryAllByText(updatedAttribute.custom).length).toBeGreaterThan(0), + ); + }); + + it('should not update user data in MessageList if updated_at has not changed', async () => { + const updatedAttribute = { custom: 'newCustomValue' }; + const dispatchUserUpdatedEvent = createChannelEventDispatcher( + { + user: { ...user, ...updatedAttribute }, + }, + 'user.updated', + ); + renderComponent({ Avatar: MockAvatar, children: }); + + await waitFor(() => + expect(screen.queryByText(updatedAttribute.custom)).not.toBeInTheDocument(), + ); + act(() => { + dispatchUserUpdatedEvent(); + }); + await waitFor(() => + expect(screen.queryByText(updatedAttribute.custom)).not.toBeInTheDocument(), + ); + }); + + it('should update user data in Thread if updated_at has changed', async () => { + const threadMessage = messages[0]; + const updatedAttribute = { custom: 'newCustomValue' }; + const dispatchUserUpdatedEvent = createChannelEventDispatcher( + { + user: { ...user, ...updatedAttribute, updated_at: new Date().toISOString() }, + }, + 'user.updated', + ); + renderComponent({ Avatar: MockAvatar, children: }, ({ openThread, thread }) => { + if (!thread) { + openThread(threadMessage, { preventDefault: () => null }); + } + }); + + await waitFor(() => + expect(screen.queryByText(updatedAttribute.custom)).not.toBeInTheDocument(), + ); + act(() => { + dispatchUserUpdatedEvent(); + }); + await waitFor(() => + expect(screen.queryAllByText(updatedAttribute.custom).length).toBeGreaterThan(0), + ); + }); + + it('should not update user data in Thread if updated_at has not changed', async () => { + const threadMessage = messages[0]; + const updatedAttribute = { custom: 'newCustomValue' }; + const dispatchUserUpdatedEvent = createChannelEventDispatcher( + { + user: { ...user, ...updatedAttribute }, + }, + 'user.updated', + ); + renderComponent({ Avatar: MockAvatar, children: }, ({ openThread, thread }) => { + if (!thread) { + openThread(threadMessage, { preventDefault: () => null }); + } + }); + + await waitFor(() => + expect(screen.queryByText(updatedAttribute.custom)).not.toBeInTheDocument(), + ); + act(() => { + dispatchUserUpdatedEvent(); + }); + await waitFor(() => + expect(screen.queryByText(updatedAttribute.custom)).not.toBeInTheDocument(), + ); + }); }); }); }); diff --git a/src/components/Channel/hooks/useCreateChannelStateContext.ts b/src/components/Channel/hooks/useCreateChannelStateContext.ts index d33c9c7be7..d8ae17f739 100644 --- a/src/components/Channel/hooks/useCreateChannelStateContext.ts +++ b/src/components/Channel/hooks/useCreateChannelStateContext.ts @@ -73,7 +73,7 @@ export const useCreateChannelStateContext = < updated_at && (isDayOrMoment(updated_at) || isDate(updated_at)) ? updated_at.toISOString() : updated_at || '' - }${user?.image}${user?.name}`, + }${user?.updated_at}`, ) .join(); @@ -86,7 +86,7 @@ export const useCreateChannelStateContext = < updated_at && (isDayOrMoment(updated_at) || isDate(updated_at)) ? updated_at.toISOString() : updated_at || '' - }${user?.image}${user?.name}`, + }${user?.updated_at}`, ) .join(); diff --git a/src/components/ChannelPreview/ChannelPreview.tsx b/src/components/ChannelPreview/ChannelPreview.tsx index 5977c101b9..175924ad90 100644 --- a/src/components/ChannelPreview/ChannelPreview.tsx +++ b/src/components/ChannelPreview/ChannelPreview.tsx @@ -58,11 +58,12 @@ export const ChannelPreview = < props: ChannelPreviewProps, ) => { const { channel, Preview = ChannelPreviewMessenger, channelUpdateCount } = props; - const { channel: activeChannel, client, setActiveChannel } = useChatContext( 'ChannelPreview', ); const { t, userLanguage } = useTranslationContext('ChannelPreview'); + const [displayTitle, setDisplayTitle] = useState(getDisplayTitle(channel, client.user)); + const [displayImage, setDisplayImage] = useState(getDisplayImage(channel, client.user)); const [lastMessage, setLastMessage] = useState>( channel.state.messages[channel.state.messages.length - 1], @@ -109,10 +110,26 @@ export const ChannelPreview = < }; }, [refreshUnreadCount, channelUpdateCount]); + useEffect(() => { + const handleEvent = () => { + setDisplayTitle((displayTitle) => { + const newDisplayTitle = getDisplayTitle(channel, client.user); + return displayTitle !== newDisplayTitle ? newDisplayTitle : displayTitle; + }); + setDisplayImage((displayImage) => { + const newDisplayImage = getDisplayImage(channel, client.user); + return displayImage !== newDisplayImage ? newDisplayImage : displayImage; + }); + }; + + client.on('user.updated', handleEvent); + return () => { + client.off('user.updated', handleEvent); + }; + }, []); + if (!Preview) return null; - const displayImage = getDisplayImage(channel, client.user); - const displayTitle = getDisplayTitle(channel, client.user); const latestMessage = getLatestMessagePreview(channel, t, userLanguage); return ( diff --git a/src/components/ChannelPreview/__tests__/ChannelPreview.test.js b/src/components/ChannelPreview/__tests__/ChannelPreview.test.js index a776bea214..9c6af751a5 100644 --- a/src/components/ChannelPreview/__tests__/ChannelPreview.test.js +++ b/src/components/ChannelPreview/__tests__/ChannelPreview.test.js @@ -1,22 +1,27 @@ import React from 'react'; -import { act, render, waitFor } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; +import { ChannelPreview } from '../ChannelPreview'; +import { Chat } from '../../Chat'; + +import { ChatContext } from '../../../context/ChatContext'; + import { dispatchMessageDeletedEvent, dispatchMessageNewEvent, dispatchMessageUpdatedEvent, + dispatchUserUpdatedEvent, generateChannel, + generateMember, generateMessage, + generateUser, getRandomInt, getTestClientWithUser, queryChannelsApi, useMockedApis, } from 'mock-builders'; -import { ChatContext } from '../../../context'; -import { ChannelPreview } from '../ChannelPreview'; - const PreviewUIComponent = (props) => ( <>
{props.channel.id}
@@ -131,7 +136,7 @@ describe('ChannelPreview', () => { const { getByTestId } = renderComponent( { - activeChannel: c1, + activteChannel: c1, channel: c0, }, render, @@ -218,4 +223,103 @@ describe('ChannelPreview', () => { await expectUnreadCountToBe(getByTestId, 0); }); }); + + describe('user.updated', () => { + let chatClient; + let channels; + let channelState; + let otherUser; + const MockAvatar = ({ image, name }) => ( + <> +
{name}
+
{image}
+ + ); + + const channelPreviewProps = { + Avatar: MockAvatar, + }; + + beforeEach(async () => { + const activeUser = generateUser({ + custom: 'custom1', + id: 'id1', + image: 'image1', + name: 'name1', + }); + otherUser = generateUser({ + custom: 'custom2', + id: 'id2', + image: 'image2', + name: 'name2', + }); + channelState = generateChannel({ + members: [generateMember({ user: activeUser }), generateMember({ user: otherUser })], + messages: [generateMessage({ user: activeUser }), generateMessage({ user: otherUser })], + }); + chatClient = await getTestClientWithUser(activeUser); + useMockedApis(chatClient, [queryChannelsApi([channelState])]); + channels = await chatClient.queryChannels(); + }); + + it("should update the direct messaging channel's preview if other user's name has changed", async () => { + const updatedAttribute = { name: 'new-name' }; + const channel = channels[0]; + render( + + + , + ); + + await waitFor(() => + expect(screen.queryByText(updatedAttribute.name)).not.toBeInTheDocument(), + ); + act(() => { + dispatchUserUpdatedEvent(chatClient, { ...otherUser, ...updatedAttribute }); + }); + await waitFor(() => + expect(screen.queryAllByText(updatedAttribute.name).length).toBeGreaterThan(0), + ); + }); + + it("should update the direct messaging channel's preview if other user's image has changed", async () => { + const updatedAttribute = { image: 'new-image' }; + const channel = channels[0]; + render( + + + , + ); + + await waitFor(() => + expect(screen.queryByText(updatedAttribute.image)).not.toBeInTheDocument(), + ); + act(() => { + dispatchUserUpdatedEvent(chatClient, { ...otherUser, ...updatedAttribute }); + }); + await waitFor(() => + expect(screen.queryAllByText(updatedAttribute.image).length).toBeGreaterThan(0), + ); + }); + + it("should not update the direct messaging channel's preview if other user attribute than name or image has changed", async () => { + const updatedAttribute = { custom: 'new-custom' }; + const channel = channels[0]; + render( + + + , + ); + + await waitFor(() => + expect(screen.queryByText(updatedAttribute.custom)).not.toBeInTheDocument(), + ); + act(() => { + dispatchUserUpdatedEvent(chatClient, { ...otherUser, ...updatedAttribute }); + }); + await waitFor(() => + expect(screen.queryByText(updatedAttribute.custom)).not.toBeInTheDocument(), + ); + }); + }); }); diff --git a/src/components/Message/utils.tsx b/src/components/Message/utils.tsx index 54baf14f7d..d534b0475a 100644 --- a/src/components/Message/utils.tsx +++ b/src/components/Message/utils.tsx @@ -246,8 +246,7 @@ export const areMessagePropsEqual = < prevMessage.text === nextMessage.text && prevMessage.type === nextMessage.type && prevMessage.updated_at === nextMessage.updated_at && - prevMessage.user?.image === nextMessage.user?.image && - prevMessage.user?.name === nextMessage.user?.name; + prevMessage.user?.updated_at === nextMessage.user?.updated_at; if (!messagesAreEqual) return false; @@ -295,7 +294,7 @@ export const areMessageUIPropsEqual = < return false; } - const messagesAreEqual = + return ( prevMessage.deleted_at === nextMessage.deleted_at && prevMessage.latest_reactions?.length === nextMessage.latest_reactions?.length && prevMessage.own_reactions?.length === nextMessage.own_reactions?.length && @@ -305,12 +304,8 @@ export const areMessageUIPropsEqual = < prevMessage.text === nextMessage.text && prevMessage.type === nextMessage.type && prevMessage.updated_at === nextMessage.updated_at && - prevMessage.user?.image === nextMessage.user?.image && - prevMessage.user?.name === nextMessage.user?.name; - - if (!messagesAreEqual) return false; - - return true; + prevMessage.user?.updated_at === nextMessage.user?.updated_at + ); }; export const messageHasReactions = < diff --git a/src/mock-builders/event/index.js b/src/mock-builders/event/index.js index 9478c09af7..573883c13c 100644 --- a/src/mock-builders/event/index.js +++ b/src/mock-builders/event/index.js @@ -12,3 +12,4 @@ export { default as dispatchNotificationAddedToChannelEvent } from './notificati export { default as dispatchNotificationMessageNewEvent } from './notificationMessageNew'; export { default as dispatchNotificationMutesUpdated } from './notificationMutesUpdated'; export { default as dispatchNotificationRemovedFromChannel } from './notificationRemovedFromChannel'; +export { default as dispatchUserUpdatedEvent } from './userUpdated'; diff --git a/src/mock-builders/event/userUpdated.js b/src/mock-builders/event/userUpdated.js new file mode 100644 index 0000000000..6e88eea0f5 --- /dev/null +++ b/src/mock-builders/event/userUpdated.js @@ -0,0 +1,7 @@ +export default (client, user) => { + client.dispatchEvent({ + created_at: new Date().toISOString(), + type: 'user.updated', + user, + }); +};