diff --git a/src/modules/Channel/context/ChannelProvider.tsx b/src/modules/Channel/context/ChannelProvider.tsx index 3c0aae610..bd7cce620 100644 --- a/src/modules/Channel/context/ChannelProvider.tsx +++ b/src/modules/Channel/context/ChannelProvider.tsx @@ -51,6 +51,7 @@ import { useHandleChannelPubsubEvents } from './hooks/useHandleChannelPubsubEven import { PublishingModuleType } from '../../internalInterfaces'; import { ChannelActionTypes } from './dux/actionTypes'; import useSendbird from '../../../lib/Sendbird/context/hooks/useSendbird'; +import { useLocalization } from '../../../lib/LocalizationContext'; export { ThreadReplySelectType } from './const'; // export for external usage @@ -228,7 +229,8 @@ const ChannelProvider = (props: ChannelContextProps) => { const [quoteMessage, setQuoteMessage] = useState(null); const [isScrolled, setIsScrolled] = useState(false); - const [messagesStore, messagesDispatcher] = useReducer(messagesReducer, messagesInitialState); + const { stringSet } = useLocalization(); + const [messagesStore, messagesDispatcher] = useReducer(messagesReducer, { ...messagesInitialState, stringSet }); const scrollRef = useRef(null); const isMentionEnabled = groupChannel.enableMention; diff --git a/src/modules/Channel/context/dux/__tests__/reducers.spec.js b/src/modules/Channel/context/dux/__tests__/reducers.spec.js index 5c77ccdc6..bb10aa1b1 100644 --- a/src/modules/Channel/context/dux/__tests__/reducers.spec.js +++ b/src/modules/Channel/context/dux/__tests__/reducers.spec.js @@ -32,7 +32,8 @@ describe('Messages-Reducers', () => { }); it('should setloading true FETCH_INITIAL_MESSAGES_START', () => { - const nextState = reducers(initialState, { + const { stringSet } = useLocalization(); + const nextState = reducers({ ...initialState, stringSet }, { type: actionTypes.FETCH_INITIAL_MESSAGES_START, }); expect(nextState.loading).toEqual(true); @@ -44,8 +45,9 @@ describe('Messages-Reducers', () => { }); it('should initialize messages FETCH_INITIAL_MESSAGES_SUCCESS', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); - const nextState = reducers(stateWithCurrentChannel, { + const nextState = reducers({ ...stateWithCurrentChannel, stringSet }, { type: actionTypes.FETCH_INITIAL_MESSAGES_SUCCESS, payload: { currentGroupChannel: mockData.currentGroupChannel, @@ -62,8 +64,9 @@ describe('Messages-Reducers', () => { }); it('should append previous messages FETCH_PREV_MESSAGES_SUCCESS', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.FETCH_PREV_MESSAGES_SUCCESS, payload: { currentGroupChannel: mockData.currentGroupChannel, @@ -80,8 +83,9 @@ describe('Messages-Reducers', () => { }); it('should append next messages FETCH_NEXT_MESSAGES_SUCCESS', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.FETCH_NEXT_MESSAGES_SUCCESS, payload: { currentGroupChannel: mockData.currentGroupChannel, @@ -98,10 +102,12 @@ describe('Messages-Reducers', () => { }); it('should get prev message list considering messageListParams FETCH_PREV_MESSAGES_SUCCESS', () => { + const { stringSet } = useLocalization(); const MESSAGE_LIST_SIZE = 20; const mockData = generateMockChannel(); - const nextState = reducers({ + const nextState = reducers({ ...mockData, stringSet }, { ...mockData, + stringSet, hasMorePrev: true, hasMoreNext: true, messageListParams: { @@ -121,11 +127,13 @@ describe('Messages-Reducers', () => { }); it('should verify there is no more messages FETCH_PREV_MESSAGES_SUCCESS', () => { + const { stringSet } = useLocalization(); // request size > response size const MESSAGE_LIST_SIZE = 20; const mockData = generateMockChannel(); const nextState = reducers({ ...mockData, + stringSet, hasMorePrev: true, hasMoreNext: true, messageListParams: { @@ -144,11 +152,13 @@ describe('Messages-Reducers', () => { }); it('should not set `hasMorePrev: false` when additional messages are fetched in FETCH_PREV_MESSAGES_SUCCESS', () => { + const { stringSet } = useLocalization(); // request size < response size const MESSAGE_LIST_SIZE = 20; const mockData = generateMockChannel(); const nextState = reducers({ ...mockData, + stringSet, hasMorePrev: true, hasMoreNext: true, messageListParams: { @@ -167,10 +177,12 @@ describe('Messages-Reducers', () => { }); it('should get next message list considering messageListParams FETCH_NEXT_MESSAGES_SUCCESS', () => { + const { stringSet } = useLocalization(); const MESSAGE_LIST_SIZE = 20; const mockData = generateMockChannel(); - const nextState = reducers({ + const nextState = reducers({ ...mockData, stringSet }, { ...mockData, + stringSet, hasMorePrev: true, hasMoreNext: true, messageListParams: { @@ -190,11 +202,13 @@ describe('Messages-Reducers', () => { }); it('should verify there is no more messages FETCH_NEXT_MESSAGES_SUCCESS', () => { + const { stringSet } = useLocalization(); // request size > response size const MESSAGE_LIST_SIZE = 20; const mockData = generateMockChannel(); const nextState = reducers({ ...mockData, + stringSet, hasMorePrev: true, hasMoreNext: true, messageListParams: { @@ -213,11 +227,13 @@ describe('Messages-Reducers', () => { }); it('should validate unexpected additional messages are fetched FETCH_NEXT_MESSAGES_SUCCESS', () => { + const { stringSet } = useLocalization(); // request size < response size const MESSAGE_LIST_SIZE = 20; const mockData = generateMockChannel(); const nextState = reducers({ ...mockData, + stringSet, hasMorePrev: true, hasMoreNext: true, messageListParams: { @@ -236,8 +252,9 @@ describe('Messages-Reducers', () => { }); it('should set pending message on SEND_MESSAGE_START', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.SEND_MESSAGE_START, payload: mockMessage1, }); @@ -253,8 +270,10 @@ describe('Messages-Reducers', () => { const succededMessage = generateMockMessage(succeededMessageId); succededMessage.sendingStatus = 'succeeded'; + const { stringSet } = useLocalization(); const currentState = { ...mockData, + stringSet, localMessages: [ { ...succededMessage, @@ -275,8 +294,9 @@ describe('Messages-Reducers', () => { }); it('should append message to end of list ON_MESSAGE_RECEIVED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.ON_MESSAGE_RECEIVED, payload: { message: mockMessage1, channel: { url: mockMessage1.channelUrl } }, }); @@ -285,8 +305,9 @@ describe('Messages-Reducers', () => { }); it('should not add message when get overlap message ON_MESSAGE_RECEIVED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.ON_MESSAGE_RECEIVED, payload: { message: mockData.allMessages[0], channel: { url: mockMessage1.channelUrl } }, }); @@ -294,6 +315,7 @@ describe('Messages-Reducers', () => { }); it('should update message if present on list ON_MESSAGE_UPDATED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const currentState = { ...mockData }; currentState.allMessages[2].status = 'failed'; @@ -303,7 +325,7 @@ describe('Messages-Reducers', () => { status: 'updated', }; - const nextState = reducers(currentState, { + const nextState = reducers({ ...currentState, stringSet }, { type: actionTypes.ON_MESSAGE_UPDATED, payload: { channel: currentState.currentGroupChannel, @@ -315,6 +337,7 @@ describe('Messages-Reducers', () => { }); it('should not update message if the message is not on the list ON_MESSAGE_UPDATED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const currentState = { ...mockData }; const updatedMessage = { @@ -322,7 +345,7 @@ describe('Messages-Reducers', () => { status: 'updated', }; - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.ON_MESSAGE_UPDATED, payload: { channel: currentState.currentGroupChannel, @@ -334,6 +357,7 @@ describe('Messages-Reducers', () => { }); it('should update threadInfo of message on ON_MESSAGE_THREAD_INFO_UPDATED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const currentState = { ...mockData }; const updateEvent = { @@ -343,7 +367,7 @@ describe('Messages-Reducers', () => { }; expect(currentState.allMessages.find(m => m.messageId === updateEvent.targetMessageId).threadInfo).toBeUndefined(); - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.ON_MESSAGE_THREAD_INFO_UPDATED, payload: { channel: currentState.currentGroupChannel, @@ -377,6 +401,7 @@ describe('Messages-Reducers', () => { }); it('should not update threadInfo of message if channel does not match on ON_MESSAGE_THREAD_INFO_UPDATED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const currentState = { ...mockData }; const updateEvent = { @@ -386,7 +411,7 @@ describe('Messages-Reducers', () => { }; expect(currentState.allMessages.find(m => m.messageId === updateEvent.targetMessageId).threadInfo).toBeUndefined(); - const nextState = reducers(currentState, { + const nextState = reducers({ ...currentState, stringSet }, { type: actionTypes.ON_MESSAGE_THREAD_INFO_UPDATED, payload: { channel: { url: updateEvent.channelUrl }, @@ -399,6 +424,7 @@ describe('Messages-Reducers', () => { }); it('should not update threadInfo of message if there is no matching message on ON_MESSAGE_THREAD_INFO_UPDATED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const currentState = { ...mockData }; const updateEvent = { @@ -408,7 +434,7 @@ describe('Messages-Reducers', () => { }; expect(currentState.allMessages.find(m => m.messageId === updateEvent.targetMessageId)).toBeUndefined(); - const nextState = reducers(currentState, { + const nextState = reducers({ ...currentState, stringSet }, { type: actionTypes.ON_MESSAGE_THREAD_INFO_UPDATED, payload: { channel: currentState.currentGroupChannel, @@ -420,10 +446,11 @@ describe('Messages-Reducers', () => { }); it('should delete message on ON_MESSAGE_DELETED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const deletedMessage = mockData.allMessages[1].messageId; - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.ON_MESSAGE_DELETED, payload: deletedMessage, }); @@ -432,12 +459,14 @@ describe('Messages-Reducers', () => { }); it('should reset all messages on RESET_MESSAGES', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.RESET_MESSAGES, }); expect(nextState).toEqual({ ...mockData, + stringSet, hasMorePrev: false, hasMoreNext: false, allMessages: [], @@ -445,11 +474,13 @@ describe('Messages-Reducers', () => { }); it('should apply reactions on ON_REACTION_UPDATED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const id = "12345678"; const reactions = [{ key: '123', value: '123' }, { key: '1234', value: '1234' }]; const nextState = reducers({ ...mockData, + stringSet, allMessages: [generateMockMessage(id)], }, { type: actionTypes.ON_REACTION_UPDATED, @@ -462,9 +493,10 @@ describe('Messages-Reducers', () => { }); it('should handle SET_CURRENT_CHANNEL', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const channel = { url: mockMessage1.channelUrl }; - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.SET_CURRENT_CHANNEL, payload: channel, }); @@ -473,17 +505,19 @@ describe('Messages-Reducers', () => { }); it('should handle SET_CHANNEL_INVALID', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.SET_CHANNEL_INVALID, }); expect(nextState.isInvalid).toEqual(true); }); it('should handle SET_EMOJI_CONTAINER', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const emojiContainer = { key: 'value' }; - const nextState = reducers(mockData, { + const nextState = reducers({ ...mockData, stringSet }, { type: actionTypes.SET_EMOJI_CONTAINER, payload: emojiContainer, }); @@ -494,7 +528,8 @@ describe('Messages-Reducers', () => { const mockData = generateMockChannel(); const messageTypes = { ADMIN: 'admin', USER: 'user', FILE: 'file' }; test('messageType filter is ADMIN', () => { - const appliedParamsState = reducers(mockData, { + const { stringSet } = useLocalization(); + const appliedParamsState = reducers({ ...mockData, stringSet }, { type: actionTypes.MESSAGE_LIST_PARAMS_CHANGED, payload: { messageTypeFilter: messageTypes.ADMIN }, }); @@ -518,7 +553,8 @@ describe('Messages-Reducers', () => { }); }); test('messageType filter is USER', () => { - const appliedParamsState = reducers(mockData, { + const { stringSet } = useLocalization(); + const appliedParamsState = reducers({ ...mockData, stringSet }, { type: actionTypes.MESSAGE_LIST_PARAMS_CHANGED, payload: { messageTypeFilter: messageTypes.USER }, }); @@ -542,7 +578,8 @@ describe('Messages-Reducers', () => { }); }); test('messageType filter is FILE', () => { - const appliedParamsState = reducers(mockData, { + const { stringSet } = useLocalization(); + const appliedParamsState = reducers({ ...mockData, stringSet }, { type: actionTypes.MESSAGE_LIST_PARAMS_CHANGED, payload: { messageTypeFilter: messageTypes.FILE }, }); @@ -568,9 +605,10 @@ describe('Messages-Reducers', () => { }); it('should filter by customType of messageListParams when message received', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const paramsCustomTypes = ['a', 'b', 'c']; - const appliedParamsState = reducers(mockData, { + const appliedParamsState = reducers({ ...mockData, stringSet }, { type: actionTypes.MESSAGE_LIST_PARAMS_CHANGED, payload: { customTypesFilter: paramsCustomTypes }, }); @@ -595,9 +633,10 @@ describe('Messages-Reducers', () => { }); it('should filter by senderUserIds of messageListParams when message received', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const paramsSenderUserIds = ['mark-1', 'mark-2', 'mark-3']; - const appliedParamsState = reducers(mockData, { + const appliedParamsState = reducers({ ...mockData, stringSet }, { type: actionTypes.MESSAGE_LIST_PARAMS_CHANGED, payload: { senderUserIdsFilter: paramsSenderUserIds }, }); @@ -622,6 +661,7 @@ describe('Messages-Reducers', () => { }); it('should filter by MESSAGE_LIST_PARAMS_CHANGED when ON_MESSAGE_UPDATED', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const changingMessage = uuidv4(); const updatingMessage = { @@ -634,7 +674,7 @@ describe('Messages-Reducers', () => { }; mockData.allMessages.unshift(updatingMessage); - const appliedParamsState = reducers(mockData, { + const appliedParamsState = reducers({ ...mockData, stringSet }, { type: actionTypes.MESSAGE_LIST_PARAMS_CHANGED, payload: { messageTypeFilter: 'user', @@ -689,6 +729,7 @@ describe('Messages-Reducers', () => { }); it('should not update with coming message when received message already exsits', () => { + const { stringSet } = useLocalization(); const mockData = generateMockChannel(); const changingMessage = uuidv4(); const updatingMessage = { @@ -702,6 +743,7 @@ describe('Messages-Reducers', () => { const onMessageUpdatedState = reducers( { ...mockData, + stringSet, allMessages: [updatingMessage, ...mockData.allMessages], }, { diff --git a/src/modules/Channel/context/dux/data.mock.js b/src/modules/Channel/context/dux/data.mock.js index da9f8ef7c..55ccd9bc6 100644 --- a/src/modules/Channel/context/dux/data.mock.js +++ b/src/modules/Channel/context/dux/data.mock.js @@ -108,7 +108,8 @@ export const generateMockChannel = () => ({ "errorCode": 0, "sendingStatus": 'succeeded', }], - hasMore: true, + hasMorePrev: true, + hasMoreNext: true, }); export const generateMockMessage = (id) => { diff --git a/src/modules/Channel/context/dux/initialState.ts b/src/modules/Channel/context/dux/initialState.ts index 3885f99c6..916acf3dd 100644 --- a/src/modules/Channel/context/dux/initialState.ts +++ b/src/modules/Channel/context/dux/initialState.ts @@ -2,6 +2,7 @@ import { CoreMessageType, SendableMessageType } from '../../../../utils'; import { GroupChannel, Member } from '@sendbird/chat/groupChannel'; import { EmojiContainer } from '@sendbird/chat'; import type { MessageListParams as MessageListParamsInternal } from '../ChannelProvider'; +import getStringSet, { StringSet } from '../../../../ui/Label/stringSet'; export interface ChannelInitialStateType { allMessages: (SendableMessageType | CoreMessageType)[]; @@ -12,6 +13,7 @@ export interface ChannelInitialStateType { unreadSince: string; unreadSinceDate: Date | null; isInvalid: boolean; + stringSet: StringSet; currentGroupChannel: GroupChannel | null; hasMorePrev: boolean; oldestMessageTimeStamp: number; @@ -31,6 +33,7 @@ const initialState: ChannelInitialStateType = { * localMessages: pending & failed messages */ localMessages: [], + stringSet: getStringSet(), currentGroupChannel: null, // for scrollup hasMorePrev: false, diff --git a/src/modules/Channel/context/dux/reducers.ts b/src/modules/Channel/context/dux/reducers.ts index f1cd61b99..f34f563f0 100644 --- a/src/modules/Channel/context/dux/reducers.ts +++ b/src/modules/Channel/context/dux/reducers.ts @@ -17,7 +17,6 @@ import { isSendableMessage, } from '../../../../utils'; import { ChannelInitialStateType } from './initialState'; -import { useLocalization } from '../../../../lib/LocalizationContext'; const getOldestMessageTimeStamp = (messages: CoreMessageType[] = []) => { const oldestMessage = messages[0]; @@ -38,8 +37,6 @@ export default function channelReducer( state: ChannelInitialStateType, action: ChannelActionTypes, ): ChannelInitialStateType { - const { stringSet } = useLocalization(); - return match(action) .with({ type: channelActions.RESET_MESSAGES }, () => { return { @@ -222,7 +219,7 @@ export default function channelReducer( const { channel, message } = action.payload; const { members } = channel; const { sender } = message; - const { currentGroupChannel } = state; + const { currentGroupChannel, stringSet } = state; const currentGroupChannelUrl = currentGroupChannel?.url; diff --git a/src/modules/Channel/context/utils.ts b/src/modules/Channel/context/utils.ts index 031f41dc4..9d09cfff7 100644 --- a/src/modules/Channel/context/utils.ts +++ b/src/modules/Channel/context/utils.ts @@ -116,6 +116,11 @@ export const mergeAndSortMessages = (oldMessages: BaseMessage[], newMessages: Ba return sortByCreatedAt(unique); }; +/** + * @deprecated This function is deprecated and will be removed in the next major update. + * Using this function may cause the violation of the rules of hooks. + * Please use the `getMessageCreatedAt` function from the `@sendbird/uikit-react/utils` module instead. + */ export const getMessageCreatedAt = (message: BaseMessage) => { const { stringSet } = useLocalization(); return format(message.createdAt, stringSet.DATE_FORMAT__MESSAGE_CREATED_AT); diff --git a/src/modules/OpenChannel/context/utils.ts b/src/modules/OpenChannel/context/utils.ts index 79c1bdee8..3c37f00ac 100644 --- a/src/modules/OpenChannel/context/utils.ts +++ b/src/modules/OpenChannel/context/utils.ts @@ -7,6 +7,11 @@ import { Logger } from '../../../lib/Sendbird/types'; import { SendableMessageType } from '../../../utils'; import { useLocalization } from '../../../lib/LocalizationContext'; +/** + * @deprecated This function is deprecated and will be removed in the next major update. + * Using this function may cause the violation of the rules of hooks. + * Please use the `getMessageCreatedAt` function from the `@sendbird/uikit-react/utils` module instead. + */ export const getMessageCreatedAt = (message: SendableMessageType): string => { const { stringSet } = useLocalization(); return format(message.createdAt, stringSet.DATE_FORMAT__MESSAGE_CREATED_AT); diff --git a/src/modules/Thread/context/utils.ts b/src/modules/Thread/context/utils.ts index f8b2f51df..3d9d080d4 100644 --- a/src/modules/Thread/context/utils.ts +++ b/src/modules/Thread/context/utils.ts @@ -58,10 +58,16 @@ export function compareIds(a: number | string, b: number | string): boolean { return aString === bString; } +/** + * @deprecated This function is deprecated and will be removed in the next major update. + * Using this function may cause the violation of the rules of hooks. + * Please use the `getMessageCreatedAt` function from the `@sendbird/uikit-react/utils` module instead. + */ export const getMessageCreatedAt = (message: SendableMessageType): string => { const { stringSet } = useLocalization(); return format(message.createdAt, stringSet.DATE_FORMAT__MESSAGE_CREATED_AT); }; + export const isReadMessage = (channel: GroupChannel, message: SendableMessageType): boolean => ( getOutgoingMessageState(channel, message) === OutgoingMessageStates.READ ); diff --git a/src/ui/MentionUserLabel/__tests__/renderToString.spec.js b/src/ui/MentionUserLabel/__tests__/renderToString.spec.js index d7c76ebb2..158c4cd27 100644 --- a/src/ui/MentionUserLabel/__tests__/renderToString.spec.js +++ b/src/ui/MentionUserLabel/__tests__/renderToString.spec.js @@ -4,7 +4,7 @@ describe("ui/MentionUserLabel/renderToString", () => { it("should render userId and nickname as expected", () => { const userId = "me"; const nickname = "nickname"; - const expected = `nickname`; + const expected = `${nickname}`; const result = renderToString({ userId, nickname }); expect(result).toEqual(expected); });