diff --git a/examples/SampleApp/ios/Podfile.lock b/examples/SampleApp/ios/Podfile.lock index 709cbd638b..805f887e41 100644 --- a/examples/SampleApp/ios/Podfile.lock +++ b/examples/SampleApp/ios/Podfile.lock @@ -2578,87 +2578,87 @@ SPEC CHECKSUMS: hermes-engine: b417d2b2aee3b89b58e63e23a51e02be91dc876d libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - op-sqlite: c33561ea312a2ae38aae032fd3a42635dc6b57e8 + op-sqlite: 2e34a191af7e843608357671c94a6e2befd4b986 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 + RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 RCTDeprecation: b2eecf2d60216df56bc5e6be5f063826d3c1ee35 RCTRequired: 78522de7dc73b81f3ed7890d145fa341f5bb32ea RCTTypeSafety: c135dd2bf50402d87fd12884cbad5d5e64850edd React: b229c49ed5898dab46d60f61ed5a0bfa2ee2fadb React-callinvoker: 2ac508e92c8bd9cf834cc7d7787d94352e4af58f React-Codegen: 4b8b4817cea7a54b83851d4c1f91f79aa73de30a - React-Core: 13cdd1558d0b3f6d9d5a22e14d89150280e79f02 - React-CoreModules: b07a6744f48305405e67c845ebf481b6551b712a - React-cxxreact: 1055a86c66ac35b4e80bd5fb766aed5f494dfff4 + React-Core: 325b4f6d9162ae8b9a6ff42fe78e260eb124180d + React-CoreModules: 558041e5258f70cd1092f82778d07b8b2ff01897 + React-cxxreact: 8fff17cbe76e6a8f9991b59552e1235429f9c74b React-debug: c76e92776a86622209279fe6d24a0147584444ed - React-defaultsnativemodule: c2e3ac39909241374c3322eb2be33f4c15fe6be4 - React-domnativemodule: 240b3c95b5300cc6537594e73ebc6e8e77585b74 - React-Fabric: 3b403ca25f74d54454b31d1d2627050e0777d42c - React-FabricComponents: 154740cfcd57943709a9d0343769d17173c0ac9c - React-FabricImage: 0863e39cea98f3ca2f8c3d92984660795cec84ae + React-defaultsnativemodule: 111fb1efc95c2bd0ee18e38e9f7b57d678e6f932 + React-domnativemodule: d5154a815306fd6050ee9346a1490d2fb17eb0e5 + React-Fabric: 51ac32f0a6790b1d3b14d90c6870e5ce5bb3854a + React-FabricComponents: 1094d6a3c2566b3c56951331c44d7d3960570ac8 + React-FabricImage: 6b210ad3c72704a9ad60dde66c397ce6257333f4 React-featureflags: efb93a998907e4ad5b88f6ed77cc140914d5c36d - React-featureflagsnativemodule: 51116d72aafea30860f315702d17eb76bbb725a3 - React-graphics: 91d9920451f633d64d31948da3ba0377b6eda8de - React-hermes: 71186f872c932e4574d5feb3ed754dda63a0b3bd - React-idlecallbacksnativemodule: 19bf1fa4b2b66fe1898ac1d185129cdcc3221c7c - React-ImageManager: 7dc7bfca8e9ecb9a7436b8a89a143a193ef5adcf - React-jserrorhandler: d8640792495ac2d78e73acbcc77a8439d1eedfef - React-jsi: 0775a66820496769ad83e629f0f5cce621a57fc7 - React-jsiexecutor: 2cf5ba481386803f3c88b85c63fa102cba5d769e - React-jsinspector: d1d9f215c7431b286acc12e83cdf0d90c265f0ed - React-jsinspectortracing: c4c1cceb9a9c266ce849c82332e35cc57ee9dae9 - React-jsitracing: 267618eec9c362658a4587c5ddcfb41b2e00c403 - React-logger: 795cd5055782db394f187f9db0477d4b25b44291 - React-Mapbuffer: 0df2a235bd0182f5cbed6c5f095e66deca12e335 - React-microtasksnativemodule: b31e56a980634f383221bfefd5111d04c14c110b - react-native-blob-util: 875bbeee07e4ada135e4edf9fc7b22acf8d9721d - react-native-cameraroll: cdc91c4c953d1a18aa3ce88b5a25698025c8c4d2 - react-native-document-picker: 19be73c0423e4bc886cef74ec282eff750698013 - react-native-image-picker: 3a03f96b11ef2b727d3d58a7ed009900b0a56a52 - react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac - react-native-safe-area-context: 0b43456abcaaa3c8323bbfafe9c5f0f9511219d2 - react-native-video: a225b4d4d3286f3253dc7b00a62e7c8e59d04d51 - React-NativeModulesApple: b74b4e3004104429461593fe460ad790cc4928c2 - React-perflogger: ab51b7592532a0ea45bf6eed7e6cae14a368b678 - React-performancetimeline: 37192fd1019c3b3b597a877dff12f3af68305c34 + React-featureflagsnativemodule: a74b09429c2e7a57412d78cc159ab86ae4f15db9 + React-graphics: 17ef0ee3ef4a4c1774cc82f1f477ecef4d67c73f + React-hermes: a9a0c8377627b5506ef9a7b6f60a805c306e3f51 + React-idlecallbacksnativemodule: 0711ec5eb53c7f790641fa00e5f6ec0355d3159b + React-ImageManager: 23b4701408390428724f0e0ebb2cbed7b37c2b24 + React-jserrorhandler: e21b438ef8b99ea8bf070ff35f00bc0215b5f769 + React-jsi: f3f51595cc4c089037b536368f016d4742bf9cf7 + React-jsiexecutor: cca6c232db461e2fd213a11e9364cfa6fdaa20eb + React-jsinspector: 8a3c2637b84ebec478f46a43432a522d7489410f + React-jsinspectortracing: ee0215d2db753cc10f45fc9aa86557718d0b16fb + React-jsitracing: 258be1fd259141f6aa43012c20c70ebc02e32087 + React-logger: 018826bfd51b9f18e87f67db1590bc510ad20664 + React-Mapbuffer: 9fbb496e7d6f7c34d5e617365ee778bf96d14eae + React-microtasksnativemodule: 36adde22631838680d1be62776e8ccb83186c06a + react-native-blob-util: d03eaad9fd1bbe90bd0eedb5bad3333215976086 + react-native-cameraroll: 10054f480dfd6e0bd02fdf08fb6d82f80b362575 + react-native-document-picker: 78c262a7f9f77df2380378aa4b3413b8646ce91b + react-native-image-picker: 5f9867b6a223594dbfdb456428638daef42e6e6c + react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 + react-native-safe-area-context: 02e0f487c16ccf1acc8a666bed2318ceeb5dc14c + react-native-video: 16b5c395d05f8af23e16bfe3dc0794a5514c882f + React-NativeModulesApple: ec44c21ae0bbb5f9a2df72db00294e33a00e07f0 + React-perflogger: 9e8d3c0dc0194eb932162812a168aa5dc662f418 + React-performancetimeline: 350424518f433dd43f063dc5f2cf3195c1a5b60f React-RCTActionSheet: 592674cf61142497e0e820688f5a696e41bf16dd - React-RCTAnimation: 8fbb8dba757b49c78f4db403133ab6399a4ce952 - React-RCTAppDelegate: 7f88baa8cb4e5d6c38bb4d84339925c70c9ac864 - React-RCTBlob: f89b162d0fe6b570a18e755eb16cbe356d3c6d17 - React-RCTFabric: f2151588dc1dc884b34b8660d72ef5237aa4b10e - React-RCTFBReactNativeSpec: 8c29630c2f379c729300e4c1e540f3d1b78d1936 - React-RCTImage: ccac9969940f170503857733f9a5f63578e106e1 - React-RCTLinking: d82427bbf18415a3732105383dff119131cadd90 - React-RCTNetwork: 12ad4d0fbde939e00251ca5ca890da2e6825cc3c - React-RCTSettings: e7865bf9f455abf427da349c855f8644b5c39afa - React-RCTText: 2cdfd88745059ec3202a0842ea75a956c7d6f27d - React-RCTVibration: a3a1458e6230dfd64b3768ebc0a4aac430d9d508 + React-RCTAnimation: e6d669872f9b3b4ab9527aab283b7c49283236b7 + React-RCTAppDelegate: de2343fe08be4c945d57e0ecce44afcc7dd8fc03 + React-RCTBlob: 3e2dce94c56218becc4b32b627fc2293149f798d + React-RCTFabric: adad07a08efb186bc1046041207527927524170d + React-RCTFBReactNativeSpec: d10ca5e0ccbfeac8c047361fedf8e4ac653887b6 + React-RCTImage: dc04b176c022d12a8f55ae7a7279b1e091066ae0 + React-RCTLinking: 88f5e37fe4f26fbc80791aa2a5f01baf9b9a3fd5 + React-RCTNetwork: f213693565efbd698b8e9c18d700a514b49c0c8e + React-RCTSettings: a2d32a90c45a3575568cad850abc45924999b8a5 + React-RCTText: 54cdcd1cbf6f6a91dc6317f5d2c2b7fc3f6bf7a0 + React-RCTVibration: 11dae0e7f577b5807bb7d31e2e881eb46f854fd4 React-rendererconsistency: aa476d937c91886dd8b2ddde3191c775585ae47a - React-rendererdebug: 5a2219e0ceb78f4ffe9ee2d80fa260bb5bac50b2 + React-rendererdebug: df10d858ac7709b9c8349d952474b0746092c690 React-rncore: 517c6c3647d45de81a7920b6959adf14fed2a5a5 - React-RuntimeApple: 40809bf5975c265b990dec2725f2cfb61f1afc75 - React-RuntimeCore: 375c2645e924fdca875918f07ed987653c517edc + React-RuntimeApple: 6922a0861c3fc4c7d544fc7d1d5cb38c779d1264 + React-RuntimeCore: 41a95876d16630ce00946eaaee7ffd5222242b44 React-runtimeexecutor: a188df372373baf5066e6e229177836488799f80 - React-RuntimeHermes: 2de8d61ec25d950ae4aebcab1a895e0bb8b18c95 - React-runtimescheduler: e8b49a60eca68a3513c259879a352ed010fed255 + React-RuntimeHermes: f2ca409c03c36bb3dcbf61bdfa2636501f9faebd + React-runtimescheduler: 7ae10fa81428c2479e0a5534943dacb8e34c9d52 React-timing: e56b95cb12c6fb9146be7ba3d671cf6b5d17b2e0 - React-utils: 8ad62100a8780798a380b769e968c4764bad1f4b - ReactAppDependencyProvider: f2e81d80afd71a8058589e19d8a134243fa53f17 - ReactCodegen: 299e99fc57c93edc7c5396ef1a39a3a4d494f25d - ReactCommon: c8fdbc582b98a07daf201cd95c1da75dd029f3ee - RNAudioRecorderPlayer: 224c7de87722938aedce04000d09baa633148f5b - RNCAsyncStorage: dac011cac81189c2b3b8654f3db97d2b6362d165 - RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 - RNFBApp: 60366dd9d6bb01327607e1561a32508592d76db9 - RNFBMessaging: 9465c2e3adb5e02cae8d40048306a30aea7f55cf - RNGestureHandler: 0a16f3f13829c01268ae55610a40b57b713c8161 - RNNotifee: 4a6ee5c7deaf00e005050052d73ee6315dff7ec9 - RNReactNativeHapticFeedback: a49e613d48d721c99cad9689a490554104c22154 - RNReanimated: c9f295fb1679867288d238bfaf3ea39225c95e1b - RNScreens: 77f93ec55b749c49549b447527ebf78e990125f3 - RNShare: 12d13ebc179faf22534c605d17b2c2fa40191850 - RNSVG: 05776cf3f0d52d3f8e7ebee34b2189da7b8638ff + React-utils: 6eabecc0e7d7bcf21b6b33357bc1fe8ae13c7c4c + ReactAppDependencyProvider: a1fb08dfdc7ebc387b2e54cfc9decd283ed821d8 + ReactCodegen: 0f8899ac1bad260bf3b362ee848ef67a70b5a306 + ReactCommon: a30b578194de911fbe1698efb8247bfe4cb6abff + RNAudioRecorderPlayer: 11df0c7b614e9767ef24d896465c3a758c592de7 + RNCAsyncStorage: 849b77e6ab3eb838361a902b492993056924faab + RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87 + RNFBApp: b5626640e0f4b4fe5be4f44375df703c0d62ee4b + RNFBMessaging: 8e38f5ca846497f8a9c91d33f311c00ca52d119c + RNGestureHandler: f7b3a72c099e1e29b5b81688678bc9108d44057c + RNNotifee: 5e3b271e8ea7456a36eec994085543c9adca9168 + RNReactNativeHapticFeedback: eb5395b503c7a8f10de5e6722ef8afd3c61bc4f5 + RNReanimated: fe5c52894886953248a81a10b2a9b6eeb5398d61 + RNScreens: fc78b9b5a1274426d7a59b7d07c272bba13604fa + RNShare: dcef43a8864fcc114fd582edba7832a906fd318d + RNSVG: 71e35e78add645b84b52b0c6f203f91028e1ab5e SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 diff --git a/package/jest-setup.js b/package/jest-setup.js index 1112edb696..75c0c8f5a3 100644 --- a/package/jest-setup.js +++ b/package/jest-setup.js @@ -25,6 +25,7 @@ registerNativeHandlers({ unsubscribe: () => {}, }), pickDocument: () => null, + pickImage: () => null, saveFile: () => null, SDK: 'stream-chat-react-native', shareImage: () => null, diff --git a/package/src/__tests__/offline-support/optimistic-update.js b/package/src/__tests__/offline-support/optimistic-update.js index 7cf6fc00b8..6ea1be1062 100644 --- a/package/src/__tests__/offline-support/optimistic-update.js +++ b/package/src/__tests__/offline-support/optimistic-update.js @@ -270,14 +270,20 @@ export const OptimisticUpdates = () => { it('pending task should exist if sendMessage request fails', async () => { const newMessage = generateMessage(); + jest.spyOn(channel.messageComposer, 'compose').mockResolvedValue({ + localMessage: newMessage, + message: newMessage, + options: {}, + }); + render( - + { useMockedApis(chatClient, [erroredPostApi()]); try { - await sendMessage({ customMessageData: newMessage }); + await sendMessage(); } catch (e) { // do nothing } @@ -446,6 +452,12 @@ export const OptimisticUpdates = () => { it('send message pending task should be executed after connection is recovered', async () => { const newMessage = generateMessage(); + jest.spyOn(channel.messageComposer, 'compose').mockResolvedValue({ + localMessage: newMessage, + message: newMessage, + options: {}, + }); + // initialValue is needed as a prop to trick the message input ctx into thinking // we are sending a message. render( @@ -455,7 +467,7 @@ export const OptimisticUpdates = () => { callback={async ({ sendMessage }) => { useMockedApis(chatClient, [erroredPostApi()]); try { - await sendMessage({ customMessageData: newMessage }); + await sendMessage(); } catch (e) { // do nothing } diff --git a/package/src/components/Attachment/__tests__/Giphy.test.js b/package/src/components/Attachment/__tests__/Giphy.test.js index df2060d89f..f721eb2571 100644 --- a/package/src/components/Attachment/__tests__/Giphy.test.js +++ b/package/src/components/Attachment/__tests__/Giphy.test.js @@ -1,6 +1,7 @@ import React from 'react'; import { + act, cleanup, fireEvent, render, @@ -177,11 +178,15 @@ describe('Giphy', () => { await waitFor(() => screen.getByTestId(`${attachment.actions[2].value}-action-button`)); - expect(screen.getByTestId('giphy-action-attachment')).toContainElement( - screen.getByTestId(`${attachment.actions[2].value}-action-button`), - ); + await waitFor(() => { + expect(screen.getByTestId('giphy-action-attachment')).toContainElement( + screen.getByTestId(`${attachment.actions[2].value}-action-button`), + ); + }); - user.press(screen.getByTestId(`${attachment.actions[2].value}-action-button`)); + act(() => { + user.press(screen.getByTestId(`${attachment.actions[2].value}-action-button`)); + }); await waitFor(() => { expect(handleAction).toHaveBeenCalledTimes(1); @@ -202,11 +207,15 @@ describe('Giphy', () => { await waitFor(() => screen.getByTestId(`${attachment.actions[1].value}-action-button`)); - expect(screen.getByTestId('giphy-action-attachment')).toContainElement( - screen.getByTestId(`${attachment.actions[1].value}-action-button`), - ); + await waitFor(() => { + expect(screen.getByTestId('giphy-action-attachment')).toContainElement( + screen.getByTestId(`${attachment.actions[1].value}-action-button`), + ); + }); - user.press(screen.getByTestId(`${attachment.actions[1].value}-action-button`)); + act(() => { + user.press(screen.getByTestId(`${attachment.actions[1].value}-action-button`)); + }); await waitFor(() => { expect(handleAction).toHaveBeenCalledTimes(1); @@ -227,11 +236,15 @@ describe('Giphy', () => { await waitFor(() => screen.getByTestId(`${attachment.actions[0].value}-action-button`)); - expect(screen.getByTestId('giphy-action-attachment')).toContainElement( - screen.getByTestId(`${attachment.actions[0].value}-action-button`), - ); + await waitFor(() => { + expect(screen.getByTestId('giphy-action-attachment')).toContainElement( + screen.getByTestId(`${attachment.actions[0].value}-action-button`), + ); + }); - user.press(screen.getByTestId(`${attachment.actions[0].value}-action-button`)); + act(() => { + user.press(screen.getByTestId(`${attachment.actions[0].value}-action-button`)); + }); await waitFor(() => { expect(handleAction).toHaveBeenCalledTimes(1); @@ -303,8 +316,13 @@ describe('Giphy', () => { expect(screen.queryByTestId('giphy-attachment')).toBeTruthy(); }); - fireEvent(screen.getByLabelText('Giphy Attachment Image'), 'error'); - expect(screen.getByAccessibilityHint('image-loading-error')).toBeTruthy(); + act(() => { + fireEvent(screen.getByLabelText('Giphy Attachment Image'), 'error'); + }); + + await waitFor(() => { + expect(screen.getByAccessibilityHint('image-loading-error')).toBeTruthy(); + }); }); it('should render a loading indicator in giphy image and when successful render the image', async () => { @@ -321,13 +339,22 @@ describe('Giphy', () => { expect(screen.getByAccessibilityHint('image-loading')).toBeTruthy(); }); - fireEvent(screen.getByLabelText('Giphy Attachment Image'), 'onLoadStart'); + act(() => { + fireEvent(screen.getByLabelText('Giphy Attachment Image'), 'onLoadStart'); + }); - expect(screen.getByAccessibilityHint('image-loading')).toBeTruthy(); + await waitFor(() => { + expect(screen.getByAccessibilityHint('image-loading')).toBeTruthy(); + }); - fireEvent(screen.getByLabelText('Giphy Attachment Image'), 'onLoadFinish'); + act(() => { + fireEvent(screen.getByLabelText('Giphy Attachment Image'), 'onLoad'); + }); waitForElementToBeRemoved(() => screen.getByAccessibilityHint('image-loading')); - expect(screen.getByLabelText('Giphy Attachment Image')).toBeTruthy(); + + await waitFor(() => { + expect(screen.getByLabelText('Giphy Attachment Image')).toBeTruthy(); + }); }); }); diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx index 4e148c7048..ba0ce85893 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx @@ -134,6 +134,7 @@ const UnMemoizedAutoCompleteSuggestionItem = ({ [{ opacity: pressed ? 0.8 : 1 }, itemStyle]} + testID='suggestion-item' > diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx index 1affece677..1b6e7d8254 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx @@ -99,6 +99,7 @@ export const AutoCompleteSuggestionList = ({ flatlist, { backgroundColor: white, maxHeight, shadowColor: black }, ]} + testID={'auto-complete-suggestion-list'} /> ); diff --git a/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.js b/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.js index 26fbfe3f3f..c3591fc026 100644 --- a/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.js +++ b/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.js @@ -1,69 +1,187 @@ import React from 'react'; -import { act, fireEvent, render, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; -import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; -import { useMockedApis } from '../../../mock-builders/api/useMockedApis'; -import { generateChannelResponse } from '../../../mock-builders/generator/channel'; -import { generateUser } from '../../../mock-builders/generator/user'; -import { getTestClientWithUser } from '../../../mock-builders/mock'; +import { OverlayProvider } from '../../../contexts'; +import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; import { AutoCompleteInput } from '../AutoCompleteInput'; -import { AutoCompleteSuggestionList } from '../AutoCompleteSuggestionList'; + +const renderComponent = ({ channelProps, client, props }) => { + return render( + + + + + + + , + ); +}; describe('AutoCompleteInput', () => { - const clientUser = generateUser(); - let chatClient; + let client; let channel; - const getAutoCompleteComponent = () => ( - - - - - - - ); + beforeEach(async () => { + const { client: chatClient, channels } = await initiateClientWithChannels(); + client = chatClient; + channel = channels[0]; + }); - const initializeChannel = async (c) => { - useMockedApis(chatClient, [getOrCreateChannelApi(c)]); + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); - channel = chatClient.channel('messaging'); + it('should render AutoCompleteInput', async () => { + const channelProps = { channel }; + const props = {}; - await channel.watch(); - }; + renderComponent({ channelProps, client, props }); - beforeEach(async () => { - chatClient = await getTestClientWithUser(clientUser); - await initializeChannel(generateChannelResponse()); + const { queryByTestId } = screen; + + const input = queryByTestId('auto-complete-text-input'); + + await waitFor(() => { + expect(input).toBeTruthy(); + }); }); - afterEach(() => { - channel = null; + it('should have the editable prop as false when the message composer config is set', async () => { + const channelProps = { channel }; + const props = {}; + + channel.messageComposer.updateConfig({ text: { enabled: false } }); + + renderComponent({ channelProps, client, props }); + + const { queryByTestId } = screen; + + const input = queryByTestId('auto-complete-text-input'); + + await waitFor(() => { + expect(input.props.editable).toBeFalsy(); + }); + }); + + it('should have the maxLength same as the one on the config of channel', async () => { + jest.spyOn(channel, 'getConfig').mockReturnValue({ + max_message_length: 10, + }); + const channelProps = { channel }; + const props = {}; + + renderComponent({ channelProps, client, props }); + + const { queryByTestId } = screen; + + const input = queryByTestId('auto-complete-text-input'); + + await waitFor(() => { + expect(input.props.maxLength).toBe(10); + }); + }); + + it('should call the textComposer handleChange when the onChangeText is triggered', async () => { + const { textComposer } = channel.messageComposer; + + const spyHandleChange = jest.spyOn(textComposer, 'handleChange'); + + const channelProps = { channel }; + const props = {}; + + renderComponent({ channelProps, client, props }); + + const { queryByTestId } = screen; + + const input = queryByTestId('auto-complete-text-input'); + + act(() => { + fireEvent.changeText(input, 'hello'); + }); + + await waitFor(() => { + expect(spyHandleChange).toHaveBeenCalled(); + expect(spyHandleChange).toHaveBeenCalledWith({ + selection: { end: 5, start: 5 }, + text: 'hello', + }); + expect(input.props.value).toBe('hello'); + }); }); - it('should render AutoCompleteInput and trigger open/close suggestions with / commands', async () => { - const { queryByTestId } = render(getAutoCompleteComponent()); + it('should style the text input with maxHeight that is set by the layout', async () => { + const channelProps = { channel }; + const props = { numberOfLines: 10 }; + + renderComponent({ channelProps, client, props }); + + const { queryByTestId } = screen; const input = queryByTestId('auto-complete-text-input'); - const onSelectionChange = input.props.onSelectionChange; + act(() => { + fireEvent(input, 'contentSizeChange', { + nativeEvent: { + contentSize: { height: 100 }, + }, + }); + }); await waitFor(() => { - expect(input).toBeTruthy(); + expect(input.props.style[1].maxHeight).toBe(1000); }); + }); + + it('should call the textComposer setSelection when the onSelectionChange is triggered', async () => { + const { textComposer } = channel.messageComposer; - await act(async () => { - await onSelectionChange({ + const spySetSelection = jest.spyOn(textComposer, 'setSelection'); + + const channelProps = { channel }; + const props = {}; + + renderComponent({ channelProps, client, props }); + + const { queryByTestId } = screen; + + const input = queryByTestId('auto-complete-text-input'); + + act(() => { + fireEvent(input, 'selectionChange', { nativeEvent: { - selection: { - end: 1, - start: 1, - }, + selection: { end: 5, start: 5 }, }, }); - await fireEvent.changeText(input, '/'); + }); + + await waitFor(() => { + expect(spySetSelection).toHaveBeenCalled(); + expect(spySetSelection).toHaveBeenCalledWith({ end: 5, start: 5 }); + }); + }); + + // TODO: Add a test for command + it.each([ + { cooldownActive: false, result: 'Send a message' }, + { cooldownActive: true, result: 'Slow mode ON' }, + ])('should have the placeholderText as Slow mode ON when cooldown is active', async (data) => { + const channelProps = { channel }; + const props = { + cooldownActive: data.cooldownActive, + }; + + renderComponent({ channelProps, client, props }); + + const { queryByTestId } = screen; + + const input = queryByTestId('auto-complete-text-input'); + + await waitFor(() => { + expect(input.props.placeholder).toBe(data.result); }); }); }); diff --git a/package/src/components/Channel/__tests__/Channel.test.js b/package/src/components/Channel/__tests__/Channel.test.js index 366d94d8ee..80559623f5 100644 --- a/package/src/components/Channel/__tests__/Channel.test.js +++ b/package/src/components/Channel/__tests__/Channel.test.js @@ -48,11 +48,14 @@ const ContextConsumer = ({ context, fn }) => { return ; }; +const channelType = 'messaging'; +const channelId = 'test-channel'; +const channelCid = `${channelType}:${channelId}`; let chatClient; let channel; const user = generateUser({ id: 'id', name: 'name' }); -const messages = [generateMessage({ user })]; +const messages = [generateMessage({ cid: channelCid, user })]; const renderComponent = (props = {}, callback = () => {}, context = ChannelContext) => render( @@ -70,8 +73,11 @@ describe('Channel', () => { beforeEach(async () => { const members = [generateMember({ user })]; const mockedChannel = generateChannelResponse({ + cid: channelCid, + id: channelId, members, messages, + type: channelType, }); chatClient = await getTestClientWithUser(user); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); diff --git a/package/src/components/ChannelList/hooks/listeners/__tests__/useChannelUpdated.test.tsx b/package/src/components/ChannelList/hooks/listeners/__tests__/useChannelUpdated.test.tsx index 705fe1928d..070463c5a9 100644 --- a/package/src/components/ChannelList/hooks/listeners/__tests__/useChannelUpdated.test.tsx +++ b/package/src/components/ChannelList/hooks/listeners/__tests__/useChannelUpdated.test.tsx @@ -1,9 +1,7 @@ import React, { useState } from 'react'; import { Image, Text } from 'react-native'; -import { act } from 'react-test-renderer'; - -import { render, waitFor } from '@testing-library/react-native'; +import { act, render, waitFor } from '@testing-library/react-native'; import type { Channel, ChannelResponse, Event, StreamChat } from 'stream-chat'; import { ChatContext, useChannelUpdated } from '../../../../../index'; diff --git a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx index 06e37d4748..8bcc63199c 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx @@ -47,6 +47,23 @@ const ChannelPreviewUIComponent = (props: ChannelPreviewUIComponentProps) => ( ); +const initChannelFromData = async ( + chatClient: StreamChat, + overrides: Record = {}, +) => { + const mockedChannel = generateChannelResponse(overrides); + useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); + await channel.watch(); + + channel.countUnread = jest.fn().mockReturnValue(0); + channel.initialized = true; + channel.lastMessage = jest.fn().mockReturnValue(generateMessage()); + channel.muteStatus = jest.fn().mockReturnValue({ muted: false }); + + return channel; +}; + describe('ChannelPreview', () => { const clientUser = generateUser(); let chatClient: StreamChat; @@ -98,12 +115,10 @@ describe('ChannelPreview', () => { it("should not update the unread count if the event's cid does not match the channel's cid", async () => { const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() }); - const c = generateChannelWrapper({ - countUnread: jest.fn().mockReturnValue(10), - on: channelOnMock, - }); + channel = await initChannelFromData(chatClient); - channel = c as unknown as Channel; + channel.countUnread = jest.fn().mockReturnValue(10); + channel.on = channelOnMock; const { getByTestId } = render(); @@ -128,12 +143,10 @@ describe('ChannelPreview', () => { countUnreadMock.mockReturnValue(10); - const c = generateChannelWrapper({ - countUnread: countUnreadMock, - on: channelOnMock, - }); + channel = await initChannelFromData(chatClient); - channel = c as unknown as Channel; + channel.countUnread = countUnreadMock; + channel.on = channelOnMock; const { getByTestId } = render(); @@ -159,11 +172,9 @@ describe('ChannelPreview', () => { it("should not update the unread count if the event's cid is undefined", async () => { const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() }); - const c = generateChannelWrapper({ - on: channelOnMock, - }); + channel = await initChannelFromData(chatClient); - channel = c as unknown as Channel; + channel.on = channelOnMock; const { getByTestId } = render(); @@ -192,11 +203,9 @@ describe('ChannelPreview', () => { it("should not update the unread count if the event's cid does not match the channel's cid", async () => { const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() }); - const c = generateChannelWrapper({ - on: channelOnMock, - }); + channel = await initChannelFromData(chatClient); - channel = c as unknown as Channel; + channel.on = channelOnMock; const { getByTestId } = render(); @@ -225,11 +234,9 @@ describe('ChannelPreview', () => { it("should not update the unread count if the event's user id does not match the client's user id", async () => { const channelOnMock = jest.fn().mockReturnValue({ unsubscribe: jest.fn() }); - const c = generateChannelWrapper({ - on: channelOnMock, - }); + channel = await initChannelFromData(chatClient); - channel = c as unknown as Channel; + channel.on = channelOnMock; const { getByTestId } = render(); diff --git a/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx b/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx index b1a95c2a63..e9835dbd24 100644 --- a/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx +++ b/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react'; import { renderHook, waitFor } from '@testing-library/react-native'; -import type { MessageResponse, StreamChat } from 'stream-chat'; +import { ChannelResponse, MessageResponse, StreamChat } from 'stream-chat'; import { ChatContext, ChatContextValue } from '../../../../contexts/chatContext/ChatContext'; import { @@ -16,11 +16,22 @@ import { LATEST_MESSAGE, } from '../../../../mock-builders/api/channelMocks'; +import { getOrCreateChannelApi } from '../../../../mock-builders/api/getOrCreateChannel'; +import { useMockedApis } from '../../../../mock-builders/api/useMockedApis'; +import { generateChannelResponse } from '../../../../mock-builders/generator/channel'; import { generateUser } from '../../../../mock-builders/generator/user'; import { getTestClientWithUser } from '../../../../mock-builders/mock'; - import { useLatestMessagePreview } from '../useLatestMessagePreview'; +const initChannelFromData = async (chatClient: StreamChat, channelData: ChannelResponse) => { + const mockedChannel = generateChannelResponse(channelData); + useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); + await channel.watch(); + + return channel; +}; + describe('useLatestMessagePreview', () => { const FORCE_UPDATE = 15; const clientUser = generateUser(); @@ -44,10 +55,15 @@ describe('useLatestMessagePreview', () => { ); it('should return a deleted message preview if the latest message is deleted', async () => { + const channel = await initChannelFromData( + chatClient, + CHANNEL_WITH_DELETED_MESSAGES as unknown as ChannelResponse, + ); + const latestMessage = { cid: 'test', type: 'deleted' } as unknown as MessageResponse; const { result } = renderHook( - () => useLatestMessagePreview(CHANNEL_WITH_DELETED_MESSAGES, FORCE_UPDATE, latestMessage), + () => useLatestMessagePreview(channel, FORCE_UPDATE, latestMessage), { wrapper: ChatProvider }, ); await waitFor(() => { @@ -56,10 +72,14 @@ describe('useLatestMessagePreview', () => { }); it('should return an "Nothing yet..." message preview if channel has no messages', async () => { + const channel = await initChannelFromData( + chatClient, + CHANNEL_WITH_NO_MESSAGES as unknown as ChannelResponse, + ); const latestMessage = undefined; const { result } = renderHook( - () => useLatestMessagePreview(CHANNEL_WITH_NO_MESSAGES, FORCE_UPDATE, latestMessage), + () => useLatestMessagePreview(channel, FORCE_UPDATE, latestMessage), { wrapper: ChatProvider }, ); await waitFor(() => { @@ -68,8 +88,12 @@ describe('useLatestMessagePreview', () => { }); it('should use latestMessage if provided', async () => { + const channel = await initChannelFromData( + chatClient, + CHANNEL_WITH_MESSAGES_TEXT as unknown as ChannelResponse, + ); const { result } = renderHook( - () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_TEXT, FORCE_UPDATE, LATEST_MESSAGE), + () => useLatestMessagePreview(channel, FORCE_UPDATE, LATEST_MESSAGE), { wrapper: ChatProvider }, ); @@ -82,10 +106,14 @@ describe('useLatestMessagePreview', () => { }); it('should return a channel with an empty message preview', async () => { + const channel = await initChannelFromData( + chatClient, + CHANNEL_WITH_EMPTY_MESSAGE as unknown as ChannelResponse, + ); const latestMessage = {} as unknown as MessageResponse; const { result } = renderHook( - () => useLatestMessagePreview(CHANNEL_WITH_EMPTY_MESSAGE, FORCE_UPDATE, latestMessage), + () => useLatestMessagePreview(channel, FORCE_UPDATE, latestMessage), { wrapper: ChatProvider }, ); @@ -98,6 +126,10 @@ describe('useLatestMessagePreview', () => { }); it('should return a mentioned user (@Max) message preview', async () => { + const channel = await initChannelFromData( + chatClient, + CHANNEL_WITH_MENTIONED_USERS as unknown as ChannelResponse, + ); const latestMessage = { mentioned_users: [{ id: 'Max', name: 'Max' }], text: 'Max', @@ -107,7 +139,7 @@ describe('useLatestMessagePreview', () => { } as unknown as MessageResponse; const { result } = renderHook( - () => useLatestMessagePreview(CHANNEL_WITH_MENTIONED_USERS, FORCE_UPDATE, latestMessage), + () => useLatestMessagePreview(channel, FORCE_UPDATE, latestMessage), { wrapper: ChatProvider }, ); await waitFor(() => { @@ -119,6 +151,11 @@ describe('useLatestMessagePreview', () => { }); it('should return the latest command preview', async () => { + const channel = await initChannelFromData( + chatClient, + CHANNEL_WITH_MESSAGES_COMMAND as unknown as ChannelResponse, + ); + const latestMessage = { command: 'giphy', user: { @@ -127,7 +164,7 @@ describe('useLatestMessagePreview', () => { } as unknown as MessageResponse; const { result } = renderHook( - () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_COMMAND, FORCE_UPDATE, latestMessage), + () => useLatestMessagePreview(channel, FORCE_UPDATE, latestMessage), { wrapper: ChatProvider }, ); await waitFor(() => { @@ -139,6 +176,10 @@ describe('useLatestMessagePreview', () => { }); it('should return an attachment preview', async () => { + const channel = await initChannelFromData( + chatClient, + CHANNEL_WITH_MESSAGES_ATTACHMENTS as unknown as ChannelResponse, + ); const latestMessage = { attachments: ['arbitrary value'], user: { @@ -147,7 +188,7 @@ describe('useLatestMessagePreview', () => { } as unknown as MessageResponse; const { result } = renderHook( - () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_ATTACHMENTS, FORCE_UPDATE, latestMessage), + () => useLatestMessagePreview(channel, FORCE_UPDATE, latestMessage), { wrapper: ChatProvider }, ); @@ -160,10 +201,14 @@ describe('useLatestMessagePreview', () => { }); it('should default to messages from the channel state if latestMessage is undefined', async () => { + const channel = await initChannelFromData( + chatClient, + CHANNEL_WITH_MESSAGES_TEXT as unknown as ChannelResponse, + ); const latestMessage = undefined; const { result } = renderHook( - () => useLatestMessagePreview(CHANNEL_WITH_MESSAGES_TEXT, FORCE_UPDATE, latestMessage), + () => useLatestMessagePreview(channel, FORCE_UPDATE, latestMessage), { wrapper: ChatProvider }, ); diff --git a/package/src/components/ImageGallery/__tests__/AnimatedVideoGallery.test.tsx b/package/src/components/ImageGallery/__tests__/AnimatedVideoGallery.test.tsx index 8a055d8fdd..139f90ec76 100644 --- a/package/src/components/ImageGallery/__tests__/AnimatedVideoGallery.test.tsx +++ b/package/src/components/ImageGallery/__tests__/AnimatedVideoGallery.test.tsx @@ -2,9 +2,7 @@ import React from 'react'; import type { SharedValue } from 'react-native-reanimated'; -import { act } from 'react-test-renderer'; - -import { fireEvent, render, screen } from '@testing-library/react-native'; +import { act, fireEvent, render, screen } from '@testing-library/react-native'; import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; import { defaultTheme } from '../../../contexts/themeContext/utils/theme'; diff --git a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx index 75867d2a57..3effec8666 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx @@ -3,6 +3,8 @@ import React from 'react'; import type { SharedValue } from 'react-native-reanimated'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; import { LocalMessage } from 'stream-chat'; @@ -22,6 +24,8 @@ import { generateMessage } from '../../../mock-builders/generator/message'; import { ImageGallery } from '../ImageGallery'; +dayjs.extend(duration); + jest.mock('../../../native.ts', () => { const View = require('react-native/Libraries/Components/View/View'); return { diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx index dfadff400b..f6f0dda93c 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx @@ -3,8 +3,6 @@ import React from 'react'; import { Text, View } from 'react-native'; import type { SharedValue } from 'react-native-reanimated'; -import { ReactTestInstance } from 'react-test-renderer'; - import { render, screen, userEvent, waitFor } from '@testing-library/react-native'; import { LocalMessage } from 'stream-chat'; @@ -188,8 +186,10 @@ describe('ImageGalleryFooter', () => { , ); + const { getByLabelText } = screen; + await waitFor(() => { - user.press(screen.queryByLabelText('Share Button') as ReactTestInstance); + user.press(getByLabelText('Share Button')); }); await waitFor(() => { @@ -231,8 +231,10 @@ describe('ImageGalleryFooter', () => { , ); + const { getByLabelText } = screen; + await waitFor(() => { - user.press(screen.queryByLabelText('Share Button') as ReactTestInstance); + user.press(getByLabelText('Share Button')); }); await waitFor(() => { @@ -280,8 +282,10 @@ describe('ImageGalleryFooter', () => { , ); + const { getByLabelText } = screen; + await waitFor(() => { - user.press(screen.queryByLabelText('Share Button') as ReactTestInstance); + user.press(getByLabelText('Share Button')); }); await waitFor(() => { diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx index 494f256ffc..1ea5816c2a 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { Text, View } from 'react-native'; -import { act } from 'react-test-renderer'; -import { fireEvent, render, screen } from '@testing-library/react-native'; +import { act, fireEvent, render, screen } from '@testing-library/react-native'; import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; import { defaultTheme } from '../../../contexts/themeContext/utils/theme'; diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx index 7da00363b1..096f4ceb8f 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx @@ -3,9 +3,7 @@ import React from 'react'; import { Text, View } from 'react-native'; import type { SharedValue } from 'react-native-reanimated'; -import { act } from 'react-test-renderer'; - -import { render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { act, render, screen, userEvent, waitFor } from '@testing-library/react-native'; import { LocalMessage } from 'stream-chat'; diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryOverlay.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryOverlay.test.tsx index 53da8b2eab..04b5e8d272 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryOverlay.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryOverlay.test.tsx @@ -3,9 +3,7 @@ import React from 'react'; import { State } from 'react-native-gesture-handler'; import type { SharedValue } from 'react-native-reanimated'; -import { act } from 'react-test-renderer'; - -import { fireEvent, render, waitFor } from '@testing-library/react-native'; +import { act, fireEvent, render, waitFor } from '@testing-library/react-native'; import { LocalMessage } from 'stream-chat'; diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryVideoControl.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryVideoControl.test.tsx index 8abf44d5b3..4788260271 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryVideoControl.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryVideoControl.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { act, ReactTestInstance } from 'react-test-renderer'; +import { ReactTestInstance } from 'react-test-renderer'; -import { render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { act, render, screen, userEvent, waitFor } from '@testing-library/react-native'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; diff --git a/package/src/components/Message/MessageSimple/__tests__/MessageContent.test.js b/package/src/components/Message/MessageSimple/__tests__/MessageContent.test.js index acf6eb4912..736f1c4613 100644 --- a/package/src/components/Message/MessageSimple/__tests__/MessageContent.test.js +++ b/package/src/components/Message/MessageSimple/__tests__/MessageContent.test.js @@ -16,7 +16,7 @@ import { getTestClientWithUser } from '../../../../mock-builders/mock'; import { Channel } from '../../../Channel/Channel'; import { Chat } from '../../../Chat/Chat'; import { Message } from '../../Message'; -import { MessageContent } from '../../MessageSimple/MessageContent'; +import { MessageContent } from '../MessageContent'; describe('MessageContent', () => { let channel; @@ -39,13 +39,11 @@ describe('MessageContent', () => { renderMessage = (options) => render( - - - - - - - , + + + + + , ); }); diff --git a/package/src/components/Message/MessageSimple/__tests__/MessageSimple.test.js b/package/src/components/Message/MessageSimple/__tests__/MessageSimple.test.js index e6c5d4bfb1..c2079bdac8 100644 --- a/package/src/components/Message/MessageSimple/__tests__/MessageSimple.test.js +++ b/package/src/components/Message/MessageSimple/__tests__/MessageSimple.test.js @@ -160,7 +160,6 @@ describe('MessageSimple', () => { renderMessage({ groupStyles: ['top'], message }); await waitFor(() => { - console.log(screen.getByTestId('message-simple-wrapper').props.style[1]); expect(screen.getByTestId('message-simple-wrapper').props.style[1]).toMatchObject({}); }); }); @@ -185,7 +184,6 @@ describe('MessageSimple', () => { renderMessage({ message }); await waitFor(() => { - console.log(screen.getByTestId('message-content-wrapper').props.style); expect(screen.getByTestId('message-content-wrapper').props.style[2]).toMatchObject({ borderWidth: 0, }); @@ -199,7 +197,6 @@ describe('MessageSimple', () => { renderMessage({ message }); await waitFor(() => { - console.log(screen.getByTestId('message-content-wrapper').props.style); expect(screen.getByTestId('message-content-wrapper').props.style[2]).toMatchObject({ borderWidth: 0, }); diff --git a/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx b/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx index dd063bca25..ab18a2a654 100644 --- a/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx +++ b/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx @@ -22,9 +22,9 @@ import { Chat } from '../../../Chat/Chat'; import { MessageList } from '../../../MessageList/MessageList'; import { MessageTextContainer } from '../MessageTextContainer'; -afterEach(cleanup); - describe('MessageTextContainer', () => { + afterEach(cleanup); + it('should render message text container', async () => { const staticUser = generateStaticUser(1); const message = generateMessage({ diff --git a/package/src/components/Message/MessageSimple/__tests__/ReactionListTop.test.js b/package/src/components/Message/MessageSimple/__tests__/ReactionListTop.test.js index c0fe78f9a7..03e60c7320 100644 --- a/package/src/components/Message/MessageSimple/__tests__/ReactionListTop.test.js +++ b/package/src/components/Message/MessageSimple/__tests__/ReactionListTop.test.js @@ -24,6 +24,8 @@ describe('ReactionListTop', () => { const messages = [generateMessage({ user })]; beforeEach(async () => { + jest.clearAllMocks(); + cleanup(); const members = [generateMember({ user })]; const mockedChannel = generateChannelResponse({ members, @@ -46,11 +48,6 @@ describe('ReactionListTop', () => { ); }); - afterEach(() => { - jest.clearAllMocks(); - cleanup(); - }); - it('renders the ReactionListTop component', async () => { renderMessage({ hasReactions: true, diff --git a/package/src/components/MessageInput/__tests__/AttachButton.test.js b/package/src/components/MessageInput/__tests__/AttachButton.test.js index bcaf7afe11..d0201770aa 100644 --- a/package/src/components/MessageInput/__tests__/AttachButton.test.js +++ b/package/src/components/MessageInput/__tests__/AttachButton.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { act, render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import { OverlayProvider } from '../../../contexts'; @@ -26,12 +26,17 @@ describe('AttachButton', () => { let client; let channel; - beforeAll(async () => { + beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); client = chatClient; channel = channels[0]; }); + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + it('should render an disabled AttachButton', async () => { const handleOnPress = jest.fn(); const channelProps = { channel }; @@ -46,8 +51,8 @@ describe('AttachButton', () => { expect(handleOnPress).toHaveBeenCalledTimes(0); }); - await act(() => { - userEvent.press(screen.getByTestId('attach-button')); + act(() => { + fireEvent.press(screen.getByTestId('attach-button')); }); await waitFor(() => { @@ -75,8 +80,8 @@ describe('AttachButton', () => { expect(handleOnPress).toHaveBeenCalledTimes(0); }); - await act(() => { - userEvent.press(screen.getByTestId('attach-button')); + act(() => { + fireEvent.press(screen.getByTestId('attach-button')); }); await waitFor(() => { @@ -104,8 +109,8 @@ describe('AttachButton', () => { expect(handleAttachButtonPress).toHaveBeenCalledTimes(0); }); - await act(() => { - userEvent.press(screen.getByTestId('attach-button')); + act(() => { + fireEvent.press(screen.getByTestId('attach-button')); }); await waitFor(() => { @@ -133,8 +138,8 @@ describe('AttachButton', () => { expect(queryByTestId('attach-button')).toBeTruthy(); }); - await act(() => { - userEvent.press(screen.getByTestId('attach-button')); + act(() => { + fireEvent.press(screen.getByTestId('attach-button')); }); await waitFor(() => { @@ -156,8 +161,8 @@ describe('AttachButton', () => { expect(queryByTestId('attach-button')).toBeTruthy(); }); - await act(() => { - userEvent.press(screen.getByTestId('attach-button')); + act(() => { + fireEvent.press(screen.getByTestId('attach-button')); }); await waitFor(() => { diff --git a/package/src/components/MessageInput/__tests__/AttachmentUploadProgressIndicator.test.js b/package/src/components/MessageInput/__tests__/AttachmentUploadProgressIndicator.test.js index 11f97c53a2..f9774366cf 100644 --- a/package/src/components/MessageInput/__tests__/AttachmentUploadProgressIndicator.test.js +++ b/package/src/components/MessageInput/__tests__/AttachmentUploadProgressIndicator.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; import { ProgressIndicatorTypes } from '../../../utils/utils'; @@ -8,6 +8,11 @@ import { ProgressIndicatorTypes } from '../../../utils/utils'; import { AttachmentUploadProgressIndicator } from '../components/AttachmentPreview/AttachmentUploadProgressIndicator'; describe('AttachmentUploadProgressIndicator', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + it('should render an inactive AttachmentUploadProgressIndicator', async () => { const action = jest.fn(); @@ -88,7 +93,6 @@ describe('AttachmentUploadProgressIndicator', () => { it('should render an active AttachmentUploadProgressIndicator and retry indicator', async () => { const action = jest.fn(); - const user = userEvent.setup(); render( @@ -104,7 +108,9 @@ describe('AttachmentUploadProgressIndicator', () => { expect(action).toHaveBeenCalledTimes(0); }); - user.press(screen.getByTestId('retry-upload-progress-indicator')); + act(() => { + fireEvent.press(screen.getByTestId('retry-upload-progress-indicator')); + }); await waitFor(() => expect(action).toHaveBeenCalledTimes(1)); }); diff --git a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js index fa709ff4b5..13d7b56a0d 100644 --- a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js +++ b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import { OverlayProvider } from '../../../contexts'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; @@ -44,13 +44,15 @@ describe('AudioAttachmentUploadPreview render', () => { let client; let channel; - beforeAll(async () => { + beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); client = chatClient; channel = channels[0]; }); afterEach(() => { + jest.clearAllMocks(); + cleanup(); act(() => { channel.messageComposer.attachmentManager.initState(); }); @@ -70,7 +72,7 @@ describe('AudioAttachmentUploadPreview render', () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments); }); @@ -85,7 +87,7 @@ describe('AudioAttachmentUploadPreview render', () => { expect(queryAllByTestId('upload-progress-indicator')).toHaveLength(1); }); - await act(() => { + act(() => { fireEvent.press(getAllByTestId('remove-upload-preview')[0]); }); @@ -108,7 +110,7 @@ describe('AudioAttachmentUploadPreview render', () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments); }); @@ -139,7 +141,7 @@ describe('AudioAttachmentUploadPreview render', () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments); }); @@ -153,7 +155,7 @@ describe('AudioAttachmentUploadPreview render', () => { expect(queryAllByTestId('retry-upload-progress-indicator')).toHaveLength(1); }); - await act(() => { + act(() => { fireEvent.press(getAllByTestId('retry-upload-progress-indicator')[0]); }); @@ -179,7 +181,7 @@ describe('AudioAttachmentUploadPreview render', () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments); }); diff --git a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewExpo.test.tsx b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewExpo.test.tsx index ec3adbd706..e6044b8a86 100644 --- a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewExpo.test.tsx +++ b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewExpo.test.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import { act } from 'react-test-renderer'; - -import { fireEvent, render, screen } from '@testing-library/react-native'; +import { act, fireEvent, render, screen } from '@testing-library/react-native'; import { MessageInputContext, diff --git a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewNative.test.tsx b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewNative.test.tsx index cee89ceb9e..6535dbdc98 100644 --- a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewNative.test.tsx +++ b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewNative.test.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import { act } from 'react-test-renderer'; - -import { fireEvent, render, screen } from '@testing-library/react-native'; +import { act, fireEvent, render, screen } from '@testing-library/react-native'; import { MessageInputContext, diff --git a/package/src/components/MessageInput/__tests__/CommandsButton.test.js b/package/src/components/MessageInput/__tests__/CommandsButton.test.js index 30f9056a29..cd226cb652 100644 --- a/package/src/components/MessageInput/__tests__/CommandsButton.test.js +++ b/package/src/components/MessageInput/__tests__/CommandsButton.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { act, render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import { OverlayProvider } from '../../../contexts'; @@ -25,12 +25,17 @@ describe('CommandsButton', () => { let client; let channel; - beforeAll(async () => { + beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); client = chatClient; channel = channels[0]; }); + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + it('should render component', async () => { const props = {}; renderComponent({ channel, client, props }); @@ -54,8 +59,8 @@ describe('CommandsButton', () => { expect(queryByTestId('commands-button')).toBeTruthy(); }); - await act(() => { - userEvent.press(getByTestId('commands-button')); + act(() => { + fireEvent.press(getByTestId('commands-button')); }); await waitFor(() => { diff --git a/package/src/components/MessageInput/__tests__/FileUploadPreview.test.js b/package/src/components/MessageInput/__tests__/FileUploadPreview.test.js index 56e5418d5f..dfee5e0cea 100644 --- a/package/src/components/MessageInput/__tests__/FileUploadPreview.test.js +++ b/package/src/components/MessageInput/__tests__/FileUploadPreview.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import { OverlayProvider } from '../../../contexts'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; @@ -49,13 +49,15 @@ describe("FileUploadPreview's render", () => { let client; let channel; - beforeAll(async () => { + beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); client = chatClient; channel = channels[0]; }); afterEach(() => { + jest.clearAllMocks(); + cleanup(); act(() => { channel.messageComposer.attachmentManager.initState(); }); @@ -84,7 +86,7 @@ describe("FileUploadPreview's render", () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); }); @@ -109,7 +111,7 @@ describe("FileUploadPreview's render", () => { const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments); }); @@ -142,7 +144,7 @@ describe("FileUploadPreview's render", () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments); }); @@ -156,7 +158,7 @@ describe("FileUploadPreview's render", () => { expect(queryAllByTestId('upload-progress-indicator')).toHaveLength(2); }); - await act(() => { + act(() => { fireEvent.press(getAllByTestId('remove-upload-preview')[0]); }); @@ -164,7 +166,7 @@ describe("FileUploadPreview's render", () => { expect(channel.messageComposer.attachmentManager.attachments).toHaveLength(1); }); - await act(() => { + act(() => { fireEvent.press(getAllByTestId('remove-upload-preview')[0]); }); @@ -190,7 +192,7 @@ describe("FileUploadPreview's render", () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); }); @@ -223,7 +225,7 @@ describe("FileUploadPreview's render", () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); }); @@ -236,7 +238,7 @@ describe("FileUploadPreview's render", () => { expect(queryAllByTestId('retry-upload-progress-indicator')).toHaveLength(2); }); - await act(() => { + act(() => { fireEvent.press(getAllByTestId('retry-upload-progress-indicator')[0]); }); @@ -264,7 +266,7 @@ describe("FileUploadPreview's render", () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); }); diff --git a/package/src/components/MessageInput/__tests__/ImageUploadPreview.test.js b/package/src/components/MessageInput/__tests__/ImageUploadPreview.test.js index d0a64714f1..7b4b1475f4 100644 --- a/package/src/components/MessageInput/__tests__/ImageUploadPreview.test.js +++ b/package/src/components/MessageInput/__tests__/ImageUploadPreview.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { act, fireEvent, render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import { OverlayProvider } from '../../../contexts'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; @@ -27,13 +27,15 @@ describe('ImageUploadPreview', () => { let client; let channel; - beforeAll(async () => { + beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); client = chatClient; channel = channels[0]; }); afterEach(() => { + jest.clearAllMocks(); + cleanup(); act(() => { channel.messageComposer.clear(); }); @@ -50,7 +52,7 @@ describe('ImageUploadPreview', () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); }); @@ -65,7 +67,7 @@ describe('ImageUploadPreview', () => { }); await act(() => { - userEvent.press(getAllByTestId('remove-upload-preview')[0]); + fireEvent.press(getAllByTestId('remove-upload-preview')[0]); }); await waitFor(() => { @@ -96,7 +98,7 @@ describe('ImageUploadPreview', () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); }); @@ -130,7 +132,7 @@ describe('ImageUploadPreview', () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); }); @@ -150,7 +152,7 @@ describe('ImageUploadPreview', () => { expect(queryAllByTestId('retry-upload-progress-indicator')).toHaveLength(1); }); - await act(() => { + act(() => { fireEvent.press(getAllByTestId('retry-upload-progress-indicator')[0]); }); @@ -172,7 +174,7 @@ describe('ImageUploadPreview', () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); }); @@ -222,7 +224,7 @@ describe('ImageUploadPreview', () => { ]; const props = {}; - await act(() => { + act(() => { channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); }); diff --git a/package/src/components/MessageInput/__tests__/InputButtons.test.js b/package/src/components/MessageInput/__tests__/InputButtons.test.js index 9ee51f4e8c..501fb72dba 100644 --- a/package/src/components/MessageInput/__tests__/InputButtons.test.js +++ b/package/src/components/MessageInput/__tests__/InputButtons.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { act, cleanup, render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import { OverlayProvider } from '../../../contexts'; @@ -27,7 +27,7 @@ describe('InputButtons', () => { let client; let channel; - beforeAll(async () => { + beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); client = chatClient; channel = channels[0]; @@ -36,9 +36,14 @@ describe('InputButtons', () => { afterEach(() => { jest.clearAllMocks(); cleanup(); + + act(() => { + channel.messageComposer.clear(); + }); }); - it('should return null if the commands are set on the textComposer', async () => { + // TODO: Add it back once the command inject PR is merged + it.skip('should return null if the commands are set on the textComposer', async () => { const props = {}; const channelProps = { channel }; @@ -53,10 +58,6 @@ describe('InputButtons', () => { expect(queryByTestId('commands-button')).toBeFalsy(); expect(queryByTestId('attach-button')).toBeFalsy(); }); - - await act(() => { - channel.messageComposer.clear(); - }); }); it('should return null if hasCommands is false and hasAttachmentUploadCapabilities is false', async () => { @@ -161,8 +162,8 @@ describe('InputButtons', () => { expect(queryByTestId('more-options-button')).toBeTruthy(); }); - await act(() => { - userEvent.press(queryByTestId('more-options-button')); + act(() => { + fireEvent.press(queryByTestId('more-options-button')); }); await waitFor(() => { @@ -170,10 +171,6 @@ describe('InputButtons', () => { expect(queryByTestId('commands-button')).toBeFalsy(); expect(queryByTestId('attach-button')).toBeTruthy(); }); - - await act(() => { - channel.messageComposer.clear(); - }); }); it('should show more options button when there is attachments', async () => { @@ -198,17 +195,13 @@ describe('InputButtons', () => { expect(queryByTestId('more-options-button')).toBeTruthy(); }); - await act(() => { - userEvent.press(queryByTestId('more-options-button')); + act(() => { + fireEvent.press(queryByTestId('more-options-button')); }); await waitFor(() => { expect(queryByTestId('commands-button')).toBeTruthy(); expect(queryByTestId('attach-button')).toBeTruthy(); }); - - await act(() => { - channel.messageComposer.clear(); - }); }); }); diff --git a/package/src/components/MessageInput/__tests__/MessageInput.test.js b/package/src/components/MessageInput/__tests__/MessageInput.test.js index c1e8c52bea..4243d7eba6 100644 --- a/package/src/components/MessageInput/__tests__/MessageInput.test.js +++ b/package/src/components/MessageInput/__tests__/MessageInput.test.js @@ -2,7 +2,7 @@ import React from 'react'; import { Alert } from 'react-native'; -import { act, fireEvent, render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import * as AttachmentPickerUtils from '../../../contexts/attachmentPickerContext/AttachmentPickerContext'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; @@ -52,7 +52,9 @@ describe('MessageInput', () => { let client; let channel; - beforeAll(async () => { + beforeEach(async () => { + jest.clearAllMocks(); + cleanup(); const { client: chatClient, channels } = await initiateClientWithChannels(); client = chatClient; channel = channels[0]; @@ -75,7 +77,9 @@ describe('MessageInput', () => { const { getByTestId, queryByTestId, queryByText } = screen; - fireEvent.press(getByTestId('attach-button')); + act(() => { + fireEvent.press(getByTestId('attach-button')); + }); await waitFor(() => { expect(queryByTestId('upload-photo-touchable')).toBeTruthy(); @@ -87,10 +91,6 @@ describe('MessageInput', () => { }); it('should start the audio recorder on long press and cleanup on unmount', async () => { - jest.clearAllMocks(); - - const userBot = userEvent.setup(); - renderComponent({ channelProps: { audioRecordingEnabled: true, channel }, client, @@ -99,8 +99,10 @@ describe('MessageInput', () => { const { queryByTestId, unmount } = screen; + const audioButton = queryByTestId('audio-button'); + act(() => { - userBot.longPress(queryByTestId('audio-button'), { duration: 1000 }); + fireEvent(audioButton, 'longPress'); }); await waitFor(() => { @@ -122,10 +124,6 @@ describe('MessageInput', () => { }); it('should trigger an alert if a normal press happened on audio recording', async () => { - jest.clearAllMocks(); - - const userBot = userEvent.setup(); - renderComponent({ channelProps: { audioRecordingEnabled: true, channel }, client, @@ -134,8 +132,10 @@ describe('MessageInput', () => { const { queryByTestId } = screen; + const audioButton = queryByTestId('audio-button'); + act(() => { - userBot.press(queryByTestId('audio-button')); + fireEvent.press(audioButton); }); await waitFor(() => { diff --git a/package/src/components/MessageInput/__tests__/SendButton.test.js b/package/src/components/MessageInput/__tests__/SendButton.test.js index 7de672de98..e501588eb6 100644 --- a/package/src/components/MessageInput/__tests__/SendButton.test.js +++ b/package/src/components/MessageInput/__tests__/SendButton.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { act, render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import { OverlayProvider } from '../../../contexts'; @@ -25,12 +25,21 @@ describe('SendButton', () => { let client; let channel; - beforeAll(async () => { + beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); client = chatClient; channel = channels[0]; }); + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + + act(() => { + channel.messageComposer.clear(); + }); + }); + it('should render a SendButton', async () => { const sendMessage = jest.fn(); @@ -45,8 +54,8 @@ describe('SendButton', () => { expect(sendMessage).toHaveBeenCalledTimes(0); }); - await act(() => { - userEvent.press(getByTestId('send-button')); + act(() => { + fireEvent.press(getByTestId('send-button')); }); await waitFor(() => { @@ -75,8 +84,8 @@ describe('SendButton', () => { expect(sendMessage).toHaveBeenCalledTimes(0); }); - await act(() => { - userEvent.press(getByTestId('send-button')); + act(() => { + fireEvent.press(getByTestId('send-button')); }); await waitFor(() => { @@ -91,7 +100,8 @@ describe('SendButton', () => { }); }); - it('should show search button if the command is enabled', async () => { + // TODO: Add it back once the command inject PR is merged + it.skip('should show search button if the command is enabled', async () => { const sendMessage = jest.fn(); const props = { sendMessage }; @@ -105,9 +115,5 @@ describe('SendButton', () => { await waitFor(() => { expect(queryByTestId('search-icon')).toBeTruthy(); }); - - await act(() => { - channel.messageComposer.clear(); - }); }); }); diff --git a/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.js b/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.js index 6a20b23bdf..c7613cd31e 100644 --- a/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.js +++ b/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.js @@ -2,7 +2,7 @@ import React from 'react'; import { Alert } from 'react-native'; -import { act, render, screen, waitFor } from '@testing-library/react-native'; +import { act, cleanup, render, screen, waitFor } from '@testing-library/react-native'; import { MessageComposer } from 'stream-chat'; @@ -65,13 +65,15 @@ describe('SendMessageDisallowedIndicator', () => { let client; let channel; - beforeAll(async () => { + beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); client = chatClient; channel = channels[0]; }); afterEach(() => { + jest.clearAllMocks(); + cleanup(); act(() => { channel.messageComposer.clear(); }); @@ -149,7 +151,7 @@ describe("SendMessageDisallowedIndicator's edited state", () => { const message = generateMessage({ attachments: [generateLocalFileUploadAttachmentData()], cid: 'messaging:channel-id', - text: 'XXX', + text: 'test', }); const { channel: customChannel, chatClient } = await editedMessageSetup({ diff --git a/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap b/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap index 030b424476..01394e1138 100644 --- a/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap +++ b/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap @@ -89,48 +89,9 @@ exports[`AttachButton should call handleAttachButtonPress when the button is cli "type": 0, } } - } - accessible={true} - collapsable={false} - focusable={true} - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onLayout={[Function]} - onResponderGrant={[Function]} - onResponderMove={[Function]} - onResponderRelease={[Function]} - onResponderTerminate={[Function]} - onResponderTerminationRequest={[Function]} - onStartShouldSetResponder={[Function]} - style={ - [ - {}, - ] - } - testID="attach-button" - > - @@ -138,7 +99,7 @@ exports[`AttachButton should call handleAttachButtonPress when the button is cli clipPath="id" fill={ { - "payload": 4286216826, + "payload": 4278190080, "type": 0, } } @@ -338,48 +299,9 @@ exports[`AttachButton should render a enabled AttachButton 1`] = ` "type": 0, } } - } - accessible={true} - collapsable={false} - focusable={true} - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onLayout={[Function]} - onResponderGrant={[Function]} - onResponderMove={[Function]} - onResponderRelease={[Function]} - onResponderTerminate={[Function]} - onResponderTerminationRequest={[Function]} - onStartShouldSetResponder={[Function]} - style={ - [ - {}, - ] - } - testID="attach-button" - > - @@ -387,7 +309,7 @@ exports[`AttachButton should render a enabled AttachButton 1`] = ` clipPath="id" fill={ { - "payload": 4286216826, + "payload": 4278190080, "type": 0, } } diff --git a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.js b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.js index e77cbad740..788c1e51ea 100644 --- a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.js +++ b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.js @@ -10,6 +10,11 @@ import { ScrollToBottomButton } from '../ScrollToBottomButton'; afterEach(cleanup); describe('ScrollToBottomButton', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + it('should render nothing if showNotification is false', async () => { const i18nInstance = new Streami18n(); const translators = await i18nInstance.getTranslators(); diff --git a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx index 12d87b84ef..e93cefa59b 100644 --- a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx @@ -110,9 +110,9 @@ describe('MessageUserReactions when the supportedReactions are defined', () => { expect(reactionButtons[1].props.accessibilityLabel).toBe('reaction-button-love-selected'); }); - it('renders reactions list', async () => { + it('renders reactions list', () => { const { getByText } = renderComponent(); - const reactionItems = await getByText('1 like'); + const reactionItems = getByText('1 like'); expect(reactionItems).toBeDefined(); }); diff --git a/package/src/components/Thread/__tests__/Thread.test.js b/package/src/components/Thread/__tests__/Thread.test.js index edb8fedefe..185e3eeaa4 100644 --- a/package/src/components/Thread/__tests__/Thread.test.js +++ b/package/src/components/Thread/__tests__/Thread.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { act, cleanup, render, waitFor } from '@testing-library/react-native'; +import { act, cleanup, render, screen, waitFor } from '@testing-library/react-native'; import { v5 as uuidv5 } from 'uuid'; import { AttachmentPickerProvider } from '../../../contexts/attachmentPickerContext/AttachmentPickerContext'; @@ -9,6 +9,7 @@ import { ChannelsStateProvider } from '../../../contexts/channelsStateContext/Ch import { ImageGalleryProvider } from '../../../contexts/imageGalleryContext/ImageGalleryContext'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; +import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; import { useMockedApis } from '../../../mock-builders/api/useMockedApis'; import { generateChannelResponse } from '../../../mock-builders/generator/channel'; import { generateMember } from '../../../mock-builders/generator/member'; @@ -22,38 +23,52 @@ import { Thread } from '../Thread'; const StreamReactNativeNamespace = '9b244ee4-7d69-4d7b-ae23-cf89e9f7b035'; -afterEach(cleanup); +const renderComponent = ({ chatClient, channel, props, thread }) => { + return render( + + + + + + + , + ); +}; describe('Thread', () => { + let chatClient; + let channel; + + beforeEach(async () => { + const { client: client, channels } = await initiateClientWithChannels(); + chatClient = client; + channel = channels[0]; + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + it('should render a new thread', async () => { - const thread = generateMessage({ text: 'Thread Message Text' }); - const thread2 = generateMessage({ text: 'Thread2 Message Text' }); + const cid = 'messaging:test-channel'; + const thread = generateMessage({ cid, text: 'Thread Message Text' }); const parent_id = thread.id; + const props = { + thread, + }; + const threadResponses = [ - generateMessage({ parent_id, text: 'Response Message Text' }), - generateMessage({ parent_id }), - generateMessage({ parent_id }), + generateMessage({ cid, parent_id, text: 'Response Message Text' }), + generateMessage({ cid, parent_id }), + generateMessage({ cid, parent_id }), ]; - const mockedChannel = generateChannelResponse({ - messages: [thread, thread2], - }); - - const chatClient = await getTestClientWithUser({ id: 'Neil' }); - useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); - await channel.watch(); channel.state.addMessagesSorted(threadResponses); - const { getAllByText, getByText, queryByText } = render( - - - - - - - , - ); + renderComponent({ channel, chatClient, props, thread }); + + const { getAllByText, getByText, queryByText } = screen; await waitFor(() => { expect(getByText('Also send to channel')).toBeTruthy(); @@ -63,16 +78,33 @@ describe('Thread', () => { }, 10000); it('should match thread snapshot', async () => { + const cid = 'messaging:test-channel'; const i18nInstance = new Streami18n(); const user1 = generateStaticUser(1); const user2 = generateStaticUser(3); - const thread = generateStaticMessage('Message3', { user: user2 }, '2020-05-05T14:50:00.000Z'); + const thread = generateStaticMessage( + 'Message3', + { cid, user: user2 }, + '2020-05-05T14:50:00.000Z', + ); const parent_id = thread.id; const threadResponses = [ - generateStaticMessage('Message4', { parent_id, user: user1 }, '2020-05-05T14:50:00.000Z'), - generateStaticMessage('Message5', { parent_id, user: user2 }, '2020-05-05T14:50:00.000Z'), - generateStaticMessage('Message6', { parent_id, user: user1 }, '2020-05-05T14:50:00.000Z'), + generateStaticMessage( + 'Message4', + { cid, parent_id, user: user1 }, + '2020-05-05T14:50:00.000Z', + ), + generateStaticMessage( + 'Message5', + { cid, parent_id, user: user2 }, + '2020-05-05T14:50:00.000Z', + ), + generateStaticMessage( + 'Message6', + { cid, parent_id, user: user1 }, + '2020-05-05T14:50:00.000Z', + ), ]; const mockedChannel = generateChannelResponse({ @@ -81,8 +113,8 @@ describe('Thread', () => { }, members: [generateMember({ user: user1 }), generateMember({ user: user1 })], messages: [ - generateStaticMessage('Message1', { user: user1 }, '2020-05-05T14:48:00.000Z'), - generateStaticMessage('Message2', { user: user2 }, '2020-05-05T14:49:00.000Z'), + generateStaticMessage('Message1', { cid, user: user1 }, '2020-05-05T14:48:00.000Z'), + generateStaticMessage('Message2', { cid, user: user2 }, '2020-05-05T14:49:00.000Z'), thread, ...threadResponses, ], diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap index e607860800..1c8a611958 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap @@ -50,6 +50,7 @@ exports[`Thread should match thread snapshot 1`] = ` [ { "attachments": [], + "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, "error": null, @@ -77,6 +78,7 @@ exports[`Thread should match thread snapshot 1`] = ` }, { "attachments": [], + "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, "error": null, @@ -104,6 +106,7 @@ exports[`Thread should match thread snapshot 1`] = ` }, { "attachments": [], + "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, "error": null, @@ -1926,7 +1929,7 @@ exports[`Thread should match thread snapshot 1`] = ` { "busy": undefined, "checked": undefined, - "disabled": undefined, + "disabled": false, "expanded": undefined, "selected": undefined, } @@ -2062,73 +2065,84 @@ exports[`Thread should match thread snapshot 1`] = ` - + - - - - - - + /> + + + + + + + + `; diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index d68c422a37..47e372f7a8 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -179,9 +179,10 @@ export type InputMessageInputContextValue = { AutoCompleteSuggestionList: React.ComponentType; /** - * Custom UI component to render [draggable handle](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) of attachment picker. + * Custom UI component to render [draggable handle](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) of attachmentpicker. * - * **Default** [AttachmentPickerBottomSheetHandle](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/AttachmentPickerBottomSheetHandle.tsx) + * **Default** + * [AttachmentPickerBottomSheetHandle](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/AttachmentPickerBottomSheetHandle.tsx) */ AttachmentPickerBottomSheetHandle: React.FC; /** @@ -199,7 +200,8 @@ export type InputMessageInputContextValue = { /** * Custom UI component for AttachmentPickerSelectionBar * - * **Default: ** [AttachmentPickerSelectionBar](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/AttachmentPicker/components/AttachmentPickerSelectionBar.tsx) + * **Default: ** + * [AttachmentPickerSelectionBar](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/AttachmentPicker/components/AttachmentPickerSelectionBar.tsx) */ AttachmentPickerSelectionBar: React.ComponentType; /** @@ -212,31 +214,36 @@ export type InputMessageInputContextValue = { /** * Custom UI component for [camera selector icon](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) * - * **Default: ** [CameraSelectorIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/CameraSelectorIcon.tsx) + * **Default: ** + * [CameraSelectorIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/CameraSelectorIcon.tsx) */ CameraSelectorIcon: React.ComponentType; /** * Custom UI component for the poll creation icon. * - * **Default: ** [CreatePollIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/CreatePollIcon.tsx) + * **Default: ** + * [CreatePollIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/CreatePollIcon.tsx) */ CreatePollIcon: React.ComponentType; /** * Custom UI component for [file selector icon](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) * - * **Default: ** [FileSelectorIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/FileSelectorIcon.tsx) + * **Default: ** + * [FileSelectorIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/FileSelectorIcon.tsx) */ FileSelectorIcon: React.ComponentType; /** * Custom UI component for [image selector icon](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) * - * **Default: ** [ImageSelectorIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/ImageSelectorIcon.tsx) + * **Default: ** + * [ImageSelectorIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/ImageSelectorIcon.tsx) */ ImageSelectorIcon: React.ComponentType; /** * Custom UI component for Android's video recorder selector icon. * - * **Default: ** [VideoRecorderSelectorIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/VideoRecorderSelectorIcon.tsx) + * **Default: ** + * [VideoRecorderSelectorIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/VideoRecorderSelectorIcon.tsx) */ VideoRecorderSelectorIcon: React.ComponentType; AudioAttachmentUploadPreview: React.ComponentType; @@ -348,8 +355,7 @@ export type InputMessageInputContextValue = { compressImageQuality?: number; /** - * Override the entire content of the CreatePoll component. The component has full access to the - * useCreatePollContext() hook. + * Override the entire content of the CreatePoll component. The component has full access to the useCreatePollContext() hook. * */ CreatePollContent?: React.ComponentType; @@ -369,8 +375,7 @@ export type InputMessageInputContextValue = { /** * Custom UI component for AutoCompleteInput. - * Has access to all of - * [MessageInputContext](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/contexts/messageInputContext/MessageInputContext.tsx) + * Has access to all of [MessageInputContext](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/contexts/messageInputContext/MessageInputContext.tsx) */ Input?: React.ComponentType< Omit & @@ -447,7 +452,8 @@ export const MessageInputProvider = ({ /** * These are the RN SDK specific middlewares that are added to the message composer to provide the default behaviour. - * TODO: Discuss and decide if we provide them by default in the SDK or leave it to the user to add them if they want the feature. + * TODO: Discuss and decide if we provide them by default in the SDK or leave it to the user to add them if they want + * the feature. */ useEffect(() => { if (value.doFileUploadRequest) { @@ -479,10 +485,16 @@ export const MessageInputProvider = ({ * Function for capturing a photo and uploading it */ const takeAndUploadImage = useStableCallback(async (mediaType?: MediaTypes) => { + if (!availableUploadSlots) { + Alert.alert(t('Maximum number of files reached')); + return; + } + const file = await NativeHandlers.takePhoto({ compressImageQuality: value.compressImageQuality, mediaType, }); + if (file.askToOpenSettings) { Alert.alert( t('Allow camera access in device settings'), @@ -494,20 +506,22 @@ export const MessageInputProvider = ({ ); } - if (!availableUploadSlots) { - Alert.alert(t('Maximum number of files reached')); + if (file.cancelled) { return; } - if (!file.cancelled) { - await uploadNewFile(file); - } + await uploadNewFile(file); }); /** * Function for picking a photo from native image picker and uploading it */ const pickAndUploadImageFromNativePicker = useStableCallback(async () => { + if (!availableUploadSlots) { + Alert.alert(t('Maximum number of files reached')); + return; + } + const result = await NativeHandlers.pickImage(); if (result.askToOpenSettings) { Alert.alert( @@ -520,16 +534,13 @@ export const MessageInputProvider = ({ ); } - if (!availableUploadSlots) { - Alert.alert(t('Maximum number of files reached')); + if (result.cancelled || !result.assets?.length) { return; } - if (result.assets && result.assets.length > 0) { - result.assets.forEach(async (asset) => { - await uploadNewFile(asset); - }); - } + result.assets.forEach(async (asset) => { + await uploadNewFile(asset); + }); }); const pickFile = useStableCallback(async () => { @@ -549,11 +560,13 @@ export const MessageInputProvider = ({ maxNumberOfFiles: availableUploadSlots, }); - if (!result.cancelled && result.assets) { - result.assets.forEach(async (asset) => { - await uploadNewFile(asset); - }); + if (result.cancelled || !result.assets?.length) { + return; } + + result.assets.forEach(async (asset) => { + await uploadNewFile(asset); + }); }); /** @@ -591,47 +604,58 @@ export const MessageInputProvider = ({ inputBoxRef.current.clear(); } - const composition = await messageComposer.compose(); - if (!composition || !composition.message) return; + try { + const composition = await messageComposer.compose(); + // This is added to ensure the input box is cleared if there's no change and user presses on the send button. + if (!composition && editedMessage) { + clearEditingState(); + } + if (!composition || !composition.message) return; - const { localMessage, message, sendOptions } = composition; - const linkInfos = parseLinksFromText(localMessage.text); + const { localMessage, message, sendOptions } = composition; + const linkInfos = parseLinksFromText(localMessage.text); - if (!channelCapabilities.sendLinks && linkInfos.length > 0) { - Alert.alert(t('Links are disabled'), t('Sending links is not allowed in this conversation')); + if (!channelCapabilities.sendLinks && linkInfos.length > 0) { + Alert.alert( + t('Links are disabled'), + t('Sending links is not allowed in this conversation'), + ); - return; - } - - if (editedMessage && editedMessage.type !== 'error') { - try { - clearEditingState(); - await value.editMessage({ localMessage, options: sendOptions }); - } catch (error) { - console.log('Failed to edit message:', error); + return; } - } else { - try { - // Since the message id does not get cleared, we have to handle this manually - // and let the poll creation dialog handle clearing the rest of the state. Once - // sending a message has been moved to the composer as an API, this will be - // redundant and can be removed. - if (localMessage.poll_id) { - messageComposer.state.partialNext({ - id: MessageComposer.generateId(), - pollId: null, + + if (editedMessage && editedMessage.type !== 'error') { + try { + clearEditingState(); + await value.editMessage({ localMessage, options: sendOptions }); + } catch (error) { + throw new Error('Error while editing message'); + } + } else { + try { + // Since the message id does not get cleared, we have to handle this manually + // and let the poll creation dialog handle clearing the rest of the state. Once + // sending a message has been moved to the composer as an API, this will be + // redundant and can be removed. + if (localMessage.poll_id) { + messageComposer.state.partialNext({ + id: MessageComposer.generateId(), + pollId: null, + }); + } else { + messageComposer.clear(); + } + await value.sendMessage({ + localMessage, + message, + options: sendOptions, }); - } else { - messageComposer.clear(); + } catch (error) { + throw new Error('Error while sending message'); } - await value.sendMessage({ - localMessage, - message, - options: sendOptions, - }); - } catch (error) { - console.log('Failed to send message:', error); } + } catch (error) { + console.error('Error while sending message:', error); } }); diff --git a/package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap b/package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap deleted file mode 100644 index a4dd5829ed..0000000000 --- a/package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap +++ /dev/null @@ -1,141 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MessageInputContext's sendMessage exit sendMessage when edit message is not boolean image upload status is uploaded successfully 1`] = ` -{ - "attachments": [ - { - "fallback": undefined, - "image_url": undefined, - "mime_type": undefined, - "originalImage": { - "uri": "http://www.jackblack.com/tenac_iousd.bmp", - }, - "original_height": undefined, - "original_width": undefined, - "type": "image", - }, - ], - "html": "

regular

", - "id": "7a85f744-cc89-4f82-a1d4-5456432cc8bf", - "mentioned_users": [ - { - "id": "dummy1", - }, - { - "id": "dummy2", - }, - ], - "quoted_message": undefined, - "text": "", - "user": { - "banned": false, - "created_at": "2020-04-27T13:39:49.331742Z", - "id": "5d6f6322-567e-4e1e-af90-97ef1ed5cc23", - "image": "fc86ddcb-bac4-400c-9afd-b0c0a1c0cd33", - "name": "50cbdd0e-ca7e-4478-9e2c-be0f1ac6a995", - "online": false, - "role": "user", - "updated_at": "2020-04-27T13:39:49.332087Z", - }, -} -`; - -exports[`MessageInputContext's sendMessage exit sendMessage when file upload status is uploaded successfully 1`] = ` -{ - "attachments": [ - { - "asset_url": undefined, - "file_size": undefined, - "mime_type": "file", - "originalFile": { - "name": "dummy.pdf", - "state": "uploaded", - "type": "file", - }, - "title": "dummy.pdf", - "type": "file", - }, - { - "asset_url": undefined, - "file_size": undefined, - "mime_type": "video/mp4", - "originalFile": { - "name": "dummy.pdf", - "state": "finished", - "type": "video/mp4", - }, - "title": "dummy.pdf", - "type": "file", - }, - { - "asset_url": undefined, - "file_size": undefined, - "mime_type": "audio/mp3", - "originalFile": { - "name": "dummy.pdf", - "state": "uploaded", - "type": "audio/mp3", - }, - "title": "dummy.pdf", - "type": "file", - }, - { - "asset_url": undefined, - "file_size": undefined, - "mime_type": "image/jpeg", - "originalFile": { - "name": "dummy.pdf", - "state": "finished", - "type": "image/jpeg", - }, - "title": "dummy.pdf", - "type": "file", - }, - ], - "mentioned_users": [ - "dummy1", - "dummy2", - ], - "parent_id": undefined, - "quoted_message_id": undefined, - "show_in_channel": undefined, - "text": "", -} -`; - -exports[`MessageInputContext's sendMessage exit sendMessage when image upload status is uploaded successfully 1`] = ` -{ - "attachments": [ - { - "fallback": undefined, - "image_url": undefined, - "mime_type": undefined, - "originalImage": { - "uri": "http://www.jackblack.com/tenac_iousd.bmp", - }, - "original_height": undefined, - "original_width": undefined, - "type": "image", - }, - { - "fallback": undefined, - "image_url": undefined, - "mime_type": undefined, - "originalImage": { - "uri": "http://www.jackblack.com/tenac_iousd.bmp", - }, - "original_height": undefined, - "original_width": undefined, - "type": "image", - }, - ], - "mentioned_users": [ - "dummy1", - "dummy2", - ], - "parent_id": undefined, - "quoted_message_id": undefined, - "show_in_channel": undefined, - "text": "", -} -`; diff --git a/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx b/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx new file mode 100644 index 0000000000..6585f39e1f --- /dev/null +++ b/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx @@ -0,0 +1,327 @@ +import React from 'react'; +import { Alert } from 'react-native'; + +import { cleanup, renderHook, waitFor } from '@testing-library/react-native'; + +import { Chat } from '../../../components'; +import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; +import { generateFileReference } from '../../../mock-builders/attachments'; + +import { NativeHandlers } from '../../../native'; + +import { ChannelContextValue, ChannelProvider } from '../../channelContext/ChannelContext'; +import { MessageComposerProvider } from '../../messageComposerContext/MessageComposerContext'; +import { + InputMessageInputContextValue, + MessageInputProvider, + useMessageInputContext, +} from '../MessageInputContext'; + +jest.spyOn(Alert, 'alert'); + +const Wrapper = ({ channel, client, props }) => { + return ( + + + + + {props.children} + + + + + ); +}; + +describe("MessageInputContext's pickFile", () => { + let channel; + let chatClient; + + beforeEach(async () => { + const { client, channels } = await initiateClientWithChannels(); + channel = channels[0]; + chatClient = client; + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + const initialProps = {}; + + it('run pickFile when availableUploadSlots is 0 and alert is triggered 1 number of times', async () => { + jest + .spyOn(channel.messageComposer.attachmentManager, 'availableUploadSlots', 'get') + .mockReturnValue(0); + const { result } = renderHook(() => useMessageInputContext(), { + initialProps, + wrapper: (props) => , + }); + + await waitFor(() => { + result.current.pickFile(); + }); + + expect(Alert.alert).toHaveBeenCalledTimes(1); + expect(Alert.alert).toHaveBeenCalledWith('Maximum number of files reached'); + }); + + it.each([ + [false, undefined, true], + [false, undefined, false], + [false, [generateFileReference(), generateFileReference()], true], + [false, [], false], + [true, [generateFileReference(), generateFileReference()], false], + ])( + 'allow uploads %p when pickDocument returns assets %p and cancelled %p', + async (allowed, assets, cancelled) => { + const { attachmentManager } = channel.messageComposer; + jest.spyOn(NativeHandlers, 'pickDocument').mockImplementation( + jest.fn().mockResolvedValue({ + assets, + cancelled, + }), + ); + + jest.spyOn(attachmentManager, 'availableUploadSlots', 'get').mockReturnValue(2); + + const { result } = renderHook(() => useMessageInputContext(), { + initialProps, + wrapper: (props) => , + }); + + const uploadFilesSpy = jest.spyOn(attachmentManager, 'uploadFiles'); + + await waitFor(() => { + result.current.pickFile(); + }); + + await waitFor(() => { + expect(NativeHandlers.pickDocument).toHaveBeenCalledTimes(1); + expect(NativeHandlers.pickDocument).toHaveBeenCalledWith({ maxNumberOfFiles: 2 }); + if (allowed) { + expect(uploadFilesSpy).toHaveBeenCalledTimes(2); + } else { + expect(uploadFilesSpy).not.toHaveBeenCalled(); + } + }); + }, + ); +}); + +describe("MessageInputContext's pickAndUploadImageFromNativePicker", () => { + let channel; + let chatClient; + + beforeEach(async () => { + const { client, channels } = await initiateClientWithChannels(); + channel = channels[0]; + chatClient = client; + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + const initialProps = {}; + + it('run pickAndUploadImageFromNativePicker when availableUploadSlots is 0 and alert is triggered 1 number of times', async () => { + jest + .spyOn(channel.messageComposer.attachmentManager, 'availableUploadSlots', 'get') + .mockReturnValue(0); + const { result } = renderHook(() => useMessageInputContext(), { + initialProps, + wrapper: (props) => , + }); + + await waitFor(() => { + result.current.pickAndUploadImageFromNativePicker(); + }); + + expect(Alert.alert).toHaveBeenCalledTimes(1); + expect(Alert.alert).toHaveBeenCalledWith('Maximum number of files reached'); + }); + + it('should show permissions alert when askToOpenSettings is true', async () => { + jest + .spyOn(channel.messageComposer.attachmentManager, 'availableUploadSlots', 'get') + .mockReturnValue(2); + + jest.spyOn(NativeHandlers, 'pickImage').mockImplementation( + jest.fn().mockResolvedValue({ + askToOpenSettings: true, + }), + ); + + const { result } = renderHook(() => useMessageInputContext(), { + initialProps, + wrapper: (props) => , + }); + + await waitFor(() => { + result.current.pickAndUploadImageFromNativePicker(); + }); + + expect(Alert.alert).toHaveBeenCalledTimes(1); + }); + + it.each([ + [false, undefined, true], + [false, undefined, false], + [false, [generateFileReference(), generateFileReference()], true], + [false, [], false], + [true, [generateFileReference(), generateFileReference()], false], + ])( + 'allow uploads %p when pickImage returns assets %p and cancelled %p', + async (allowed, assets, cancelled) => { + const { attachmentManager } = channel.messageComposer; + jest.spyOn(NativeHandlers, 'pickImage').mockImplementation( + jest.fn().mockResolvedValue({ + assets, + cancelled, + }), + ); + + jest.spyOn(attachmentManager, 'availableUploadSlots', 'get').mockReturnValue(2); + + const { result } = renderHook(() => useMessageInputContext(), { + initialProps, + wrapper: (props) => , + }); + + const uploadFilesSpy = jest.spyOn(attachmentManager, 'uploadFiles'); + + await waitFor(() => { + result.current.pickAndUploadImageFromNativePicker(); + }); + + await waitFor(() => { + expect(NativeHandlers.pickImage).toHaveBeenCalledTimes(1); + if (allowed) { + expect(uploadFilesSpy).toHaveBeenCalledTimes(2); + } else { + expect(uploadFilesSpy).not.toHaveBeenCalled(); + } + }); + }, + ); +}); + +describe("MessageInputContext's takeAndUploadImage", () => { + let channel; + let chatClient; + + beforeEach(async () => { + const { client, channels } = await initiateClientWithChannels(); + channel = channels[0]; + chatClient = client; + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + const initialProps = {}; + + it('run takeAndUploadImage when availableUploadSlots is 0 and alert is triggered 1 number of times', async () => { + jest + .spyOn(channel.messageComposer.attachmentManager, 'availableUploadSlots', 'get') + .mockReturnValue(0); + const { result } = renderHook(() => useMessageInputContext(), { + initialProps, + wrapper: (props) => , + }); + + await waitFor(() => { + result.current.takeAndUploadImage(); + }); + + expect(Alert.alert).toHaveBeenCalledTimes(1); + expect(Alert.alert).toHaveBeenCalledWith('Maximum number of files reached'); + }); + + it('should show permissions alert when askToOpenSettings is true', async () => { + jest + .spyOn(channel.messageComposer.attachmentManager, 'availableUploadSlots', 'get') + .mockReturnValue(2); + + jest.spyOn(NativeHandlers, 'takePhoto').mockImplementation( + jest.fn().mockResolvedValue({ + askToOpenSettings: true, + }), + ); + + const { result } = renderHook(() => useMessageInputContext(), { + initialProps, + wrapper: (props) => , + }); + + await waitFor(() => { + result.current.takeAndUploadImage(); + }); + + expect(Alert.alert).toHaveBeenCalledTimes(1); + }); + + it.each([ + [false, undefined, true], + [false, undefined, false], + [false, generateFileReference(), true], + [false, [], false], + [true, generateFileReference(), false], + ])( + 'allow uploads %p when pickImage returns assets %p and cancelled %p', + async (allowed, asset, cancelled) => { + const { attachmentManager } = channel.messageComposer; + jest.spyOn(NativeHandlers, 'takePhoto').mockImplementation( + jest.fn().mockResolvedValue({ + ...asset, + cancelled, + }), + ); + + jest.spyOn(attachmentManager, 'availableUploadSlots', 'get').mockReturnValue(2); + + const { result } = renderHook(() => useMessageInputContext(), { + initialProps, + wrapper: (props) => , + }); + + const uploadFilesSpy = jest.spyOn(attachmentManager, 'uploadFiles'); + + await waitFor(() => { + result.current.takeAndUploadImage(); + }); + + await waitFor(() => { + expect(NativeHandlers.takePhoto).toHaveBeenCalledTimes(1); + expect(NativeHandlers.takePhoto).toHaveBeenCalledWith({ + compressImageQuality: undefined, + mediaType: undefined, + }); + if (allowed) { + expect(uploadFilesSpy).toHaveBeenCalledTimes(1); + } else { + expect(uploadFilesSpy).not.toHaveBeenCalled(); + } + }); + }, + ); +}); diff --git a/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx b/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx deleted file mode 100644 index 7a2e8be634..0000000000 --- a/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { PropsWithChildren } from 'react'; -import { Alert } from 'react-native'; -import { act } from 'react-test-renderer'; - -import { renderHook, waitFor } from '@testing-library/react-native'; - -import { generateFileAttachment } from '../../../mock-builders/generator/attachment'; - -import { generateMessage } from '../../../mock-builders/generator/message'; -import { generateUser } from '../../../mock-builders/generator/user'; -import { NativeHandlers } from '../../../native'; - -import * as AttachmentPickerContext from '../../attachmentPickerContext/AttachmentPickerContext'; - -import { - InputMessageInputContextValue, - MessageInputContextValue, - MessageInputProvider, - useMessageInputContext, -} from '../MessageInputContext'; - -const user1 = generateUser(); -const message = generateMessage({ user: user1 }); -type WrapperType = Partial; - -const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( - - {children} - -); - -describe("MessageInputContext's pickFile", () => { - afterEach(jest.clearAllMocks); - jest.spyOn(Alert, 'alert'); - jest.spyOn(NativeHandlers, 'pickDocument').mockImplementation( - jest.fn().mockResolvedValue({ - assets: [ - generateFileAttachment({ size: 500000000 }), - generateFileAttachment({ size: 600000000 }), - ], - cancelled: false, - }), - ); - jest.spyOn(AttachmentPickerContext, 'useAttachmentPickerContext').mockImplementation(() => ({ - selectedFiles: [], - setSelectedFiles: jest.fn(), - })); - - const initialProps = { - editing: message, - maxNumberOfFiles: 2, - }; - - it.each([[3, 1]])( - 'run pickFile when numberOfUploads is %d and alert is triggered %d number of times', - async (numberOfUploads, numberOfTimesCalled) => { - const { rerender, result } = renderHook(() => useMessageInputContext(), { - initialProps, - wrapper: (props) => ( - - ), - }); - - act(() => { - result.current.setNumberOfUploads(numberOfUploads); - }); - - rerender({ editing: message, maxNumberOfFiles: 2 }); - - await waitFor(() => { - result.current.pickFile(); - }); - - expect(Alert.alert).toHaveBeenCalledTimes(numberOfTimesCalled); - expect(Alert.alert).toHaveBeenCalledWith('Maximum number of files reached'); - }, - ); - - it('trigger file size threshold limit alert when file size above the limit', async () => { - const { result } = renderHook(() => useMessageInputContext(), { - initialProps, - wrapper: (props) => ( - // @ts-ignore - - ), - }); - - await waitFor(() => { - result.current.pickFile(); - }); - - expect(Alert.alert).toHaveBeenCalledTimes(2); - expect(Alert.alert).toHaveBeenCalledWith( - 'File is too large: {{ size }}, maximum upload size is {{ limit }}', - ); - }); -}); diff --git a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx index 322656999c..77b7f7869b 100644 --- a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx @@ -1,360 +1,268 @@ -import React, { PropsWithChildren } from 'react'; -import { act } from 'react-test-renderer'; +import React from 'react'; -import { renderHook, waitFor } from '@testing-library/react-native'; +import { act, cleanup, renderHook, waitFor } from '@testing-library/react-native'; import { LocalMessage } from 'stream-chat'; -import { - generateFileUploadPreview, - generateImageUploadPreview, -} from '../../../mock-builders/generator/attachment'; +import { Chat } from '../../../components'; +import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; + +import { generateLocalFileUploadAttachmentData } from '../../../mock-builders/attachments'; import { generateMessage } from '../../../mock-builders/generator/message'; -import { generateUser } from '../../../mock-builders/generator/user'; +import * as UseMessageComposerAPIContext from '../../messageComposerContext/MessageComposerAPIContext'; -import { FileState } from '../../../utils/utils'; -import * as AttachmentPickerContext from '../../attachmentPickerContext/AttachmentPickerContext'; +import { MessageComposerAPIContextValue } from '../../messageComposerContext/MessageComposerAPIContext'; +import { MessageComposerProvider } from '../../messageComposerContext/MessageComposerContext'; +import { + OwnCapabilitiesContextValue, + OwnCapabilitiesProvider, +} from '../../ownCapabilitiesContext/OwnCapabilitiesContext'; import { InputMessageInputContextValue, - MessageInputContextValue, MessageInputProvider, useMessageInputContext, } from '../MessageInputContext'; -type WrapperType = Partial; - -const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( - - {children} - -); - -const newMessage = generateMessage({ id: 'new-id' }); -describe("MessageInputContext's sendMessage", () => { - jest.spyOn(AttachmentPickerContext, 'useAttachmentPickerContext').mockImplementation(() => ({ - setSelectedFiles: jest.fn(), - setSelectedImages: jest.fn(), - })); - const message: LocalMessage | undefined = generateMessage({ - created_at: 'Sat Jul 02 2022 23:55:13 GMT+0530 (India Standard Time)', - id: '7a85f744-cc89-4f82-a1d4-5456432cc8bf', - updated_at: 'Sat Jul 02 2022 23:55:13 GMT+0530 (India Standard Time)', - user: generateUser({ - id: '5d6f6322-567e-4e1e-af90-97ef1ed5cc23', - image: 'fc86ddcb-bac4-400c-9afd-b0c0a1c0cd33', - name: '50cbdd0e-ca7e-4478-9e2c-be0f1ac6a995', - }), - }) as unknown as LocalMessage; - - it('exit sendMessage when file upload status failed', async () => { - const initialProps = { - editing: undefined, - }; - const files = generateFileUploadPreview({ state: FileState.UPLOAD_FAILED }); - const images = generateImageUploadPreview({ state: FileState.UPLOAD_FAILED }); - const { result } = renderHook(() => useMessageInputContext(), { - initialProps, - wrapper: (props) => ( - - ), - }); +const Wrapper = ({ messageComposerContextValue, client, props }) => { + return ( + + + + + {props.children} + + + + + ); +}; - act(() => { - result.current.setFileUploads([files]); - result.current.setImageUploads([images]); - }); +describe("MessageInputContext's sendMessage", () => { + let channel; + let chatClient; - act(() => { - result.current.sendMessage(); - }); + beforeEach(async () => { + const { client, channels } = await initiateClientWithChannels(); + channel = channels[0]; + chatClient = client; + }); - await expect(result.current.sending.current).toBeFalsy(); + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + channel.messageComposer.clear(); }); - it('exit sendMessage when image upload status is uploading', async () => { - const images = generateImageUploadPreview({ state: FileState.UPLOADING }); + it('should get into the catch block if the message composer compose throws an error', async () => { + const sendMessageMock = jest.fn(); const initialProps = { - editing: message, - sendImageAsync: true, + sendMessage: sendMessageMock, }; + const consoleErrorMock = jest.spyOn(console, 'error'); - const { rerender, result } = renderHook(() => useMessageInputContext(), { + const { result } = renderHook(() => useMessageInputContext(), { initialProps, wrapper: (props) => ( ), }); - act(() => { - result.current.setImageUploads([images]); - result.current.setMentionedUsers([]); - result.current.setText(''); - }); + const composerComposeMock = jest.spyOn(channel.messageComposer, 'compose'); + composerComposeMock.mockRejectedValue(new Error('Error composing message')); await waitFor(() => { result.current.sendMessage(); }); - rerender({ editing: newMessage, sendImageAsync: true }); - await waitFor(() => { - expect(result.current.asyncIds).toHaveLength(1); + expect(sendMessageMock).not.toHaveBeenCalled(); + expect(consoleErrorMock).toHaveBeenCalled(); }); - await expect(result.current.sending.current).toBeFalsy(); }); - it('exit sendMessage when image upload status is uploading and sendImageAsync is available', async () => { - const images = generateImageUploadPreview({ state: FileState.UPLOADING }); + it('should get into the catch block if the sendMessage throws an error', async () => { + const sendMessageMock = jest.fn(); + sendMessageMock.mockRejectedValue(new Error('Error sending message')); + const initialProps = { - editing: message, + sendMessage: sendMessageMock, }; + const consoleErrorMock = jest.spyOn(console, 'error'); const { result } = renderHook(() => useMessageInputContext(), { initialProps, wrapper: (props) => ( ), }); - act(() => { - result.current.setImageUploads([images]); - result.current.setMentionedUsers([]); - result.current.setText(''); + await act(async () => { + const text = 'Hello there'; + await channel.messageComposer.textComposer.handleChange({ + selection: { + end: text.length, + start: text.length, + }, + text, + }); }); - act(() => { + await waitFor(() => { result.current.sendMessage(); }); - await expect(result.current.sending.current).toBeFalsy(); + await waitFor(() => { + expect(sendMessageMock).toHaveBeenCalled(); + expect(consoleErrorMock).toHaveBeenCalled(); + }); }); - it('exit sendMessage when file upload status is uploading', async () => { - const files = generateFileUploadPreview({ state: FileState.UPLOADING }); + it('should not call composer clear if composition has poll id in it', async () => { + const sendMessageMock = jest.fn(); + const clearSpy = jest.spyOn(channel.messageComposer, 'clear'); const initialProps = { - editing: message, + sendMessage: sendMessageMock, }; + const { pollComposer } = channel.messageComposer; + jest.spyOn(chatClient, 'createPoll').mockResolvedValue({ poll: { id: 'test-poll-id' } }); + const { result } = renderHook(() => useMessageInputContext(), { initialProps, wrapper: (props) => ( ), }); - act(() => { - result.current.setFileUploads([files]); - result.current.setMentionedUsers([]); - result.current.setText(''); + await act(async () => { + await pollComposer.updateFields({ + id: 'test-poll', + name: 'Test Poll', + options: [ + { id: 1, text: '1' }, + { id: 2, text: '2' }, + ], + }); + await channel.messageComposer.createPoll(); }); - act(() => { + await waitFor(() => { result.current.sendMessage(); }); - await expect(result.current.sending.current).toBeFalsy(); + await waitFor(() => { + expect(clearSpy).not.toHaveBeenCalled(); + expect(sendMessageMock).toHaveBeenCalledTimes(1); + }); }); - it('exit sendMessage when image upload status is uploaded successfully', async () => { + it('should send message', async () => { const sendMessageMock = jest.fn(); - const clearQuotedMessageStateMock = jest.fn(); - const images = [ - generateImageUploadPreview({ state: FileState.UPLOADED }), - generateImageUploadPreview({ state: FileState.FINISHED }), - ]; + const clearSpy = jest.spyOn(channel.messageComposer, 'clear'); const initialProps = { - clearQuotedMessageState: clearQuotedMessageStateMock, - editing: undefined, - quotedMessage: false, sendMessage: sendMessageMock, }; + const { result } = renderHook(() => useMessageInputContext(), { initialProps, wrapper: (props) => ( ), }); - act(() => { - result.current.setImageUploads(images); - result.current.setMentionedUsers(['dummy1', 'dummy2']); - result.current.setText(''); + await act(async () => { + const text = 'Hello there'; + await channel.messageComposer.textComposer.handleChange({ + selection: { + end: text.length, + start: text.length, + }, + text, + }); }); await waitFor(() => { result.current.sendMessage(); }); - expect(sendMessageMock.mock.calls[0][0]).toMatchSnapshot(); - expect(clearQuotedMessageStateMock).toHaveBeenCalled(); - expect(result.current.sending.current).toBeFalsy(); - expect(result.current.fileUploads.length).toBe(0); - expect(result.current.imageUploads.length).toBe(0); - expect(result.current.mentionedUsers.length).toBe(0); - }); - - it('exit sendMessage when image upload has an error and catch block is executed', () => { - const setQuotedMessageStateMock = jest.fn(); - const clearQuotedMessageStateMock = jest.fn(); - const generatedQuotedMessage: boolean | LocalMessage = message; - const images = [ - generateImageUploadPreview({ state: FileState.UPLOADED }), - generateImageUploadPreview({ state: FileState.FINISHED }), - ]; - const initialProps = { - clearQuotedMessageState: clearQuotedMessageStateMock, - editing: undefined, - quotedMessage: generatedQuotedMessage, - setQuotedMessageState: setQuotedMessageStateMock, - }; - const { result } = renderHook(() => useMessageInputContext(), { - initialProps, - wrapper: (props) => ( - - ), + await waitFor(() => { + expect(clearSpy).toHaveBeenCalled(); + expect(sendMessageMock).toHaveBeenCalledTimes(1); }); + }); +}); - act(() => { - result.current.setImageUploads(images); - result.current.setMentionedUsers(['dummy1', 'dummy2']); - result.current.setText(''); - }); +describe("MessageInputContext's editMessage", () => { + let channel; + let chatClient; - act(() => { - result.current.sendMessage(); - }); + beforeAll(async () => { + const { client, channels } = await initiateClientWithChannels(); + channel = channels[0]; + chatClient = client; + }); - expect(setQuotedMessageStateMock).toHaveBeenCalled(); - expect(result.current.sending.current).toBeFalsy(); + afterEach(() => { + jest.clearAllMocks(); + cleanup(); }); - it('exit sendMessage when edit message is not boolean image upload status is uploaded successfully', () => { - const sendMessageMock = jest.fn(); - const clearQuotedMessageStateMock = jest.fn(); - const clearEditingStateMock = jest.fn(); - const editMessageMock = jest.fn().mockResolvedValue({ data: {} }); - const images = generateImageUploadPreview({ state: FileState.UPLOADED }); - const generatedMessage: boolean | LocalMessage = message; + it('should clear the edited state when the composition is empty', async () => { + const editMessageMock = jest.fn(); const initialProps = { - clearEditingState: clearEditingStateMock, - clearQuotedMessageState: clearQuotedMessageStateMock, - editing: generatedMessage, editMessage: editMessageMock, - quotedMessage: false, - sendMessage: sendMessageMock, }; - const { result } = renderHook(() => useMessageInputContext(), { - initialProps, - wrapper: (props) => ( - - ), - }); - act(() => { - result.current.setImageUploads([images]); - result.current.setMentionedUsers(['dummy1', 'dummy2']); - result.current.setText(''); - }); + const clearEditingStateMock = jest.fn(); - act(() => { - result.current.sendMessage(); - }); + jest.spyOn(UseMessageComposerAPIContext, 'useMessageComposerAPIContext').mockReturnValue({ + clearEditingState: clearEditingStateMock, + } as unknown as MessageComposerAPIContextValue); - expect(editMessageMock.mock.calls[0][0]).toMatchSnapshot(); - expect(clearEditingStateMock).toHaveBeenCalled(); - expect(result.current.sending.current).toBeFalsy(); - expect(result.current.fileUploads.length).toBe(0); - expect(result.current.imageUploads.length).toBe(0); - expect(result.current.mentionedUsers.length).toBe(0); - }); + const message = generateMessage({ + attachments: [generateLocalFileUploadAttachmentData()], + cid: 'messaging:channel-id', + text: 'test', + }) as LocalMessage; - it('exit sendMessage when file upload status is uploaded successfully', () => { - const files = [ - generateFileUploadPreview({ state: FileState.UPLOADED }), - generateFileUploadPreview({ state: FileState.FINISHED, type: 'video/mp4' }), - generateFileUploadPreview({ state: FileState.UPLOADED, type: 'audio/mp3' }), - generateFileUploadPreview({ state: FileState.FINISHED, type: 'image/jpeg' }), - ]; - const sendMessageMock = jest.fn(); - const clearQuotedMessageStateMock = jest.fn(); - const initialProps = { - clearQuotedMessageState: clearQuotedMessageStateMock, - editing: undefined, - quotedMessage: false, - sendMessage: sendMessageMock, - }; const { result } = renderHook(() => useMessageInputContext(), { initialProps, wrapper: (props) => ( ), }); - act(() => { - result.current.setFileUploads(files); - result.current.setMentionedUsers(['dummy1', 'dummy2']); - result.current.setText(''); - }); - - act(() => { + await waitFor(() => { result.current.sendMessage(); }); - expect(sendMessageMock.mock.calls[0][0]).toMatchSnapshot(); - expect(clearQuotedMessageStateMock).toHaveBeenCalled(); - expect(result.current.sending.current).toBeFalsy(); - expect(result.current.fileUploads.length).toBe(0); - expect(result.current.imageUploads.length).toBe(0); - expect(result.current.mentionedUsers.length).toBe(0); + await waitFor(() => { + expect(clearEditingStateMock).toHaveBeenCalled(); + }); }); }); diff --git a/package/src/mock-builders/api/channelMocks.tsx b/package/src/mock-builders/api/channelMocks.tsx index 35839a1e70..9c41c63fe1 100644 --- a/package/src/mock-builders/api/channelMocks.tsx +++ b/package/src/mock-builders/api/channelMocks.tsx @@ -1,8 +1,9 @@ import type { Attachment, Channel, LocalMessage, MessageResponse, UserResponse } from 'stream-chat'; import { + CHANNEL_MEMBERS, GROUP_CHANNEL_MEMBERS_MOCK, - ONE_MEMBER_WITH_EMPTY_USER_MOCK, + ONE_MEMBER_WITH_EMPTY_USER, } from '../../mock-builders/api/queryMembers'; const channelName = 'okechukwu'; @@ -12,140 +13,118 @@ const CHANNEL = { } as unknown as Channel; const CHANNEL_WITH_MESSAGES_TEXT = { - data: { name: channelName }, - state: { - members: GROUP_CHANNEL_MEMBERS_MOCK, - messages: [ - { - args: 'string', - attachments: [], - channel: CHANNEL, - cid: 'stridkncnng', - command: 'giphy', - command_info: { name: 'string' }, - created_at: new Date('2021-02-12T12:12:35.862Z'), - deleted_at: new Date('2021-02-12T12:12:35.862Z'), - id: 'ljkblk', - text: 'jkbkbiubicbi', - type: 'MessageLabel', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, - { - args: 'string', - attachments: [], - channel: CHANNEL, - cid: 'stridodong', - command: 'giphy', - command_info: { name: 'string' }, - created_at: new Date('2021-02-12T12:12:35.862Z'), - deleted_at: new Date('2021-02-12T12:12:35.862Z'), - id: 'jbkjb', - text: 'jkbkbiubicbi', - type: 'MessageLabel', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, - ], - }, -} as unknown as Channel; + members: CHANNEL_MEMBERS, + messages: [ + { + args: 'string', + attachments: [], + channel: CHANNEL, + cid: 'stridkncnng', + command: 'giphy', + command_info: { name: 'string' }, + created_at: new Date('2021-02-12T12:12:35.862Z'), + deleted_at: new Date('2021-02-12T12:12:35.862Z'), + id: 'ljkblk', + text: 'jkbkbiubicbi', + type: 'MessageLabel', + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, + { + args: 'string', + attachments: [], + channel: CHANNEL, + cid: 'stridodong', + command: 'giphy', + command_info: { name: 'string' }, + created_at: new Date('2021-02-12T12:12:35.862Z'), + deleted_at: new Date('2021-02-12T12:12:35.862Z'), + id: 'jbkjb', + text: 'jkbkbiubicbi', + type: 'MessageLabel', + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, + ], + name: channelName, +}; -const CHANNEL_WITH_DELETED_MESSAGES = { - data: { name: channelName }, - state: { - members: GROUP_CHANNEL_MEMBERS_MOCK, - messages: [ - { - type: 'deleted', - } as unknown as MessageResponse, - { - type: 'deleted', - } as unknown as MessageResponse, - ], - }, -} as unknown as Channel; +const CHANNEL_WITH_DELETED_MESSAGES = {}; const CHANNEL_WITH_NO_MESSAGES = { - data: { name: channelName }, - state: { - members: GROUP_CHANNEL_MEMBERS_MOCK, - messages: [], - }, -} as unknown as Channel; + members: CHANNEL_MEMBERS, + messages: [], + name: channelName, +}; const CHANNEL_WITH_MESSAGE_COMMAND = { - data: { name: channelName }, - state: { - members: GROUP_CHANNEL_MEMBERS_MOCK, - messages: [ - { - args: 'string', - attachments: [], - channel: CHANNEL, - cid: 'stridkncnng', - command: 'giphy', - command_info: { name: 'string' }, - created_at: new Date('2021-02-12T12:12:35.862Z'), - deleted_at: new Date('2021-02-12T12:12:35.862Z'), - id: 'ljkblk', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, - { - args: 'string', - attachments: [], - channel: CHANNEL, - cid: 'stridodong', - command: 'giphy', - command_info: { name: 'string' }, - created_at: new Date('2021-02-12T12:12:35.862Z'), - deleted_at: new Date('2021-02-12T12:12:35.862Z'), - id: 'jbkjb', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, - ], - }, -} as unknown as Channel; + members: CHANNEL_MEMBERS, + messages: [ + { + args: 'string', + attachments: [], + channel: CHANNEL, + cid: 'stridkncnng', + command: 'giphy', + command_info: { name: 'string' }, + created_at: new Date('2021-02-12T12:12:35.862Z'), + deleted_at: new Date('2021-02-12T12:12:35.862Z'), + id: 'ljkblk', + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, + { + args: 'string', + attachments: [], + channel: CHANNEL, + cid: 'stridodong', + command: 'giphy', + command_info: { name: 'string' }, + created_at: new Date('2021-02-12T12:12:35.862Z'), + deleted_at: new Date('2021-02-12T12:12:35.862Z'), + id: 'jbkjb', + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, + ], +}; const CHANNEL_WITH_MESSAGES_ATTACHMENTS = { - data: { name: channelName }, - state: { - members: GROUP_CHANNEL_MEMBERS_MOCK, - messages: [ - { - args: 'string', - attachments: [ - { - actions: [], - asset_url: 'string', - author_icon: 'string', - author_link: 'string', - author_name: 'string', - color: 'string', - fallback: 'string', - fields: [], - file_size: 25, - footer: 'string', - footer_icon: 'string', - image_url: 'string', - mime_type: 'string', - og_scrape_url: 'string', - original_height: 5, - original_width: 4, - pretext: 'string', - text: 'string', - thumb_url: 'string', - title: 'string', - title_link: 'string', - type: 'string', - } as Attachment, - ], - channel: CHANNEL, - created_at: new Date('2021-02-12T12:12:35.862Z'), - deleted_at: new Date('2021-02-12T12:12:35.862Z'), - id: 'ljkblk', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, - ], - }, -} as unknown as Channel; + members: CHANNEL_MEMBERS, + messages: [ + { + args: 'string', + attachments: [ + { + actions: [], + asset_url: 'string', + author_icon: 'string', + author_link: 'string', + author_name: 'string', + color: 'string', + fallback: 'string', + fields: [], + file_size: 25, + footer: 'string', + footer_icon: 'string', + image_url: 'string', + mime_type: 'string', + og_scrape_url: 'string', + original_height: 5, + original_width: 4, + pretext: 'string', + text: 'string', + thumb_url: 'string', + title: 'string', + title_link: 'string', + type: 'string', + } as Attachment, + ], + channel: CHANNEL, + created_at: new Date('2021-02-12T12:12:35.862Z'), + deleted_at: new Date('2021-02-12T12:12:35.862Z'), + id: 'ljkblk', + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, + ], + name: channelName, +}; const LATEST_MESSAGE = { args: 'string', @@ -173,74 +152,70 @@ const FORMATTED_MESSAGE: LocalMessage = { }; const CHANNEL_WITH_MENTIONED_USERS = { - state: { - members: ONE_MEMBER_WITH_EMPTY_USER_MOCK, - messages: [ - { - args: 'string', - attachments: [], - cid: 'stridkncnng', - command_info: { name: 'string' }, - created_at: new Date('2021-02-12T12:12:35.862Z'), - deleted_at: new Date('2021-02-12T12:12:35.862Z'), - mentioned_users: [ - { id: 'Max', name: 'Max' }, - { id: 'Ada', name: 'Ada' }, - { id: 'Enzo', name: 'Enzo' }, - ] as UserResponse[], - text: 'Max', - } as unknown as MessageResponse, - { - args: 'string', - attachments: [], - cid: 'stridodong', - command_info: { name: 'string' }, - created_at: new Date('2021-02-12T12:12:35.862Z'), - deleted_at: new Date('2021-02-12T12:12:35.862Z'), - mentioned_users: [ - { id: 'Max', name: 'Max' }, - { id: 'Ada', name: 'Ada' }, - { id: 'Enzo', name: 'Enzo' }, - ] as UserResponse[], - text: 'Max', - } as unknown as MessageResponse, - ], - }, -} as unknown as Channel; + members: ONE_MEMBER_WITH_EMPTY_USER, + messages: [ + { + args: 'string', + attachments: [], + cid: 'stridkncnng', + command_info: { name: 'string' }, + created_at: new Date('2021-02-12T12:12:35.862Z'), + deleted_at: new Date('2021-02-12T12:12:35.862Z'), + mentioned_users: [ + { id: 'Max', name: 'Max' }, + { id: 'Ada', name: 'Ada' }, + { id: 'Enzo', name: 'Enzo' }, + ] as UserResponse[], + text: 'Max', + } as unknown as MessageResponse, + { + args: 'string', + attachments: [], + cid: 'stridodong', + command_info: { name: 'string' }, + created_at: new Date('2021-02-12T12:12:35.862Z'), + deleted_at: new Date('2021-02-12T12:12:35.862Z'), + mentioned_users: [ + { id: 'Max', name: 'Max' }, + { id: 'Ada', name: 'Ada' }, + { id: 'Enzo', name: 'Enzo' }, + ] as UserResponse[], + text: 'Max', + } as unknown as MessageResponse, + ], +}; const CHANNEL_WITH_EMPTY_MESSAGE = { - state: { - members: ONE_MEMBER_WITH_EMPTY_USER_MOCK, - messages: [ - { - args: 'string', - attachments: [], - cid: 'stridkncnng', - command_info: { name: 'string' }, - created_at: new Date('2021-02-12T12:12:35.862Z'), - deleted_at: new Date('2021-02-12T12:12:35.862Z'), - mentioned_users: [ - { id: 'Max', name: 'Max' }, - { id: 'Ada', name: 'Ada' }, - { id: 'Enzo', name: 'Enzo' }, - ] as UserResponse[], - } as unknown as MessageResponse, - { - args: 'string', - attachments: [], - cid: 'stridodong', - command_info: { name: 'string' }, - created_at: new Date('2021-02-12T12:12:35.862Z'), - deleted_at: new Date('2021-02-12T12:12:35.862Z'), - mentioned_users: [ - { id: 'Max', name: 'Max' }, - { id: 'Ada', name: 'Ada' }, - { id: 'Enzo', name: 'Enzo' }, - ] as UserResponse[], - } as unknown as MessageResponse, - ], - }, -} as unknown as Channel; + members: ONE_MEMBER_WITH_EMPTY_USER, + messages: [ + { + args: 'string', + attachments: [], + cid: 'stridkncnng', + command_info: { name: 'string' }, + created_at: new Date('2021-02-12T12:12:35.862Z'), + deleted_at: new Date('2021-02-12T12:12:35.862Z'), + mentioned_users: [ + { id: 'Max', name: 'Max' }, + { id: 'Ada', name: 'Ada' }, + { id: 'Enzo', name: 'Enzo' }, + ] as UserResponse[], + } as unknown as MessageResponse, + { + args: 'string', + attachments: [], + cid: 'stridodong', + command_info: { name: 'string' }, + created_at: new Date('2021-02-12T12:12:35.862Z'), + deleted_at: new Date('2021-02-12T12:12:35.862Z'), + mentioned_users: [ + { id: 'Max', name: 'Max' }, + { id: 'Ada', name: 'Ada' }, + { id: 'Enzo', name: 'Enzo' }, + ] as UserResponse[], + } as unknown as MessageResponse, + ], +}; const CHANNEL_WITH_MESSAGES = { data: { name: channelName }, @@ -248,7 +223,7 @@ const CHANNEL_WITH_MESSAGES = { members: GROUP_CHANNEL_MEMBERS_MOCK, messages: [FORMATTED_MESSAGE, FORMATTED_MESSAGE], }, -} as unknown as Channel; +}; export { CHANNEL, diff --git a/package/src/mock-builders/api/getOrCreateChannel.ts b/package/src/mock-builders/api/getOrCreateChannel.ts index dff2343a08..c88e600897 100644 --- a/package/src/mock-builders/api/getOrCreateChannel.ts +++ b/package/src/mock-builders/api/getOrCreateChannel.ts @@ -2,7 +2,7 @@ import { mockedApiResponse } from './utils'; export type GetOrCreateChannelApiParams = { - draft: Record; + draft?: Record; channel?: Record; members?: Record[]; messages?: Record[]; diff --git a/package/src/mock-builders/api/queryMembers.js b/package/src/mock-builders/api/queryMembers.js index 74006ea1b6..e0bc27c003 100644 --- a/package/src/mock-builders/api/queryMembers.js +++ b/package/src/mock-builders/api/queryMembers.js @@ -15,24 +15,8 @@ export const queryMembersApi = (members = []) => { return mockedApiResponse(result, 'get'); }; -export const ONE_CHANNEL_MEMBER_MOCK = { - okey: { - banned: false, - channel_role: 'channel_member', - created_at: '2021-01-27T11:54:34.173125Z', - role: 'member', - shadow_banned: false, - updated_at: '2021-02-12T12:12:35.862282Z', - user: { - id: 'okechukwu nwagba martin', - name: 'okechukwu nwagba martin', - }, - user_id: 'okechukwu nwagba martin', - }, -}; - -export const GROUP_CHANNEL_MEMBERS_MOCK = { - ben: { +export const CHANNEL_MEMBERS = [ + { banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -45,7 +29,7 @@ export const GROUP_CHANNEL_MEMBERS_MOCK = { }, user_id: 'ben', }, - nick: { + { banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -58,7 +42,7 @@ export const GROUP_CHANNEL_MEMBERS_MOCK = { }, user_id: 'nick', }, - okey: { + { banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -71,7 +55,7 @@ export const GROUP_CHANNEL_MEMBERS_MOCK = { }, user_id: 'okechukwu nwagba', }, - qatest1: { + { banned: false, channel_role: 'channel_member', created_at: '2021-01-28T09:08:43.274508Z', @@ -85,7 +69,7 @@ export const GROUP_CHANNEL_MEMBERS_MOCK = { user_id: 'qatest1', }, - thierry: { + { banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -98,10 +82,35 @@ export const GROUP_CHANNEL_MEMBERS_MOCK = { }, user_id: 'thierry', }, +]; + +export const ONE_CHANNEL_MEMBER = [ + { + banned: false, + channel_role: 'channel_member', + created_at: '2021-01-27T11:54:34.173125Z', + role: 'member', + shadow_banned: false, + updated_at: '2021-02-12T12:12:35.862282Z', + user: { + id: 'okechukwu nwagba martin', + name: 'okechukwu nwagba martin', + }, + user_id: 'okechukwu nwagba martin', + }, +]; + +export const ONE_CHANNEL_MEMBER_MOCK = { + okey: ONE_CHANNEL_MEMBER[0], }; -export const ONE_MEMBER_WITH_EMPTY_USER_MOCK = { - okey: { +export const GROUP_CHANNEL_MEMBERS_MOCK = CHANNEL_MEMBERS.reduce((acc, member) => { + acc[member.user_id] = member; + return acc; +}, {}); + +export const ONE_MEMBER_WITH_EMPTY_USER = [ + { banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -111,4 +120,8 @@ export const ONE_MEMBER_WITH_EMPTY_USER_MOCK = { user: {}, user_id: 'okechukwu nwagba martin', }, +]; + +export const ONE_MEMBER_WITH_EMPTY_USER_MOCK = { + okey: ONE_MEMBER_WITH_EMPTY_USER[0], };